From dd6b8d4ceb5fd60aa0bdf76ce3f8ea232c5dfe41 Mon Sep 17 00:00:00 2001 From: Matteo Beccati Date: Sat, 28 Mar 2009 02:34:02 +0000 Subject: [PATCH] MFB: - Updated pdo_pgsql from PHP_5_3 --- ext/pdo_pgsql/config.w32 | 4 +- ext/pdo_pgsql/package.xml | 62 ------ ext/pdo_pgsql/package2.xml | 87 +++++++++ ext/pdo_pgsql/pdo_pgsql.c | 4 +- ext/pdo_pgsql/pgsql_driver.c | 291 +++++++++++++++++++++++----- ext/pdo_pgsql/pgsql_statement.c | 203 ++++++++++++++++--- ext/pdo_pgsql/php_pdo_pgsql_int.h | 24 ++- ext/pdo_pgsql/tests/bug46274.phpt | 85 ++++++++ ext/pdo_pgsql/tests/bug46274_2.phpt | 87 +++++++++ ext/pdo_pgsql/tests/common.phpt | 1 + 10 files changed, 706 insertions(+), 142 deletions(-) delete mode 100644 ext/pdo_pgsql/package.xml create mode 100644 ext/pdo_pgsql/package2.xml create mode 100644 ext/pdo_pgsql/tests/bug46274.phpt create mode 100644 ext/pdo_pgsql/tests/bug46274_2.phpt diff --git a/ext/pdo_pgsql/config.w32 b/ext/pdo_pgsql/config.w32 index 833d3135ed..9707774020 100644 --- a/ext/pdo_pgsql/config.w32 +++ b/ext/pdo_pgsql/config.w32 @@ -5,7 +5,7 @@ ARG_WITH("pdo-pgsql", "PostgreSQL support for PDO", "no"); if (PHP_PDO_PGSQL != "no") { if (CHECK_LIB("libpq.lib", "pdo_pgsql", PHP_PDO_PGSQL) && - CHECK_HEADER_ADD_INCLUDE("libpq-fe.h", "CFLAGS_PDO_PGSQL", PHP_PDO_PGSQL + ";" + PHP_PHP_BUILD + "\\include\\pgsql")) { + CHECK_HEADER_ADD_INCLUDE("libpq-fe.h", "CFLAGS_PDO_PGSQL", PHP_PDO_PGSQL + ";" + PHP_PHP_BUILD + "\\include\\pgsql;" + PHP_PHP_BUILD + "\\include\\libpq;")) { EXTENSION("pdo_pgsql", "pdo_pgsql.c pgsql_driver.c pgsql_statement.c"); if (CHECK_HEADER_ADD_INCLUDE("pg_config.h", "CFLAGS_PDO_PGSQL", PHP_PDO_PGSQL + ";" + PHP_PHP_BUILD + "\\include\\pgsql")) { @@ -14,8 +14,8 @@ if (PHP_PDO_PGSQL != "no") { AC_DEFINE('HAVE_PDO_PGSQL', 1, 'Have PostgreSQL library'); ADD_FLAG('CFLAGS_PDO_PGSQL', "/D HAVE_PQPARAMETERSTATUS=1 /D HAVE_PQPROTOCOLVERSION=1 /D HAVE_PGTRANSACTIONSTATUS=1 /D HAVE_PQUNESCAPEBYTEA=1 /D HAVE_PQRESULTERRORFIELD=1 /D HAVE_PQESCAPE_CONN=1 /D HAVE_PQESCAPE_BYTEA_CONN=1"); + ADD_EXTENSION_DEP('pdo_pgsql', 'pdo'); } else { WARNING("pdo_pgsql not enabled; libraries and headers not found"); } - ADD_EXTENSION_DEP('pdo_pgsql', 'pdo'); } diff --git a/ext/pdo_pgsql/package.xml b/ext/pdo_pgsql/package.xml deleted file mode 100644 index 7c79c8e8f2..0000000000 --- a/ext/pdo_pgsql/package.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - PDO_PGSQL - PostgreSQL driver for PDO - - - edink - Edin Kadribasic - edink@php.net - lead - - - iliaa - Ilia Alshanetsky - iliaa@php.net - lead - - - wez - Wez Furlong - wez@php.net - lead - - - - - This extension provides an PostgreSQL driver for PDO. - - PHP - - beta - 0.9 - 2005-07-27 - - - Now features native prepared statements and numerous other improvements. - - You need to install the PDO core module before you can make use of this one. - You also require PostgreSQL client libraries installed on the machine where you intend to build and/or use it. - - If you are running on windows, you can download the binary from here: - http://snaps.php.net/win32/PECL_5_0/php_pdo_pgsql.dll - - - - - - - - - - - - - - - - - - - diff --git a/ext/pdo_pgsql/package2.xml b/ext/pdo_pgsql/package2.xml new file mode 100644 index 0000000000..e397958a7d --- /dev/null +++ b/ext/pdo_pgsql/package2.xml @@ -0,0 +1,87 @@ + + + PDO_PGSQL + pecl.php.net + PostgreSQL driver for PDO + This extension provides an PostgreSQL driver for PDO. + + + Edin Kadribasic + edink + edink@php.net + yes + + + Ilia Alshanetsky + iliaa + iliaa@php.net + yes + + + Wez Furlong + wez + wez@php.net + yes + + 2006-05-01 + + 1.0.2 + 1.0.2 + + + stable + stable + + PHP + +This PECL release corresponds to PHP 5.1.3. + +- Fixed bug #36727 (segfault in pdo_pgsql bindValue() when no parameters are + defined). (Tony) +- Fixed bug #36382 (PDO/PgSQL's getColumnMeta() crashes). (Derick) +- Fixed bug #36176 (PDO_PGSQL - PDO::exec() does not return number of rows + affected by the operation). (Ilia) +- Fixed prepared statement name conflict handling in PDO_PGSQL. (Thies, Ilia) +- repackage with package2.xml +- Added PDO::pgsqlLOBCreate(), PDO::pgsqlLOBOpen() and PDO::pgsqlLOBUnlink(). + +You require PostgreSQL client libraries installed on the machine where you +intend to build and/or use this package. + +If you are running on windows, you can download the binary from here: +http://pecl4win.php.net/ext.php/php_pdo_pgsql.dll + + + + + + + + + + + + + + + + + 5.0.3 + + + 1.4.0 + + + pdo + pecl.php.net + 1.0.3 + PDO + + + + PDO_PGSQL + + diff --git a/ext/pdo_pgsql/pdo_pgsql.c b/ext/pdo_pgsql/pdo_pgsql.c index e3430a0192..78f8d3ab26 100644 --- a/ext/pdo_pgsql/pdo_pgsql.c +++ b/ext/pdo_pgsql/pdo_pgsql.c @@ -70,7 +70,7 @@ zend_module_entry pdo_pgsql_module_entry = { PHP_RINIT(pdo_pgsql), PHP_RSHUTDOWN(pdo_pgsql), PHP_MINFO(pdo_pgsql), - "0.9", + "1.0.2", STANDARD_MODULE_PROPERTIES }; /* }}} */ @@ -86,7 +86,7 @@ ZEND_GET_MODULE(pdo_pgsql) PHP_MINIT_FUNCTION(pdo_pgsql) { php_pdo_register_driver(&pdo_pgsql_driver); - REGISTER_PDO_CONST_LONG("PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT", PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT); + REGISTER_PDO_CLASS_CONST_LONG("PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT", PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT); return SUCCESS; } /* }}} */ diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index deb5ba9909..ea88fc77f4 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -115,6 +115,81 @@ static int pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *in } /* }}} */ +/* {{{ pdo_pgsql_create_lob_stream */ +static size_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count TSRMLS_DC) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_write(self->conn, self->lfd, (char*)buf, count); +} + +static size_t pgsql_lob_read(php_stream *stream, char *buf, size_t count TSRMLS_DC) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_read(self->conn, self->lfd, buf, count); +} + +static int pgsql_lob_close(php_stream *stream, int close_handle TSRMLS_DC) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + pdo_dbh_t *dbh = self->dbh; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + efree(self); + php_pdo_dbh_delref(dbh TSRMLS_CC); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream TSRMLS_DC) +{ + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, off_t offset, int whence, + off_t *newoffset TSRMLS_DC) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + int pos = lo_lseek(self->conn, self->lfd, offset, whence); + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +php_stream_ops pdo_pgsql_lob_stream_ops = { + pgsql_lob_write, + pgsql_lob_read, + pgsql_lob_close, + pgsql_lob_flush, + "pdo_pgsql lob stream", + pgsql_lob_seek, + NULL, + NULL, + NULL +}; + +php_stream *pdo_pgsql_create_lob_stream(pdo_dbh_t *dbh, int lfd, Oid oid TSRMLS_DC) +{ + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + self->dbh = dbh; + self->lfd = lfd; + self->oid = oid; + self->conn = H->server; + + stm = php_stream_alloc(&pdo_pgsql_lob_stream_ops, self, 0, "r+b"); + + if (stm) { + php_pdo_dbh_addref(dbh TSRMLS_CC); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + static int pgsql_handle_closer(pdo_dbh_t *dbh TSRMLS_DC) /* {{{ */ { pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; @@ -140,11 +215,10 @@ static int pgsql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); int scrollable; #if HAVE_PQPREPARE - PGresult *res; int ret; char *nsql = NULL; int nsql_len = 0; - ExecStatusType status; + int emulate = 0; #endif S->H = H; @@ -163,8 +237,18 @@ static int pgsql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, } #if HAVE_PQPREPARE - if (!driver_options || pdo_attr_lval(driver_options, - PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT, 0 TSRMLS_CC) == 0) { + + if (driver_options) { + if (pdo_attr_lval(driver_options, + PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT, 0 TSRMLS_CC) == 1) { + emulate = 1; + } else if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, + 0 TSRMLS_CC) == 1) { + emulate = 1; + } + } + + if (!emulate && PQprotocolVersion(H->server) > 2) { stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; stmt->named_rewrite_template = "$%d"; ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len TSRMLS_CC); @@ -179,41 +263,15 @@ static int pgsql_handle_preparer(pdo_dbh_t *dbh, const char *sql, long sql_len, } spprintf(&S->stmt_name, 0, "pdo_pgsql_stmt_%08x", (unsigned int)stmt); - res = PQprepare(H->server, S->stmt_name, sql, 0, NULL); + /* that's all for now; we'll defer the actual prepare until the first execute call */ + if (nsql) { - efree(nsql); - } - if (!res) { - pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); - return 0; + S->query = nsql; + } else { + S->query = estrdup(sql); } - /* check if the connection is using protocol version 2.0. - * if that is the reason that the prepare failed, we want to fall - * through and let PDO emulate it for us */ - status = PQresultStatus(res); - switch (status) { - case PGRES_COMMAND_OK: - case PGRES_TUPLES_OK: - /* it worked */ - PQclear(res); - return 1; - - case PGRES_BAD_RESPONSE: - /* server is probably too old; fall through and let - * PDO emulate it */ - efree(S->stmt_name); - S->stmt_name = NULL; - PQclear(res); - break; - - default: - /* protocol 3.0 and above; hard error */ - pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); - PQclear(res); - return 0; - } - /* fall through */ + return 1; } #endif @@ -418,6 +476,17 @@ static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, long attr, zval *return_value return 1; } +/* {{{ */ +static int pdo_pgsql_check_liveness(pdo_dbh_t *dbh TSRMLS_DC) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + static int pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh TSRMLS_DC) { pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; @@ -450,6 +519,131 @@ static int pgsql_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC) return pdo_pgsql_transaction_cmd("ROLLBACK", dbh TSRMLS_CC); } +/* {{{ proto string PDO::pgsqlLOBCreate() + Creates a new large object, returning its identifier. Must be called inside a transaction. */ +static PHP_METHOD(PDO, pgsqlLOBCreate) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + dbh = zend_object_store_get_object(getThis() TSRMLS_CC); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + lfd = lo_creat(H->server, INV_READ|INV_WRITE); + + if (lfd != InvalidOid) { + char *buf; + spprintf(&buf, 0, "%lu", (long) lfd); + RETURN_STRING(buf, 0); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000"); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto resource PDO::pgsqlLOBOpen(string oid [, string mode = 'rb']) + Opens an existing large object stream. Must be called inside a transaction. */ +static PHP_METHOD(PDO, pgsqlLOBOpen) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + int oidstrlen; + char *modestr = "rb"; + int modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", + &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_FALSE; + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + if (strpbrk(modestr, "+w")) { + mode = INV_READ|INV_WRITE; + } + + dbh = zend_object_store_get_object(getThis() TSRMLS_CC); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + lfd = lo_open(H->server, oid, mode); + + if (lfd >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(dbh, lfd, oid TSRMLS_CC); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000"); + } + RETURN_FALSE; +} +/* }}} */ + +/* {{{ proto bool PDO::pgsqlLOBUnlink(string oid) + Deletes the large object identified by oid. Must be called inside a transaction. */ +static PHP_METHOD(PDO, pgsqlLOBUnlink) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + int oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", + &oidstr, &oidlen)) { + RETURN_FALSE; + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = zend_object_store_get_object(getThis() TSRMLS_CC); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000"); + RETURN_FALSE; +} +/* }}} */ + + +static const zend_function_entry dbh_methods[] = { + PHP_ME(PDO, pgsqlLOBCreate, NULL, ZEND_ACC_PUBLIC) + PHP_ME(PDO, pgsqlLOBOpen, NULL, ZEND_ACC_PUBLIC) + PHP_ME(PDO, pgsqlLOBUnlink, NULL, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind TSRMLS_DC) +{ + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return dbh_methods; + default: + return NULL; + } +} + static int pdo_pgsql_set_attr(pdo_dbh_t *dbh, long attr, zval *val TSRMLS_DC) { return 0; @@ -467,8 +661,8 @@ static struct pdo_dbh_methods pgsql_methods = { pdo_pgsql_last_insert_id, pdo_pgsql_fetch_error_func, pdo_pgsql_get_attribute, - NULL, /* check_liveness */ - NULL /* get_driver_methods */ + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods /* get_driver_methods */ }; static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) /* {{{ */ @@ -476,6 +670,7 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_ pdo_pgsql_db_handle *H; int ret = 0; char *conn_str, *p, *e; + long connect_timeout = 30; H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); dbh->driver_data = H; @@ -492,23 +687,25 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_ *p = ' '; } + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30 TSRMLS_CC); + } + /* support both full connection string & connection string + login and/or password */ if (dbh->username && dbh->password) { - spprintf(&conn_str, 0, "%s user=%s password=%s", dbh->data_source, dbh->username, dbh->password); + spprintf(&conn_str, 0, "%s user=%s password=%s connect_timeout=%ld", dbh->data_source, dbh->username, dbh->password, connect_timeout); } else if (dbh->username) { - spprintf(&conn_str, 0, "%s user=%s", dbh->data_source, dbh->username); + spprintf(&conn_str, 0, "%s user=%s connect_timeout=%ld", dbh->data_source, dbh->username, connect_timeout); } else if (dbh->password) { - spprintf(&conn_str, 0, "%s password=%s", dbh->data_source, dbh->password); + spprintf(&conn_str, 0, "%s password=%s connect_timeout=%ld", dbh->data_source, dbh->password, connect_timeout); } else { - conn_str = (char *) dbh->data_source; + spprintf(&conn_str, 0, "%s connect_timeout=%ld", (char *) dbh->data_source, connect_timeout); } H->server = PQconnectdb(conn_str); - - if (conn_str != dbh->data_source) { - efree(conn_str); - } - + + efree(conn_str); + if (PQstatus(H->server) != CONNECTION_OK) { pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); goto cleanup; diff --git a/ext/pdo_pgsql/pgsql_statement.c b/ext/pdo_pgsql/pgsql_statement.c index e4406acc46..df853987c2 100644 --- a/ext/pdo_pgsql/pgsql_statement.c +++ b/ext/pdo_pgsql/pgsql_statement.c @@ -12,7 +12,9 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Edin Kadribasic | + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | +----------------------------------------------------------------------+ */ @@ -29,6 +31,9 @@ #include "pdo/php_pdo_driver.h" #include "php_pdo_pgsql.h" #include "php_pdo_pgsql_int.h" +#if HAVE_NETINET_IN_H +#include +#endif /* from postgresql/src/include/catalog/pg_type.h */ #define BOOLOID 16 @@ -36,9 +41,9 @@ #define INT8OID 20 #define INT2OID 21 #define INT4OID 23 +#define TEXTOID 25 #define OIDOID 26 - static int pgsql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) { pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; @@ -51,6 +56,18 @@ static int pgsql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) #if HAVE_PQPREPARE if (S->stmt_name) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + if (S->is_prepared) { + spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, q); + efree(q); + if (res) { + PQclear(res); + } + } efree(S->stmt_name); S->stmt_name = NULL; } @@ -66,7 +83,14 @@ static int pgsql_stmt_dtor(pdo_stmt_t *stmt TSRMLS_DC) efree(S->param_formats); S->param_formats = NULL; } - + if (S->param_types) { + efree(S->param_types); + S->param_types = NULL; + } + if (S->query) { + efree(S->query); + S->query = NULL; + } #endif if (S->cursor_name) { @@ -97,12 +121,10 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) pdo_pgsql_db_handle *H = S->H; ExecStatusType status; - if (stmt->executed) { - /* ensure that we free any previous unfetched results */ - if(S->result) { - PQclear(S->result); - S->result = NULL; - } + /* ensure that we free any previous unfetched results */ + if(S->result) { + PQclear(S->result); + S->result = NULL; } S->current_row = 0; @@ -111,13 +133,52 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt TSRMLS_DC) if (S->stmt_name) { /* using a prepared statement */ + if (!S->is_prepared) { +stmt_retry: + /* we deferred the prepare until now, because we didn't + * know anything about the parameter types; now we do */ + S->result = PQprepare(H->server, S->stmt_name, S->query, + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types); + status = PQresultStatus(S->result); + switch (status) { + case PGRES_COMMAND_OK: + case PGRES_TUPLES_OK: + /* it worked */ + S->is_prepared = 1; + PQclear(S->result); + break; + default: { + char *sqlstate = pdo_pgsql_sqlstate(S->result); + /* 42P05 means that the prepared statement already existed. this can happen if you use + * a connection pooling software line pgpool which doesn't close the db-connection once + * php disconnects. if php dies (no chanche to run RSHUTDOWN) during execution it has no + * chance to DEALLOCATE the prepared statements it has created. so, if we hit a 42P05 we + * deallocate it and retry ONCE (thies 2005.12.15) + */ + if (!strcmp(sqlstate, "42P05")) { + char buf[100]; /* stmt_name == "pdo_pgsql_cursor_%08x" */ + PGresult *res; + snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, buf); + if (res) { + PQclear(res); + } + goto stmt_retry; + } else { + pdo_pgsql_error_stmt(stmt, status, sqlstate); + return 0; + } + } + } + } S->result = PQexecPrepared(H->server, S->stmt_name, stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, (const char**)S->param_values, S->param_lengths, - NULL, + S->param_formats, 0); } else #endif @@ -159,7 +220,13 @@ static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data * if (S->stmt_name && param->is_param) { switch (event_type) { - case PDO_PARAM_EVT_ALLOC: + case PDO_PARAM_EVT_FREE: + if (param->driver_data) { + efree(param->driver_data); + } + break; + + case PDO_PARAM_EVT_NORMALIZE: /* decode name from $1, $2 into 0, 1 etc. */ if (param->name) { if (param->name[0] == '$') { @@ -178,6 +245,13 @@ static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data * } break; + case PDO_PARAM_EVT_ALLOC: + case PDO_PARAM_EVT_EXEC_POST: + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + /* work is handled by EVT_NORMALIZE */ + return 1; + case PDO_PARAM_EVT_EXEC_PRE: if (!stmt->bound_param_map) { return 0; @@ -192,9 +266,54 @@ static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data * S->param_formats = ecalloc( zend_hash_num_elements(stmt->bound_param_map), sizeof(int)); - + S->param_types = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(Oid)); } if (param->paramno >= 0) { + if (param->paramno > zend_hash_num_elements(stmt->bound_param_map)) { + pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); + return 0; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && + Z_TYPE_P(param->parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, ¶m->parameter); + if (stm) { + if (php_stream_is(stm, &pdo_pgsql_lob_stream_ops)) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stm->abstract; + pdo_pgsql_bound_param *P = param->driver_data; + + if (P == NULL) { + P = ecalloc(1, sizeof(*P)); + param->driver_data = P; + } + P->oid = htonl(self->oid); + S->param_values[param->paramno] = (char*)&P->oid; + S->param_lengths[param->paramno] = sizeof(P->oid); + S->param_formats[param->paramno] = 1; + S->param_types[param->paramno] = OIDOID; + return 1; + } else { + int len; + + SEPARATE_ZVAL_IF_NOT_REF(¶m->parameter); + Z_TYPE_P(param->parameter) = IS_STRING; + + if ((len = php_stream_copy_to_mem(stm, &Z_STRVAL_P(param->parameter), PHP_STREAM_COPY_ALL, 0)) > 0) { + Z_STRLEN_P(param->parameter) = len; + } else { + ZVAL_EMPTY_STRING(param->parameter); + } + } + } else { + /* expected a stream resource */ + pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); + return 0; + } + } + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL || Z_TYPE_P(param->parameter) == IS_NULL) { S->param_values[param->paramno] = NULL; @@ -202,13 +321,20 @@ static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data * } else if (Z_TYPE_P(param->parameter) == IS_BOOL) { S->param_values[param->paramno] = Z_BVAL_P(param->parameter) ? "t" : "f"; S->param_lengths[param->paramno] = 1; - S->param_formats[param->paramno] = 1; + S->param_formats[param->paramno] = 0; } else { SEPARATE_ZVAL_IF_NOT_REF(¶m->parameter); convert_to_string(param->parameter); S->param_values[param->paramno] = Z_STRVAL_P(param->parameter); S->param_lengths[param->paramno] = Z_STRLEN_P(param->parameter); + S->param_formats[param->paramno] = 0; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + S->param_types[param->paramno] = 0; S->param_formats[param->paramno] = 1; + } else { + S->param_types[param->paramno] = 0; } } break; @@ -263,6 +389,7 @@ static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC) { pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; struct pdo_column_data *cols = stmt->columns; + struct pdo_bound_param_data *param; if (!S->result) { return 0; @@ -279,10 +406,25 @@ static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC) case BOOLOID: cols[colno].param_type = PDO_PARAM_BOOL; break; + + case OIDOID: + /* did the user bind the column as a LOB ? */ + if (stmt->bound_columns && ( + SUCCESS == zend_hash_index_find(stmt->bound_columns, + colno, (void**)¶m) || + SUCCESS == zend_hash_find(stmt->bound_columns, + cols[colno].name, cols[colno].namelen, + (void**)¶m))) { + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + cols[colno].param_type = PDO_PARAM_LOB; + break; + } + } + cols[colno].param_type = PDO_PARAM_INT; + break; case INT2OID: case INT4OID: - case OIDOID: cols[colno].param_type = PDO_PARAM_INT; break; @@ -442,14 +584,31 @@ static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, unsigned break; case PDO_PARAM_LOB: - *ptr = php_pdo_pgsql_unescape_bytea(*ptr, &tmp_len); - *len = tmp_len; - *caller_frees = 1; + if (S->cols[colno].pgsql_type == OIDOID) { + /* ooo, a real large object */ + char *end_ptr; + Oid oid = (Oid)strtoul(*ptr, &end_ptr, 10); + int loid = lo_open(S->H->server, oid, INV_READ); + if (loid >= 0) { + *ptr = (char*)pdo_pgsql_create_lob_stream(stmt->dbh, loid, oid TSRMLS_CC); + *len = 0; + return *ptr ? 1 : 0; + } + *ptr = NULL; + *len = 0; + return 0; + } else { + *ptr = php_pdo_pgsql_unescape_bytea(*ptr, &tmp_len); + *len = tmp_len; + *caller_frees = 1; + } break; case PDO_PARAM_NULL: case PDO_PARAM_STR: case PDO_PARAM_STMT: case PDO_PARAM_INPUT_OUTPUT: + case PDO_PARAM_ZVAL: + default: break; } } @@ -486,27 +645,23 @@ static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, long colno, zval *return /* Failed to get system catalogue, but return success * with the data we have collected so far */ - PQclear(res); - return 1; + goto done; } /* We want exactly one row returned */ if (1 != PQntuples(res)) { - PQclear(res); - return 1; + goto done; } add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0), 1); - +done: PQclear(res); return 1; } static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt TSRMLS_DC) { -#if HAVE_PQPREPARE return 1; -#endif } struct pdo_stmt_methods pgsql_stmt_methods = { diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index 9c3a41de63..53b697ec15 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -12,7 +12,9 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Edin Kadribasic | + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | +----------------------------------------------------------------------+ */ @@ -22,6 +24,7 @@ #define PHP_PDO_PGSQL_INT_H #include +#include #include #define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" @@ -57,17 +60,17 @@ typedef struct { char *cursor_name; #if HAVE_PQPREPARE char *stmt_name; + char *query; char **param_values; int *param_lengths; int *param_formats; + Oid *param_types; + zend_bool is_prepared; #endif } pdo_pgsql_stmt; typedef struct { - char *repr; - long repr_len; - int pgsql_type; - void *thing; /* for LOBS, REFCURSORS etc. */ + Oid oid; } pdo_pgsql_bound_param; extern pdo_driver_t pdo_pgsql_driver; @@ -88,6 +91,17 @@ enum { PDO_PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT = PDO_ATTR_DRIVER_SPECIFIC, }; +struct pdo_pgsql_lob_self { + pdo_dbh_t *dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + + +php_stream *pdo_pgsql_create_lob_stream(pdo_dbh_t *stmt, int lfd, Oid oid TSRMLS_DC); +extern php_stream_ops pdo_pgsql_lob_stream_ops; + #endif /* PHP_PDO_PGSQL_INT_H */ /* diff --git a/ext/pdo_pgsql/tests/bug46274.phpt b/ext/pdo_pgsql/tests/bug46274.phpt new file mode 100644 index 0000000000..c34839ad43 --- /dev/null +++ b/ext/pdo_pgsql/tests/bug46274.phpt @@ -0,0 +1,85 @@ +--TEST-- +Bug #46274 (pdo_pgsql - Segfault when using PDO::ATTR_STRINGIFY_FETCHES and blob) +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + +$db->query('CREATE TABLE test_one_blob (id SERIAL NOT NULL, blob1 BYTEA)'); + +$stmt = $db->prepare("INSERT INTO test_one_blob (blob1) VALUES (:foo)"); + +$data = 'foo'; +$blob = fopen('php://memory', 'a'); +fwrite($blob, $data); +rewind($blob); + +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$blob = ''; +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$data = ''; +$blob = fopen('php://memory', 'a'); +fwrite($blob, $data); +rewind($blob); + +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$blob = NULL; +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$res = $db->query("SELECT blob1 from test_one_blob"); +// Resource +var_dump($res->fetch()); + +// Empty string +var_dump($res->fetch()); + +// Empty string +var_dump($res->fetch()); + +// NULL +var_dump($res->fetch()); + +$db->query('DROP TABLE test_one_blob'); + +?> +--EXPECTF-- +array(2) { + ["blob1"]=> + string(3) "foo" + [0]=> + string(3) "foo" +} +array(2) { + ["blob1"]=> + string(0) "" + [0]=> + string(0) "" +} +array(2) { + ["blob1"]=> + string(0) "" + [0]=> + string(0) "" +} +array(2) { + ["blob1"]=> + NULL + [0]=> + NULL +} diff --git a/ext/pdo_pgsql/tests/bug46274_2.phpt b/ext/pdo_pgsql/tests/bug46274_2.phpt new file mode 100644 index 0000000000..5e35556880 --- /dev/null +++ b/ext/pdo_pgsql/tests/bug46274_2.phpt @@ -0,0 +1,87 @@ +--TEST-- +Bug #46274 (pdo_pgsql - Segfault when using PDO::ATTR_STRINGIFY_FETCHES and blob) +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + +$db->query('CREATE TABLE test_one_blob (id SERIAL NOT NULL, blob1 BYTEA)'); + +$stmt = $db->prepare("INSERT INTO test_one_blob (blob1) VALUES (:foo)"); + +$data = 'foo'; +$blob = fopen('php://memory', 'a'); +fwrite($blob, $data); +rewind($blob); + +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$blob = ''; +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$data = ''; +$blob = fopen('php://memory', 'a'); +fwrite($blob, $data); +rewind($blob); + +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$blob = NULL; +$stmt->bindparam(':foo', $blob, PDO::PARAM_LOB); +$stmt->execute(); + +$res = $db->query("SELECT blob1 from test_one_blob"); +// Resource +var_dump($x = $res->fetch()); +var_dump(fread($x['blob1'], 10)); + +// Empty string +var_dump($res->fetch()); + +// Empty string +var_dump($res->fetch()); + +// NULL +var_dump($res->fetch()); + +$db->query('DROP TABLE test_one_blob'); + +?> +--EXPECTF-- +array(2) { + ["blob1"]=> + resource(%d) of type (stream) + [0]=> + resource(%d) of type (stream) +} +string(3) "foo" +array(2) { + ["blob1"]=> + string(0) "" + [0]=> + string(0) "" +} +array(2) { + ["blob1"]=> + string(0) "" + [0]=> + string(0) "" +} +array(2) { + ["blob1"]=> + NULL + [0]=> + NULL +} diff --git a/ext/pdo_pgsql/tests/common.phpt b/ext/pdo_pgsql/tests/common.phpt index 383700904c..2ecc2025f0 100644 --- a/ext/pdo_pgsql/tests/common.phpt +++ b/ext/pdo_pgsql/tests/common.phpt @@ -5,6 +5,7 @@ Postgres if (!extension_loaded('pdo_pgsql')) print 'skip'; ?> --REDIRECTTEST-- # magic auto-configuration +# Also update config.inc if you make changes here... $config = array( 'TESTS' => 'ext/pdo/tests' -- 2.40.0