diff options
author | kaiwu <kaiwu2004@gmail.com> | 2025-03-01 12:42:23 +0800 |
---|---|---|
committer | kaiwu <kaiwu2004@gmail.com> | 2025-03-01 12:42:23 +0800 |
commit | 3f33461e4948bf05e60bdff35ec6c57a649c7860 (patch) | |
tree | 284c2ba95a41536ae1bff6bea710db0709a64739 /ngx_postgres-1.0 | |
download | openresty-3f33461e4948bf05e60bdff35ec6c57a649c7860.tar.gz openresty-3f33461e4948bf05e60bdff35ec6c57a649c7860.zip |
openresty bundle
Diffstat (limited to 'ngx_postgres-1.0')
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 +} |