summaryrefslogtreecommitdiff
path: root/ngx_postgres-1.0
diff options
context:
space:
mode:
authorkaiwu <kaiwu2004@gmail.com>2025-03-01 12:42:23 +0800
committerkaiwu <kaiwu2004@gmail.com>2025-03-01 12:42:23 +0800
commit3f33461e4948bf05e60bdff35ec6c57a649c7860 (patch)
tree284c2ba95a41536ae1bff6bea710db0709a64739 /ngx_postgres-1.0
downloadopenresty-3f33461e4948bf05e60bdff35ec6c57a649c7860.tar.gz
openresty-3f33461e4948bf05e60bdff35ec6c57a649c7860.zip
openresty bundle
Diffstat (limited to 'ngx_postgres-1.0')
-rw-r--r--ngx_postgres-1.0/CHANGES155
-rw-r--r--ngx_postgres-1.0/LICENSE25
-rw-r--r--ngx_postgres-1.0/README.md424
-rw-r--r--ngx_postgres-1.0/TODO.md23
-rw-r--r--ngx_postgres-1.0/config216
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_ddebug.h77
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_escape.c97
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_escape.h36
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_handler.c410
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_handler.h49
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_keepalive.c344
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_keepalive.h65
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_module.c1338
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_module.h193
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_output.c648
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_output.h54
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_processor.c514
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_processor.h52
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_rewrite.c114
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_rewrite.h43
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_upstream.c598
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_upstream.h74
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_util.c406
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_util.h59
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_variable.c288
-rw-r--r--ngx_postgres-1.0/src/ngx_postgres_variable.h50
-rw-r--r--ngx_postgres-1.0/src/resty_dbd_stream.h59
-rw-r--r--ngx_postgres-1.0/t/000_init.t174
-rw-r--r--ngx_postgres-1.0/t/auth.t110
-rw-r--r--ngx_postgres-1.0/t/bigpipe.t141
-rw-r--r--ngx_postgres-1.0/t/errors.t141
-rw-r--r--ngx_postgres-1.0/t/escape.t361
-rw-r--r--ngx_postgres-1.0/t/eval.t75
-rw-r--r--ngx_postgres-1.0/t/form.t71
-rw-r--r--ngx_postgres-1.0/t/methods.t335
-rw-r--r--ngx_postgres-1.0/t/output.t447
-rw-r--r--ngx_postgres-1.0/t/restful.t338
-rw-r--r--ngx_postgres-1.0/t/restful_json.t244
-rw-r--r--ngx_postgres-1.0/t/rewrites.t348
-rw-r--r--ngx_postgres-1.0/t/sanity.t298
-rw-r--r--ngx_postgres-1.0/t/variables.t407
-rw-r--r--ngx_postgres-1.0/valgrind.suppress331
42 files changed, 10232 insertions, 0 deletions
diff --git a/ngx_postgres-1.0/CHANGES b/ngx_postgres-1.0/CHANGES
new file mode 100644
index 0000000..18e6ea8
--- /dev/null
+++ b/ngx_postgres-1.0/CHANGES
@@ -0,0 +1,155 @@
+2011-12-27 VERSION 0.9
+ * Improve debug logging.
+ From Yichun Zhang (agentzh).
+
+2011-12-23
+ * Fix compatibility with poll, select and /dev/poll event models.
+ Reported by Yichun Zhang (agentzh).
+
+2011-11-10
+ * Fix compatibility with PostgreSQL 9.x.
+ Reported by Yichun Zhang (agentzh).
+
+ * Fix compatibility with nginx-1.1.4+.
+ From Yichun Zhang (agentzh).
+
+2011-06-21
+ * Enforce writing of proper SQL queries by replacing "row <row>"
+ output format with "text" and returning whole result-set with
+ values separated by newlines when using "postgres_output"
+ directive.
+
+ * Enforce writing of proper SQL queries by requiring result-set
+ to contain exactly single value when using "binary_value" or
+ "value" output formats when using "postgres_output" directive.
+
+2011-06-17
+ * Fix "duplicated last chunk" issue.
+ Reported by Silly Sad, diagnosed by Maxim Dounin.
+
+ * Improve build-time PostgreSQL client library discovery process
+ by using PostgreSQL's pg_config.
+ Patch from Silly Sad.
+
+2010-12-23 VERSION 0.8
+ * Add option to return content in binary format using
+ "binary_value" output format in "postgres_output" directive.
+ Mostly done by Yichun Zhang (agentzh).
+
+2010-11-01
+ * Support "postgres_pass", "postgres_query", "postgres_rewrite"
+ and "postgres_output" directives in "if" pseudo-locations.
+ From Yichun Zhang (agentzh).
+
+2010-10-02
+ * Major rewrite of "postgres_escape" directive.
+
+2010-09-30 VERSION 0.7
+ * Add option to send original response body with error responses
+ set by "postgres_rewrite" directive.
+
+2010-08-25
+ * Fix error that could lead to failed connection to the database.
+
+ * Log more details on failed connection to the database.
+
+2010-08-15 VERSION 0.6
+ * Fix linking issue that manifested itself when nginx was build
+ with both: ngx_postgres and ngx_supervisord modules.
+ Reported by Sergey A. Osokin.
+
+2010-08-09
+ * Fix pointer signedness mismatch, which broke build on Darwin
+ and probably few other operating systems.
+ Reported by sahuguet, fixed by Yichun Zhang (agentzh).
+
+2010-08-03 VERSION 0.5
+ * Fix compatibility with nginx-0.8.47+.
+
+2010-07-20
+ * Add "postgres_escape" directive.
+
+2010-07-05 VERSION 0.4
+ * Optimize generation of RDS output.
+
+2010-07-02
+ * Fix serious bug that under certain conditions (query evaluated
+ to empty string, failed connection to the database, etc) would
+ lead to segmentation fault on versions older than nginx-0.8.17
+ (including nginx-0.7.x).
+
+2010-06-30
+ * When returning row or value, use Content-Type specified by
+ "default_type" directive instead of "text/plain".
+
+ * Allow column to be specified by its name instead of its number
+ (in "postgres_output" and "postgres_set" directives).
+
+2010-06-23
+ * Add "postgres_rewrite" directive.
+
+ * Add "$postgres_affected" variable.
+
+2010-06-22
+ * Fix issue that would stop gzip filter from processing
+ responses in RDS format.
+ Found by Qing Lin (kindy), fixed by Yichun Zhang (agentzh).
+
+2010-06-21
+ * Add "postgres_output" directive.
+
+2010-06-18
+ * Add "$postgres_query" variable.
+
+2010-06-16
+ * Add "postgres_set" directive.
+
+ * Add "$postgres_columns" and "$postgres_rows" variables.
+
+2010-06-13 VERSION 0.3
+ * Allow configuration of method-specific queries.
+
+ * Restrict "postgres_pass" directive to "location" context.
+
+2010-06-07
+ * Free keepalive connections on nginx shutdown.
+ Requested by Yichun Zhang (agentzh).
+
+ * Fix memory leak that was happening when nginx was configured
+ to use non-existing database tables, etc.
+ Found by Valgrind, reported by Yichun Zhang (agentzh).
+
+2010-06-04
+ * Use recently standardized error codes in RDS format.
+
+2010-06-03
+ * Allow request methods other than GET and HEAD.
+ From Yichun Zhang (agentzh) via ngx_drizzle.
+
+2010-05-12 VERSION 0.2
+ * Add various improvements to build and testing infrastructures.
+ Mostly done by Yichun Zhang (agentzh).
+
+ * Put more restrictions on "postgres_pass" and "postgres_query"
+ directives. Handle their bad configuration properly.
+
+2010-05-10
+ * Log PostgreSQL errors into error.log.
+ Reminded by Yichun Zhang (agentzh).
+
+2010-05-06
+ * Remove connection timeout from re-used keepalive connection.
+
+ * Fix libpq headers detection on Debian.
+ Patch from Weibin Yao.
+
+ * Add "postgres_get_value" directive.
+ Requested by Johan Bergstroem.
+
+ * Bring back fail-safe check that got lost during pre-release
+ refactorization. Without this check performance was reduced
+ few times under high load, because about 1% of keepalive
+ connections got disconnected.
+
+2010-05-05 VERSION 0.1
+ * Initial release.
diff --git a/ngx_postgres-1.0/LICENSE b/ngx_postgres-1.0/LICENSE
new file mode 100644
index 0000000..98c3bcf
--- /dev/null
+++ b/ngx_postgres-1.0/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/ngx_postgres-1.0/README.md b/ngx_postgres-1.0/README.md
new file mode 100644
index 0000000..3b50827
--- /dev/null
+++ b/ngx_postgres-1.0/README.md
@@ -0,0 +1,424 @@
+About
+=====
+`ngx_postgres` is an upstream module that allows `nginx` to communicate directly
+with `PostgreSQL` database.
+
+Response is generated in `rds` format, so it's compatible with `ngx_rds_json`
+and `ngx_drizzle` modules.
+
+
+Status
+======
+This module is production-ready and it's compatible with following nginx
+releases:
+
+- 0.7.x (tested with 0.7.60 to 0.7.69),
+- 0.8.x (tested with 0.8.0 to 0.8.55),
+- 0.9.x (tested with 0.9.0 to 0.9.7),
+- 1.0.x (tested with 1.0.0 to 1.0.11),
+- 1.1.x (tested with 1.1.0 to 1.1.12).
+- 1.2.x (tested with 1.2.3 to 1.2.3).
+- 1.3.x (tested with 1.3.4 to 1.3.4).
+
+
+Configuration directives
+========================
+postgres_server
+---------------
+* **syntax**: `postgres_server ip[:port] dbname=dbname user=user password=pass`
+* **default**: `none`
+* **context**: `upstream`
+
+Set details about the database server.
+
+
+postgres_keepalive
+------------------
+* **syntax**: `postgres_keepalive off | max=count [mode=single|multi] [overflow=ignore|reject]`
+* **default**: `max=10 mode=single overflow=ignore`
+* **context**: `upstream`
+
+Configure keepalive parameters:
+
+- `max` - maximum number of keepalive connections (per worker process),
+- `mode` - backend matching mode,
+- `overflow` - either `ignore` the fact that keepalive connection pool is full
+ and allow request, but close connection afterwards or `reject` request with
+ `503 Service Unavailable` response.
+
+
+postgres_pass
+-------------
+* **syntax**: `postgres_pass upstream`
+* **default**: `none`
+* **context**: `location`, `if location`
+
+Set name of an upstream block that will be used for the database connections
+(it can include variables).
+
+
+postgres_query
+--------------
+* **syntax**: `postgres_query [methods] query`
+* **default**: `none`
+* **context**: `http`, `server`, `location`, `if location`
+
+Set query string (it can include variables). When methods are specified then
+query is used only for them, otherwise it's used for all methods.
+
+This directive can be used more than once within same context.
+
+
+postgres_rewrite
+----------------
+* **syntax**: `postgres_rewrite [methods] condition [=]status_code`
+* **default**: `none`
+* **context**: `http`, `server`, `location`, `if location`
+
+Rewrite response `status_code` when given condition is met (first one wins!):
+
+- `no_changes` - no rows were affected by the query,
+- `changes` - at least one row was affected by the query,
+- `no_rows` - no rows were returned in the result-set,
+- `rows` - at least one row was returned in the result-set.
+
+When `status_code` is prefixed with `=` sign then original response body is
+send to the client instead of the default error page for given `status_code`.
+
+By design both `no_changes` and `changes` apply only to `INSERT`,
+`UPDATE`, `DELETE`, `MOVE`, `FETCH` and `COPY` SQL queries.
+
+This directive can be used more than once within same context.
+
+
+postgres_output
+---------------
+* **syntax**: `postgres_output rds|text|value|binary_value|none`
+* **default**: `rds`
+* **context**: `http`, `server`, `location`, `if location`
+
+Set output format:
+
+- `rds` - return all values from the result-set in `rds` format
+ (with appropriate `Content-Type`),
+- `text` - return all values from the result-set in text format
+ (with default `Content-Type`), values are separated by new line,
+- `value` - return single value from the result-set in text format
+ (with default `Content-Type`),
+- `binary_value` - return single value from the result-set in binary format
+ (with default `Content-Type`),
+- `none` - don't return anything, this should be used only when
+ extracting values with `postgres_set` for use with other modules (without
+ `Content-Type`).
+
+
+postgres_set
+------------
+* **syntax**: `postgres_set $variable row column [optional|required]`
+* **default**: `none`
+* **context**: `http`, `server`, `location`
+
+Get single value from the result-set and keep it in $variable.
+
+When requirement level is set to `required` and value is either out-of-range,
+`NULL` or zero-length, then nginx returns `500 Internal Server Error` response.
+Such condition is silently ignored when requirement level is set to `optional`
+(default).
+
+Row and column numbers start at 0. Column name can be used instead of column
+number.
+
+This directive can be used more than once within same context.
+
+
+postgres_escape
+---------------
+* **syntax**: `postgres_escape $escaped [[=]$unescaped]`
+* **default**: `none`
+* **context**: `http`, `server`, `location`
+
+Escape and quote `$unescaped` string. Result is stored in `$escaped` variable
+which can be safely used in SQL queries.
+
+Because nginx cannot tell the difference between empty and non-existing strings,
+all empty strings are by default escaped to `NULL` value. This behavior can be
+disabled by prefixing `$unescaped` string with `=` sign.
+
+
+postgres_connect_timeout
+------------------------
+* **syntax**: `postgres_connect_timeout timeout`
+* **default**: `10s`
+* **context**: `http`, `server`, `location`
+
+Set timeout for connecting to the database.
+
+
+postgres_result_timeout
+-----------------------
+* **syntax**: `postgres_result_timeout timeout`
+* **default**: `30s`
+* **context**: `http`, `server`, `location`
+
+Set timeout for receiving result from the database.
+
+
+Configuration variables
+=======================
+$postgres_columns
+-----------------
+Number of columns in received result-set.
+
+
+$postgres_rows
+--------------
+Number of rows in received result-set.
+
+
+$postgres_affected
+------------------
+Number of rows affected by `INSERT`, `UPDATE`, `DELETE`, `MOVE`, `FETCH`
+or `COPY` SQL query.
+
+
+$postgres_query
+---------------
+SQL query, as seen by `PostgreSQL` database.
+
+
+Sample configurations
+=====================
+Sample configuration #1
+-----------------------
+Return content of table `cats` (in `rds` format).
+
+ http {
+ upstream database {
+ postgres_server 127.0.0.1 dbname=test
+ user=test password=test;
+ }
+
+ server {
+ location / {
+ postgres_pass database;
+ postgres_query "SELECT * FROM cats";
+ }
+ }
+ }
+
+
+Sample configuration #2
+-----------------------
+Return only those rows from table `sites` that match `host` filter which
+is evaluated for each request based on its `$http_host` variable.
+
+ http {
+ upstream database {
+ postgres_server 127.0.0.1 dbname=test
+ user=test password=test;
+ }
+
+ server {
+ location / {
+ postgres_pass database;
+ postgres_query SELECT * FROM sites WHERE host='$http_host'";
+ }
+ }
+ }
+
+
+Sample configuration #3
+-----------------------
+Pass request to the backend selected from the database (traffic router).
+
+ http {
+ upstream database {
+ postgres_server 127.0.0.1 dbname=test
+ user=test password=test;
+ }
+
+ server {
+ location / {
+ eval_subrequest_in_memory off;
+
+ eval $backend {
+ postgres_pass database;
+ postgres_query "SELECT * FROM backends LIMIT 1";
+ postgres_output value 0 0;
+ }
+
+ proxy_pass $backend;
+ }
+ }
+ }
+
+Required modules (other than `ngx_postgres`):
+
+- [nginx-eval-module (agentzh's fork)](http://github.com/agentzh/nginx-eval-module),
+
+
+Sample configuration #4
+-----------------------
+Restrict access to local files by authenticating against `PostgreSQL` database.
+
+ http {
+ upstream database {
+ postgres_server 127.0.0.1 dbname=test
+ user=test password=test;
+ }
+
+ server {
+ location = /auth {
+ internal;
+
+ postgres_escape $user $remote_user;
+ postgres_escape $pass $remote_passwd;
+
+ postgres_pass database;
+ postgres_query "SELECT login FROM users WHERE login=$user AND pass=$pass";
+ postgres_rewrite no_rows 403;
+ postgres_output none;
+ }
+
+ location / {
+ auth_request /auth;
+ root /files;
+ }
+ }
+ }
+
+Required modules (other than `ngx_postgres`):
+
+- [ngx_http_auth_request_module](http://mdounin.ru/hg/ngx_http_auth_request_module/),
+- [ngx_coolkit](http://github.com/FRiCKLE/ngx_coolkit).
+
+
+Sample configuration #5
+-----------------------
+Simple RESTful webservice returning JSON responses with appropriate HTTP status
+codes.
+
+ http {
+ upstream database {
+ postgres_server 127.0.0.1 dbname=test
+ user=test password=test;
+ }
+
+ server {
+ set $random 123;
+
+ location = /numbers/ {
+ postgres_pass database;
+ rds_json on;
+
+ postgres_query HEAD GET "SELECT * FROM numbers";
+
+ postgres_query POST "INSERT INTO numbers VALUES('$random') RETURNING *";
+ postgres_rewrite POST changes 201;
+
+ postgres_query DELETE "DELETE FROM numbers";
+ postgres_rewrite DELETE no_changes 204;
+ postgres_rewrite DELETE changes 204;
+ }
+
+ location ~ /numbers/(?<num>\d+) {
+ postgres_pass database;
+ rds_json on;
+
+ postgres_query HEAD GET "SELECT * FROM numbers WHERE number='$num'";
+ postgres_rewrite HEAD GET no_rows 410;
+
+ postgres_query PUT "UPDATE numbers SET number='$num' WHERE number='$num' RETURNING *";
+ postgres_rewrite PUT no_changes 410;
+
+ postgres_query DELETE "DELETE FROM numbers WHERE number='$num'";
+ postgres_rewrite DELETE no_changes 410;
+ postgres_rewrite DELETE changes 204;
+ }
+ }
+ }
+
+Required modules (other than `ngx_postgres`):
+
+- [ngx_rds_json](http://github.com/agentzh/rds-json-nginx-module).
+
+Sample configuration #6
+-----------------------
+Use GET parameter in SQL query.
+
+ location /quotes {
+ set_unescape_uri $txt $arg_txt;
+ postgres_escape $txt;
+ postgres_pass database;
+ postgres_query "SELECT * FROM quotes WHERE quote=$txt";
+ }
+
+Required modules (other than `ngx_postgres`):
+
+- [ngx_set_misc](http://github.com/agentzh/set-misc-nginx-module).
+
+Testing
+=======
+`ngx_postgres` comes with complete test suite based on [Test::Nginx](http://github.com/agentzh/test-nginx).
+
+You can test core functionality by running:
+
+`$ TEST_NGINX_IGNORE_MISSING_DIRECTIVES=1 prove`
+
+You can also test interoperability with following modules:
+
+- [ngx_coolkit](http://github.com/FRiCKLE/ngx_coolkit),
+- [ngx_echo](github.com/agentzh/echo-nginx-module),
+- [ngx_form_input](http://github.com/calio/form-input-nginx-module),
+- [ngx_set_misc](http://github.com/agentzh/set-misc-nginx-module),
+- [ngx_http_auth_request_module](http://mdounin.ru/hg/ngx_http_auth_request_module/),
+- [nginx-eval-module (agentzh's fork)](http://github.com/agentzh/nginx-eval-module),
+- [ngx_rds_json](http://github.com/agentzh/rds-json-nginx-module).
+
+by running:
+
+`$ prove`
+
+
+License
+=======
+ Copyright (c) 2010-2017, FRiCKLE Piotr Sikora <info@frickle.com>
+ Copyright (c) 2009-2017, Xiaozhe Wang <chaoslawful@gmail.com>
+ Copyright (c) 2009-2017, Yichun Zhang <agentzh@gmail.com>
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+This software includes also parts of the code from:
+
+- `nginx` (copyrighted by **Igor Sysoev** under BSD license),
+- `ngx_http_upstream_keepalive` module (copyrighted by **Maxim Dounin**
+ under BSD license).
+
+
+See also
+========
+- [ngx_rds_json](http://github.com/agentzh/rds-json-nginx-module),
+- [ngx_drizzle](http://github.com/chaoslawful/drizzle-nginx-module),
+- [ngx_lua](http://github.com/chaoslawful/lua-nginx-module),
+- [nginx-eval-module (agentzh's fork)](http://github.com/agentzh/nginx-eval-module).
diff --git a/ngx_postgres-1.0/TODO.md b/ngx_postgres-1.0/TODO.md
new file mode 100644
index 0000000..6548fcc
--- /dev/null
+++ b/ngx_postgres-1.0/TODO.md
@@ -0,0 +1,23 @@
+Features that sooner or later will be added to `ngx_postgres`:
+
+* Add support for SSL connections to the database.
+
+* Add support for dropping of idle keep-alived connections to the
+ database.
+
+* Add `$postgres_error` variable.
+
+* Add support for sending mulitple queries in one go (transactions,
+ multiple SELECTs, etc), this will require changes in the processing
+ flow __and__ RDS format.
+
+* Add `postgres_escape_bytea` or `postgres_escape_binary`.
+
+* Use `PQescapeStringConn()` instead of `PQescapeString()`, this will
+ require lazy-evaluation of the variables after we acquire connection,
+ but before we send query to the database.
+ Notes: Don't break `$postgres_query`.
+
+* Cancel long-running queries using `PQcancel()`.
+
+* Detect server version using `PQserverVersion()`.
diff --git a/ngx_postgres-1.0/config b/ngx_postgres-1.0/config
new file mode 100644
index 0000000..4b4caa4
--- /dev/null
+++ b/ngx_postgres-1.0/config
@@ -0,0 +1,216 @@
+ngx_feature_name=
+ngx_feature_run=no
+ngx_feature_incs="#include <libpq-fe.h>"
+ngx_feature_test="(void) PQconndefaults();"
+
+if [ -n "$LIBPQ_INC" -o -n "$LIBPQ_LIB" ]; then
+ # specified by LIBPQ_INC and LIBPQ_LIB
+ ngx_feature="libpq library in directories specified by LIBPQ_INC ($LIBPQ_INC) and LIBPQ_LIB ($LIBPQ_LIB)"
+ ngx_feature_path="$LIBPQ_INC"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R$LIBPQ_LIB -L$LIBPQ_LIB -lpq"
+ else
+ ngx_feature_libs="-L$LIBPQ_LIB -lpq"
+ fi
+ . auto/feature
+else
+ if [ -z "$PG_CONFIG" ]; then
+ PG_CONFIG=pg_config
+ fi
+
+ if type $PG_CONFIG >/dev/null 2>&1; then
+ # based on information from pg_config
+ ngx_feature="libpq library (via $PG_CONFIG)"
+ ngx_feature_path="`$PG_CONFIG --includedir`"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R`$PG_CONFIG --libdir` -L`$PG_CONFIG --libdir` -lpq"
+ else
+ ngx_feature_libs="-L`$PG_CONFIG --libdir` -lpq"
+ fi
+ . auto/feature
+ fi
+
+ # auto-discovery
+ if [ $ngx_found = no ]; then
+ # system-wide
+ ngx_feature="libpq library"
+ ngx_feature_path=
+ ngx_feature_libs="-lpq"
+ . auto/feature
+ fi
+
+ if [ $ngx_found = no ]; then
+ # Debian
+ ngx_feature="libpq library in /usr/../postgresql/"
+ ngx_feature_path="/usr/include/postgresql"
+ . auto/feature
+ fi
+
+ if [ $ngx_found = no ]; then
+ # FreeBSD
+ ngx_feature="libpq library in /usr/local/"
+ ngx_feature_path="/usr/local/include"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lpq"
+ else
+ ngx_feature_libs="-L/usr/local/lib -lpq"
+ fi
+ . auto/feature
+ fi
+
+ if [ $ngx_found = no ]; then
+ # OpenBSD
+ ngx_feature="libpq library in /usr/local/../postgresql/"
+ ngx_feature_path="/usr/local/include/postgresql"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R/usr/local/lib -L/usr/local/lib -lpq"
+ else
+ ngx_feature_libs="-L/usr/local/lib -lpq"
+ fi
+ . auto/feature
+ fi
+
+ if [ $ngx_found = no ]; then
+ # NetBSD
+ ngx_feature="libpq library in /usr/pkg/"
+ ngx_feature_path="/usr/pkg/include"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R/usr/pkg/lib -L/usr/pkg/lib -lpq"
+ else
+ ngx_feature_libs="-L/usr/pkg/lib -lpq"
+ fi
+ . auto/feature
+ fi
+
+ if [ $ngx_found = no ]; then
+ # MacPorts
+ ngx_feature="libpq library in /opt/local/"
+ ngx_feature_path="/opt/local/include"
+ if [ $NGX_RPATH = YES ]; then
+ ngx_feature_libs="-R/opt/local/lib -L/opt/local/lib -lpq"
+ else
+ ngx_feature_libs="-L/opt/local/lib -lpq"
+ fi
+ . auto/feature
+ fi
+fi
+
+if [ $ngx_found = no ]; then
+ cat << END
+ $0: error: ngx_postgres addon was unable to find the libpq library.
+END
+ exit 1
+fi
+
+ngx_version=`grep nginx_version src/core/nginx.h | sed -e 's/^.* \(.*\)$/\1/'`
+
+if [ -z "$ngx_version" ]; then
+ cat << END
+ $0: error: ngx_postgres addon was unable to detect version of nginx.
+END
+ exit 1
+fi
+
+# work-around for versions of nginx older than nginx-0.9.0
+if [ $ngx_version -ge 9000 ]; then
+ ngx_feature_name="NGX_POSTGRES_LIBRARY_VERSION"
+ ngx_feature_run=value
+else
+ ngx_feature_name="NGX_POSTGRES_LIBRARY_VERSION_DETECTED"
+ ngx_feature_run=no
+fi
+
+lib_version=90100
+ngx_feature="libpq library version 9.1"
+ngx_feature_test="printf(\"%d\", PQlibVersion())"
+. auto/feature
+
+if [ $ngx_found = no ]; then
+ lib_version=90000
+ ngx_feature="libpq library version 9.0"
+ ngx_feature_test="(void) PQescapeLiteral(NULL, NULL, 0);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ lib_version=80400
+ ngx_feature="libpq library version 8.4"
+ ngx_feature_test="PQinitOpenSSL(0, 0);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ lib_version=80300
+ ngx_feature="libpq library version 8.3"
+ ngx_feature_test="(void) PQconnectionNeedsPassword(NULL);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ lib_version=80200
+ ngx_feature="libpq library version 8.2"
+ ngx_feature_test="(void) PQsendDescribePortal(NULL, NULL);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ lib_version=80104
+ ngx_feature="libpq library version 8.1.4"
+ ngx_feature_test="(void) PQescapeByteaConn(NULL, NULL, 0, 0);
+ (void) PQregisterThreadLock(NULL);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ lib_version=80100
+ ngx_feature="libpq library version 8.1.0"
+ ngx_feature_test="(void) PQregisterThreadLock(NULL);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ lib_version=80008
+ ngx_feature="libpq library version 8.0.8"
+ ngx_feature_test="(void) PQescapeByteaConn(NULL, NULL, 0, 0);
+ (void) PQcancel(NULL, NULL, 0);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ lib_version=80000
+ ngx_feature="libpq library version 8.0.0"
+ ngx_feature_test="(void) PQcancel(NULL, NULL, 0);
+ printf(\"$lib_version\")"
+ . auto/feature
+fi
+
+if [ $ngx_found = no ]; then
+ cat << END
+ $0: error: ngx_postgres addon was unable to detect version of the libpq library.
+END
+ exit 1
+fi
+
+# work-around for versions of nginx older than nginx-0.9.0
+if [ $ngx_version -lt 9000 ]; then
+ have=NGX_POSTGRES_LIBRARY_VERSION value=$lib_version . auto/define
+fi
+
+ngx_addon_name=ngx_postgres
+
+HTTP_MODULES="$HTTP_MODULES ngx_postgres_module"
+
+CORE_INCS="$CORE_INCS $ngx_feature_path"
+CORE_LIBS="$CORE_LIBS $ngx_feature_libs"
+
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/src/ngx_postgres_escape.c $ngx_addon_dir/src/ngx_postgres_handler.c $ngx_addon_dir/src/ngx_postgres_keepalive.c $ngx_addon_dir/src/ngx_postgres_module.c $ngx_addon_dir/src/ngx_postgres_output.c $ngx_addon_dir/src/ngx_postgres_processor.c $ngx_addon_dir/src/ngx_postgres_rewrite.c $ngx_addon_dir/src/ngx_postgres_upstream.c $ngx_addon_dir/src/ngx_postgres_util.c $ngx_addon_dir/src/ngx_postgres_variable.c"
+NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_addon_dir/src/ngx_postgres_escape.h $ngx_addon_dir/src/ngx_postgres_handler.h $ngx_addon_dir/src/ngx_postgres_keepalive.h $ngx_addon_dir/src/ngx_postgres_module.h $ngx_addon_dir/src/ngx_postgres_output.h $ngx_addon_dir/src/ngx_postgres_processor.h $ngx_addon_dir/src/ngx_postgres_rewrite.h $ngx_addon_dir/src/ngx_postgres_upstream.h $ngx_addon_dir/src/ngx_postgres_util.h $ngx_addon_dir/src/ngx_postgres_variable.h $ngx_addon_dir/src/ngx_postgres_ddebug.h $ngx_addon_dir/src/resty_dbd_stream.h"
+
+have=NGX_POSTGRES_MODULE . auto/have
diff --git a/ngx_postgres-1.0/src/ngx_postgres_ddebug.h b/ngx_postgres-1.0/src/ngx_postgres_ddebug.h
new file mode 100644
index 0000000..38253fc
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_ddebug.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_DDEBUG_H_
+#define _NGX_POSTGRES_DDEBUG_H_
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+
+#if defined(DDEBUG) && (DDEBUG)
+
+# if (NGX_HAVE_VARIADIC_MACROS)
+
+# define dd(...) fprintf(stderr, "postgres *** %s: ", __func__); \
+ fprintf(stderr, __VA_ARGS__); \
+ fprintf(stderr, " *** %s line %d.\n", __FILE__, __LINE__)
+
+# else
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <stdarg.h>
+
+static void
+dd(const char * fmt, ...)
+{
+ /* TODO */
+}
+
+# endif
+
+#else
+
+# if (NGX_HAVE_VARIADIC_MACROS)
+
+# define dd(...)
+
+# else
+
+#include <stdarg.h>
+
+static void
+dd(const char * fmt, ...)
+{
+}
+
+# endif
+
+#endif
+
+#endif /* _NGX_POSTGRES_DDEBUG_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_escape.c b/ngx_postgres-1.0/src/ngx_postgres_escape.c
new file mode 100644
index 0000000..dd78194
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_escape.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_escape.h"
+#include "ngx_postgres_module.h"
+
+#include <libpq-fe.h>
+
+
+uintptr_t ngx_postgres_script_exit_code = (uintptr_t) NULL;
+
+
+void
+ngx_postgres_escape_string(ngx_http_script_engine_t *e)
+{
+ ngx_postgres_escape_t *pge;
+ ngx_http_variable_value_t *v;
+ u_char *p, *s;
+
+ v = e->sp - 1;
+
+ dd("entering: \"%.*s\"", (int) v->len, v->data);
+
+ pge = (ngx_postgres_escape_t *) e->ip;
+ e->ip += sizeof(ngx_postgres_escape_t);
+
+ if ((v == NULL) || (v->not_found)) {
+ v->data = (u_char *) "NULL";
+ v->len = sizeof("NULL") - 1;
+ dd("returning (NULL)");
+ goto done;
+ }
+
+ if (v->len == 0) {
+ if (pge->empty) {
+ v->data = (u_char *) "''";
+ v->len = 2;
+ dd("returning (empty/empty)");
+ goto done;
+ } else {
+ v->data = (u_char *) "NULL";
+ v->len = sizeof("NULL") - 1;
+ dd("returning (empty/NULL)");
+ goto done;
+ }
+ }
+
+ s = p = ngx_pnalloc(e->request->pool, 2 * v->len + 2);
+ if (p == NULL) {
+ e->ip = (u_char *) &ngx_postgres_script_exit_code;
+ e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ dd("returning (NGX_HTTP_INTERNAL_SERVER_ERROR)");
+ return;
+ }
+
+ *p++ = '\'';
+ v->len = PQescapeString((char *) p, (const char *) v->data, v->len);
+ p[v->len] = '\'';
+ v->len += 2;
+ v->data = s;
+
+ dd("returning");
+
+done:
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_escape.h b/ngx_postgres-1.0/src/ngx_postgres_escape.h
new file mode 100644
index 0000000..4312de6
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_escape.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_ESCAPE_H_
+#define _NGX_POSTGRES_ESCAPE_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+void ngx_postgres_escape_string(ngx_http_script_engine_t *);
+
+#endif /* _NGX_POSTGRES_ESCAPE_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_handler.c b/ngx_postgres-1.0/src/ngx_postgres_handler.c
new file mode 100644
index 0000000..cf8c1bb
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_handler.c
@@ -0,0 +1,410 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_handler.h"
+#include "ngx_postgres_module.h"
+#include "ngx_postgres_output.h"
+#include "ngx_postgres_processor.h"
+#include "ngx_postgres_util.h"
+
+
+ngx_int_t
+ngx_postgres_handler(ngx_http_request_t *r)
+{
+ ngx_postgres_loc_conf_t *pglcf;
+ ngx_postgres_ctx_t *pgctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_upstream_t *u;
+ ngx_connection_t *c;
+ ngx_str_t host;
+ ngx_url_t url;
+ ngx_int_t rc;
+
+ dd("entering");
+
+ if (r->subrequest_in_memory) {
+ /* TODO: add support for subrequest in memory by
+ * emitting output into u->buffer instead */
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: ngx_postgres module does not support"
+ " subrequests in memory");
+
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ pglcf = ngx_http_get_module_loc_conf(r, ngx_postgres_module);
+
+ if ((pglcf->query.def == NULL) && !(pglcf->query.methods_set & r->method)) {
+ if (pglcf->query.methods_set != 0) {
+ dd("returning NGX_HTTP_NOT_ALLOWED");
+ return NGX_HTTP_NOT_ALLOWED;
+ }
+
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: missing \"postgres_query\" in location \"%V\"",
+ &clcf->name);
+
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ rc = ngx_http_discard_request_body(r);
+ if (rc != NGX_OK) {
+ dd("returning rc:%d", (int) rc);
+ return rc;
+ }
+
+#if defined(nginx_version) \
+ && (((nginx_version >= 7063) && (nginx_version < 8000)) \
+ || (nginx_version >= 8007))
+
+ if (ngx_http_upstream_create(r) != NGX_OK) {
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ u = r->upstream;
+
+#else /* 0.7.x < 0.7.63, 0.8.x < 0.8.7 */
+
+ u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
+ if (u == NULL) {
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ u->peer.log = r->connection->log;
+ u->peer.log_error = NGX_ERROR_ERR;
+# if (NGX_THREADS)
+ u->peer.lock = &r->connection->lock;
+# endif
+ r->upstream = u;
+#endif
+
+ if (pglcf->upstream_cv) {
+ /* use complex value */
+ if (ngx_http_complex_value(r, pglcf->upstream_cv, &host) != NGX_OK) {
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ if (host.len == 0) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: empty \"postgres_pass\" (was: \"%V\")"
+ " in location \"%V\"", &pglcf->upstream_cv->value,
+ &clcf->name);
+
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ url.host = host;
+ url.no_resolve = 1;
+
+ pglcf->upstream.upstream = ngx_postgres_find_upstream(r, &url);
+ if (pglcf->upstream.upstream == NULL) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: upstream name \"%V\" not found", &host);
+
+ dd("returning NGX_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ }
+
+ pgctx = ngx_pcalloc(r->pool, sizeof(ngx_postgres_ctx_t));
+ if (pgctx == NULL) {
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * pgctx->response = NULL
+ * pgctx->var_query = { 0, NULL }
+ * pgctx->variables = NULL
+ * pgctx->status = 0
+ */
+
+ pgctx->var_cols = NGX_ERROR;
+ pgctx->var_rows = NGX_ERROR;
+ pgctx->var_affected = NGX_ERROR;
+
+ if (pglcf->variables != NULL) {
+ pgctx->variables = ngx_array_create(r->pool, pglcf->variables->nelts,
+ sizeof(ngx_str_t));
+ if (pgctx->variables == NULL) {
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ /* fake ngx_array_push'ing */
+ pgctx->variables->nelts = pglcf->variables->nelts;
+
+ ngx_memzero(pgctx->variables->elts,
+ pgctx->variables->nelts * pgctx->variables->size);
+ }
+
+ ngx_http_set_ctx(r, pgctx, ngx_postgres_module);
+
+ u->schema.len = sizeof("postgres://") - 1;
+ u->schema.data = (u_char *) "postgres://";
+
+ u->output.tag = (ngx_buf_tag_t) &ngx_postgres_module;
+
+ u->conf = &pglcf->upstream;
+
+ u->create_request = ngx_postgres_create_request;
+ u->reinit_request = ngx_postgres_reinit_request;
+ u->process_header = ngx_postgres_process_header;
+ u->abort_request = ngx_postgres_abort_request;
+ u->finalize_request = ngx_postgres_finalize_request;
+
+ /* we bypass the upstream input filter mechanism in
+ * ngx_http_upstream_process_headers */
+
+ u->input_filter_init = ngx_postgres_input_filter_init;
+ u->input_filter = ngx_postgres_input_filter;
+ u->input_filter_ctx = NULL;
+
+#if defined(nginx_version) && (nginx_version >= 8011)
+ r->main->count++;
+#endif
+
+ ngx_http_upstream_init(r);
+
+ /* override the read/write event handler to our own */
+ u->write_event_handler = ngx_postgres_wev_handler;
+ u->read_event_handler = ngx_postgres_rev_handler;
+
+ /* a bit hack-ish way to return error response (clean-up part) */
+ if ((u->peer.connection) && (u->peer.connection->fd == 0)) {
+ c = u->peer.connection;
+ u->peer.connection = NULL;
+
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ if (c->pool) {
+ ngx_destroy_pool(c->pool);
+ }
+#endif
+
+ ngx_free_connection(c);
+
+ ngx_postgres_upstream_finalize_request(r, u,
+#if defined(nginx_version) && (nginx_version >= 8017)
+ NGX_HTTP_SERVICE_UNAVAILABLE);
+#else
+ pgctx->status ? pgctx->status : NGX_HTTP_INTERNAL_SERVER_ERROR);
+#endif
+ }
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+}
+
+void
+ngx_postgres_wev_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_connection_t *pgxc;
+
+ dd("entering");
+
+ /* just to ensure u->reinit_request always gets called for
+ * upstream_next */
+ u->request_sent = 1;
+
+ pgxc = u->peer.connection;
+
+ if (pgxc->write->timedout) {
+ dd("postgres connection write timeout");
+
+ ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
+
+ dd("returning");
+ return;
+ }
+
+ if (ngx_postgres_upstream_test_connect(pgxc) != NGX_OK) {
+ dd("postgres connection is broken");
+
+ ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+
+ dd("returning");
+ return;
+ }
+
+ ngx_postgres_process_events(r);
+
+ dd("returning");
+}
+
+void
+ngx_postgres_rev_handler(ngx_http_request_t *r, ngx_http_upstream_t *u)
+{
+ ngx_connection_t *pgxc;
+
+ dd("entering");
+
+ /* just to ensure u->reinit_request always gets called for
+ * upstream_next */
+ u->request_sent = 1;
+
+ pgxc = u->peer.connection;
+
+ if (pgxc->read->timedout) {
+ dd("postgres connection read timeout");
+
+ ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
+
+ dd("returning");
+ return;
+ }
+
+ if (ngx_postgres_upstream_test_connect(pgxc) != NGX_OK) {
+ dd("postgres connection is broken");
+
+ ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+
+ dd("returning");
+ return;
+ }
+
+ ngx_postgres_process_events(r);
+
+ dd("returning");
+}
+
+ngx_int_t
+ngx_postgres_create_request(ngx_http_request_t *r)
+{
+ dd("entering");
+
+ r->upstream->request_bufs = NULL;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_reinit_request(ngx_http_request_t *r)
+{
+ ngx_http_upstream_t *u;
+
+ dd("entering");
+
+ u = r->upstream;
+
+ /* override the read/write event handler to our own */
+ u->write_event_handler = ngx_postgres_wev_handler;
+ u->read_event_handler = ngx_postgres_rev_handler;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+void
+ngx_postgres_abort_request(ngx_http_request_t *r)
+{
+ dd("entering & returning (dummy function)");
+}
+
+void
+ngx_postgres_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering");
+
+ if (rc == NGX_OK) {
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ ngx_postgres_output_chain(r, pgctx->response);
+ }
+
+ dd("returning");
+}
+
+ngx_int_t
+ngx_postgres_process_header(ngx_http_request_t *r)
+{
+ dd("entering");
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: ngx_postgres_process_header should not"
+ " be called by the upstream");
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+}
+
+ngx_int_t
+ngx_postgres_input_filter_init(void *data)
+{
+ ngx_http_request_t *r = data;
+
+ dd("entering");
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: ngx_postgres_input_filter_init should not"
+ " be called by the upstream");
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+}
+
+ngx_int_t
+ngx_postgres_input_filter(void *data, ssize_t bytes)
+{
+ ngx_http_request_t *r = data;
+
+ dd("entering");
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: ngx_postgres_input_filter should not"
+ " be called by the upstream");
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_handler.h b/ngx_postgres-1.0/src/ngx_postgres_handler.h
new file mode 100644
index 0000000..1b6715b
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_handler.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_HANDLER_H_
+#define _NGX_POSTGRES_HANDLER_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+ngx_int_t ngx_postgres_handler(ngx_http_request_t *);
+void ngx_postgres_wev_handler(ngx_http_request_t *,
+ ngx_http_upstream_t *);
+void ngx_postgres_rev_handler(ngx_http_request_t *,
+ ngx_http_upstream_t *);
+ngx_int_t ngx_postgres_create_request(ngx_http_request_t *);
+ngx_int_t ngx_postgres_reinit_request(ngx_http_request_t *);
+void ngx_postgres_abort_request(ngx_http_request_t *);
+void ngx_postgres_finalize_request(ngx_http_request_t *, ngx_int_t);
+ngx_int_t ngx_postgres_process_header(ngx_http_request_t *);
+ngx_int_t ngx_postgres_input_filter_init(void *);
+ngx_int_t ngx_postgres_input_filter(void *, ssize_t);
+
+#endif /* _NGX_POSTGRES_HANDLER_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_keepalive.c b/ngx_postgres-1.0/src/ngx_postgres_keepalive.c
new file mode 100644
index 0000000..6575338
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_keepalive.c
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * Copyright (C) 2008, Maxim Dounin
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_keepalive.h"
+
+
+ngx_int_t
+ngx_postgres_keepalive_init(ngx_pool_t *pool,
+ ngx_postgres_upstream_srv_conf_t *pgscf)
+{
+ ngx_postgres_keepalive_cache_t *cached;
+ ngx_uint_t i;
+
+ dd("entering");
+
+ cached = ngx_pcalloc(pool,
+ sizeof(ngx_postgres_keepalive_cache_t) * pgscf->max_cached);
+ if (cached == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ ngx_queue_init(&pgscf->cache);
+ ngx_queue_init(&pgscf->free);
+
+ for (i = 0; i < pgscf->max_cached; i++) {
+ ngx_queue_insert_head(&pgscf->free, &cached[i].queue);
+ cached[i].srv_conf = pgscf;
+ }
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_keepalive_get_peer_single(ngx_peer_connection_t *pc,
+ ngx_postgres_upstream_peer_data_t *pgp,
+ ngx_postgres_upstream_srv_conf_t *pgscf)
+{
+ ngx_postgres_keepalive_cache_t *item;
+ ngx_queue_t *q;
+ ngx_connection_t *c;
+
+ dd("entering");
+
+ if (!ngx_queue_empty(&pgscf->cache)) {
+ dd("non-empty queue");
+
+ q = ngx_queue_head(&pgscf->cache);
+ ngx_queue_remove(q);
+
+ item = ngx_queue_data(q, ngx_postgres_keepalive_cache_t, queue);
+ c = item->connection;
+
+ ngx_queue_insert_head(&pgscf->free, q);
+
+ c->idle = 0;
+ c->log = pc->log;
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ c->pool->log = pc->log;
+#endif
+ c->read->log = pc->log;
+ c->write->log = pc->log;
+
+ pgp->name.data = item->name.data;
+ pgp->name.len = item->name.len;
+
+ pgp->sockaddr = item->sockaddr;
+
+ pgp->pgconn = item->pgconn;
+
+ pc->connection = c;
+ pc->cached = 1;
+
+ pc->name = &pgp->name;
+
+ pc->sockaddr = &pgp->sockaddr;
+ pc->socklen = item->socklen;
+
+ dd("returning NGX_DONE");
+
+ return NGX_DONE;
+ }
+
+ dd("returning NGX_DECLINED");
+ return NGX_DECLINED;
+}
+
+ngx_int_t
+ngx_postgres_keepalive_get_peer_multi(ngx_peer_connection_t *pc,
+ ngx_postgres_upstream_peer_data_t *pgp,
+ ngx_postgres_upstream_srv_conf_t *pgscf)
+{
+ ngx_postgres_keepalive_cache_t *item;
+ ngx_queue_t *q, *cache;
+ ngx_connection_t *c;
+
+ dd("entering");
+
+ cache = &pgscf->cache;
+
+ for (q = ngx_queue_head(cache);
+ q != ngx_queue_sentinel(cache);
+ q = ngx_queue_next(q))
+ {
+ item = ngx_queue_data(q, ngx_postgres_keepalive_cache_t, queue);
+ c = item->connection;
+
+ if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr,
+ item->socklen, pc->socklen) == 0)
+ {
+ ngx_queue_remove(q);
+ ngx_queue_insert_head(&pgscf->free, q);
+
+ c->idle = 0;
+ c->log = pc->log;
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ c->pool->log = pc->log;
+#endif
+ c->read->log = pc->log;
+ c->write->log = pc->log;
+
+ pc->connection = c;
+ pc->cached = 1;
+
+ /* we do not need to resume the peer name
+ * because we already take the right value outside */
+
+ pgp->pgconn = item->pgconn;
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+ }
+ }
+
+ dd("returning NGX_DECLINED");
+ return NGX_DECLINED;
+}
+
+void
+ngx_postgres_keepalive_free_peer(ngx_peer_connection_t *pc,
+ ngx_postgres_upstream_peer_data_t *pgp,
+ ngx_postgres_upstream_srv_conf_t *pgscf, ngx_uint_t state)
+{
+ ngx_postgres_keepalive_cache_t *item;
+ ngx_queue_t *q;
+ ngx_connection_t *c;
+ ngx_http_upstream_t *u;
+
+ dd("entering");
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "postgres: free keepalive peer");
+
+ if (state & NGX_PEER_FAILED) {
+ pgp->failed = 1;
+ }
+
+ u = pgp->upstream;
+
+ if ((!pgp->failed) && (pc->connection != NULL)
+ && (u->headers_in.status_n == NGX_HTTP_OK))
+ {
+ c = pc->connection;
+
+ if (c->read->timer_set) {
+ ngx_del_timer(c->read);
+ }
+
+ if (c->write->timer_set) {
+ ngx_del_timer(c->write);
+ }
+
+ if (c->write->active && (ngx_event_flags & NGX_USE_LEVEL_EVENT)) {
+ if (ngx_del_event(c->write, NGX_WRITE_EVENT, 0) != NGX_OK) {
+ return;
+ }
+ }
+
+ pc->connection = NULL;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "postgres: free keepalive peer: saving connection %p",
+ c);
+
+ if (ngx_queue_empty(&pgscf->free)) {
+ /* connection pool is already full */
+
+ q = ngx_queue_last(&pgscf->cache);
+ ngx_queue_remove(q);
+
+ item = ngx_queue_data(q, ngx_postgres_keepalive_cache_t,
+ queue);
+
+ ngx_postgres_upstream_free_connection(pc->log, item->connection,
+ item->pgconn, pgscf);
+
+ } else {
+ q = ngx_queue_head(&pgscf->free);
+ ngx_queue_remove(q);
+
+ item = ngx_queue_data(q, ngx_postgres_keepalive_cache_t,
+ queue);
+ }
+
+ item->connection = c;
+ ngx_queue_insert_head(&pgscf->cache, q);
+
+ c->write->handler = ngx_postgres_keepalive_dummy_handler;
+ c->read->handler = ngx_postgres_keepalive_close_handler;
+
+ c->data = item;
+ c->idle = 1;
+ c->log = ngx_cycle->log;
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ c->pool->log = ngx_cycle->log;
+#endif
+ c->read->log = ngx_cycle->log;
+ c->write->log = ngx_cycle->log;
+
+ item->socklen = pc->socklen;
+ ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen);
+
+ item->pgconn = pgp->pgconn;
+
+ item->name.data = pgp->name.data;
+ item->name.len = pgp->name.len;
+ }
+
+ dd("returning");
+}
+
+void
+ngx_postgres_keepalive_dummy_handler(ngx_event_t *ev)
+{
+ dd("entering & returning (dummy handler)");
+}
+
+void
+ngx_postgres_keepalive_close_handler(ngx_event_t *ev)
+{
+ ngx_postgres_upstream_srv_conf_t *pgscf;
+ ngx_postgres_keepalive_cache_t *item;
+ ngx_connection_t *c;
+ PGresult *res;
+
+ dd("entering");
+
+ c = ev->data;
+ item = c->data;
+
+ if (c->close) {
+ goto close;
+ }
+
+ if (PQconsumeInput(item->pgconn) && !PQisBusy(item->pgconn)) {
+ res = PQgetResult(item->pgconn);
+ if (res == NULL) {
+ dd("returning");
+ return;
+ }
+
+ PQclear(res);
+
+ dd("received result on idle keepalive connection");
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "postgres: received result on idle keepalive connection");
+ }
+
+close:
+
+ pgscf = item->srv_conf;
+
+ ngx_postgres_upstream_free_connection(ev->log, c, item->pgconn, pgscf);
+
+ ngx_queue_remove(&item->queue);
+ ngx_queue_insert_head(&pgscf->free, &item->queue);
+
+ dd("returning");
+}
+
+void
+ngx_postgres_keepalive_cleanup(void *data)
+{
+ ngx_postgres_upstream_srv_conf_t *pgscf = data;
+ ngx_postgres_keepalive_cache_t *item;
+ ngx_queue_t *q;
+
+ dd("entering");
+
+ /* ngx_queue_empty is broken when used on unitialized queue */
+ if (pgscf->cache.prev == NULL) {
+ dd("returning");
+ return;
+ }
+
+ /* just to be on the safe-side */
+ pgscf->max_cached = 0;
+
+ while (!ngx_queue_empty(&pgscf->cache)) {
+ q = ngx_queue_head(&pgscf->cache);
+ ngx_queue_remove(q);
+
+ item = ngx_queue_data(q, ngx_postgres_keepalive_cache_t,
+ queue);
+
+ dd("postgres: disconnecting %p", item->connection);
+
+ ngx_postgres_upstream_free_connection(item->connection->log,
+ item->connection,
+ item->pgconn, pgscf);
+ }
+
+ dd("returning");
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_keepalive.h b/ngx_postgres-1.0/src/ngx_postgres_keepalive.h
new file mode 100644
index 0000000..31e024a
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_keepalive.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * Copyright (C) 2008, Maxim Dounin
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_KEEPALIVE_H_
+#define _NGX_POSTGRES_KEEPALIVE_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <libpq-fe.h>
+
+#include "ngx_postgres_module.h"
+#include "ngx_postgres_upstream.h"
+
+
+typedef struct {
+ ngx_queue_t queue;
+ ngx_postgres_upstream_srv_conf_t *srv_conf;
+ ngx_connection_t *connection;
+ PGconn *pgconn;
+ struct sockaddr sockaddr;
+ socklen_t socklen;
+ ngx_str_t name;
+} ngx_postgres_keepalive_cache_t;
+
+
+ngx_int_t ngx_postgres_keepalive_init(ngx_pool_t *,
+ ngx_postgres_upstream_srv_conf_t *);
+ngx_int_t ngx_postgres_keepalive_get_peer_single(ngx_peer_connection_t *,
+ ngx_postgres_upstream_peer_data_t *,
+ ngx_postgres_upstream_srv_conf_t *);
+ngx_int_t ngx_postgres_keepalive_get_peer_multi(ngx_peer_connection_t *,
+ ngx_postgres_upstream_peer_data_t *,
+ ngx_postgres_upstream_srv_conf_t *);
+void ngx_postgres_keepalive_free_peer(ngx_peer_connection_t *,
+ ngx_postgres_upstream_peer_data_t *,
+ ngx_postgres_upstream_srv_conf_t *, ngx_uint_t);
+void ngx_postgres_keepalive_dummy_handler(ngx_event_t *);
+void ngx_postgres_keepalive_close_handler(ngx_event_t *);
+void ngx_postgres_keepalive_cleanup(void *);
+
+#endif /* _NGX_POSTGRES_KEEPALIVE_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_module.c b/ngx_postgres-1.0/src/ngx_postgres_module.c
new file mode 100644
index 0000000..407a366
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_module.c
@@ -0,0 +1,1338 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_escape.h"
+#include "ngx_postgres_handler.h"
+#include "ngx_postgres_keepalive.h"
+#include "ngx_postgres_module.h"
+#include "ngx_postgres_output.h"
+#include "ngx_postgres_upstream.h"
+#include "ngx_postgres_util.h"
+#include "ngx_postgres_variable.h"
+#include "ngx_postgres_rewrite.h"
+
+
+#define NGX_CONF_TAKE34 (NGX_CONF_TAKE3|NGX_CONF_TAKE4)
+
+
+static ngx_command_t ngx_postgres_module_commands[] = {
+
+ { ngx_string("postgres_server"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
+ ngx_postgres_conf_server,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_keepalive"),
+ NGX_HTTP_UPS_CONF|NGX_CONF_1MORE,
+ ngx_postgres_conf_keepalive,
+ NGX_HTTP_SRV_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_pass"),
+ NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_postgres_conf_pass,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_query"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|
+ NGX_HTTP_LIF_CONF|NGX_CONF_1MORE,
+ ngx_postgres_conf_query,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_rewrite"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|
+ NGX_HTTP_LIF_CONF|NGX_CONF_2MORE,
+ ngx_postgres_conf_rewrite,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_output"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|
+ NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1,
+ ngx_postgres_conf_output,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_set"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE34,
+ ngx_postgres_conf_set,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_escape"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
+ ngx_postgres_conf_escape,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ 0,
+ NULL },
+
+ { ngx_string("postgres_connect_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_postgres_loc_conf_t, upstream.connect_timeout),
+ NULL },
+
+ { ngx_string("postgres_result_timeout"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_postgres_loc_conf_t, upstream.read_timeout),
+ NULL },
+
+ ngx_null_command
+};
+
+static ngx_http_variable_t ngx_postgres_module_variables[] = {
+
+ { ngx_string("postgres_columns"), NULL,
+ ngx_postgres_variable_columns, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_string("postgres_rows"), NULL,
+ ngx_postgres_variable_rows, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_string("postgres_affected"), NULL,
+ ngx_postgres_variable_affected, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_string("postgres_query"), NULL,
+ ngx_postgres_variable_query, 0,
+ NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_NOHASH, 0 },
+
+ { ngx_null_string, NULL, NULL, 0, 0, 0 }
+};
+
+static ngx_http_module_t ngx_postgres_module_ctx = {
+ ngx_postgres_add_variables, /* preconfiguration */
+ NULL, /* postconfiguration */
+
+ NULL, /* create main configuration */
+ NULL, /* init main configuration */
+
+ ngx_postgres_create_upstream_srv_conf, /* create server configuration */
+ NULL, /* merge server configuration */
+
+ ngx_postgres_create_loc_conf, /* create location configuration */
+ ngx_postgres_merge_loc_conf /* merge location configuration */
+};
+
+ngx_module_t ngx_postgres_module = {
+ NGX_MODULE_V1,
+ &ngx_postgres_module_ctx, /* module context */
+ ngx_postgres_module_commands, /* module directives */
+ NGX_HTTP_MODULE, /* module type */
+ NULL, /* init master */
+ NULL, /* init module */
+ NULL, /* init process */
+ NULL, /* init thread */
+ NULL, /* exit thread */
+ NULL, /* exit process */
+ NULL, /* exit master */
+ NGX_MODULE_V1_PADDING
+};
+
+ngx_conf_bitmask_t ngx_postgres_http_methods[] = {
+ { ngx_string("GET"), NGX_HTTP_GET },
+ { ngx_string("HEAD"), NGX_HTTP_HEAD },
+ { ngx_string("POST"), NGX_HTTP_POST },
+ { ngx_string("PUT"), NGX_HTTP_PUT },
+ { ngx_string("DELETE"), NGX_HTTP_DELETE },
+ { ngx_string("MKCOL"), NGX_HTTP_MKCOL },
+ { ngx_string("COPY"), NGX_HTTP_COPY },
+ { ngx_string("MOVE"), NGX_HTTP_MOVE },
+ { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS },
+ { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND },
+ { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH },
+ { ngx_string("LOCK"), NGX_HTTP_LOCK },
+ { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK },
+#if defined(nginx_version) && (nginx_version >= 8041)
+ { ngx_string("PATCH"), NGX_HTTP_PATCH },
+#endif
+ { ngx_null_string, 0 }
+};
+
+ngx_conf_enum_t ngx_postgres_upstream_mode_options[] = {
+ { ngx_string("multi"), 0 },
+ { ngx_string("single"), 1 },
+ { ngx_null_string, 0 }
+};
+
+ngx_conf_enum_t ngx_postgres_upstream_overflow_options[] = {
+ { ngx_string("ignore"), 0 },
+ { ngx_string("reject"), 1 },
+ { ngx_null_string, 0 }
+};
+
+ngx_conf_enum_t ngx_postgres_requirement_options[] = {
+ { ngx_string("optional"), 0 },
+ { ngx_string("required"), 1 },
+ { ngx_null_string, 0 }
+};
+
+ngx_postgres_rewrite_enum_t ngx_postgres_rewrite_handlers[] = {
+ { ngx_string("no_changes"), 0, ngx_postgres_rewrite_changes },
+ { ngx_string("changes"), 1, ngx_postgres_rewrite_changes },
+ { ngx_string("no_rows"), 2, ngx_postgres_rewrite_rows },
+ { ngx_string("rows"), 3, ngx_postgres_rewrite_rows },
+ { ngx_null_string, 0, NULL }
+};
+
+ngx_postgres_output_enum_t ngx_postgres_output_handlers[] = {
+ { ngx_string("none"), 0, NULL },
+ { ngx_string("rds"), 0, ngx_postgres_output_rds },
+ { ngx_string("text") , 0, ngx_postgres_output_text },
+ { ngx_string("value"), 0, ngx_postgres_output_value },
+ { ngx_string("binary_value"), 1, ngx_postgres_output_value },
+ { ngx_null_string, 0, NULL }
+};
+
+
+ngx_int_t
+ngx_postgres_add_variables(ngx_conf_t *cf)
+{
+ ngx_http_variable_t *var, *v;
+
+ dd("entering");
+
+ for (v = ngx_postgres_module_variables; v->name.len; v++) {
+ var = ngx_http_add_variable(cf, &v->name, v->flags);
+ if (var == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ var->get_handler = v->get_handler;
+ var->data = v->data;
+ }
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+void *
+ngx_postgres_create_upstream_srv_conf(ngx_conf_t *cf)
+{
+ ngx_postgres_upstream_srv_conf_t *conf;
+ ngx_pool_cleanup_t *cln;
+
+ dd("entering");
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_postgres_upstream_srv_conf_t));
+ if (conf == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->peers = NULL
+ * conf->current = 0
+ * conf->servers = NULL
+ * conf->free = { NULL, NULL }
+ * conf->cache = { NULL, NULL }
+ * conf->active_conns = 0
+ * conf->reject = 0
+ */
+
+ conf->pool = cf->pool;
+
+ /* enable keepalive (single) by default */
+ conf->max_cached = 10;
+ conf->single = 1;
+
+ cln = ngx_pool_cleanup_add(cf->pool, 0);
+ cln->handler = ngx_postgres_keepalive_cleanup;
+ cln->data = conf;
+
+ dd("returning");
+ return conf;
+}
+
+void *
+ngx_postgres_create_loc_conf(ngx_conf_t *cf)
+{
+ ngx_postgres_loc_conf_t *conf;
+
+ dd("entering");
+
+ conf = ngx_pcalloc(cf->pool, sizeof(ngx_postgres_loc_conf_t));
+ if (conf == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ /*
+ * set by ngx_pcalloc():
+ *
+ * conf->upstream.* = 0 / NULL
+ * conf->upstream_cv = NULL
+ * conf->query.methods_set = 0
+ * conf->query.methods = NULL
+ * conf->query.def = NULL
+ * conf->output_binary = 0
+ */
+
+ conf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
+ conf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
+
+ conf->rewrites = NGX_CONF_UNSET_PTR;
+ conf->output_handler = NGX_CONF_UNSET_PTR;
+ conf->variables = NGX_CONF_UNSET_PTR;
+
+ /* the hardcoded values */
+ conf->upstream.cyclic_temp_file = 0;
+ conf->upstream.buffering = 1;
+ conf->upstream.ignore_client_abort = 1;
+ conf->upstream.send_lowat = 0;
+ conf->upstream.bufs.num = 0;
+ conf->upstream.busy_buffers_size = 0;
+ conf->upstream.max_temp_file_size = 0;
+ conf->upstream.temp_file_write_size = 0;
+ conf->upstream.intercept_errors = 1;
+ conf->upstream.intercept_404 = 1;
+ conf->upstream.pass_request_headers = 0;
+ conf->upstream.pass_request_body = 0;
+
+ dd("returning");
+ return conf;
+}
+
+char *
+ngx_postgres_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+ ngx_postgres_loc_conf_t *prev = parent;
+ ngx_postgres_loc_conf_t *conf = child;
+
+ dd("entering");
+
+ ngx_conf_merge_msec_value(conf->upstream.connect_timeout,
+ prev->upstream.connect_timeout, 10000);
+
+ ngx_conf_merge_msec_value(conf->upstream.read_timeout,
+ prev->upstream.read_timeout, 30000);
+
+ if ((conf->upstream.upstream == NULL) && (conf->upstream_cv == NULL)) {
+ conf->upstream.upstream = prev->upstream.upstream;
+ conf->upstream_cv = prev->upstream_cv;
+ }
+
+ if ((conf->query.def == NULL) && (conf->query.methods == NULL)) {
+ conf->query.methods_set = prev->query.methods_set;
+ conf->query.methods = prev->query.methods;
+ conf->query.def = prev->query.def;
+ }
+
+ ngx_conf_merge_ptr_value(conf->rewrites, prev->rewrites, NULL);
+
+ if (conf->output_handler == NGX_CONF_UNSET_PTR) {
+ if (prev->output_handler == NGX_CONF_UNSET_PTR) {
+ /* default */
+ conf->output_handler = ngx_postgres_output_rds;
+ conf->output_binary = 0;
+ } else {
+ /* merge */
+ conf->output_handler = prev->output_handler;
+ conf->output_binary = prev->output_binary;
+ }
+ }
+
+ ngx_conf_merge_ptr_value(conf->variables, prev->variables, NULL);
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+/*
+ * Based on: ngx_http_upstream.c/ngx_http_upstream_server
+ * Copyright (C) Igor Sysoev
+ */
+char *
+ngx_postgres_conf_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_postgres_upstream_srv_conf_t *pgscf = conf;
+ ngx_postgres_upstream_server_t *pgs;
+ ngx_http_upstream_srv_conf_t *uscf;
+ ngx_url_t u;
+ ngx_uint_t i;
+
+ dd("entering");
+
+ uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
+
+ if (pgscf->servers == NULL) {
+ pgscf->servers = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_postgres_upstream_server_t));
+ if (pgscf->servers == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ uscf->servers = pgscf->servers;
+ }
+
+ pgs = ngx_array_push(pgscf->servers);
+ if (pgs == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(pgs, sizeof(ngx_postgres_upstream_server_t));
+
+ /* parse the first name:port argument */
+
+ ngx_memzero(&u, sizeof(ngx_url_t));
+
+ u.url = value[1];
+ u.default_port = 5432; /* PostgreSQL default */
+
+ if (ngx_parse_url(cf->pool, &u) != NGX_OK) {
+ if (u.err) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: %s in upstream \"%V\"",
+ u.err, &u.url);
+ }
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pgs->addrs = u.addrs;
+ pgs->naddrs = u.naddrs;
+ pgs->port = u.port;
+
+ /* parse various options */
+ for (i = 2; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "dbname=", sizeof("dbname=") - 1)
+ == 0)
+ {
+ pgs->dbname.len = value[i].len - (sizeof("dbname=") - 1);
+ pgs->dbname.data = &value[i].data[sizeof("dbname=") - 1];
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "user=", sizeof("user=") - 1)
+ == 0)
+ {
+ pgs->user.len = value[i].len - (sizeof("user=") - 1);
+ pgs->user.data = &value[i].data[sizeof("user=") - 1];
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "password=", sizeof("password=") - 1)
+ == 0)
+ {
+ pgs->password.len = value[i].len - (sizeof("password=") - 1);
+ pgs->password.data = &value[i].data[sizeof("password=") - 1];
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid parameter \"%V\" in"
+ " \"postgres_server\"", &value[i]);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ uscf->peer.init_upstream = ngx_postgres_upstream_init;
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+char *
+ngx_postgres_conf_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_postgres_upstream_srv_conf_t *pgscf = conf;
+ ngx_conf_enum_t *e;
+ ngx_uint_t i, j;
+ ngx_int_t n;
+
+ dd("entering");
+
+ if (pgscf->max_cached != 10 /* default */) {
+ dd("returning");
+ return "is duplicate";
+ }
+
+ if ((cf->args->nelts == 2) && (ngx_strcmp(value[1].data, "off") == 0)) {
+ pgscf->max_cached = 0;
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+ }
+
+ for (i = 1; i < cf->args->nelts; i++) {
+
+ if (ngx_strncmp(value[i].data, "max=", sizeof("max=") - 1)
+ == 0)
+ {
+ value[i].len = value[i].len - (sizeof("max=") - 1);
+ value[i].data = &value[i].data[sizeof("max=") - 1];
+
+ n = ngx_atoi(value[i].data, value[i].len);
+ if (n == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid \"max\" value \"%V\""
+ " in \"%V\" directive",
+ &value[i], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pgscf->max_cached = (ngx_uint_t) n;
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "mode=", sizeof("mode=") - 1)
+ == 0)
+ {
+ value[i].len = value[i].len - (sizeof("mode=") - 1);
+ value[i].data = &value[i].data[sizeof("mode=") - 1];
+
+ e = ngx_postgres_upstream_mode_options;
+ for (j = 0; e[j].name.len; j++) {
+ if ((e[j].name.len == value[i].len)
+ && (ngx_strcasecmp(e[j].name.data, value[i].data) == 0))
+ {
+ pgscf->single = e[j].value;
+ break;
+ }
+ }
+
+ if (e[j].name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid \"mode\" value \"%V\""
+ " in \"%V\" directive",
+ &value[i], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ if (ngx_strncmp(value[i].data, "overflow=", sizeof("overflow=") - 1)
+ == 0)
+ {
+ value[i].len = value[i].len - (sizeof("overflow=") - 1);
+ value[i].data = &value[i].data[sizeof("overflow=") - 1];
+
+ e = ngx_postgres_upstream_overflow_options;
+ for (j = 0; e[j].name.len; j++) {
+ if ((e[j].name.len == value[i].len)
+ && (ngx_strcasecmp(e[j].name.data, value[i].data) == 0))
+ {
+ pgscf->reject = e[j].value;
+ break;
+ }
+ }
+
+ if (e[j].name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid \"overflow\" value \"%V\""
+ " in \"%V\" directive",
+ &value[i], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ continue;
+ }
+
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid parameter \"%V\" in"
+ " \"%V\" directive",
+ &value[i], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+char *
+ngx_postgres_conf_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_postgres_loc_conf_t *pglcf = conf;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_compile_complex_value_t ccv;
+ ngx_url_t url;
+
+ dd("entering");
+
+ if ((pglcf->upstream.upstream != NULL) || (pglcf->upstream_cv != NULL)) {
+ dd("returning");
+ return "is duplicate";
+ }
+
+ if (value[1].len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: empty upstream in \"%V\" directive",
+ &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+
+ clcf->handler = ngx_postgres_handler;
+
+ if (clcf->name.data[clcf->name.len - 1] == '/') {
+ clcf->auto_redirect = 1;
+ }
+
+ if (ngx_http_script_variables_count(&value[1])) {
+ /* complex value */
+ dd("complex value");
+
+ pglcf->upstream_cv = ngx_palloc(cf->pool,
+ sizeof(ngx_http_complex_value_t));
+ if (pglcf->upstream_cv == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &value[1];
+ ccv.complex_value = pglcf->upstream_cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+ } else {
+ /* simple value */
+ dd("simple value");
+
+ ngx_memzero(&url, sizeof(ngx_url_t));
+
+ url.url = value[1];
+ url.no_resolve = 1;
+
+ pglcf->upstream.upstream = ngx_http_upstream_add(cf, &url, 0);
+ if (pglcf->upstream.upstream == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+ }
+}
+
+char *
+ngx_postgres_conf_query(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_str_t sql = value[cf->args->nelts - 1];
+ ngx_postgres_loc_conf_t *pglcf = conf;
+ ngx_http_compile_complex_value_t ccv;
+ ngx_postgres_mixed_t *query;
+ ngx_conf_bitmask_t *b;
+ ngx_uint_t methods, i, j;
+
+ dd("entering");
+
+ if (sql.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: empty query in \"%V\" directive",
+ &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 2) {
+ /* default query */
+ dd("default query");
+
+ if (pglcf->query.def != NULL) {
+ dd("returning");
+ return "is duplicate";
+ }
+
+ pglcf->query.def = ngx_palloc(cf->pool, sizeof(ngx_postgres_mixed_t));
+ if (pglcf->query.def == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ methods = 0xFFFF;
+ query = pglcf->query.def;
+ } else {
+ /* method-specific query */
+ dd("method-specific query");
+
+ methods = 0;
+
+ for (i = 1; i < cf->args->nelts - 1; i++) {
+ b = ngx_postgres_http_methods;
+ for (j = 0; b[j].name.len; j++) {
+ if ((b[j].name.len == value[i].len)
+ && (ngx_strcasecmp(b[j].name.data, value[i].data) == 0))
+ {
+ if (pglcf->query.methods_set & b[j].mask) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: method \"%V\" is"
+ " duplicate in \"%V\" directive",
+ &value[i], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ methods |= b[j].mask;
+ break;
+ }
+ }
+
+ if (b[j].name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid method \"%V\""
+ " in \"%V\" directive",
+ &value[i], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (pglcf->query.methods == NULL) {
+ pglcf->query.methods = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_postgres_mixed_t));
+ if (pglcf->query.methods == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ query = ngx_array_push(pglcf->query.methods);
+ if (query == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pglcf->query.methods_set |= methods;
+ }
+
+ if (ngx_http_script_variables_count(&sql)) {
+ /* complex value */
+ dd("complex value");
+
+ query->key = methods;
+
+ query->cv = ngx_palloc(cf->pool, sizeof(ngx_http_complex_value_t));
+ if (query->cv == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
+
+ ccv.cf = cf;
+ ccv.value = &sql;
+ ccv.complex_value = query->cv;
+
+ if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ } else {
+ /* simple value */
+ dd("simple value");
+
+ query->key = methods;
+ query->sv = sql;
+ query->cv = NULL;
+ }
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+char *
+ngx_postgres_conf_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_str_t what = value[cf->args->nelts - 2];
+ ngx_str_t to = value[cf->args->nelts - 1];
+ ngx_postgres_loc_conf_t *pglcf = conf;
+ ngx_postgres_rewrite_conf_t *pgrcf;
+ ngx_postgres_rewrite_t *rewrite;
+ ngx_postgres_rewrite_enum_t *e;
+ ngx_conf_bitmask_t *b;
+ ngx_uint_t methods, keep_body, i, j;
+
+ dd("entering");
+
+ e = ngx_postgres_rewrite_handlers;
+ for (i = 0; e[i].name.len; i++) {
+ if ((e[i].name.len == what.len)
+ && (ngx_strcasecmp(e[i].name.data, what.data) == 0))
+ {
+ break;
+ }
+ }
+
+ if (e[i].name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid condition \"%V\""
+ " in \"%V\" directive", &what, &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (pglcf->rewrites == NGX_CONF_UNSET_PTR) {
+ pglcf->rewrites = ngx_array_create(cf->pool, 2,
+ sizeof(ngx_postgres_rewrite_conf_t));
+ if (pglcf->rewrites == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ } else {
+ pgrcf = pglcf->rewrites->elts;
+ for (j = 0; j < pglcf->rewrites->nelts; j++) {
+ if (pgrcf[j].key == e[i].key) {
+ pgrcf = &pgrcf[j];
+ goto found;
+ }
+ }
+ }
+
+ pgrcf = ngx_array_push(pglcf->rewrites);
+ if (pgrcf == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ ngx_memzero(pgrcf, sizeof(ngx_postgres_rewrite_conf_t));
+
+ pgrcf->key = e[i].key;
+ pgrcf->handler = e[i].handler;
+
+found:
+
+ if (cf->args->nelts == 3) {
+ /* default rewrite */
+ dd("default rewrite");
+
+ if (pgrcf->def != NULL) {
+ dd("returning");
+ return "is duplicate";
+ }
+
+ pgrcf->def = ngx_palloc(cf->pool, sizeof(ngx_postgres_rewrite_t));
+ if (pgrcf->def == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ methods = 0xFFFF;
+ rewrite = pgrcf->def;
+ } else {
+ /* method-specific rewrite */
+ dd("method-specific rewrite");
+
+ methods = 0;
+
+ for (i = 1; i < cf->args->nelts - 2; i++) {
+ b = ngx_postgres_http_methods;
+ for (j = 0; b[j].name.len; j++) {
+ if ((b[j].name.len == value[i].len)
+ && (ngx_strcasecmp(b[j].name.data, value[i].data) == 0))
+ {
+ if (pgrcf->methods_set & b[j].mask) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: method \"%V\" for"
+ " condition \"%V\" is duplicate"
+ " in \"%V\" directive",
+ &value[i], &what, &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ methods |= b[j].mask;
+ break;
+ }
+ }
+
+ if (b[j].name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid method \"%V\" for"
+ " condition \"%V\" in \"%V\" directive",
+ &value[i], &what, &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ if (pgrcf->methods == NULL) {
+ pgrcf->methods = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_postgres_rewrite_t));
+ if (pgrcf->methods == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ rewrite = ngx_array_push(pgrcf->methods);
+ if (rewrite == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pgrcf->methods_set |= methods;
+ }
+
+ if (to.data[0] == '=') {
+ keep_body = 1;
+ to.len--;
+ to.data++;
+ } else {
+ keep_body = 0;
+ }
+
+ rewrite->key = methods;
+ rewrite->status = ngx_atoi(to.data, to.len);
+ if ((rewrite->status == NGX_ERROR)
+ || (rewrite->status < NGX_HTTP_OK)
+ || (rewrite->status > NGX_HTTP_INSUFFICIENT_STORAGE)
+ || ((rewrite->status >= NGX_HTTP_SPECIAL_RESPONSE)
+ && (rewrite->status < NGX_HTTP_BAD_REQUEST)))
+ {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid status value \"%V\" for"
+ " condition \"%V\" in \"%V\" directive",
+ &to, &what, &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (keep_body) {
+ rewrite->status = -rewrite->status;
+ }
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+char *
+ngx_postgres_conf_output(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_postgres_loc_conf_t *pglcf = conf;
+ ngx_postgres_output_enum_t *e;
+ ngx_uint_t i;
+
+ dd("entering");
+
+ if (pglcf->output_handler != NGX_CONF_UNSET_PTR) {
+ dd("returning");
+ return "is duplicate";
+ }
+
+ e = ngx_postgres_output_handlers;
+ for (i = 0; e[i].name.len; i++) {
+ if ((e[i].name.len == value[1].len)
+ && (ngx_strcasecmp(e[i].name.data, value[1].data) == 0))
+ {
+ pglcf->output_handler = e[i].handler;
+ break;
+ }
+ }
+
+ if (e[i].name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid output format \"%V\""
+ " in \"%V\" directive", &value[1], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pglcf->output_binary = e[i].binary;
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+char *
+ngx_postgres_conf_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_postgres_loc_conf_t *pglcf = conf;
+ ngx_postgres_variable_t *pgvar;
+ ngx_conf_enum_t *e;
+ ngx_int_t idx;
+ ngx_uint_t i;
+
+ dd("entering");
+
+ if (value[1].len < 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: empty variable name in \"%V\" directive",
+ &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (value[1].data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid variable name \"%V\""
+ " in \"%V\" directive", &value[1], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ value[1].len--;
+ value[1].data++;
+
+ if (value[3].len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: empty column in \"%V\" directive",
+ &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (pglcf->variables == NGX_CONF_UNSET_PTR) {
+ pglcf->variables = ngx_array_create(cf->pool, 4,
+ sizeof(ngx_postgres_variable_t));
+ if (pglcf->variables == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ pgvar = ngx_array_push(pglcf->variables);
+ if (pgvar == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pgvar->idx = pglcf->variables->nelts - 1;
+
+ pgvar->var = ngx_http_add_variable(cf, &value[1], 0);
+ if (pgvar->var == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ idx = ngx_http_get_variable_index(cf, &value[1]);
+ if (idx == NGX_ERROR) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ /*
+ * Check if "$variable" was previously defined,
+ * back-off even if it was marked as "CHANGEABLE".
+ */
+ if (pgvar->var->get_handler != NULL) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: variable \"$%V\" is duplicate"
+ " in \"%V\" directive", &value[1], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pgvar->var->get_handler = ngx_postgres_variable_get_custom;
+ pgvar->var->data = (uintptr_t) pgvar;
+
+ pgvar->value.row = ngx_atoi(value[2].data, value[2].len);
+ if (pgvar->value.row == NGX_ERROR) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid row number \"%V\""
+ " in \"%V\" directive", &value[2], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pgvar->value.column = ngx_atoi(value[3].data, value[3].len);
+ if (pgvar->value.column == NGX_ERROR) {
+ /* get column by name */
+ pgvar->value.col_name = ngx_pnalloc(cf->pool, value[3].len + 1);
+ if (pgvar->value.col_name == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ (void) ngx_cpystrn(pgvar->value.col_name,
+ value[3].data, value[3].len + 1);
+ }
+
+ if (cf->args->nelts == 4) {
+ /* default value */
+ pgvar->value.required = 0;
+ } else {
+ /* user-specified value */
+ e = ngx_postgres_requirement_options;
+ for (i = 0; e[i].name.len; i++) {
+ if ((e[i].name.len == value[4].len)
+ && (ngx_strcasecmp(e[i].name.data, value[4].data) == 0))
+ {
+ pgvar->value.required = e[i].value;
+ break;
+ }
+ }
+
+ if (e[i].name.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid requirement option \"%V\""
+ " in \"%V\" directive", &value[4], &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+ }
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+/*
+ * Based on: ngx_http_rewrite_module.c/ngx_http_rewrite_set
+ * Copyright (C) Igor Sysoev
+ */
+char *
+ngx_postgres_conf_escape(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_str_t *value = cf->args->elts;
+ ngx_str_t src = value[cf->args->nelts - 1];
+ ngx_int_t index;
+ ngx_http_variable_t *v;
+ ngx_http_script_var_code_t *vcode;
+ ngx_http_script_var_handler_code_t *vhcode;
+ ngx_postgres_rewrite_loc_conf_t *rlcf;
+ ngx_postgres_escape_t *pge;
+ ngx_str_t dst;
+ ngx_uint_t empty;
+
+ dd("entering");
+
+ if ((src.len != 0) && (src.data[0] == '=')) {
+ empty = 1;
+ src.len--;
+ src.data++;
+ } else {
+ empty = 0;
+ }
+
+ if (src.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: empty value in \"%V\" directive",
+ &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (cf->args->nelts == 2) {
+ dst = src;
+ } else {
+ dst = value[1];
+ }
+
+ if (dst.len < 2) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: empty variable name in \"%V\" directive",
+ &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (dst.data[0] != '$') {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "postgres: invalid variable name \"%V\""
+ " in \"%V\" directive", &dst, &cmd->name);
+
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ dst.len--;
+ dst.data++;
+
+ v = ngx_http_add_variable(cf, &dst, NGX_HTTP_VAR_CHANGEABLE);
+ if (v == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ index = ngx_http_get_variable_index(cf, &dst);
+ if (index == NGX_ERROR) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ if (v->get_handler == NULL
+ && ngx_strncasecmp(dst.data, (u_char *) "http_", 5) != 0
+ && ngx_strncasecmp(dst.data, (u_char *) "sent_http_", 10) != 0
+ && ngx_strncasecmp(dst.data, (u_char *) "upstream_http_", 14) != 0)
+ {
+ v->get_handler = ngx_postgres_rewrite_var;
+ v->data = index;
+ }
+
+ rlcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_rewrite_module);
+
+ if (ngx_postgres_rewrite_value(cf, rlcf, &src) != NGX_CONF_OK) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pge = ngx_http_script_start_code(cf->pool, &rlcf->codes,
+ sizeof(ngx_postgres_escape_t));
+ if (pge == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ pge->code = ngx_postgres_escape_string;
+ pge->empty = empty;
+
+ if (v->set_handler) {
+ vhcode = ngx_http_script_start_code(cf->pool, &rlcf->codes,
+ sizeof(ngx_http_script_var_handler_code_t));
+ if (vhcode == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ vhcode->code = ngx_http_script_var_set_handler_code;
+ vhcode->handler = v->set_handler;
+ vhcode->data = v->data;
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+ }
+
+ vcode = ngx_http_script_start_code(cf->pool, &rlcf->codes,
+ sizeof(ngx_http_script_var_code_t));
+ if (vcode == NULL) {
+ dd("returning NGX_CONF_ERROR");
+ return NGX_CONF_ERROR;
+ }
+
+ vcode->code = ngx_http_script_set_var_code;
+ vcode->index = (uintptr_t) index;
+
+ dd("returning NGX_CONF_OK");
+ return NGX_CONF_OK;
+}
+
+ngx_http_upstream_srv_conf_t *
+ngx_postgres_find_upstream(ngx_http_request_t *r, ngx_url_t *url)
+{
+ ngx_http_upstream_main_conf_t *umcf;
+ ngx_http_upstream_srv_conf_t **uscfp;
+ ngx_uint_t i;
+
+ dd("entering");
+
+ umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);
+
+ uscfp = umcf->upstreams.elts;
+
+ for (i = 0; i < umcf->upstreams.nelts; i++) {
+
+ if ((uscfp[i]->host.len != url->host.len)
+ || (ngx_strncasecmp(uscfp[i]->host.data, url->host.data,
+ url->host.len) != 0))
+ {
+ dd("host doesn't match");
+ continue;
+ }
+
+ if (uscfp[i]->port != url->port) {
+ dd("port doesn't match: %d != %d",
+ (int) uscfp[i]->port, (int) url->port);
+ continue;
+ }
+
+ #if (nginx_version < 1011006)
+ if (uscfp[i]->default_port && url->default_port
+ && (uscfp[i]->default_port != url->default_port))
+ {
+ dd("default_port doesn't match");
+ continue;
+ }
+ #endif
+
+ dd("returning");
+ return uscfp[i];
+ }
+
+ dd("returning NULL");
+ return NULL;
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_module.h b/ngx_postgres-1.0/src/ngx_postgres_module.h
new file mode 100644
index 0000000..03f49fd
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_module.h
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_MODULE_H_
+#define _NGX_POSTGRES_MODULE_H_
+
+#include <nginx.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <libpq-fe.h>
+
+
+extern ngx_module_t ngx_postgres_module;
+
+
+typedef struct {
+ ngx_http_script_code_pt code;
+ ngx_uint_t empty;
+} ngx_postgres_escape_t;
+
+typedef struct {
+ ngx_uint_t key;
+ ngx_str_t sv;
+ ngx_http_complex_value_t *cv;
+} ngx_postgres_mixed_t;
+
+typedef struct {
+ ngx_uint_t key;
+ ngx_int_t status;
+} ngx_postgres_rewrite_t;
+
+typedef struct {
+ ngx_int_t row;
+ ngx_int_t column;
+ u_char *col_name;
+ ngx_uint_t required;
+} ngx_postgres_value_t;
+
+typedef struct {
+ ngx_uint_t idx;
+ ngx_http_variable_t *var;
+ ngx_postgres_value_t value;
+} ngx_postgres_variable_t;
+
+typedef struct {
+ ngx_uint_t methods_set;
+ ngx_array_t *methods; /* method-specific */
+ ngx_postgres_mixed_t *def; /* default */
+} ngx_postgres_query_conf_t;
+
+typedef struct ngx_postgres_rewrite_conf_s ngx_postgres_rewrite_conf_t;
+
+typedef ngx_int_t (*ngx_postgres_rewrite_handler_pt)
+ (ngx_http_request_t *, ngx_postgres_rewrite_conf_t *);
+
+struct ngx_postgres_rewrite_conf_s {
+ /* condition */
+ ngx_uint_t key;
+ ngx_postgres_rewrite_handler_pt handler;
+ /* methods */
+ ngx_uint_t methods_set;
+ ngx_array_t *methods; /* method-specific */
+ ngx_postgres_rewrite_t *def; /* default */
+};
+
+typedef struct {
+ ngx_str_t name;
+ ngx_uint_t key;
+ ngx_postgres_rewrite_handler_pt handler;
+} ngx_postgres_rewrite_enum_t;
+
+typedef ngx_int_t (*ngx_postgres_output_handler_pt)
+ (ngx_http_request_t *, PGresult *);
+
+typedef struct {
+ ngx_str_t name;
+ unsigned binary:1;
+ ngx_postgres_output_handler_pt handler;
+} ngx_postgres_output_enum_t;
+
+typedef struct {
+#if defined(nginx_version) && (nginx_version >= 8022)
+ ngx_addr_t *addrs;
+#else
+ ngx_peer_addr_t *addrs;
+#endif
+ ngx_uint_t naddrs;
+ in_port_t port;
+ ngx_str_t dbname;
+ ngx_str_t user;
+ ngx_str_t password;
+} ngx_postgres_upstream_server_t;
+
+typedef struct {
+ struct sockaddr *sockaddr;
+ socklen_t socklen;
+ ngx_str_t name;
+ ngx_str_t host;
+ in_port_t port;
+ ngx_str_t dbname;
+ ngx_str_t user;
+ ngx_str_t password;
+} ngx_postgres_upstream_peer_t;
+
+typedef struct {
+ ngx_uint_t single;
+ ngx_uint_t number;
+ ngx_str_t *name;
+ ngx_postgres_upstream_peer_t peer[1];
+} ngx_postgres_upstream_peers_t;
+
+typedef struct {
+ ngx_postgres_upstream_peers_t *peers;
+ ngx_uint_t current;
+ ngx_array_t *servers;
+ ngx_pool_t *pool;
+ /* keepalive */
+ ngx_flag_t single;
+ ngx_queue_t free;
+ ngx_queue_t cache;
+ ngx_uint_t active_conns;
+ ngx_uint_t max_cached;
+ ngx_uint_t reject;
+} ngx_postgres_upstream_srv_conf_t;
+
+typedef struct {
+ /* upstream */
+ ngx_http_upstream_conf_t upstream;
+ ngx_http_complex_value_t *upstream_cv;
+ /* queries */
+ ngx_postgres_query_conf_t query;
+ /* rewrites */
+ ngx_array_t *rewrites;
+ /* output */
+ ngx_postgres_output_handler_pt output_handler;
+ unsigned output_binary:1;
+ /* custom variables */
+ ngx_array_t *variables;
+} ngx_postgres_loc_conf_t;
+
+typedef struct {
+ ngx_chain_t *response;
+ ngx_int_t var_cols;
+ ngx_int_t var_rows;
+ ngx_int_t var_affected;
+ ngx_str_t var_query;
+ ngx_array_t *variables;
+ ngx_int_t status;
+} ngx_postgres_ctx_t;
+
+
+ngx_int_t ngx_postgres_add_variables(ngx_conf_t *);
+void *ngx_postgres_create_upstream_srv_conf(ngx_conf_t *);
+void *ngx_postgres_create_loc_conf(ngx_conf_t *);
+char *ngx_postgres_merge_loc_conf(ngx_conf_t *, void *, void *);
+char *ngx_postgres_conf_server(ngx_conf_t *, ngx_command_t *, void *);
+char *ngx_postgres_conf_keepalive(ngx_conf_t *, ngx_command_t *, void *);
+char *ngx_postgres_conf_pass(ngx_conf_t *, ngx_command_t *, void *);
+char *ngx_postgres_conf_query(ngx_conf_t *, ngx_command_t *, void *);
+char *ngx_postgres_conf_rewrite(ngx_conf_t *, ngx_command_t *, void *);
+char *ngx_postgres_conf_output(ngx_conf_t *, ngx_command_t *, void *);
+char *ngx_postgres_conf_set(ngx_conf_t *, ngx_command_t *, void *);
+char *ngx_postgres_conf_escape(ngx_conf_t *, ngx_command_t *, void *);
+
+ngx_http_upstream_srv_conf_t *ngx_postgres_find_upstream(ngx_http_request_t *,
+ ngx_url_t *);
+
+#endif /* _NGX_POSTGRES_MODULE_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_output.c b/ngx_postgres-1.0/src/ngx_postgres_output.c
new file mode 100644
index 0000000..9fa5481
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_output.c
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_module.h"
+#include "ngx_postgres_output.h"
+
+
+ngx_int_t
+ngx_postgres_output_value(ngx_http_request_t *r, PGresult *res)
+{
+ ngx_postgres_ctx_t *pgctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_chain_t *cl;
+ ngx_buf_t *b;
+ size_t size;
+
+ dd("entering");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgctx->var_rows != 1) || (pgctx->var_cols != 1)) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: \"postgres_output value\" received %d value(s)"
+ " instead of expected single value in location \"%V\"",
+ pgctx->var_rows * pgctx->var_cols, &clcf->name);
+
+ dd("returning NGX_DONE, status NGX_HTTP_INTERNAL_SERVER_ERROR");
+ pgctx->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return NGX_DONE;
+ }
+
+ if (PQgetisnull(res, 0, 0)) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: \"postgres_output value\" received NULL value"
+ " in location \"%V\"", &clcf->name);
+
+ dd("returning NGX_DONE, status NGX_HTTP_INTERNAL_SERVER_ERROR");
+ pgctx->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return NGX_DONE;
+ }
+
+ size = PQgetlength(res, 0, 0);
+ if (size == 0) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: \"postgres_output value\" received empty value"
+ " in location \"%V\"", &clcf->name);
+
+ dd("returning NGX_DONE, status NGX_HTTP_INTERNAL_SERVER_ERROR");
+ pgctx->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return NGX_DONE;
+ }
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ b->memory = 1;
+ b->tag = r->upstream->output.tag;
+
+ b->last = ngx_copy(b->last, PQgetvalue(res, 0, 0), size);
+
+ if (b->last != b->end) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ cl->next = NULL;
+
+ /* set output response */
+ pgctx->response = cl;
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+}
+
+ngx_int_t
+ngx_postgres_output_text(ngx_http_request_t *r, PGresult *res)
+{
+ ngx_postgres_ctx_t *pgctx;
+ ngx_chain_t *cl;
+ ngx_buf_t *b;
+ size_t size;
+ ngx_int_t col_count, row_count, col, row;
+
+ dd("entering");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ col_count = pgctx->var_cols;
+ row_count = pgctx->var_rows;
+
+ /* pre-calculate total length up-front for single buffer allocation */
+ size = 0;
+
+ for (row = 0; row < row_count; row++) {
+ for (col = 0; col < col_count; col++) {
+ if (PQgetisnull(res, row, col)) {
+ size += sizeof("(null)") - 1;
+ } else {
+ size += PQgetlength(res, row, col); /* field string data */
+ }
+ }
+ }
+
+ size += row_count * col_count - 1; /* delimiters */
+
+ if ((row_count == 0) || (size == 0)) {
+ dd("returning NGX_DONE (empty result)");
+ return NGX_DONE;
+ }
+
+ b = ngx_create_temp_buf(r->pool, size);
+ if (b == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ cl = ngx_alloc_chain_link(r->pool);
+ if (cl == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ cl->buf = b;
+ b->memory = 1;
+ b->tag = r->upstream->output.tag;
+
+ /* fill data */
+ for (row = 0; row < row_count; row++) {
+ for (col = 0; col < col_count; col++) {
+ if (PQgetisnull(res, row, col)) {
+ b->last = ngx_copy(b->last, "(null)", sizeof("(null)") - 1);
+ } else {
+ size = PQgetlength(res, row, col);
+ if (size) {
+ b->last = ngx_copy(b->last, PQgetvalue(res, row, col),
+ size);
+ }
+ }
+
+ if ((row != row_count - 1) || (col != col_count - 1)) {
+ b->last = ngx_copy(b->last, "\n", 1);
+ }
+ }
+ }
+
+ if (b->last != b->end) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ cl->next = NULL;
+
+ /* set output response */
+ pgctx->response = cl;
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+}
+
+ngx_int_t
+ngx_postgres_output_rds(ngx_http_request_t *r, PGresult *res)
+{
+ ngx_postgres_ctx_t *pgctx;
+ ngx_chain_t *first, *last;
+ ngx_int_t col_count, row_count, aff_count, row;
+
+ dd("entering");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ col_count = pgctx->var_cols;
+ row_count = pgctx->var_rows;
+ aff_count = (pgctx->var_affected == NGX_ERROR) ? 0 : pgctx->var_affected;
+
+ /* render header */
+ first = last = ngx_postgres_render_rds_header(r, r->pool, res, col_count,
+ aff_count);
+ if (last == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK) {
+ goto done;
+ }
+
+ /* render columns */
+ last->next = ngx_postgres_render_rds_columns(r, r->pool, res, col_count);
+ if (last->next == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+ last = last->next;
+
+ /* render rows */
+ for (row = 0; row < row_count; row++) {
+ last->next = ngx_postgres_render_rds_row(r, r->pool, res, col_count,
+ row, (row == row_count - 1));
+ if (last->next == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+ last = last->next;
+ }
+
+ /* render row terminator (for empty result-set only) */
+ if (row == 0) {
+ last->next = ngx_postgres_render_rds_row_terminator(r, r->pool);
+ if (last->next == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+ last = last->next;
+ }
+
+done:
+
+ last->next = NULL;
+
+ /* set output response */
+ pgctx->response = first;
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+}
+
+ngx_chain_t *
+ngx_postgres_render_rds_header(ngx_http_request_t *r, ngx_pool_t *pool,
+ PGresult *res, ngx_int_t col_count, ngx_int_t aff_count)
+{
+ ngx_chain_t *cl;
+ ngx_buf_t *b;
+ size_t size;
+ char *errstr;
+ size_t errstr_len;
+
+ dd("entering");
+
+ errstr = PQresultErrorMessage(res);
+ errstr_len = ngx_strlen(errstr);
+
+ size = sizeof(uint8_t) /* endian type */
+ + sizeof(uint32_t) /* format version */
+ + sizeof(uint8_t) /* result type */
+ + sizeof(uint16_t) /* standard error code */
+ + sizeof(uint16_t) /* driver-specific error code */
+ + sizeof(uint16_t) /* driver-specific error string length */
+ + (uint16_t) errstr_len /* driver-specific error string data */
+ + sizeof(uint64_t) /* rows affected */
+ + sizeof(uint64_t) /* insert id */
+ + sizeof(uint16_t) /* column count */
+ ;
+
+ b = ngx_create_temp_buf(pool, size);
+ if (b == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl = ngx_alloc_chain_link(pool);
+ if (cl == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl->buf = b;
+ b->memory = 1;
+ b->tag = r->upstream->output.tag;
+
+ /* fill data */
+#if NGX_HAVE_LITTLE_ENDIAN
+ *b->last++ = 0;
+#else
+ *b->last++ = 1;
+#endif
+
+ *(uint32_t *) b->last = (uint32_t) resty_dbd_stream_version;
+ b->last += sizeof(uint32_t);
+
+ *b->last++ = 0;
+
+ *(uint16_t *) b->last = (uint16_t) 0;
+ b->last += sizeof(uint16_t);
+
+ *(uint16_t *) b->last = (uint16_t) PQresultStatus(res);
+ b->last += sizeof(uint16_t);
+
+ *(uint16_t *) b->last = (uint16_t) errstr_len;
+ b->last += sizeof(uint16_t);
+
+ if (errstr_len) {
+ b->last = ngx_copy(b->last, (u_char *) errstr, errstr_len);
+ }
+
+ *(uint64_t *) b->last = (uint64_t) aff_count;
+ b->last += sizeof(uint64_t);
+
+ *(uint64_t *) b->last = (uint64_t) PQoidValue(res);
+ b->last += sizeof(uint64_t);
+
+ *(uint16_t *) b->last = (uint16_t) col_count;
+ b->last += sizeof(uint16_t);
+
+ if (b->last != b->end) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ dd("returning");
+ return cl;
+}
+
+ngx_chain_t *
+ngx_postgres_render_rds_columns(ngx_http_request_t *r, ngx_pool_t *pool,
+ PGresult *res, ngx_int_t col_count)
+{
+ ngx_chain_t *cl;
+ ngx_buf_t *b;
+ size_t size;
+ ngx_int_t col;
+ Oid col_type;
+ char *col_name;
+ size_t col_name_len;
+
+ dd("entering");
+
+ /* pre-calculate total length up-front for single buffer allocation */
+ size = col_count
+ * (sizeof(uint16_t) /* standard column type */
+ + sizeof(uint16_t) /* driver-specific column type */
+ + sizeof(uint16_t) /* column name string length */
+ )
+ ;
+
+ for (col = 0; col < col_count; col++) {
+ size += ngx_strlen(PQfname(res, col)); /* column name string data */
+ }
+
+ b = ngx_create_temp_buf(pool, size);
+ if (b == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl = ngx_alloc_chain_link(pool);
+ if (cl == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl->buf = b;
+ b->memory = 1;
+ b->tag = r->upstream->output.tag;
+
+ /* fill data */
+ for (col = 0; col < col_count; col++) {
+ col_type = PQftype(res, col);
+ col_name = PQfname(res, col);
+ col_name_len = (uint16_t) ngx_strlen(col_name);
+
+ *(uint16_t *) b->last = (uint16_t) ngx_postgres_rds_col_type(col_type);
+ b->last += sizeof(uint16_t);
+
+ *(uint16_t *) b->last = col_type;
+ b->last += sizeof(uint16_t);
+
+ *(uint16_t *) b->last = col_name_len;
+ b->last += sizeof(uint16_t);
+
+ b->last = ngx_copy(b->last, col_name, col_name_len);
+ }
+
+ if (b->last != b->end) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ dd("returning");
+ return cl;
+}
+
+ngx_chain_t *
+ngx_postgres_render_rds_row(ngx_http_request_t *r, ngx_pool_t *pool,
+ PGresult *res, ngx_int_t col_count, ngx_int_t row, ngx_int_t last_row)
+{
+ ngx_chain_t *cl;
+ ngx_buf_t *b;
+ size_t size;
+ ngx_int_t col;
+
+ dd("entering, row:%d", (int) row);
+
+ /* pre-calculate total length up-front for single buffer allocation */
+ size = sizeof(uint8_t) /* row number */
+ + (col_count * sizeof(uint32_t)) /* field string length */
+ ;
+
+ if (last_row) {
+ size += sizeof(uint8_t);
+ }
+
+ for (col = 0; col < col_count; col++) {
+ size += PQgetlength(res, row, col); /* field string data */
+ }
+
+ b = ngx_create_temp_buf(pool, size);
+ if (b == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl = ngx_alloc_chain_link(pool);
+ if (cl == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl->buf = b;
+ b->memory = 1;
+ b->tag = r->upstream->output.tag;
+
+ /* fill data */
+ *b->last++ = (uint8_t) 1; /* valid row */
+
+ for (col = 0; col < col_count; col++) {
+ if (PQgetisnull(res, row, col)) {
+ *(uint32_t *) b->last = (uint32_t) -1;
+ b->last += sizeof(uint32_t);
+ } else {
+ size = PQgetlength(res, row, col);
+ *(uint32_t *) b->last = (uint32_t) size;
+ b->last += sizeof(uint32_t);
+
+ if (size) {
+ b->last = ngx_copy(b->last, PQgetvalue(res, row, col), size);
+ }
+ }
+ }
+
+ if (last_row) {
+ *b->last++ = (uint8_t) 0; /* row terminator */
+ }
+
+ if (b->last != b->end) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ dd("returning");
+ return cl;
+}
+
+ngx_chain_t *
+ngx_postgres_render_rds_row_terminator(ngx_http_request_t *r, ngx_pool_t *pool)
+{
+ ngx_chain_t *cl;
+ ngx_buf_t *b;
+
+ dd("entering");
+
+ b = ngx_create_temp_buf(pool, sizeof(uint8_t));
+ if (b == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl = ngx_alloc_chain_link(pool);
+ if (cl == NULL) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ cl->buf = b;
+ b->memory = 1;
+ b->tag = r->upstream->output.tag;
+
+ /* fill data */
+ *b->last++ = (uint8_t) 0; /* row terminator */
+
+ if (b->last != b->end) {
+ dd("returning NULL");
+ return NULL;
+ }
+
+ dd("returning");
+ return cl;
+}
+
+ngx_int_t
+ngx_postgres_output_chain(ngx_http_request_t *r, ngx_chain_t *cl)
+{
+ ngx_http_upstream_t *u = r->upstream;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_postgres_loc_conf_t *pglcf;
+ ngx_postgres_ctx_t *pgctx;
+ ngx_int_t rc;
+
+ dd("entering");
+
+ if (!r->header_sent) {
+ ngx_http_clear_content_length(r);
+
+ pglcf = ngx_http_get_module_loc_conf(r, ngx_postgres_module);
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ r->headers_out.status = pgctx->status ? ngx_abs(pgctx->status)
+ : NGX_HTTP_OK;
+
+ if (pglcf->output_handler == &ngx_postgres_output_rds) {
+ /* RDS for output rds */
+ r->headers_out.content_type.data = (u_char *) rds_content_type;
+ r->headers_out.content_type.len = rds_content_type_len;
+ r->headers_out.content_type_len = rds_content_type_len;
+ } else if (pglcf->output_handler != NULL) {
+ /* default type for output value|row */
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ r->headers_out.content_type = clcf->default_type;
+ r->headers_out.content_type_len = clcf->default_type.len;
+ }
+
+ r->headers_out.content_type_lowcase = NULL;
+
+ rc = ngx_http_send_header(r);
+ if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
+ dd("returning rc:%d", (int) rc);
+ return rc;
+ }
+ }
+
+ if (cl == NULL) {
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+ }
+
+ rc = ngx_http_output_filter(r, cl);
+ if (rc == NGX_ERROR || rc > NGX_OK) {
+ dd("returning rc:%d", (int) rc);
+ return rc;
+ }
+
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ ngx_chain_update_chains(r->pool, &u->free_bufs, &u->busy_bufs, &cl,
+ u->output.tag);
+#else
+ ngx_chain_update_chains(&u->free_bufs, &u->busy_bufs, &cl, u->output.tag);
+#endif
+
+ dd("returning rc:%d", (int) rc);
+ return rc;
+}
+
+rds_col_type_t
+ngx_postgres_rds_col_type(Oid col_type)
+{
+ switch (col_type) {
+ case 20: /* int8 */
+ return rds_col_type_bigint;
+ case 1560: /* bit */
+ return rds_col_type_bit;
+ case 1562: /* varbit */
+ return rds_col_type_bit_varying;
+ case 16: /* bool */
+ return rds_col_type_bool;
+ case 18: /* char */
+ return rds_col_type_char;
+ case 19: /* name */
+ /* FALLTROUGH */
+ case 25: /* text */
+ /* FALLTROUGH */
+ case 1043: /* varchar */
+ return rds_col_type_varchar;
+ case 1082: /* date */
+ return rds_col_type_date;
+ case 701: /* float8 */
+ return rds_col_type_double;
+ case 23: /* int4 */
+ return rds_col_type_integer;
+ case 1186: /* interval */
+ return rds_col_type_interval;
+ case 1700: /* numeric */
+ return rds_col_type_decimal;
+ case 700: /* float4 */
+ return rds_col_type_real;
+ case 21: /* int2 */
+ return rds_col_type_smallint;
+ case 1266: /* timetz */
+ return rds_col_type_time_with_time_zone;
+ case 1083: /* time */
+ return rds_col_type_time;
+ case 1184: /* timestamptz */
+ return rds_col_type_timestamp_with_time_zone;
+ case 1114: /* timestamp */
+ return rds_col_type_timestamp;
+ case 142: /* xml */
+ return rds_col_type_xml;
+ case 17: /* bytea */
+ return rds_col_type_blob;
+ default:
+ return rds_col_type_unknown;
+ }
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_output.h b/ngx_postgres-1.0/src/ngx_postgres_output.h
new file mode 100644
index 0000000..a5115c2
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_output.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_OUTPUT_H_
+#define _NGX_POSTGRES_OUTPUT_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <libpq-fe.h>
+
+#include "ngx_postgres_module.h"
+#include "resty_dbd_stream.h"
+
+
+ngx_int_t ngx_postgres_output_value(ngx_http_request_t *, PGresult *);
+ngx_int_t ngx_postgres_output_text(ngx_http_request_t *, PGresult *);
+ngx_int_t ngx_postgres_output_rds(ngx_http_request_t *, PGresult *);
+ngx_chain_t *ngx_postgres_render_rds_header(ngx_http_request_t *,
+ ngx_pool_t *, PGresult *, ngx_int_t, ngx_int_t);
+ngx_chain_t *ngx_postgres_render_rds_columns(ngx_http_request_t *,
+ ngx_pool_t *, PGresult *, ngx_int_t);
+ngx_chain_t *ngx_postgres_render_rds_row(ngx_http_request_t *, ngx_pool_t *,
+ PGresult *, ngx_int_t, ngx_int_t, ngx_int_t);
+ngx_chain_t *ngx_postgres_render_rds_row_terminator(ngx_http_request_t *,
+ ngx_pool_t *);
+ngx_int_t ngx_postgres_output_chain(ngx_http_request_t *, ngx_chain_t *);
+rds_col_type_t ngx_postgres_rds_col_type(Oid);
+
+#endif /* _NGX_POSTGRES_OUTPUT_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_processor.c b/ngx_postgres-1.0/src/ngx_postgres_processor.c
new file mode 100644
index 0000000..d25c054
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_processor.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_output.h"
+#include "ngx_postgres_processor.h"
+#include "ngx_postgres_util.h"
+#include "ngx_postgres_variable.h"
+
+
+void
+ngx_postgres_process_events(ngx_http_request_t *r)
+{
+ ngx_postgres_upstream_peer_data_t *pgdt;
+ ngx_connection_t *pgxc;
+ ngx_http_upstream_t *u;
+ ngx_int_t rc;
+
+ dd("entering");
+
+ u = r->upstream;
+ pgxc = u->peer.connection;
+ pgdt = u->peer.data;
+
+ if (!ngx_postgres_upstream_is_my_peer(&u->peer)) {
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: trying to connect to something that"
+ " is not PostgreSQL database");
+
+ goto failed;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: process events");
+
+ switch (pgdt->state) {
+ case state_db_connect:
+ dd("state_db_connect");
+ rc = ngx_postgres_upstream_connect(r, pgxc, pgdt);
+ break;
+ case state_db_send_query:
+ dd("state_db_send_query");
+ rc = ngx_postgres_upstream_send_query(r, pgxc, pgdt);
+ break;
+ case state_db_get_result:
+ dd("state_db_get_result");
+ rc = ngx_postgres_upstream_get_result(r, pgxc, pgdt);
+ break;
+ case state_db_get_ack:
+ dd("state_db_get_ack");
+ rc = ngx_postgres_upstream_get_ack(r, pgxc, pgdt);
+ break;
+ case state_db_idle:
+ dd("state_db_idle, re-using keepalive connection");
+ pgxc->log->action = "sending query to PostgreSQL database";
+ pgdt->state = state_db_send_query;
+ rc = ngx_postgres_upstream_send_query(r, pgxc, pgdt);
+ break;
+ default:
+ dd("unknown state:%d", pgdt->state);
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: unknown state:%d", pgdt->state);
+
+ goto failed;
+ }
+
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ ngx_postgres_upstream_finalize_request(r, u, rc);
+ } else if (rc == NGX_ERROR) {
+ goto failed;
+ }
+
+ dd("returning");
+ return;
+
+failed:
+
+ ngx_postgres_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+
+ dd("returning");
+}
+
+ngx_int_t
+ngx_postgres_upstream_connect(ngx_http_request_t *r, ngx_connection_t *pgxc,
+ ngx_postgres_upstream_peer_data_t *pgdt)
+{
+ PostgresPollingStatusType pgrc;
+
+ dd("entering");
+
+ pgrc = PQconnectPoll(pgdt->pgconn);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: polling while connecting, rc:%d", (int) pgrc);
+
+ if (pgrc == PGRES_POLLING_READING || pgrc == PGRES_POLLING_WRITING) {
+
+ /*
+ * Fix for Linux issue found by chaoslawful (via agentzh):
+ * "According to the source of libpq (around fe-connect.c:1215), during
+ * the state switch from CONNECTION_STARTED to CONNECTION_MADE, there's
+ * no socket read/write operations (just a plain getsockopt call and a
+ * getsockname call). Therefore, for edge-triggered event model, we
+ * have to call PQconnectPoll one more time (immediately) when we see
+ * CONNECTION_MADE is returned, or we're very likely to wait for a
+ * writable event that has already appeared and will never appear
+ * again :)"
+ */
+ if (PQstatus(pgdt->pgconn) == CONNECTION_MADE && pgxc->write->ready) {
+ dd("re-polling on connection made");
+
+ pgrc = PQconnectPoll(pgdt->pgconn);
+ dd("re-polling rc:%d", (int) pgrc);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: re-polling while connecting, rc:%d",
+ (int) pgrc);
+
+ if (pgrc == PGRES_POLLING_READING || pgrc == PGRES_POLLING_WRITING)
+ {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: busy while connecting, rc:%d",
+ (int) pgrc);
+
+ dd("returning NGX_AGAIN");
+ return NGX_AGAIN;
+ }
+
+ goto done;
+ }
+
+#if defined(DDEBUG) && (DDEBUG)
+ switch (PQstatus(pgdt->pgconn)) {
+ case CONNECTION_NEEDED:
+ dd("connecting (waiting for connect()))");
+ break;
+ case CONNECTION_STARTED:
+ dd("connecting (waiting for connection to be made)");
+ break;
+ case CONNECTION_MADE:
+ dd("connecting (connection established)");
+ break;
+ case CONNECTION_AWAITING_RESPONSE:
+ dd("connecting (credentials sent, waiting for response)");
+ break;
+ case CONNECTION_AUTH_OK:
+ dd("connecting (authenticated)");
+ break;
+ case CONNECTION_SETENV:
+ dd("connecting (negotiating envinroment)");
+ break;
+ case CONNECTION_SSL_STARTUP:
+ dd("connecting (negotiating SSL)");
+ break;
+ default:
+ /*
+ * This cannot happen, PQconnectPoll would return
+ * PGRES_POLLING_FAILED in that case.
+ */
+ dd("connecting (unknown state:%d)", (int) PQstatus(pgdt->pgconn));
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+#endif /* DDEBUG */
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: busy while connecting, rc:%d", (int) pgrc);
+
+ dd("returning NGX_AGAIN");
+ return NGX_AGAIN;
+ }
+
+done:
+
+ /* remove connection timeout from new connection */
+ if (pgxc->write->timer_set) {
+ ngx_del_timer(pgxc->write);
+ }
+
+ if (pgrc != PGRES_POLLING_OK) {
+ dd("connection failed");
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: connection failed: %s",
+ PQerrorMessage(pgdt->pgconn));
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ dd("connected successfully");
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: connected successfully");
+
+ pgxc->log->action = "sending query to PostgreSQL database";
+ pgdt->state = state_db_send_query;
+
+ dd("returning");
+ return ngx_postgres_upstream_send_query(r, pgxc, pgdt);
+}
+
+ngx_int_t
+ngx_postgres_upstream_send_query(ngx_http_request_t *r, ngx_connection_t *pgxc,
+ ngx_postgres_upstream_peer_data_t *pgdt)
+{
+ ngx_postgres_loc_conf_t *pglcf;
+ ngx_int_t pgrc;
+ u_char *query;
+
+ dd("entering");
+
+ pglcf = ngx_http_get_module_loc_conf(r, ngx_postgres_module);
+
+ query = ngx_pnalloc(r->pool, pgdt->query.len + 1);
+ if (query == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ (void) ngx_cpystrn(query, pgdt->query.data, pgdt->query.len + 1);
+
+ dd("sending query: %s", query);
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: sending query: \"%s\"", query);
+
+ if (pglcf->output_binary) {
+ pgrc = PQsendQueryParams(pgdt->pgconn, (const char *) query,
+ 0, NULL, NULL, NULL, NULL, /* binary */ 1);
+ } else {
+ pgrc = PQsendQuery(pgdt->pgconn, (const char *) query);
+ }
+
+ if (pgrc == 0) {
+ dd("sending query failed");
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: sending query failed: %s",
+ PQerrorMessage(pgdt->pgconn));
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ /* set result timeout */
+ ngx_add_timer(pgxc->read, r->upstream->conf->read_timeout);
+
+ dd("query sent successfully");
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: query sent successfully");
+
+ pgxc->log->action = "waiting for result from PostgreSQL database";
+ pgdt->state = state_db_get_result;
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+}
+
+ngx_int_t
+ngx_postgres_upstream_get_result(ngx_http_request_t *r, ngx_connection_t *pgxc,
+ ngx_postgres_upstream_peer_data_t *pgdt)
+{
+ ExecStatusType pgrc;
+ PGresult *res;
+ ngx_int_t rc;
+
+ dd("entering");
+
+ /* remove connection timeout from re-used keepalive connection */
+ if (pgxc->write->timer_set) {
+ ngx_del_timer(pgxc->write);
+ }
+
+ if (!PQconsumeInput(pgdt->pgconn)) {
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: failed to consume input: %s",
+ PQerrorMessage(pgdt->pgconn));
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ if (PQisBusy(pgdt->pgconn)) {
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: busy while receiving result");
+
+ dd("returning NGX_AGAIN");
+ return NGX_AGAIN;
+ }
+
+ dd("receiving result");
+
+ res = PQgetResult(pgdt->pgconn);
+ if (res == NULL) {
+ dd("receiving result failed");
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: failed to receive result: %s",
+ PQerrorMessage(pgdt->pgconn));
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ pgrc = PQresultStatus(res);
+ if ((pgrc != PGRES_COMMAND_OK) && (pgrc != PGRES_TUPLES_OK)) {
+ dd("receiving result failed");
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: failed to receive result: %s: %s",
+ PQresStatus(pgrc),
+ PQerrorMessage(pgdt->pgconn));
+
+ PQclear(res);
+
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ dd("result received successfully, cols:%d rows:%d",
+ PQnfields(res), PQntuples(res));
+
+ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pgxc->log, 0,
+ "postgres: result received successfully, cols:%d rows:%d",
+ PQnfields(res), PQntuples(res));
+
+ pgxc->log->action = "processing result from PostgreSQL database";
+ rc = ngx_postgres_process_response(r, res);
+
+ PQclear(res);
+
+ if (rc != NGX_DONE) {
+ dd("returning rc:%d", (int) rc);
+ return rc;
+ }
+
+ dd("result processed successfully");
+
+ pgxc->log->action = "waiting for ACK from PostgreSQL database";
+ pgdt->state = state_db_get_ack;
+
+ dd("returning");
+ return ngx_postgres_upstream_get_ack(r, pgxc, pgdt);
+}
+
+ngx_int_t
+ngx_postgres_process_response(ngx_http_request_t *r, PGresult *res)
+{
+ ngx_postgres_loc_conf_t *pglcf;
+ ngx_postgres_ctx_t *pgctx;
+ ngx_postgres_rewrite_conf_t *pgrcf;
+ ngx_postgres_variable_t *pgvar;
+ ngx_str_t *store;
+ char *affected;
+ size_t affected_len;
+ ngx_uint_t i;
+ ngx_int_t rc;
+
+ dd("entering");
+
+ pglcf = ngx_http_get_module_loc_conf(r, ngx_postgres_module);
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ /* set $postgres_columns */
+ pgctx->var_cols = PQnfields(res);
+
+ /* set $postgres_rows */
+ pgctx->var_rows = PQntuples(res);
+
+ /* set $postgres_affected */
+ if (ngx_strncmp(PQcmdStatus(res), "SELECT", sizeof("SELECT") - 1)) {
+ affected = PQcmdTuples(res);
+ affected_len = ngx_strlen(affected);
+ if (affected_len) {
+ pgctx->var_affected = ngx_atoi((u_char *) affected, affected_len);
+ }
+ }
+
+ if (pglcf->rewrites) {
+ /* process rewrites */
+ pgrcf = pglcf->rewrites->elts;
+ for (i = 0; i < pglcf->rewrites->nelts; i++) {
+ rc = pgrcf[i].handler(r, &pgrcf[i]);
+ if (rc != NGX_DECLINED) {
+ if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
+ dd("returning NGX_DONE, status %d", (int) rc);
+ pgctx->status = rc;
+ return NGX_DONE;
+ }
+
+ pgctx->status = rc;
+ break;
+ }
+ }
+ }
+
+ if (pglcf->variables) {
+ /* set custom variables */
+ pgvar = pglcf->variables->elts;
+ store = pgctx->variables->elts;
+
+ for (i = 0; i < pglcf->variables->nelts; i++) {
+ store[i] = ngx_postgres_variable_set_custom(r, res, &pgvar[i]);
+ if ((store[i].len == 0) && (pgvar[i].value.required)) {
+ dd("returning NGX_DONE, status NGX_HTTP_INTERNAL_SERVER_ERROR");
+ pgctx->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ return NGX_DONE;
+ }
+ }
+ }
+
+ if (pglcf->output_handler) {
+ /* generate output */
+ dd("returning");
+ return pglcf->output_handler(r, res);
+ }
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+}
+
+ngx_int_t
+ngx_postgres_upstream_get_ack(ngx_http_request_t *r, ngx_connection_t *pgxc,
+ ngx_postgres_upstream_peer_data_t *pgdt)
+{
+ PGresult *res;
+
+ dd("entering");
+
+ if (!PQconsumeInput(pgdt->pgconn)) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ if (PQisBusy(pgdt->pgconn)) {
+ dd("returning NGX_AGAIN");
+ return NGX_AGAIN;
+ }
+
+ /* remove result timeout */
+ if (pgxc->read->timer_set) {
+ ngx_del_timer(pgxc->read);
+ }
+
+ dd("receiving ACK (ready for next query)");
+
+ res = PQgetResult(pgdt->pgconn);
+ if (res != NULL) {
+ dd("receiving ACK failed");
+ ngx_log_error(NGX_LOG_ERR, pgxc->log, 0,
+ "postgres: receiving ACK failed: multiple queries(?)");
+
+ PQclear(res);
+
+ dd("returning NGX_HTTP_INTERNAL_SERVER_ERROR");
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ dd("ACK received successfully");
+
+ pgxc->log->action = "being idle on PostgreSQL database";
+ pgdt->state = state_db_idle;
+
+ dd("returning");
+ return ngx_postgres_upstream_done(r, r->upstream, pgdt);
+}
+
+ngx_int_t
+ngx_postgres_upstream_done(ngx_http_request_t *r, ngx_http_upstream_t *u,
+ ngx_postgres_upstream_peer_data_t *pgdt)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering");
+
+ /* flag for keepalive */
+ u->headers_in.status_n = NGX_HTTP_OK;
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if (pgctx->status >= NGX_HTTP_SPECIAL_RESPONSE) {
+ ngx_postgres_upstream_finalize_request(r, u, pgctx->status);
+ } else {
+ ngx_postgres_upstream_finalize_request(r, u, NGX_OK);
+ }
+
+ dd("returning NGX_DONE");
+ return NGX_DONE;
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_processor.h b/ngx_postgres-1.0/src/ngx_postgres_processor.h
new file mode 100644
index 0000000..ee818f8
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_processor.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_PROCESSOR_H_
+#define _NGX_POSTGRES_PROCESSOR_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <libpq-fe.h>
+
+#include "ngx_postgres_upstream.h"
+
+
+void ngx_postgres_process_events(ngx_http_request_t *);
+ngx_int_t ngx_postgres_upstream_connect(ngx_http_request_t *,
+ ngx_connection_t *, ngx_postgres_upstream_peer_data_t *);
+ngx_int_t ngx_postgres_upstream_send_query(ngx_http_request_t *,
+ ngx_connection_t *, ngx_postgres_upstream_peer_data_t *);
+ngx_int_t ngx_postgres_upstream_get_result(ngx_http_request_t *,
+ ngx_connection_t *, ngx_postgres_upstream_peer_data_t *);
+ngx_int_t ngx_postgres_process_response(ngx_http_request_t *, PGresult *);
+ngx_int_t ngx_postgres_upstream_get_ack(ngx_http_request_t *,
+ ngx_connection_t *, ngx_postgres_upstream_peer_data_t *);
+ngx_int_t ngx_postgres_upstream_done(ngx_http_request_t *,
+ ngx_http_upstream_t *, ngx_postgres_upstream_peer_data_t *);
+
+#endif /* _NGX_POSTGRES_PROCESSOR_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_rewrite.c b/ngx_postgres-1.0/src/ngx_postgres_rewrite.c
new file mode 100644
index 0000000..44f3e7e
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_rewrite.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_module.h"
+#include "ngx_postgres_rewrite.h"
+
+
+ngx_int_t
+ngx_postgres_rewrite(ngx_http_request_t *r,
+ ngx_postgres_rewrite_conf_t *pgrcf)
+{
+ ngx_postgres_rewrite_t *rewrite;
+ ngx_uint_t i;
+
+ dd("entering");
+
+ if (pgrcf->methods_set & r->method) {
+ /* method-specific */
+ rewrite = pgrcf->methods->elts;
+ for (i = 0; i < pgrcf->methods->nelts; i++) {
+ if (rewrite[i].key & r->method) {
+ dd("returning status:%d", (int) rewrite[i].status);
+ return rewrite[i].status;
+ }
+ }
+ } else if (pgrcf->def) {
+ /* default */
+ dd("returning status:%d", (int) pgrcf->def->status);
+ return pgrcf->def->status;
+ }
+
+ dd("returning NGX_DECLINED");
+ return NGX_DECLINED;
+}
+
+ngx_int_t
+ngx_postgres_rewrite_changes(ngx_http_request_t *r,
+ ngx_postgres_rewrite_conf_t *pgrcf)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgrcf->key % 2 == 0) && (pgctx->var_affected == 0)) {
+ /* no_changes */
+ dd("returning");
+ return ngx_postgres_rewrite(r, pgrcf);
+ }
+
+ if ((pgrcf->key % 2 == 1) && (pgctx->var_affected > 0)) {
+ /* changes */
+ dd("returning");
+ return ngx_postgres_rewrite(r, pgrcf);
+ }
+
+ dd("returning NGX_DECLINED");
+ return NGX_DECLINED;
+}
+
+ngx_int_t
+ngx_postgres_rewrite_rows(ngx_http_request_t *r,
+ ngx_postgres_rewrite_conf_t *pgrcf)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgrcf->key % 2 == 0) && (pgctx->var_rows == 0)) {
+ /* no_rows */
+ dd("returning");
+ return ngx_postgres_rewrite(r, pgrcf);
+ }
+
+ if ((pgrcf->key % 2 == 1) && (pgctx->var_rows > 0)) {
+ /* rows */
+ dd("returning");
+ return ngx_postgres_rewrite(r, pgrcf);
+ }
+
+ dd("returning NGX_DECLINED");
+ return NGX_DECLINED;
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_rewrite.h b/ngx_postgres-1.0/src/ngx_postgres_rewrite.h
new file mode 100644
index 0000000..54afa6c
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_rewrite.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_REWRITE_H_
+#define _NGX_POSTGRES_REWRITE_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include "ngx_postgres_module.h"
+
+
+ngx_int_t ngx_postgres_rewrite(ngx_http_request_t *,
+ ngx_postgres_rewrite_conf_t *);
+ngx_int_t ngx_postgres_rewrite_changes(ngx_http_request_t *,
+ ngx_postgres_rewrite_conf_t *);
+ngx_int_t ngx_postgres_rewrite_rows(ngx_http_request_t *,
+ ngx_postgres_rewrite_conf_t *);
+
+#endif /* _NGX_POSTGRES_REWRITE_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_upstream.c b/ngx_postgres-1.0/src/ngx_postgres_upstream.c
new file mode 100644
index 0000000..919029b
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_upstream.c
@@ -0,0 +1,598 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include <nginx.h>
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_module.h"
+#include "ngx_postgres_keepalive.h"
+#include "ngx_postgres_processor.h"
+
+
+ngx_int_t
+ngx_postgres_upstream_init(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *uscf)
+{
+ ngx_postgres_upstream_srv_conf_t *pgscf;
+ ngx_postgres_upstream_server_t *server;
+ ngx_postgres_upstream_peers_t *peers;
+ ngx_uint_t i, j, n;
+
+ dd("entering");
+
+ uscf->peer.init = ngx_postgres_upstream_init_peer;
+
+ pgscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_postgres_module);
+
+ if (pgscf->servers == NULL || pgscf->servers->nelts == 0) {
+ ngx_log_error(NGX_LOG_ERR, cf->log, 0,
+ "postgres: no \"postgres_server\" defined"
+ " in upstream \"%V\" in %s:%ui",
+ &uscf->host, uscf->file_name, uscf->line);
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ /* pgscf->servers != NULL */
+
+ server = uscf->servers->elts;
+
+ n = 0;
+
+ for (i = 0; i < uscf->servers->nelts; i++) {
+ n += server[i].naddrs;
+ }
+
+ peers = ngx_pcalloc(cf->pool, sizeof(ngx_postgres_upstream_peers_t)
+ + sizeof(ngx_postgres_upstream_peer_t) * (n - 1));
+
+ if (peers == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ peers->single = (n == 1);
+ peers->number = n;
+ peers->name = &uscf->host;
+
+ n = 0;
+
+ for (i = 0; i < uscf->servers->nelts; i++) {
+ for (j = 0; j < server[i].naddrs; j++) {
+ peers->peer[n].sockaddr = server[i].addrs[j].sockaddr;
+ peers->peer[n].socklen = server[i].addrs[j].socklen;
+ peers->peer[n].name = server[i].addrs[j].name;
+ peers->peer[n].port = server[i].port;
+ peers->peer[n].dbname = server[i].dbname;
+ peers->peer[n].user = server[i].user;
+ peers->peer[n].password = server[i].password;
+
+ peers->peer[n].host.data = ngx_pnalloc(cf->pool,
+ NGX_SOCKADDR_STRLEN);
+ if (peers->peer[n].host.data == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ peers->peer[n].host.len = ngx_sock_ntop(peers->peer[n].sockaddr,
+#if defined(nginx_version) && (nginx_version >= 1005003)
+ peers->peer[n].socklen,
+#endif
+ peers->peer[n].host.data,
+ NGX_SOCKADDR_STRLEN, 0);
+ if (peers->peer[n].host.len == 0) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ n++;
+ }
+ }
+
+ pgscf->peers = peers;
+ pgscf->active_conns = 0;
+
+ if (pgscf->max_cached) {
+ dd("returning");
+ return ngx_postgres_keepalive_init(cf->pool, pgscf);
+ }
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_upstream_init_peer(ngx_http_request_t *r,
+ ngx_http_upstream_srv_conf_t *uscf)
+{
+ ngx_postgres_upstream_peer_data_t *pgdt;
+ ngx_postgres_upstream_srv_conf_t *pgscf;
+ ngx_postgres_loc_conf_t *pglcf;
+ ngx_postgres_ctx_t *pgctx;
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_http_upstream_t *u;
+ ngx_postgres_mixed_t *query;
+ ngx_str_t sql;
+ ngx_uint_t i;
+
+ dd("entering");
+
+ pgdt = ngx_pcalloc(r->pool, sizeof(ngx_postgres_upstream_peer_data_t));
+ if (pgdt == NULL) {
+ goto failed;
+ }
+
+ u = r->upstream;
+
+ pgdt->upstream = u;
+ pgdt->request = r;
+
+ pgscf = ngx_http_conf_upstream_srv_conf(uscf, ngx_postgres_module);
+ pglcf = ngx_http_get_module_loc_conf(r, ngx_postgres_module);
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ pgdt->srv_conf = pgscf;
+ pgdt->loc_conf = pglcf;
+
+ u->peer.data = pgdt;
+ u->peer.get = ngx_postgres_upstream_get_peer;
+ u->peer.free = ngx_postgres_upstream_free_peer;
+
+ if (pglcf->query.methods_set & r->method) {
+ /* method-specific query */
+ dd("using method-specific query");
+
+ query = pglcf->query.methods->elts;
+ for (i = 0; i < pglcf->query.methods->nelts; i++) {
+ if (query[i].key & r->method) {
+ query = &query[i];
+ break;
+ }
+ }
+
+ if (i == pglcf->query.methods->nelts) {
+ goto failed;
+ }
+ } else {
+ /* default query */
+ dd("using default query");
+
+ query = pglcf->query.def;
+ }
+
+ if (query->cv) {
+ /* complex value */
+ dd("using complex value");
+
+ if (ngx_http_complex_value(r, query->cv, &sql) != NGX_OK) {
+ goto failed;
+ }
+
+ if (sql.len == 0) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: empty \"postgres_query\" (was: \"%V\")"
+ " in location \"%V\"", &query->cv->value,
+ &clcf->name);
+
+ goto failed;
+ }
+
+ pgdt->query = sql;
+ } else {
+ /* simple value */
+ dd("using simple value");
+
+ pgdt->query = query->sv;
+ }
+
+ /* set $postgres_query */
+ pgctx->var_query = pgdt->query;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+
+failed:
+
+#if defined(nginx_version) && (nginx_version >= 8017)
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+#else
+ r->upstream->peer.data = NULL;
+
+ dd("returning NGX_OK (NGX_ERROR)");
+ return NGX_OK;
+#endif
+}
+
+ngx_int_t
+ngx_postgres_upstream_get_peer(ngx_peer_connection_t *pc, void *data)
+{
+ ngx_postgres_upstream_peer_data_t *pgdt = data;
+ ngx_postgres_upstream_srv_conf_t *pgscf;
+#if defined(nginx_version) && (nginx_version < 8017)
+ ngx_postgres_ctx_t *pgctx;
+#endif
+ ngx_postgres_upstream_peers_t *peers;
+ ngx_postgres_upstream_peer_t *peer;
+ ngx_connection_t *pgxc = NULL;
+ int fd;
+ ngx_event_t *rev, *wev;
+ ngx_int_t rc;
+ u_char *connstring, *last;
+ size_t len;
+
+ dd("entering");
+
+#if defined(nginx_version) && (nginx_version < 8017)
+ if (data == NULL) {
+ goto failed;
+ }
+
+ pgctx = ngx_http_get_module_ctx(pgdt->request, ngx_postgres_module);
+#endif
+
+ pgscf = pgdt->srv_conf;
+
+ pgdt->failed = 0;
+
+ if (pgscf->max_cached && pgscf->single) {
+ rc = ngx_postgres_keepalive_get_peer_single(pc, pgdt, pgscf);
+ if (rc != NGX_DECLINED) {
+ /* re-use keepalive peer */
+ dd("re-using keepalive peer (single)");
+
+ pgdt->state = state_db_send_query;
+
+ ngx_postgres_process_events(pgdt->request);
+
+ dd("returning NGX_AGAIN");
+ return NGX_AGAIN;
+ }
+ }
+
+ peers = pgscf->peers;
+
+ if (pgscf->current > peers->number - 1) {
+ pgscf->current = 0;
+ }
+
+ peer = &peers->peer[pgscf->current++];
+
+ pgdt->name.len = peer->name.len;
+ pgdt->name.data = peer->name.data;
+
+ pgdt->sockaddr = *peer->sockaddr;
+
+ pc->name = &pgdt->name;
+ pc->sockaddr = &pgdt->sockaddr;
+ pc->socklen = peer->socklen;
+ pc->cached = 0;
+
+ if ((pgscf->max_cached) && (!pgscf->single)) {
+ rc = ngx_postgres_keepalive_get_peer_multi(pc, pgdt, pgscf);
+ if (rc != NGX_DECLINED) {
+ /* re-use keepalive peer */
+ dd("re-using keepalive peer (multi)");
+
+ pgdt->state = state_db_send_query;
+
+ ngx_postgres_process_events(pgdt->request);
+
+ dd("returning NGX_AGAIN");
+ return NGX_AGAIN;
+ }
+ }
+
+ if ((pgscf->reject) && (pgscf->active_conns >= pgscf->max_cached)) {
+ ngx_log_error(NGX_LOG_INFO, pc->log, 0,
+ "postgres: keepalive connection pool is full,"
+ " rejecting request to upstream \"%V\"", &peer->name);
+
+ /* a bit hack-ish way to return error response (setup part) */
+ pc->connection = ngx_get_connection(0, pc->log);
+
+#if defined(nginx_version) && (nginx_version < 8017)
+ pgctx->status = NGX_HTTP_SERVICE_UNAVAILABLE;
+#endif
+
+ dd("returning NGX_AGAIN (NGX_HTTP_SERVICE_UNAVAILABLE)");
+ return NGX_AGAIN;
+ }
+
+ /* sizeof("...") - 1 + 1 (for spaces and '\0' omitted */
+ len = sizeof("hostaddr=") + peer->host.len
+ + sizeof("port=") + sizeof("65535") - 1
+ + sizeof("dbname=") + peer->dbname.len
+ + sizeof("user=") + peer->user.len
+ + sizeof("password=") + peer->password.len
+ + sizeof("sslmode=disable");
+
+ connstring = ngx_pnalloc(pgdt->request->pool, len);
+ if (connstring == NULL) {
+#if defined(nginx_version) && (nginx_version >= 8017)
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+#else
+ goto failed;
+#endif
+ }
+
+ /* TODO add unix sockets */
+ last = ngx_snprintf(connstring, len - 1,
+ "hostaddr=%V port=%d dbname=%V user=%V password=%V"
+ " sslmode=disable",
+ &peer->host, peer->port, &peer->dbname, &peer->user,
+ &peer->password);
+ *last = '\0';
+
+ dd("PostgreSQL connection string: %s", connstring);
+
+ /*
+ * internal checks in PQsetnonblocking are taking care of any
+ * PQconnectStart failures, so we don't need to check them here.
+ */
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "postgres: connecting");
+
+ pgdt->pgconn = PQconnectStart((const char *)connstring);
+ if (PQsetnonblocking(pgdt->pgconn, 1) == -1) {
+ ngx_log_error(NGX_LOG_ERR, pc->log, 0,
+ "postgres: connection failed: %s in upstream \"%V\"",
+ PQerrorMessage(pgdt->pgconn), &peer->name);
+
+ PQfinish(pgdt->pgconn);
+ pgdt->pgconn = NULL;
+
+#if defined(nginx_version) && (nginx_version >= 8017)
+ dd("returning NGX_DECLINED");
+ return NGX_DECLINED;
+#else
+ pgctx->status = NGX_HTTP_BAD_GATEWAY;
+ goto failed;
+#endif
+ }
+
+#if defined(DDEBUG) && (DDEBUG > 1)
+ PQtrace(pgdt->pgconn, stderr);
+#endif
+
+ dd("connection status:%d", (int) PQstatus(pgdt->pgconn));
+
+ /* take spot in keepalive connection pool */
+ pgscf->active_conns++;
+
+ /* add the file descriptor (fd) into an nginx connection structure */
+
+ fd = PQsocket(pgdt->pgconn);
+ if (fd == -1) {
+ ngx_log_error(NGX_LOG_ERR, pc->log, 0,
+ "postgres: failed to get connection fd");
+
+ goto invalid;
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
+ "postgres: connection fd:%d", fd);
+
+ pgxc = pc->connection = ngx_get_connection(fd, pc->log);
+
+ if (pgxc == NULL) {
+ ngx_log_error(NGX_LOG_ERR, pc->log, 0,
+ "postgres: failed to get a free nginx connection");
+
+ goto invalid;
+ }
+
+ pgxc->log = pc->log;
+ pgxc->log_error = pc->log_error;
+ pgxc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1);
+
+ rev = pgxc->read;
+ wev = pgxc->write;
+
+ rev->log = pc->log;
+ wev->log = pc->log;
+
+ /* register the connection with postgres connection fd into the
+ * nginx event model */
+
+ if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
+ dd("NGX_USE_RTSIG_EVENT");
+ if (ngx_add_conn(pgxc) != NGX_OK) {
+ goto bad_add;
+ }
+
+ } else if (ngx_event_flags & NGX_USE_CLEAR_EVENT) {
+ dd("NGX_USE_CLEAR_EVENT");
+ if (ngx_add_event(rev, NGX_READ_EVENT, NGX_CLEAR_EVENT) != NGX_OK) {
+ goto bad_add;
+ }
+
+ if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_CLEAR_EVENT) != NGX_OK) {
+ goto bad_add;
+ }
+
+ } else {
+ dd("NGX_USE_LEVEL_EVENT");
+ if (ngx_add_event(rev, NGX_READ_EVENT, NGX_LEVEL_EVENT) != NGX_OK) {
+ goto bad_add;
+ }
+
+ if (ngx_add_event(wev, NGX_WRITE_EVENT, NGX_LEVEL_EVENT) != NGX_OK) {
+ goto bad_add;
+ }
+ }
+
+ pgxc->log->action = "connecting to PostgreSQL database";
+ pgdt->state = state_db_connect;
+
+ dd("returning NGX_AGAIN");
+ return NGX_AGAIN;
+
+bad_add:
+
+ ngx_log_error(NGX_LOG_ERR, pc->log, 0,
+ "postgres: failed to add nginx connection");
+
+invalid:
+
+ ngx_postgres_upstream_free_connection(pc->log, pc->connection,
+ pgdt->pgconn, pgscf);
+
+#if defined(nginx_version) && (nginx_version >= 8017)
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+#else
+
+failed:
+
+ /* a bit hack-ish way to return error response (setup part) */
+ pc->connection = ngx_get_connection(0, pc->log);
+
+ dd("returning NGX_AGAIN (NGX_ERROR)");
+ return NGX_AGAIN;
+#endif
+}
+
+void
+ngx_postgres_upstream_free_peer(ngx_peer_connection_t *pc,
+ void *data, ngx_uint_t state)
+{
+ ngx_postgres_upstream_peer_data_t *pgdt = data;
+ ngx_postgres_upstream_srv_conf_t *pgscf;
+
+ dd("entering");
+
+#if defined(nginx_version) && (nginx_version < 8017)
+ if (data == NULL) {
+ dd("returning");
+ return;
+ }
+#endif
+
+ pgscf = pgdt->srv_conf;
+
+ if (pgscf->max_cached) {
+ ngx_postgres_keepalive_free_peer(pc, pgdt, pgscf, state);
+ }
+
+ if (pc->connection) {
+ dd("free connection to PostgreSQL database");
+
+ ngx_postgres_upstream_free_connection(pc->log, pc->connection,
+ pgdt->pgconn, pgscf);
+
+ pgdt->pgconn = NULL;
+ pc->connection = NULL;
+ }
+
+ dd("returning");
+}
+
+ngx_flag_t
+ngx_postgres_upstream_is_my_peer(const ngx_peer_connection_t *peer)
+{
+ dd("entering & returning");
+ return (peer->get == ngx_postgres_upstream_get_peer);
+}
+
+void
+ngx_postgres_upstream_free_connection(ngx_log_t *log, ngx_connection_t *c,
+ PGconn *pgconn, ngx_postgres_upstream_srv_conf_t *pgscf)
+{
+ ngx_event_t *rev, *wev;
+
+ dd("entering");
+
+ PQfinish(pgconn);
+
+ if (c) {
+ rev = c->read;
+ wev = c->write;
+
+ if (rev->timer_set) {
+ ngx_del_timer(rev);
+ }
+
+ if (wev->timer_set) {
+ ngx_del_timer(wev);
+ }
+
+ if (ngx_del_conn) {
+ ngx_del_conn(c, NGX_CLOSE_EVENT);
+ } else {
+ if (rev->active || rev->disabled) {
+ ngx_del_event(rev, NGX_READ_EVENT, NGX_CLOSE_EVENT);
+ }
+
+ if (wev->active || wev->disabled) {
+ ngx_del_event(wev, NGX_WRITE_EVENT, NGX_CLOSE_EVENT);
+ }
+ }
+
+#if defined(nginx_version) && nginx_version >= 1007005
+ if (rev->posted) {
+#else
+ if (rev->prev) {
+#endif
+ ngx_delete_posted_event(rev);
+ }
+
+#if defined(nginx_version) && nginx_version >= 1007005
+ if (wev->posted) {
+#else
+ if (wev->prev) {
+#endif
+ ngx_delete_posted_event(wev);
+ }
+
+ rev->closed = 1;
+ wev->closed = 1;
+
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ if (c->pool) {
+ ngx_destroy_pool(c->pool);
+ }
+#endif
+
+ ngx_free_connection(c);
+
+ c->fd = (ngx_socket_t) -1;
+ }
+
+ /* free spot in keepalive connection pool */
+ pgscf->active_conns--;
+
+ dd("returning");
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_upstream.h b/ngx_postgres-1.0/src/ngx_postgres_upstream.h
new file mode 100644
index 0000000..9ad46ed
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_upstream.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Xiaozhe Wang <chaoslawful@gmail.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_HTTP_UPSTREAM_POSTGRES_H_
+#define _NGX_HTTP_UPSTREAM_POSTGRES_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <libpq-fe.h>
+
+#include "ngx_postgres_module.h"
+
+
+typedef enum {
+ state_db_connect,
+ state_db_send_query,
+ state_db_get_result,
+ state_db_get_ack,
+ state_db_idle
+} ngx_postgres_state_t;
+
+typedef struct {
+ ngx_postgres_upstream_srv_conf_t *srv_conf;
+ ngx_postgres_loc_conf_t *loc_conf;
+ ngx_http_upstream_t *upstream;
+ ngx_http_request_t *request;
+ PGconn *pgconn;
+ ngx_postgres_state_t state;
+ ngx_str_t query;
+ ngx_str_t name;
+ struct sockaddr sockaddr;
+ unsigned failed;
+} ngx_postgres_upstream_peer_data_t;
+
+
+ngx_int_t ngx_postgres_upstream_init(ngx_conf_t *,
+ ngx_http_upstream_srv_conf_t *);
+ngx_int_t ngx_postgres_upstream_init_peer(ngx_http_request_t *,
+ ngx_http_upstream_srv_conf_t *);
+ngx_int_t ngx_postgres_upstream_get_peer(ngx_peer_connection_t *, void *);
+void ngx_postgres_upstream_free_peer(ngx_peer_connection_t *, void *,
+ ngx_uint_t);
+ngx_flag_t ngx_postgres_upstream_is_my_peer(const ngx_peer_connection_t *);
+void ngx_postgres_upstream_free_connection(ngx_log_t *,
+ ngx_connection_t *, PGconn *,
+ ngx_postgres_upstream_srv_conf_t *);
+
+
+#endif /* _NGX_HTTP_UPSTREAM_POSTGRES_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_util.c b/ngx_postgres-1.0/src/ngx_postgres_util.c
new file mode 100644
index 0000000..ae7de38
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_util.c
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * Copyright (C) 2002-2010, Igor Sysoev
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include <nginx.h>
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_util.h"
+
+
+/*
+ * All functions in this file are copied directly from ngx_http_upstream.c,
+ * beacuse they are declared as static there.
+ */
+
+
+void
+ngx_postgres_upstream_finalize_request(ngx_http_request_t *r,
+ ngx_http_upstream_t *u, ngx_int_t rc)
+{
+#if defined(nginx_version) && (nginx_version < 1009001)
+ ngx_time_t *tp;
+#endif
+
+ dd("entering");
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "finalize http upstream request: %i", rc);
+
+ if (u->cleanup) {
+ *u->cleanup = NULL;
+ }
+
+ if (u->resolved && u->resolved->ctx) {
+ ngx_resolve_name_done(u->resolved->ctx);
+ u->resolved->ctx = NULL;
+ }
+
+#if defined(nginx_version) && (nginx_version >= 1009001)
+ if (u->state && u->state->response_time) {
+ u->state->response_time = ngx_current_msec - u->state->response_time;
+#else
+ if (u->state && u->state->response_sec) {
+ tp = ngx_timeofday();
+ u->state->response_sec = tp->sec - u->state->response_sec;
+ u->state->response_msec = tp->msec - u->state->response_msec;
+#endif
+
+ if (u->pipe) {
+ u->state->response_length = u->pipe->read_length;
+ }
+ }
+
+ if (u->finalize_request) {
+ u->finalize_request(r, rc);
+ }
+
+ if (u->peer.free) {
+ u->peer.free(&u->peer, u->peer.data, 0);
+ }
+
+ if (u->peer.connection) {
+
+#if 0 /* we don't support SSL at this time, was: (NGX_HTTP_SSL) */
+
+ /* TODO: do not shutdown persistent connection */
+
+ if (u->peer.connection->ssl) {
+
+ /*
+ * We send the "close notify" shutdown alert to the upstream only
+ * and do not wait its "close notify" shutdown alert.
+ * It is acceptable according to the TLS standard.
+ */
+
+ u->peer.connection->ssl->no_wait_shutdown = 1;
+
+ (void) ngx_ssl_shutdown(u->peer.connection);
+ }
+#endif
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "close http upstream connection: %d",
+ u->peer.connection->fd);
+
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ if (u->peer.connection->pool) {
+ ngx_destroy_pool(u->peer.connection->pool);
+ }
+#endif
+
+ ngx_close_connection(u->peer.connection);
+ }
+
+ u->peer.connection = NULL;
+
+ if (u->pipe && u->pipe->temp_file) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream temp fd: %d",
+ u->pipe->temp_file->file.fd);
+ }
+
+ if (u->header_sent
+ && (rc == NGX_ERROR || rc >= NGX_HTTP_SPECIAL_RESPONSE))
+ {
+ rc = 0;
+ }
+
+ if (rc == NGX_DECLINED) {
+ dd("returning");
+ return;
+ }
+
+ r->connection->log->action = "sending to client";
+
+ if (rc == 0) {
+ rc = ngx_http_send_special(r, NGX_HTTP_LAST);
+ }
+
+ ngx_http_finalize_request(r, rc);
+
+ dd("returning");
+}
+
+void
+ngx_postgres_upstream_next(ngx_http_request_t *r,
+ ngx_http_upstream_t *u, ngx_int_t ft_type)
+{
+ ngx_uint_t status, state;
+
+ dd("entering");
+
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http next upstream, %xi", ft_type);
+
+#if 0
+ ngx_http_busy_unlock(u->conf->busy_lock, &u->busy_lock);
+#endif
+
+ if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) {
+ state = NGX_PEER_NEXT;
+ } else {
+ state = NGX_PEER_FAILED;
+ }
+
+ if (ft_type != NGX_HTTP_UPSTREAM_FT_NOLIVE) {
+ u->peer.free(&u->peer, u->peer.data, state);
+ }
+
+ if (ft_type == NGX_HTTP_UPSTREAM_FT_TIMEOUT) {
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, NGX_ETIMEDOUT,
+ "upstream timed out");
+ }
+
+ if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) {
+ status = 0;
+
+ } else {
+ switch(ft_type) {
+
+ case NGX_HTTP_UPSTREAM_FT_TIMEOUT:
+ status = NGX_HTTP_GATEWAY_TIME_OUT;
+ break;
+
+ case NGX_HTTP_UPSTREAM_FT_HTTP_500:
+ status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ break;
+
+ case NGX_HTTP_UPSTREAM_FT_HTTP_404:
+ status = NGX_HTTP_NOT_FOUND;
+ break;
+
+ /*
+ * NGX_HTTP_UPSTREAM_FT_BUSY_LOCK and NGX_HTTP_UPSTREAM_FT_MAX_WAITING
+ * never reach here
+ */
+
+ default:
+ status = NGX_HTTP_BAD_GATEWAY;
+ }
+ }
+
+ if (r->connection->error) {
+ ngx_postgres_upstream_finalize_request(r, u,
+ NGX_HTTP_CLIENT_CLOSED_REQUEST);
+
+ dd("returning");
+ return;
+ }
+
+ if (status) {
+ u->state->status = status;
+
+ if (u->peer.tries == 0 || !(u->conf->next_upstream & ft_type)) {
+ ngx_postgres_upstream_finalize_request(r, u, status);
+
+ dd("returning");
+ return;
+ }
+ }
+
+ if (u->peer.connection) {
+ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "close http upstream connection: %d",
+ u->peer.connection->fd);
+
+#if 0 /* we don't support SSL at this time, was: (NGX_HTTP_SSL) */
+
+ if (u->peer.connection->ssl) {
+ u->peer.connection->ssl->no_wait_shutdown = 1;
+ u->peer.connection->ssl->no_send_shutdown = 1;
+
+ (void) ngx_ssl_shutdown(u->peer.connection);
+ }
+#endif
+
+#if defined(nginx_version) && (nginx_version >= 1001004)
+ if (u->peer.connection->pool) {
+ ngx_destroy_pool(u->peer.connection->pool);
+ }
+#endif
+
+ ngx_close_connection(u->peer.connection);
+ }
+
+#if 0
+ if (u->conf->busy_lock && !u->busy_locked) {
+ ngx_http_upstream_busy_lock(p);
+ return;
+ }
+#endif
+
+ /* TODO: ngx_http_upstream_connect(r, u); */
+ if (status == 0) {
+ status = NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ dd("returning");
+ return ngx_postgres_upstream_finalize_request(r, u, status);
+}
+
+ngx_int_t
+ngx_postgres_upstream_test_connect(ngx_connection_t *c)
+{
+ int err;
+ socklen_t len;
+
+ dd("entering");
+
+#if (NGX_HAVE_KQUEUE)
+
+ if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) {
+ if (c->write->pending_eof) {
+ c->log->action = "connecting to upstream";
+ (void) ngx_connection_error(c, c->write->kq_errno,
+ "kevent() reported that connect() failed");
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ } else
+#endif
+ {
+ err = 0;
+ len = sizeof(int);
+
+ /*
+ * BSDs and Linux return 0 and set a pending error in err
+ * Solaris returns -1 and sets errno
+ */
+
+ if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void *) &err, &len) == -1)
+ {
+ err = ngx_errno;
+ }
+
+ if (err) {
+ c->log->action = "connecting to upstream";
+ (void) ngx_connection_error(c, err, "connect() failed");
+
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+ }
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_rewrite_var(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+ uintptr_t data)
+{
+ ngx_http_variable_t *var;
+ ngx_http_core_main_conf_t *cmcf;
+ ngx_postgres_rewrite_loc_conf_t *rlcf;
+
+ rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);
+
+ if (rlcf->uninitialized_variable_warn == 0) {
+ *v = ngx_http_variable_null_value;
+ return NGX_OK;
+ }
+
+ cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
+
+ var = cmcf->variables.elts;
+
+ /*
+ * the ngx_http_rewrite_module sets variables directly in r->variables,
+ * and they should be handled by ngx_http_get_indexed_variable(),
+ * so the handler is called only if the variable is not initialized
+ */
+
+ ngx_log_error(NGX_LOG_WARN, r->connection->log, 0,
+ "using uninitialized \"%V\" variable", &var[data].name);
+
+ *v = ngx_http_variable_null_value;
+
+ return NGX_OK;
+}
+
+char *
+ngx_postgres_rewrite_value(ngx_conf_t *cf, ngx_postgres_rewrite_loc_conf_t *lcf,
+ ngx_str_t *value)
+{
+ ngx_int_t n;
+ ngx_http_script_compile_t sc;
+ ngx_http_script_value_code_t *val;
+ ngx_http_script_complex_value_code_t *complex;
+
+ n = ngx_http_script_variables_count(value);
+
+ if (n == 0) {
+ val = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_value_code_t));
+ if (val == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ n = ngx_atoi(value->data, value->len);
+
+ if (n == NGX_ERROR) {
+ n = 0;
+ }
+
+ val->code = ngx_http_script_value_code;
+ val->value = (uintptr_t) n;
+ val->text_len = (uintptr_t) value->len;
+ val->text_data = (uintptr_t) value->data;
+
+ return NGX_CONF_OK;
+ }
+
+ complex = ngx_http_script_start_code(cf->pool, &lcf->codes,
+ sizeof(ngx_http_script_complex_value_code_t));
+ if (complex == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ complex->code = ngx_http_script_complex_value_code;
+ complex->lengths = NULL;
+
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = value;
+ sc.lengths = &complex->lengths;
+ sc.values = &lcf->codes;
+ sc.variables = n;
+ sc.complete_lengths = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_util.h b/ngx_postgres-1.0/src/ngx_postgres_util.h
new file mode 100644
index 0000000..02938a9
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_util.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * Copyright (c) 2009-2010, Yichun Zhang <agentzh@gmail.com>
+ * Copyright (C) 2002-2010, Igor Sysoev
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_UTIL_H_
+#define _NGX_POSTGRES_UTIL_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+
+extern ngx_module_t ngx_http_rewrite_module;
+
+
+typedef struct {
+ ngx_array_t *codes; /* uintptr_t */
+
+ ngx_uint_t stack_size;
+
+ ngx_flag_t log;
+ ngx_flag_t uninitialized_variable_warn;
+} ngx_postgres_rewrite_loc_conf_t;
+
+
+void ngx_postgres_upstream_finalize_request(ngx_http_request_t *,
+ ngx_http_upstream_t *, ngx_int_t);
+void ngx_postgres_upstream_next(ngx_http_request_t *,
+ ngx_http_upstream_t *, ngx_int_t);
+ngx_int_t ngx_postgres_upstream_test_connect(ngx_connection_t *);
+
+ngx_int_t ngx_postgres_rewrite_var(ngx_http_request_t *,
+ ngx_http_variable_value_t *, uintptr_t);
+char *ngx_postgres_rewrite_value(ngx_conf_t *,
+ ngx_postgres_rewrite_loc_conf_t *, ngx_str_t *);
+
+#endif /* _NGX_POSTGRES_UTIL_H_ */
diff --git a/ngx_postgres-1.0/src/ngx_postgres_variable.c b/ngx_postgres-1.0/src/ngx_postgres_variable.c
new file mode 100644
index 0000000..e7f4f38
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_variable.c
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DDEBUG
+#define DDEBUG 0
+#endif
+
+#include "ngx_postgres_ddebug.h"
+#include "ngx_postgres_module.h"
+#include "ngx_postgres_variable.h"
+
+
+ngx_int_t
+ngx_postgres_variable_columns(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering: \"$postgres_columns\"");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgctx == NULL) || (pgctx->var_cols == NGX_ERROR)) {
+ v->not_found = 1;
+ dd("returning NGX_OK (not_found)");
+ return NGX_OK;
+ }
+
+ v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN);
+ if (v->data == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%i", pgctx->var_cols) - v->data;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_variable_rows(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering: \"$postgres_rows\"");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgctx == NULL) || (pgctx->var_rows == NGX_ERROR)) {
+ v->not_found = 1;
+ dd("returning NGX_OK (not_found)");
+ return NGX_OK;
+ }
+
+ v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN);
+ if (v->data == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%i", pgctx->var_rows) - v->data;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_variable_affected(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering: \"$postgres_affected\"");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgctx == NULL) || (pgctx->var_affected == NGX_ERROR)) {
+ v->not_found = 1;
+ dd("returning NGX_OK (not_found)");
+ return NGX_OK;
+ }
+
+ v->data = ngx_pnalloc(r->pool, NGX_INT32_LEN);
+ if (v->data == NULL) {
+ dd("returning NGX_ERROR");
+ return NGX_ERROR;
+ }
+
+ v->len = ngx_sprintf(v->data, "%i", pgctx->var_affected) - v->data;
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_variable_query(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_postgres_ctx_t *pgctx;
+
+ dd("entering: \"$postgres_query\"");
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgctx == NULL) || (pgctx->var_query.len == 0)) {
+ v->not_found = 1;
+ dd("returning NGX_OK (not_found)");
+ return NGX_OK;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->len = pgctx->var_query.len;
+ v->data = pgctx->var_query.data;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_int_t
+ngx_postgres_variable_get_custom(ngx_http_request_t *r,
+ ngx_http_variable_value_t *v, uintptr_t data)
+{
+ ngx_postgres_variable_t *pgvar = (ngx_postgres_variable_t *) data;
+ ngx_postgres_ctx_t *pgctx;
+ ngx_str_t *store;
+
+ dd("entering: \"$%.*s\"", (int) pgvar->var->name.len,
+ pgvar->var->name.data);
+
+ pgctx = ngx_http_get_module_ctx(r, ngx_postgres_module);
+
+ if ((pgctx == NULL) || (pgctx->variables == NULL)) {
+ v->not_found = 1;
+ dd("returning NGX_OK (not_found)");
+ return NGX_OK;
+ }
+
+ store = pgctx->variables->elts;
+
+ /* idx is always valid */
+ if (store[pgvar->idx].len == 0) {
+ v->not_found = 1;
+ dd("returning NGX_OK (not_found)");
+ return NGX_OK;
+ }
+
+ v->valid = 1;
+ v->no_cacheable = 0;
+ v->not_found = 0;
+ v->len = store[pgvar->idx].len;
+ v->data = store[pgvar->idx].data;
+
+ dd("returning NGX_OK");
+ return NGX_OK;
+}
+
+ngx_str_t
+ngx_postgres_variable_set_custom(ngx_http_request_t *r, PGresult *res,
+ ngx_postgres_variable_t *pgvar)
+{
+ ngx_http_core_loc_conf_t *clcf;
+ ngx_postgres_value_t *pgv;
+ ngx_int_t col_count, row_count, col, len;
+ ngx_str_t value = ngx_null_string;
+
+ dd("entering: \"$%.*s\"", (int) pgvar->var->name.len,
+ pgvar->var->name.data);
+
+ col_count = PQnfields(res);
+ row_count = PQntuples(res);
+
+ pgv = &pgvar->value;
+
+ if (pgv->column != NGX_ERROR) {
+ /* get column by number */
+ col = pgv->column;
+ } else {
+ /* get column by name */
+ col = PQfnumber(res, (char const *) pgv->col_name);
+ if (col == NGX_ERROR) {
+ if (pgv->required) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: \"postgres_set\" for variable \"$%V\""
+ " requires value from column \"%s\" that wasn't"
+ " found in the received result-set in location"
+ " \"%V\"",
+ &pgvar->var->name, pgv->col_name, &clcf->name);
+ }
+
+ dd("returning empty value");
+ return value;
+ }
+ }
+
+ if ((pgv->row >= row_count) || (col >= col_count)) {
+ if (pgv->required) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: \"postgres_set\" for variable \"$%V\""
+ " requires value out of range of the received"
+ " result-set (rows:%d cols:%d) in location \"%V\"",
+ &pgvar->var->name, row_count, col_count, &clcf->name);
+ }
+
+ dd("returning empty value");
+ return value;
+ }
+
+ if (PQgetisnull(res, pgv->row, col)) {
+ if (pgv->required) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: \"postgres_set\" for variable \"$%V\""
+ " requires non-NULL value in location \"%V\"",
+ &pgvar->var->name, &clcf->name);
+ }
+
+ dd("returning empty value");
+ return value;
+ }
+
+ len = PQgetlength(res, pgv->row, col);
+ if (len == 0) {
+ if (pgv->required) {
+ clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+ "postgres: \"postgres_set\" for variable \"$%V\""
+ " requires non-zero length value in location \"%V\"",
+ &pgvar->var->name, &clcf->name);
+ }
+
+ dd("returning empty value");
+ return value;
+ }
+
+ value.data = ngx_pnalloc(r->pool, len);
+ if (value.data == NULL) {
+ dd("returning empty value");
+ return value;
+ }
+
+ ngx_memcpy(value.data, PQgetvalue(res, pgv->row, col), len);
+ value.len = len;
+
+ dd("returning non-empty value");
+ return value;
+}
diff --git a/ngx_postgres-1.0/src/ngx_postgres_variable.h b/ngx_postgres-1.0/src/ngx_postgres_variable.h
new file mode 100644
index 0000000..abdb199
--- /dev/null
+++ b/ngx_postgres-1.0/src/ngx_postgres_variable.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2010, FRiCKLE Piotr Sikora <info@frickle.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _NGX_POSTGRES_VARIABLE_H_
+#define _NGX_POSTGRES_VARIABLE_H_
+
+#include <ngx_core.h>
+#include <ngx_http.h>
+#include <libpq-fe.h>
+
+#include "ngx_postgres_module.h"
+
+
+ngx_int_t ngx_postgres_variable_columns(ngx_http_request_t *,
+ ngx_http_variable_value_t *, uintptr_t);
+ngx_int_t ngx_postgres_variable_rows(ngx_http_request_t *,
+ ngx_http_variable_value_t *, uintptr_t);
+ngx_int_t ngx_postgres_variable_affected(ngx_http_request_t *,
+ ngx_http_variable_value_t *, uintptr_t);
+ngx_int_t ngx_postgres_variable_query(ngx_http_request_t *,
+ ngx_http_variable_value_t *, uintptr_t);
+ngx_int_t ngx_postgres_variable_get_custom(ngx_http_request_t *,
+ ngx_http_variable_value_t *, uintptr_t);
+ngx_str_t ngx_postgres_variable_set_custom(ngx_http_request_t *r,
+ PGresult *, ngx_postgres_variable_t *);
+
+#endif /* _NGX_POSTGRES_VARIABLE_H_ */
diff --git a/ngx_postgres-1.0/src/resty_dbd_stream.h b/ngx_postgres-1.0/src/resty_dbd_stream.h
new file mode 100644
index 0000000..ebb0cb3
--- /dev/null
+++ b/ngx_postgres-1.0/src/resty_dbd_stream.h
@@ -0,0 +1,59 @@
+#ifndef RESTY_DBD_STREAME_H
+#define RESTY_DBD_STREAME_H
+
+#define resty_dbd_stream_version 3
+#define resty_dbd_stream_version_string "0.0.3"
+
+#define rds_content_type \
+ "application/x-resty-dbd-stream"
+
+#define rds_content_type_len \
+ (sizeof(rds_content_type) - 1)
+
+
+typedef enum {
+ rds_rough_col_type_int = 0 << 14,
+ rds_rough_col_type_float = 1 << 14,
+ rds_rough_col_type_str = 2 << 14,
+ rds_rough_col_type_bool = 3 << 14
+
+} rds_rough_col_type_t;
+
+
+/* The following types (or spellings thereof) are specified
+ * by SQL:
+ * bigint, bit, bit varying, boolean, char, character varying,
+ * character, varchar, date, double precision, integer,
+ * interval, numeric, decimal, real, smallint,
+ * time (with or without time zone),
+ * timestamp (with or without time zone), xml */
+
+typedef enum {
+ rds_col_type_unknown = 0 | rds_rough_col_type_str,
+ rds_col_type_bigint = 1 | rds_rough_col_type_int,
+ rds_col_type_bit = 2 | rds_rough_col_type_str,
+ rds_col_type_bit_varying = 3 | rds_rough_col_type_str,
+
+ rds_col_type_bool = 4 | rds_rough_col_type_bool,
+ rds_col_type_char = 5 | rds_rough_col_type_str,
+ rds_col_type_varchar = 6 | rds_rough_col_type_str,
+ rds_col_type_date = 7 | rds_rough_col_type_str,
+ rds_col_type_double = 8 | rds_rough_col_type_float,
+ rds_col_type_integer = 9 | rds_rough_col_type_int,
+ rds_col_type_interval = 10 | rds_rough_col_type_float,
+ rds_col_type_decimal = 11 | rds_rough_col_type_float,
+ rds_col_type_real = 12 | rds_rough_col_type_float,
+ rds_col_type_smallint = 13 | rds_rough_col_type_int,
+ rds_col_type_time_with_time_zone = 14 | rds_rough_col_type_str,
+ rds_col_type_time = 15 | rds_rough_col_type_str,
+ rds_col_type_timestamp_with_time_zone = 16 | rds_rough_col_type_str,
+ rds_col_type_timestamp = 17 | rds_rough_col_type_str,
+ rds_col_type_xml = 18 | rds_rough_col_type_str,
+
+ /* our additions */
+ rds_col_type_blob = 19 | rds_rough_col_type_str
+
+} rds_col_type_t;
+
+#endif /* RESTY_DBD_STREAME_H */
+
diff --git a/ngx_postgres-1.0/t/000_init.t b/ngx_postgres-1.0/t/000_init.t
new file mode 100644
index 0000000..832244e
--- /dev/null
+++ b/ngx_postgres-1.0/t/000_init.t
@@ -0,0 +1,174 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(1);
+
+plan tests => repeat_each() * 2 * blocks();
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+no_shuffle();
+run_tests();
+
+__DATA__
+
+=== TEST 1: cats - drop table
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "DROP TABLE cats";
+ error_page 500 = /ignore;
+ }
+
+ location /ignore { echo "ignore"; }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 2: cats - create table
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "CREATE TABLE cats (id integer, name text)";
+ }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 3: cats - insert value
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "INSERT INTO cats (id) VALUES (2)";
+ }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: cats - insert value
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "INSERT INTO cats (id, name) VALUES (3, 'bob')";
+ }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 5: numbers - drop table
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "DROP TABLE numbers";
+ error_page 500 = /ignore;
+ }
+
+ location /ignore { echo "ignore"; }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 6: numbers - create table
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "CREATE TABLE numbers (number integer)";
+ }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 7: users - drop table
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "DROP TABLE users";
+ error_page 500 = /ignore;
+ }
+
+ location /ignore { echo "ignore"; }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 8: users - create table
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "CREATE TABLE users (login text, pass text)";
+ }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
+
+
+
+=== TEST 9: users - insert value
+--- http_config eval: $::http_config
+--- config
+ location = /init {
+ postgres_pass database;
+ postgres_query "INSERT INTO users (login, pass) VALUES ('ngx_test', 'ngx_test')";
+ }
+--- request
+GET /init
+--- error_code: 200
+--- timeout: 10
+--- no_error_log
+[error]
diff --git a/ngx_postgres-1.0/t/auth.t b/ngx_postgres-1.0/t/auth.t
new file mode 100644
index 0000000..40468e4
--- /dev/null
+++ b/ngx_postgres-1.0/t/auth.t
@@ -0,0 +1,110 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 3 - 2 * 1);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: authorized (auth basic)
+--- http_config eval: $::http_config
+--- config
+ location = /auth {
+ internal;
+ postgres_escape $user $remote_user;
+ postgres_escape $pass $remote_passwd;
+ postgres_pass database;
+ postgres_query "select login from users where login=$user and pass=$pass";
+ postgres_rewrite no_rows 403;
+ postgres_set $login 0 0 required;
+ postgres_output none;
+ }
+
+ location /test {
+ auth_request /auth;
+ auth_request_set $auth_user $login;
+ echo -n "hi, $auth_user!";
+ }
+--- more_headers
+Authorization: Basic bmd4X3Rlc3Q6bmd4X3Rlc3Q=
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body chomp
+hi, ngx_test!
+--- timeout: 10
+
+
+
+=== TEST 2: unauthorized (auth basic)
+--- http_config eval: $::http_config
+--- config
+ location = /auth {
+ internal;
+ postgres_escape $user $remote_user;
+ postgres_escape $pass $remote_passwd;
+ postgres_pass database;
+ postgres_query "select login from users where login=$user and pass=$pass";
+ postgres_rewrite no_rows 403;
+ postgres_set $login 0 0 required;
+ postgres_output none;
+ }
+
+ location /test {
+ auth_request /auth;
+ auth_request_set $auth_user $login;
+ echo -n "hi, $auth_user!";
+ }
+--- more_headers
+Authorization: Basic bW9udHk6c29tZV9wYXNz
+--- request
+GET /test
+--- error_code: 403
+--- response_headers
+Content-Type: text/html
+--- timeout: 10
+
+
+
+=== TEST 3: unauthorized (no authorization header)
+--- http_config eval: $::http_config
+--- config
+ location = /auth {
+ internal;
+ postgres_escape $user $remote_user;
+ postgres_escape $pass $remote_passwd;
+ postgres_pass database;
+ postgres_query "select login from users where login=$user and pass=$pass";
+ postgres_rewrite no_rows 403;
+ postgres_set $login 0 0 required;
+ postgres_output none;
+ }
+
+ location /test {
+ auth_request /auth;
+ auth_request_set $auth_user $login;
+ echo -n "hi, $auth_user!";
+ }
+--- request
+GET /test
+--- error_code: 403
+--- response_headers
+Content-Type: text/html
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/bigpipe.t b/ngx_postgres-1.0/t/bigpipe.t
new file mode 100644
index 0000000..4ee951d
--- /dev/null
+++ b/ngx_postgres-1.0/t/bigpipe.t
@@ -0,0 +1,141 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 2);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: synchronous
+--- http_config eval: $::http_config
+--- config
+ location /bigpipe {
+ echo "<html>(...template with javascript and divs...)";
+ echo -n "<script type=\"text/javascript\">loader.load(";
+ echo_location /_query1;
+ echo ")</script>";
+ echo -n "<script type=\"text/javascript\">loader.load(";
+ echo_location /_query2;
+ echo ")</script>";
+ echo "</html>";
+ }
+
+ location /_query1 {
+ internal;
+ postgres_pass database;
+ postgres_query "SELECT * FROM cats ORDER BY id ASC";
+ rds_json on;
+ }
+
+ location /_query2 {
+ internal;
+ postgres_pass database;
+ postgres_query "SELECT * FROM cats ORDER BY id DESC";
+ rds_json on;
+ }
+--- request
+GET /bigpipe
+--- error_code: 200
+--- response_body
+<html>(...template with javascript and divs...)
+<script type="text/javascript">loader.load([{"id":2,"name":null},{"id":3,"name":"bob"}])</script>
+<script type="text/javascript">loader.load([{"id":3,"name":"bob"},{"id":2,"name":null}])</script>
+</html>
+--- timeout: 10
+--- skip_nginx: 2: < 0.7.46
+
+
+
+=== TEST 2: asynchronous (without echo filter)
+--- http_config eval: $::http_config
+--- config
+ location /bigpipe {
+ echo "<html>(...template with javascript and divs...)";
+ echo -n "<script type=\"text/javascript\">loader.load(";
+ echo_location_async /_query1;
+ echo ")</script>";
+ echo -n "<script type=\"text/javascript\">loader.load(";
+ echo_location_async /_query2;
+ echo ")</script>";
+ echo "</html>";
+ }
+
+ location /_query1 {
+ internal;
+ postgres_pass database;
+ postgres_query "SELECT * FROM cats ORDER BY id ASC";
+ rds_json on;
+ }
+
+ location /_query2 {
+ internal;
+ postgres_pass database;
+ postgres_query "SELECT * FROM cats ORDER BY id DESC";
+ rds_json on;
+ }
+--- request
+GET /bigpipe
+--- error_code: 200
+--- response_body
+<html>(...template with javascript and divs...)
+<script type="text/javascript">loader.load([{"id":2,"name":null},{"id":3,"name":"bob"}])</script>
+<script type="text/javascript">loader.load([{"id":3,"name":"bob"},{"id":2,"name":null}])</script>
+</html>
+--- timeout: 10
+--- skip_nginx: 2: < 0.7.46
+
+
+
+=== TEST 3: asynchronous (with echo filter)
+--- http_config eval: $::http_config
+--- config
+ location /bigpipe {
+ echo_before_body "<html>(...template with javascript and divs...)";
+ echo_before_body -n "<script type=\"text/javascript\">loader.load(";
+ echo -n " "; # XXX we need this to help our echo filters
+ echo_location_async /_query1;
+ echo ")</script>";
+ echo -n "<script type=\"text/javascript\">loader.load(";
+ echo_location_async /_query2;
+ echo_after_body ")</script>";
+ echo_after_body "</html>";
+ }
+
+ location /_query1 {
+ internal;
+ postgres_pass database;
+ postgres_query "SELECT * FROM cats ORDER BY id ASC";
+ rds_json on;
+ }
+
+ location /_query2 {
+ internal;
+ postgres_pass database;
+ postgres_query "SELECT * FROM cats ORDER BY id DESC";
+ rds_json on;
+ }
+--- request
+GET /bigpipe
+--- error_code: 200
+--- response_body
+<html>(...template with javascript and divs...)
+<script type="text/javascript">loader.load( [{"id":2,"name":null},{"id":3,"name":"bob"}])</script>
+<script type="text/javascript">loader.load([{"id":3,"name":"bob"},{"id":2,"name":null}])</script>
+</html>
+--- timeout: 10
+--- skip_nginx: 2: < 0.7.46
diff --git a/ngx_postgres-1.0/t/errors.t b/ngx_postgres-1.0/t/errors.t
new file mode 100644
index 0000000..a502203
--- /dev/null
+++ b/ngx_postgres-1.0/t/errors.t
@@ -0,0 +1,141 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * blocks();
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: bad query
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "i'm bad";
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 2: wrong credentials
+--- http_config
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=wrong_pass;
+ }
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set name='bob' where name='bob'";
+ }
+--- request
+GET /postgres
+--- error_code: 502
+--- timeout: 10
+
+
+
+=== TEST 3: no database
+--- http_config
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:1 dbname=ngx_test
+ user=ngx_test password=ngx_test;
+ }
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set name='bob' where name='bob'";
+ }
+--- request
+GET /postgres
+--- error_code: 502
+--- timeout: 10
+
+
+
+=== TEST 4: multiple queries
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats; select * from cats";
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 5: missing query
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 6: empty query
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ set $query "";
+ postgres_pass database;
+ postgres_query $query;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 7: empty pass
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ set $database "";
+ postgres_pass $database;
+ postgres_query "update cats set name='bob' where name='bob'";
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 8: non-existing table
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update table_that_doesnt_exist set name='bob'";
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/escape.t b/ngx_postgres-1.0/t/escape.t
new file mode 100644
index 0000000..8dad7f0
--- /dev/null
+++ b/ngx_postgres-1.0/t/escape.t
@@ -0,0 +1,361 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 3);
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: '
+--- config
+ location /test {
+ set $test "he'llo";
+ postgres_escape $escaped $test;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'he''llo'
+--- timeout: 10
+
+
+
+=== TEST 2: \
+--- config
+ location /test {
+ set $test "he\\llo";
+ postgres_escape $escaped $test;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'he\\llo'
+--- timeout: 10
+
+
+
+=== TEST 3: \'
+--- config
+ location /test {
+ set $test "he\\'llo";
+ postgres_escape $escaped $test;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'he\\''llo'
+--- timeout: 10
+
+
+
+=== TEST 4: NULL
+--- config
+ location /test {
+ postgres_escape $escaped $remote_user;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+NULL
+--- timeout: 10
+
+
+
+=== TEST 5: empty string
+--- config
+ location /test {
+ set $empty "";
+ postgres_escape $escaped $empty;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+NULL
+--- timeout: 10
+
+
+
+=== TEST 6: UTF-8
+--- config
+ location /test {
+ set $utf8 "你好";
+ postgres_escape $escaped $utf8;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'你好'
+--- timeout: 10
+
+
+
+=== TEST 7: user arg
+--- config
+ location /test {
+ postgres_escape $escaped $arg_say;
+ echo $escaped;
+ }
+--- request
+GET /test?say=he'llo!
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'he''llo!'
+--- timeout: 10
+
+
+
+=== TEST 8: NULL (empty)
+--- config
+ location /test {
+ postgres_escape $escaped =$remote_user;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 9: empty string (empty)
+--- config
+ location /test {
+ set $empty "";
+ postgres_escape $escaped =$empty;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 10: in-place escape
+--- config
+ location /test {
+ set $test "t'\\est";
+ postgres_escape $test;
+ echo $test;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'t''\\est'
+--- timeout: 10
+
+
+
+=== TEST 11: re-useable variable name (test1)
+--- config
+ location /test1 {
+ set $a "a";
+ postgres_escape $escaped $a;
+ echo $escaped;
+ }
+ location /test2 {
+ set $b "b";
+ postgres_escape $escaped $b;
+ echo $escaped;
+ }
+--- request
+GET /test1
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'a'
+--- timeout: 10
+
+
+
+=== TEST 12: re-useable variable name (test2)
+--- config
+ location /test1 {
+ set $a "a";
+ postgres_escape $escaped $a;
+ echo $escaped;
+ }
+ location /test2 {
+ set $b "b";
+ postgres_escape $escaped $b;
+ echo $escaped;
+ }
+--- request
+GET /test2
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'b'
+--- timeout: 10
+
+
+
+=== TEST 13: concatenate multiple sources
+--- config
+ location /test {
+ set $test "t'\\est";
+ set $hello " he'llo";
+ postgres_escape $escaped "$test$hello world!";
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'t''\\est he''llo world!'
+--- timeout: 10
+
+
+
+=== TEST 14: concatenate multiple empty sources
+--- config
+ location /test {
+ set $a "";
+ set $b "";
+ postgres_escape $escaped "$a$b";
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+NULL
+--- timeout: 10
+
+
+
+=== TEST 15: concatenate multiple empty sources (empty)
+--- config
+ location /test {
+ set $a "";
+ set $b "";
+ postgres_escape $escaped "=$a$b";
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 16: in-place escape on empty string
+--- config
+ location /test {
+ set $test "";
+ postgres_escape $test;
+ echo $test;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+NULL
+--- timeout: 10
+
+
+
+=== TEST 17: in-place escape on empty string (empty)
+--- config
+ location /test {
+ set $test "";
+ postgres_escape =$test;
+ echo $test;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+''
+--- timeout: 10
+
+
+
+=== TEST 18: escape anonymous regex capture
+--- config
+ location ~ /(.*) {
+ postgres_escape $escaped $1;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'test'
+--- timeout: 10
+
+
+
+=== TEST 19: escape named regex capture
+--- config
+ location ~ /(?<test>.*) {
+ postgres_escape $escaped $test;
+ echo $escaped;
+ }
+--- request
+GET /test
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body
+'test'
+--- timeout: 10
+--- skip_nginx: 3: < 0.8.25
diff --git a/ngx_postgres-1.0/t/eval.t b/ngx_postgres-1.0/t/eval.t
new file mode 100644
index 0000000..c87761d
--- /dev/null
+++ b/ngx_postgres-1.0/t/eval.t
@@ -0,0 +1,75 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 3);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- http_config eval: $::http_config
+--- config
+ location /eval {
+ eval_subrequest_in_memory off;
+
+ eval $backend {
+ postgres_pass database;
+ postgres_query "select '$scheme://127.0.0.1:$server_port/echo'";
+ postgres_output value;
+ }
+
+ proxy_pass $backend;
+ }
+
+ location /echo {
+ echo -n "it works!";
+ }
+--- request
+GET /eval
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body chomp
+it works!
+--- timeout: 10
+--- skip_nginx: 3: < 0.8.25
+
+
+
+=== TEST 2: sanity (simple case)
+--- http_config eval: $::http_config
+--- config
+ location /eval {
+ eval_subrequest_in_memory off;
+
+ eval $echo {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_output value;
+ }
+
+ echo -n $echo;
+ }
+--- request
+GET /eval
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body chomp
+test
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/form.t b/ngx_postgres-1.0/t/form.t
new file mode 100644
index 0000000..4771232
--- /dev/null
+++ b/ngx_postgres-1.0/t/form.t
@@ -0,0 +1,71 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 3);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ set_form_input $sql 'sql';
+ set_unescape_uri $sql;
+ postgres_query $sql;
+ }
+--- more_headers
+Content-Type: application/x-www-form-urlencoded
+--- request
+POST /postgres
+sql=select%20*%20from%20cats;
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{02}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{02}\x{00}". # col name len
+"id". # col name data
+"\x{06}\x{80}". # std col type (varchar/str)
+"\x{19}\x{00}". # driver col type
+"\x{04}\x{00}". # col name len
+"name". # col name data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"2". # field data
+"\x{ff}\x{ff}\x{ff}\x{ff}". # field len
+"". # field data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"3". # field data
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"bob". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/methods.t b/ngx_postgres-1.0/t/methods.t
new file mode 100644
index 0000000..93b80e8
--- /dev/null
+++ b/ngx_postgres-1.0/t/methods.t
@@ -0,0 +1,335 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 3 - 2 * 2);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: default query
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'default' as echo";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{07}\x{00}\x{00}\x{00}". # field len
+"default". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 2: method-specific query
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query LOCK GET UNLOCK "select 'GET' as echo";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"GET". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 3: method-specific complex query (check 1)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query LOCK GET UNLOCK "select '$request_method' as echo";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"GET". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 4: method-specific complex query (check 2)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query LOCK GET UNLOCK "select '$request_method' as echo";
+ }
+--- request
+LOCK /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{04}\x{00}\x{00}\x{00}". # field len
+"LOCK". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 5: method-specific complex query (using not allowed method)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query LOCK GET UNLOCK "select '$request_method' as echo";
+ }
+--- request
+HEAD /postgres
+--- error_code: 405
+--- timeout: 10
+
+
+
+=== TEST 6: method-specific query and default query (using defined method)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'default' as echo";
+ postgres_query LOCK GET UNLOCK "select '$request_method' as echo";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"GET". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 7: method-specific query and default query (using other method)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'default' as echo";
+ postgres_query LOCK GET UNLOCK "select '$request_method' as echo";
+ }
+--- request
+POST /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{07}\x{00}\x{00}\x{00}". # field len
+"default". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 8: inheritance
+--- http_config eval: $::http_config
+--- config
+ postgres_query "select 'default' as echo";
+ postgres_query LOCK GET UNLOCK "select '$request_method' as echo";
+
+ location /postgres {
+ postgres_pass database;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"GET". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 9: inheritance (mixed, don't inherit)
+--- http_config eval: $::http_config
+--- config
+ postgres_query "select 'default' as echo";
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query LOCK GET UNLOCK "select '$request_method' as echo";
+ }
+--- request
+HEAD /postgres
+--- error_code: 405
+--- timeout: 10
+
+
+
+=== TEST 10: HTTP PATCH request method
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query PATCH "select '$request_method' as echo";
+ }
+--- request
+PATCH /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{05}\x{00}\x{00}\x{00}". # field len
+"PATCH". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- skip_nginx: 3: < 0.8.41
diff --git a/ngx_postgres-1.0/t/output.t b/ngx_postgres-1.0/t/output.t
new file mode 100644
index 0000000..b4ba503
--- /dev/null
+++ b/ngx_postgres-1.0/t/output.t
@@ -0,0 +1,447 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 3 - 4 * 2);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: none - sanity
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_output none;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+! Content-Type
+--- response_body eval
+""
+--- timeout: 10
+
+
+
+=== TEST 2: value - sanity
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_output value;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body chomp
+test
+--- timeout: 10
+
+
+
+=== TEST 3: value - sanity (with different default_type)
+--- http_config eval: $::http_config
+--- config
+ default_type text/html;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_output value;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/html
+--- response_body chomp
+test
+--- timeout: 10
+
+
+
+=== TEST 4: value - NULL value
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select NULL as echo";
+ postgres_output value;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 5: value - empty value
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select '' as echo";
+ postgres_output value;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 6: text - sanity
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'a', 'b', 'c', 'd'";
+ postgres_output text;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body eval
+"a".
+"\x{0a}". # new line - delimiter
+"b".
+"\x{0a}". # new line - delimiter
+"c".
+"\x{0a}". # new line - delimiter
+"d"
+--- timeout: 10
+
+
+
+=== TEST 7: rds - sanity (configured)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'default' as echo";
+ postgres_output rds;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{07}\x{00}\x{00}\x{00}". # field len
+"default". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 8: rds - sanity (default)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'default' as echo";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{07}\x{00}\x{00}\x{00}". # field len
+"default". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 9: inheritance
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+ postgres_output value;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body chomp
+test
+--- timeout: 10
+
+
+
+=== TEST 10: inheritance (mixed, don't inherit)
+--- http_config eval: $::http_config
+--- config
+ postgres_output text;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_output none;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+! Content-Type
+--- response_body eval
+""
+--- timeout: 10
+
+
+
+=== TEST 11: value - sanity (request with known extension)
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_output value;
+ }
+--- request
+GET /postgres.jpg
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body chomp
+test
+--- timeout: 10
+
+
+
+=== TEST 12: value - bytea returned in text format
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select E'\\001'::bytea as res";
+ postgres_output value;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body_like chomp
+^(?:\\001|\\x01)$
+--- timeout: 10
+
+
+
+=== TEST 13: binary value - bytea returned in binary format
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select E'\\001'::bytea as res";
+ postgres_output binary_value;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body eval
+"\1"
+--- timeout: 10
+
+
+
+=== TEST 14: binary value - int2 returned in binary format
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 3::int2 as res";
+ postgres_output binary_value;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body eval
+"\0\3"
+--- timeout: 10
+
+
+
+=== TEST 15: value - "if" pseudo-location
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ if ($arg_foo) {
+ postgres_pass database;
+ postgres_query "select id from cats order by id limit 1";
+ postgres_output value;
+ break;
+ }
+
+ return 404;
+ }
+--- request
+GET /postgres?foo=1
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body chomp
+2
+--- timeout: 10
+
+
+
+=== TEST 16: text - NULL value
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats order by id";
+ postgres_output text;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body eval
+"2".
+"\x{0a}". # new line - delimiter
+"(null)".
+"\x{0a}". # new line - delimiter
+"3".
+"\x{0a}". # new line - delimiter
+"bob"
+--- timeout: 10
+
+
+
+=== TEST 17: text - empty result
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats where id=1";
+ postgres_output text;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+--- response_body eval
+""
+--- timeout: 10
+
+
+
+=== TEST 18: value - empty result
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats where id=1";
+ postgres_output value;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 19: value - too many values
+--- http_config eval: $::http_config
+--- config
+ default_type text/plain;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_output value;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/restful.t b/ngx_postgres-1.0/t/restful.t
new file mode 100644
index 0000000..74b111b
--- /dev/null
+++ b/ngx_postgres-1.0/t/restful.t
@@ -0,0 +1,338 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(1);
+
+plan tests => repeat_each() * (blocks() * 3);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+our $config = <<'_EOC_';
+ set $random 123;
+
+ location = /auth {
+ internal;
+
+ postgres_escape $user $remote_user;
+ postgres_escape $pass $remote_passwd;
+
+ postgres_pass database;
+ postgres_query "SELECT login FROM users WHERE login=$user AND pass=$pass";
+ postgres_rewrite no_rows 403;
+ postgres_output none;
+ }
+
+ location = /numbers/ {
+ auth_request /auth;
+ postgres_pass database;
+
+ postgres_query HEAD GET "SELECT * FROM numbers";
+
+ postgres_query POST "INSERT INTO numbers VALUES('$random') RETURNING *";
+ postgres_rewrite POST changes 201;
+
+ postgres_query DELETE "DELETE FROM numbers";
+ postgres_rewrite DELETE no_changes 204;
+ postgres_rewrite DELETE changes 204;
+ }
+
+ location ~ /numbers/(\d+) {
+ auth_request /auth;
+ postgres_pass database;
+
+ postgres_query HEAD GET "SELECT * FROM numbers WHERE number='$1'";
+ postgres_rewrite HEAD GET no_rows 410;
+
+ postgres_query PUT "UPDATE numbers SET number='$1' WHERE number='$1' RETURNING *";
+ postgres_rewrite PUT no_changes 410;
+
+ postgres_query DELETE "DELETE FROM numbers WHERE number='$1'";
+ postgres_rewrite DELETE no_changes 410;
+ postgres_rewrite DELETE changes 204;
+ }
+_EOC_
+
+our $request_headers = <<'_EOC_';
+Authorization: Basic bmd4X3Rlc3Q6bmd4X3Rlc3Q=
+_EOC_
+
+no_shuffle();
+run_tests();
+
+__DATA__
+
+=== TEST 1: clean collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+DELETE /numbers/
+--- error_code: 204
+--- response_headers
+! Content-Type
+--- response_body eval
+""
+--- timeout: 10
+
+
+
+=== TEST 2: list empty collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{06}\x{00}". # col name len
+"number". # col name data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 3: insert resource into collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+POST /numbers/
+--- error_code: 201
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{06}\x{00}". # col name len
+"number". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"123". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 4: list collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{06}\x{00}". # col name len
+"number". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"123". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 5: get resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/123
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{06}\x{00}". # col name len
+"number". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"123". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 6: update resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers
+Authorization: Basic bmd4X3Rlc3Q6bmd4X3Rlc3Q=
+Content-Length: 0
+--- request
+PUT /numbers/123
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{06}\x{00}". # col name len
+"number". # col name data
+"\x{01}". # valid row flag
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"123". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 7: remove resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+DELETE /numbers/123
+--- error_code: 204
+--- response_headers
+! Content-Type
+--- response_body eval
+""
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 8: update non-existing resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers
+Authorization: Basic bmd4X3Rlc3Q6bmd4X3Rlc3Q=
+Content-Length: 0
+--- request
+PUT /numbers/123
+--- error_code: 410
+--- response_headers
+Content-Type: text/html
+--- response_body_like: 410 Gone
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 9: get non-existing resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/123
+--- error_code: 410
+--- response_headers
+Content-Type: text/html
+--- response_body_like: 410 Gone
+--- timeout: 10
+
+
+
+=== TEST 10: remove non-existing resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+DELETE /numbers/123
+--- error_code: 410
+--- response_headers
+Content-Type: text/html
+--- response_body_like: 410 Gone
+--- timeout: 10
+
+
+
+=== TEST 11: list empty collection (done)
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{06}\x{00}". # col name len
+"number". # col name data
+"\x{00}" # row list terminator
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/restful_json.t b/ngx_postgres-1.0/t/restful_json.t
new file mode 100644
index 0000000..aae262b
--- /dev/null
+++ b/ngx_postgres-1.0/t/restful_json.t
@@ -0,0 +1,244 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(1);
+
+plan tests => repeat_each() * (blocks() * 3);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+our $config = <<'_EOC_';
+ set $random 123;
+
+ location = /auth {
+ internal;
+
+ postgres_escape $user $remote_user;
+ postgres_escape $pass $remote_passwd;
+
+ postgres_pass database;
+ postgres_query "SELECT login FROM users WHERE login=$user AND pass=$pass";
+ postgres_rewrite no_rows 403;
+ postgres_output none;
+ }
+
+ location = /numbers/ {
+ auth_request /auth;
+ postgres_pass database;
+ rds_json on;
+
+ postgres_query HEAD GET "SELECT * FROM numbers";
+
+ postgres_query POST "INSERT INTO numbers VALUES('$random') RETURNING *";
+ postgres_rewrite POST changes 201;
+
+ postgres_query DELETE "DELETE FROM numbers";
+ postgres_rewrite DELETE no_changes 204;
+ postgres_rewrite DELETE changes 204;
+ }
+
+ location ~ /numbers/(\d+) {
+ auth_request /auth;
+ postgres_pass database;
+ rds_json on;
+
+ postgres_query HEAD GET "SELECT * FROM numbers WHERE number='$1'";
+ postgres_rewrite HEAD GET no_rows 410;
+
+ postgres_query PUT "UPDATE numbers SET number='$1' WHERE number='$1' RETURNING *";
+ postgres_rewrite PUT no_changes 410;
+
+ postgres_query DELETE "DELETE FROM numbers WHERE number='$1'";
+ postgres_rewrite DELETE no_changes 410;
+ postgres_rewrite DELETE changes 204;
+ }
+_EOC_
+
+our $request_headers = <<'_EOC_';
+Authorization: Basic bmd4X3Rlc3Q6bmd4X3Rlc3Q=
+_EOC_
+
+no_shuffle();
+run_tests();
+
+__DATA__
+
+=== TEST 1: clean collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+DELETE /numbers/
+--- error_code: 204
+--- response_headers
+! Content-Type
+--- response_body eval
+""
+--- timeout: 10
+
+
+
+=== TEST 2: list empty collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/
+--- error_code: 200
+--- response_headers
+Content-Type: application/json
+--- response_body chomp
+[]
+--- timeout: 10
+
+
+
+=== TEST 3: insert resource into collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+POST /numbers/
+--- error_code: 201
+--- response_headers
+Content-Type: application/json
+--- response_body chomp
+[{"number":123}]
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 4: list collection
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/
+--- error_code: 200
+--- response_headers
+Content-Type: application/json
+--- response_body chomp
+[{"number":123}]
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 5: get resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/123
+--- error_code: 200
+--- response_headers
+Content-Type: application/json
+--- response_body chomp
+[{"number":123}]
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 6: update resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers
+Authorization: Basic bmd4X3Rlc3Q6bmd4X3Rlc3Q=
+Content-Length: 0
+--- request
+PUT /numbers/123
+--- error_code: 200
+--- response_headers
+Content-Type: application/json
+--- response_body chomp
+[{"number":123}]
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 7: remove resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+DELETE /numbers/123
+--- error_code: 204
+--- response_headers
+! Content-Type
+--- response_body eval
+""
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 8: update non-existing resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers
+Authorization: Basic bmd4X3Rlc3Q6bmd4X3Rlc3Q=
+Content-Length: 0
+--- request
+PUT /numbers/123
+--- error_code: 410
+--- response_headers
+Content-Type: text/html
+--- response_body_like: 410 Gone
+--- timeout: 10
+--- skip_slave: 3: CentOS
+
+
+
+=== TEST 9: get non-existing resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/123
+--- error_code: 410
+--- response_headers
+Content-Type: text/html
+--- response_body_like: 410 Gone
+--- timeout: 10
+
+
+
+=== TEST 10: remove non-existing resource
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+DELETE /numbers/123
+--- error_code: 410
+--- response_headers
+Content-Type: text/html
+--- response_body_like: 410 Gone
+--- timeout: 10
+
+
+
+=== TEST 11: list empty collection (done)
+--- http_config eval: $::http_config
+--- config eval: $::config
+--- more_headers eval: $::request_headers
+--- request
+GET /numbers/
+--- error_code: 200
+--- response_headers
+Content-Type: application/json
+--- response_body chomp
+[]
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/rewrites.t b/ngx_postgres-1.0/t/rewrites.t
new file mode 100644
index 0000000..3cc191e
--- /dev/null
+++ b/ngx_postgres-1.0/t/rewrites.t
@@ -0,0 +1,348 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 2 + 1 * 1);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: no changes (SELECT)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 2: no changes (UPDATE)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set id=3 where name='noone'";
+ postgres_rewrite no_changes 206;
+ postgres_rewrite changes 500;
+ }
+--- request
+GET /postgres
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 3: one change
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set id=3 where name='bob'";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 206;
+ }
+--- request
+GET /postgres
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 4: rows
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite rows 206;
+ }
+--- request
+GET /postgres
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 5: no rows
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats where name='noone'";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite rows 206;
+ }
+--- request
+GET /postgres
+--- error_code: 410
+--- response_headers
+Content-Type: text/html
+--- timeout: 10
+
+
+
+=== TEST 6: inheritance
+--- http_config eval: $::http_config
+--- config
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite rows 206;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ }
+--- request
+GET /postgres
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 7: inheritance (mixed, don't inherit)
+--- http_config eval: $::http_config
+--- config
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite rows 206;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite rows 206;
+ }
+--- request
+GET /postgres
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 8: rows (method-specific)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite POST PUT rows 201;
+ postgres_rewrite HEAD GET rows 206;
+ postgres_rewrite rows 206;
+ }
+--- request
+GET /postgres
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 9: rows (default)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite POST PUT rows 201;
+ postgres_rewrite rows 206;
+ }
+--- request
+GET /postgres
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 10: rows (none)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite POST PUT rows 201;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 11: no changes (UPDATE) with 202 response
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set id=3 where name='noone'";
+ postgres_rewrite no_changes 202;
+ postgres_rewrite changes 500;
+ }
+--- request
+GET /postgres
+--- error_code: 202
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+--- skip_nginx: 2: < 0.8.41
+
+
+
+=== TEST 12: no changes (UPDATE) with 409 response
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set id=3 where name='noone'";
+ postgres_rewrite no_changes 409;
+ postgres_rewrite changes 500;
+ }
+--- request
+GET /postgres
+--- error_code: 409
+--- response_headers
+Content-Type: text/html
+--- timeout: 10
+
+
+
+=== TEST 13: no changes (UPDATE) with 409 status and our body
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set id=3 where name='noone'";
+ postgres_rewrite no_changes =409;
+ postgres_rewrite changes 500;
+ }
+--- request
+GET /postgres
+--- error_code: 409
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
+
+
+
+=== TEST 14: rows with 409 status and our body (with integrity check)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite no_rows 500;
+ postgres_rewrite rows =409;
+ }
+--- request
+GET /postgres
+--- error_code: 409
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{02}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{02}\x{00}". # col name len
+"id". # col name data
+"\x{06}\x{80}". # std col type (varchar/str)
+"\x{19}\x{00}". # driver col type
+"\x{04}\x{00}". # col name len
+"name". # col name data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"2". # field data
+"\x{ff}\x{ff}\x{ff}\x{ff}". # field len
+"". # field data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"3". # field data
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"bob". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 15: rows - "if" pseudo-location
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ if ($arg_foo) {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ postgres_rewrite no_changes 500;
+ postgres_rewrite changes 500;
+ postgres_rewrite no_rows 410;
+ postgres_rewrite rows 206;
+ break;
+ }
+
+ return 404;
+ }
+--- request
+GET /postgres?foo=1
+--- error_code: 206
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- timeout: 10
diff --git a/ngx_postgres-1.0/t/sanity.t b/ngx_postgres-1.0/t/sanity.t
new file mode 100644
index 0000000..25c3d7e
--- /dev/null
+++ b/ngx_postgres-1.0/t/sanity.t
@@ -0,0 +1,298 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 5);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- http_config
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ postgres_keepalive off;
+ }
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{02}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{02}\x{00}". # col name len
+"id". # col name data
+"\x{06}\x{80}". # std col type (varchar/str)
+"\x{19}\x{00}". # driver col type
+"\x{04}\x{00}". # col name len
+"name". # col name data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"2". # field data
+"\x{ff}\x{ff}\x{ff}\x{ff}". # field len
+"". # field data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"3". # field data
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"bob". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- no_error_log
+[alert]
+[error]
+
+
+
+=== TEST 2: keep-alive
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{02}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{02}\x{00}". # col name len
+"id". # col name data
+"\x{06}\x{80}". # std col type (varchar/str)
+"\x{19}\x{00}". # driver col type
+"\x{04}\x{00}". # col name len
+"name". # col name data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"2". # field data
+"\x{ff}\x{ff}\x{ff}\x{ff}". # field len
+"". # field data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"3". # field data
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"bob". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- no_error_log
+[alert]
+[error]
+
+
+
+=== TEST 3: update
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set name='bob' where name='bob'";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{01}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{00}\x{00}" # col count
+--- timeout: 10
+--- no_error_log
+[alert]
+[error]
+
+
+
+=== TEST 4: select empty result
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats where name='tom'";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{02}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{02}\x{00}". # col name len
+"id". # col name data
+"\x{06}\x{80}". # std col type (varchar/str)
+"\x{19}\x{00}". # driver col type
+"\x{04}\x{00}". # col name len
+"name". # col name data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- no_error_log
+[alert]
+[error]
+
+
+
+=== TEST 5: variables in postgres_pass
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ set $backend database;
+ postgres_pass $backend;
+ postgres_query "update cats set name='bob' where name='bob'";
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{01}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{01}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{00}\x{00}" # col count
+--- timeout: 10
+--- no_error_log
+[alert]
+[error]
+
+
+
+=== TEST 6: HEAD request
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ }
+--- request
+HEAD /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+""
+--- timeout: 10
+--- no_error_log
+[alert]
+[error]
+
+
+
+=== TEST 7: "if" pseudo-location
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ if ($arg_foo) {
+ postgres_pass database;
+ postgres_query "select * from cats";
+ break;
+ }
+
+ return 404;
+ }
+--- request
+GET /postgres?foo=1
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{02}\x{00}". # col count
+"\x{09}\x{00}". # std col type (integer/int)
+"\x{17}\x{00}". # driver col type
+"\x{02}\x{00}". # col name len
+"id". # col name data
+"\x{06}\x{80}". # std col type (varchar/str)
+"\x{19}\x{00}". # driver col type
+"\x{04}\x{00}". # col name len
+"name". # col name data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"2". # field data
+"\x{ff}\x{ff}\x{ff}\x{ff}". # field len
+"". # field data
+"\x{01}". # valid row flag
+"\x{01}\x{00}\x{00}\x{00}". # field len
+"3". # field data
+"\x{03}\x{00}\x{00}\x{00}". # field len
+"bob". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+--- no_error_log
+[alert]
+[error]
diff --git a/ngx_postgres-1.0/t/variables.t b/ngx_postgres-1.0/t/variables.t
new file mode 100644
index 0000000..8938411
--- /dev/null
+++ b/ngx_postgres-1.0/t/variables.t
@@ -0,0 +1,407 @@
+# vi:filetype=perl
+
+use lib 'lib';
+use Test::Nginx::Socket;
+
+repeat_each(2);
+
+plan tests => repeat_each() * (blocks() * 3 + 1 * 4 + 1 * 1 - 5 * 2);
+
+$ENV{TEST_NGINX_POSTGRESQL_HOST} ||= '127.0.0.1';
+$ENV{TEST_NGINX_POSTGRESQL_PORT} ||= 5432;
+
+our $http_config = <<'_EOC_';
+ upstream database {
+ postgres_server $TEST_NGINX_POSTGRESQL_HOST:$TEST_NGINX_POSTGRESQL_PORT
+ dbname=ngx_test user=ngx_test password=ngx_test;
+ }
+_EOC_
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: sanity
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_set $test 0 0;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Test: test
+--- response_body eval
+"\x{00}". # endian
+"\x{03}\x{00}\x{00}\x{00}". # format version 0.0.3
+"\x{00}". # result type
+"\x{00}\x{00}". # std errcode
+"\x{02}\x{00}". # driver errcode
+"\x{00}\x{00}". # driver errstr len
+"". # driver errstr data
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # rows affected
+"\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}\x{00}". # insert id
+"\x{01}\x{00}". # col count
+"\x{00}\x{80}". # std col type (unknown/str)
+"\x{c1}\x{02}". # driver col type
+"\x{04}\x{00}". # col name len
+"echo". # col name data
+"\x{01}". # valid row flag
+"\x{04}\x{00}\x{00}\x{00}". # field len
+"test". # field data
+"\x{00}" # row list terminator
+--- timeout: 10
+
+
+
+=== TEST 2: out-of-range value (optional)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_set $test 0 1;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+! X-Test
+--- timeout: 10
+
+
+
+=== TEST 3: NULL value (optional)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select NULL as echo";
+ postgres_set $test 0 0;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+! X-Test
+--- timeout: 10
+
+
+
+=== TEST 4: zero-length value (optional)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select '' as echo";
+ postgres_set $test 0 0;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+! X-Test
+--- timeout: 10
+
+
+
+=== TEST 5: out-of-range value (required)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_set $test 0 1 required;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 6: NULL value (required)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select NULL as echo";
+ postgres_set $test 0 0 required;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 7: zero-length value (required)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select '' as echo";
+ postgres_set $test 0 0 required;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 8: $postgres_columns
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'a', 'b', 'c'";
+ add_header "X-Columns" $postgres_columns;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Columns: 3
+--- timeout: 10
+
+
+
+=== TEST 9: $postgres_rows
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'a', 'b', 'c'";
+ add_header "X-Rows" $postgres_rows;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Rows: 1
+--- timeout: 10
+
+
+
+=== TEST 10: $postgres_query (simple value)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ add_header "X-Query" $postgres_query;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Query: select 'test' as echo
+--- timeout: 10
+
+
+
+=== TEST 11: $postgres_query (simple value)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select '$request_method' as echo";
+ add_header "X-Query" $postgres_query;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Query: select 'GET' as echo
+--- timeout: 10
+
+
+
+=== TEST 12: variables used in non-ngx_postgres location
+--- http_config
+--- config
+ location /etc {
+ root /;
+ add_header "X-Columns" $postgres_columns;
+ add_header "X-Rows" $postgres_rows;
+ add_header "X-Affected" $postgres_affected;
+ add_header "X-Query" $postgres_query;
+ postgres_set $pg 0 0 required;
+ add_header "X-Custom" $pg;
+ }
+--- request
+GET /etc/passwd
+--- error_code: 200
+--- response_headers
+Content-Type: text/plain
+! X-Columns
+! X-Rows
+! X-Affected
+! X-Query
+! X-Custom
+--- timeout: 10
+
+
+
+=== TEST 13: $postgres_affected (SELECT)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select '$request_method' as echo";
+ add_header "X-Affected" $postgres_affected;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+! X-Affected
+--- timeout: 10
+
+
+
+=== TEST 14: $postgres_affected (UPDATE, no changes)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set id=3 where name='noone'";
+ add_header "X-Affected" $postgres_affected;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Affected: 0
+--- timeout: 10
+
+
+
+=== TEST 15: $postgres_affected (UPDATE, one change)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "update cats set id=3 where name='bob'";
+ add_header "X-Affected" $postgres_affected;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Affected: 1
+--- timeout: 10
+
+
+
+=== TEST 16: inheritance
+--- http_config eval: $::http_config
+--- config
+ postgres_set $test 0 0 required;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select NULL as echo";
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
+
+
+
+=== TEST 17: inheritance (mixed, don't inherit)
+--- http_config eval: $::http_config
+--- config
+ postgres_set $test 0 0 required;
+
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select NULL as echo";
+ postgres_set $test2 2 2;
+ add_header "X-Test" $test2;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+! X-Test
+--- timeout: 10
+
+
+
+=== TEST 18: column by name (existing)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_set $test 0 "echo";
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+X-Test: test
+--- timeout: 10
+
+
+
+=== TEST 19: column by name (not existing, optional)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_set $test 0 "test" optional;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 200
+--- response_headers
+Content-Type: application/x-resty-dbd-stream
+! X-Test
+--- timeout: 10
+
+
+
+=== TEST 20: column by name (not existing, required)
+--- http_config eval: $::http_config
+--- config
+ location /postgres {
+ postgres_pass database;
+ postgres_query "select 'test' as echo";
+ postgres_set $test 0 "test" required;
+ add_header "X-Test" $test;
+ }
+--- request
+GET /postgres
+--- error_code: 500
+--- timeout: 10
diff --git a/ngx_postgres-1.0/valgrind.suppress b/ngx_postgres-1.0/valgrind.suppress
new file mode 100644
index 0000000..e0b8e89
--- /dev/null
+++ b/ngx_postgres-1.0/valgrind.suppress
@@ -0,0 +1,331 @@
+{
+<insert_a_suppression_name_here>
+Memcheck:Leak
+fun:malloc
+fun:ngx_alloc
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_create_pool
+ fun:ngx_event_accept
+ fun:ngx_event_process_posted
+ fun:ngx_process_events_and_timers
+}
+{
+<insert_a_suppression_name_here>
+Memcheck:Leak
+fun:malloc
+fun:ngx_alloc
+fun:ngx_create_pool
+fun:ngx_http_init_request
+fun:ngx_event_process_posted
+fun:ngx_process_events_and_timers
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_create_pool
+ fun:ngx_event_accept
+ fun:ngx_epoll_process_events
+ fun:ngx_process_events_and_timers
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_create_pool
+ fun:ngx_http_init_request
+ fun:ngx_epoll_process_events
+ fun:ngx_process_events_and_timers
+}
+{
+<insert_a_suppression_name_here>
+Memcheck:Leak
+fun:malloc
+fun:ngx_alloc
+fun:ngx_malloc
+fun:ngx_pnalloc
+}
+{
+<insert_a_suppression_name_here>
+Memcheck:Leak
+fun:malloc
+fun:ngx_alloc
+fun:ngx_malloc
+fun:ngx_palloc
+}
+{
+<insert_a_suppression_name_here>
+Memcheck:Addr4
+fun:ngx_init_cycle
+fun:ngx_master_process_cycle
+fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Param
+ epoll_ctl(event)
+ fun:epoll_ctl
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_event_process_init
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:(below main)
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_calloc
+ fun:ngx_event_process_init
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_pcalloc
+ fun:ngx_hash_init
+ fun:ngx_http_variables_init_vars
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_pcalloc
+ fun:ngx_hash_keys_array_init
+ fun:ngx_http_variables_add_core_vars
+ fun:ngx_http_core_preconfiguration
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_array_push
+ fun:ngx_hash_add_key
+ fun:ngx_http_add_variable
+ fun:ngx_http_echo_add_variables
+ fun:ngx_http_echo_handler_init
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_pcalloc
+ fun:ngx_http_upstream_drizzle_create_srv_conf
+ fun:ngx_http_core_server
+ fun:ngx_conf_parse
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_pcalloc
+ fun:ngx_http_upstream_drizzle_create_srv_conf
+ fun:ngx_http_upstream
+ fun:ngx_conf_parse
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_pcalloc
+ fun:ngx_http_upstream_drizzle_create_srv_conf
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_array_push
+ fun:ngx_hash_add_key
+ fun:ngx_http_variables_add_core_vars
+ fun:ngx_http_core_preconfiguration
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_pcalloc
+ fun:ngx_init_cycle
+ fun:main
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_hash_init
+ fun:ngx_http_upstream_init_main_conf
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+
+{
+ nginx-core-process-init
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_event_process_init
+}
+{
+ nginx-core-crc32-init
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_crc32_table_init
+ fun:main
+}
+{
+ palloc_large_for_init_request
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_pcalloc
+ fun:ngx_http_init_request
+ fun:ngx_epoll_process_events
+ fun:ngx_process_events_and_timers
+}
+{
+ palloc_large_for_create_temp_buf
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_create_temp_buf
+ fun:ngx_http_init_request
+ fun:ngx_epoll_process_events
+ fun:ngx_process_events_and_timers
+}
+{
+ accept_create_pool
+ Memcheck:Leak
+ fun:memalign
+ fun:posix_memalign
+ fun:ngx_memalign
+ fun:ngx_create_pool
+ fun:ngx_event_accept
+ fun:ngx_epoll_process_events
+ fun:ngx_process_events_and_timers
+}
+{
+ create_pool_for_init_req
+ Memcheck:Leak
+ fun:memalign
+ fun:posix_memalign
+ fun:ngx_memalign
+ fun:ngx_create_pool
+ fun:ngx_http_init_request
+ fun:ngx_epoll_process_events
+ fun:ngx_process_events_and_timers
+}
+{
+ create_pool_posix_memalign
+ Memcheck:Leak
+ fun:memalign
+ fun:posix_memalign
+ fun:ngx_memalign
+ fun:ngx_create_pool
+ fun:main
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:malloc
+ fun:ngx_alloc
+ fun:ngx_palloc_large
+ fun:ngx_palloc
+ fun:ngx_array_push
+ fun:ngx_hash_add_key
+ fun:ngx_http_add_variable
+ fun:ngx_http_ssi_preconfiguration
+ fun:ngx_http_block
+ fun:ngx_conf_parse
+ fun:ngx_init_cycle
+ fun:main
+}
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Cond
+ fun:index
+ fun:expand_dynamic_string_token
+ fun:_dl_map_object
+ fun:map_doit
+ fun:_dl_catch_error
+ fun:do_preload
+ fun:dl_main
+}