From 131033352d3a0d6ff59b9af4dbc8b2908ec30d11 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Tue, 29 Nov 2005 02:11:39 +0000 Subject: [PATCH] Added PDO::pgsqlLOBCreate(), PDO::pgsqlLOBOpen() and PDO::pgsqlLOBUnlink(). --- ext/pdo_pgsql/package.xml | 8 +- ext/pdo_pgsql/pdo_pgsql.c | 2 +- ext/pdo_pgsql/pgsql_driver.c | 201 ++++++++++++++++++++++++- ext/pdo_pgsql/pgsql_statement.c | 74 +++++++-- ext/pdo_pgsql/php_pdo_pgsql_int.h | 21 ++- ext/pdo_pgsql/tests/bug_33876.phpt | 1 + ext/pdo_pgsql/tests/common.phpt | 1 + ext/pdo_pgsql/tests/config.inc | 14 ++ ext/pdo_pgsql/tests/large_objects.phpt | 61 ++++++++ 9 files changed, 361 insertions(+), 22 deletions(-) create mode 100644 ext/pdo_pgsql/tests/config.inc create mode 100644 ext/pdo_pgsql/tests/large_objects.phpt diff --git a/ext/pdo_pgsql/package.xml b/ext/pdo_pgsql/package.xml index 2f093a9efa..d5086be5f9 100644 --- a/ext/pdo_pgsql/package.xml +++ b/ext/pdo_pgsql/package.xml @@ -30,8 +30,8 @@ PHP stable - 1.0 - 2005-11-26 + 1.0.1 + 2005-11-28 Now features native prepared statements and numerous other improvements. @@ -42,6 +42,8 @@ intend to build and/or use it. If you are running on windows, you can download the binary from here: http://pecl4win.php.net/ext.php/php_pdo_pgsql.dll + +Added PDO::pgsqlLOBCreate(), PDO::pgsqlLOBOpen() and PDO::pgsqlLOBUnlink(). @@ -57,7 +59,7 @@ http://pecl4win.php.net/ext.php/php_pdo_pgsql.dll - + diff --git a/ext/pdo_pgsql/pdo_pgsql.c b/ext/pdo_pgsql/pdo_pgsql.c index d812cb1d8a..6a5bae3667 100644 --- a/ext/pdo_pgsql/pdo_pgsql.c +++ b/ext/pdo_pgsql/pdo_pgsql.c @@ -61,7 +61,7 @@ zend_module_entry pdo_pgsql_module_entry = { PHP_RINIT(pdo_pgsql), PHP_RSHUTDOWN(pdo_pgsql), PHP_MINFO(pdo_pgsql), - "1.0", + "1.0.1", STANDARD_MODULE_PROPERTIES }; /* }}} */ diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 96d2202536..7345745b23 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.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 | +----------------------------------------------------------------------+ */ @@ -108,6 +110,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_DC); + 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; @@ -397,6 +474,124 @@ static int pgsql_handle_rollback(pdo_dbh_t *dbh TSRMLS_DC) return pdo_pgsql_transaction_cmd("ROLLBACK", dbh TSRMLS_CC); } +/* {{{ string pgSQL::pgsqlLOBCreate() + Creates a new large object, returning its identifier. Must be called inside a transaction. */ +static PHP_METHOD(pgSQL, 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", lfd); + RETURN_STRING(buf, 0); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000"); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ resource pgSQL::pgsqlLOBOpen(string oid [, string mode = 'rb']) + Opens an existing large object stream. Must be called inside a transaction. */ +static PHP_METHOD(pgSQL, 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; +} +/* }}} */ + +/* {{{ bool pgSQL::pgsqlLOBUnlink(int oid) + Deletes the large object identified by oid. Must be called inside a transaction. */ +static PHP_METHOD(pgSQL, pgsqlLOBUnlink) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + long lfd; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", + &lfd)) { + 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, lfd)) { + RETURN_TRUE; + } + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, "HY000"); + RETURN_FALSE; +} + + + +static function_entry dbh_methods[] = { + PHP_ME(pgSQL, pgsqlLOBCreate, NULL, ZEND_ACC_PUBLIC) + PHP_ME(pgSQL, pgsqlLOBOpen, NULL, ZEND_ACC_PUBLIC) + PHP_ME(pgSQL, pgsqlLOBUnlink, NULL, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; + +static 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 struct pdo_dbh_methods pgsql_methods = { pgsql_handle_closer, pgsql_handle_preparer, @@ -410,7 +605,7 @@ static struct pdo_dbh_methods pgsql_methods = { pdo_pgsql_fetch_error_func, pdo_pgsql_get_attribute, NULL, /* check_liveness */ - NULL /* get_driver_methods */ + pdo_pgsql_get_driver_methods /* get_driver_methods */ }; static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_DC) /* {{{ */ @@ -462,7 +657,7 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_ H->pgoid = -1; dbh->methods = &pgsql_methods; - dbh->alloc_own_columns = 1; + dbh->alloc_own_columns = 0; dbh->max_escaped_char_length = 2; ret = 1; diff --git a/ext/pdo_pgsql/pgsql_statement.c b/ext/pdo_pgsql/pgsql_statement.c index fed9741513..088694b105 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 | +----------------------------------------------------------------------+ */ @@ -39,7 +41,6 @@ #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; @@ -184,6 +185,12 @@ static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data * #if HAVE_PQPREPARE if (S->stmt_name && param->is_param) { switch (event_type) { + case PDO_PARAM_EVT_FREE: + if (param->driver_data) { + efree(param->driver_data); + } + break; + case PDO_PARAM_EVT_ALLOC: /* decode name from $1, $2 into 0, 1 etc. */ if (param->name) { @@ -224,10 +231,26 @@ static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data * php_stream *stm; php_stream_from_zval_no_verify(stm, ¶m->parameter); if (stm) { - SEPARATE_ZVAL_IF_NOT_REF(¶m->parameter); - Z_TYPE_P(param->parameter) = IS_STRING; - Z_STRLEN_P(param->parameter) = php_stream_copy_to_mem(stm, - &Z_STRVAL_P(param->parameter), PHP_STREAM_COPY_ALL, 0); + 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 { + SEPARATE_ZVAL_IF_NOT_REF(¶m->parameter); + Z_TYPE_P(param->parameter) = IS_STRING; + Z_STRLEN_P(param->parameter) = php_stream_copy_to_mem(stm, + &Z_STRVAL_P(param->parameter), PHP_STREAM_COPY_ALL, 0); + } } else { /* expected a stream resource */ pdo_pgsql_error_stmt(stmt, PGRES_FATAL_ERROR, "HY105"); @@ -308,6 +331,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; @@ -324,10 +348,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; @@ -487,9 +526,24 @@ 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: diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index 1b48f63ee1..5f799161cd 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" @@ -66,10 +69,7 @@ typedef struct { } 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; @@ -90,6 +90,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/bug_33876.phpt b/ext/pdo_pgsql/tests/bug_33876.phpt index 1184e7627e..725d44db9d 100644 --- a/ext/pdo_pgsql/tests/bug_33876.phpt +++ b/ext/pdo_pgsql/tests/bug_33876.phpt @@ -3,6 +3,7 @@ PDO PgSQL Bug #33876 --SKIPIF-- 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' diff --git a/ext/pdo_pgsql/tests/config.inc b/ext/pdo_pgsql/tests/config.inc new file mode 100644 index 0000000000..6d616cddb5 --- /dev/null +++ b/ext/pdo_pgsql/tests/config.inc @@ -0,0 +1,14 @@ + $v) { + putenv("$k=$v"); +} diff --git a/ext/pdo_pgsql/tests/large_objects.phpt b/ext/pdo_pgsql/tests/large_objects.phpt new file mode 100644 index 0000000000..f6ee9e29c0 --- /dev/null +++ b/ext/pdo_pgsql/tests/large_objects.phpt @@ -0,0 +1,61 @@ +--TEST-- +PDO PgSQL Large Objects +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + +$db->exec('CREATE TABLE test (blobid integer not null primary key, bloboid OID)'); + +$db->beginTransaction(); +$oid = $db->pgsqlLOBCreate(); +try { +$stm = $db->pgsqlLOBOpen($oid); +fwrite($stm, "Hello dude\n"); + +$stmt = $db->prepare("INSERT INTO test (blobid, bloboid) values (?, ?)"); +$stmt->bindValue(1, 1); +/* bind as LOB; the oid from the pgsql stream will be inserted instead + * of the stream contents. Binding other streams will attempt to bind + * as bytea, and will most likely lead to an error. + * You can also just bind the $oid in as a string. */ +$stmt->bindParam(2, $stm, PDO::PARAM_LOB); +$stmt->execute(); +$stm = null; + +/* Pull it out */ +$stmt = $db->prepare("SELECT * from test"); +$stmt->execute(); +$stmt->bindColumn('bloboid', $lob, PDO::PARAM_LOB); +echo "Fetching:\n"; +while (($row = $stmt->fetch(PDO::FETCH_ASSOC))) { + var_dump($row['blobid']); + var_dump(stream_get_contents($lob)); +} +echo "Fetched!\n"; +} catch (Exception $e) { + /* catch exceptions so that we can guarantee to clean + * up the LOB */ + echo "Exception! at line ", $e->getLine(), "\n"; + var_dump($e->getMessage()); +} + +/* Now to remove the large object from the database, so it doesn't + * linger and clutter up the storage */ +$db->pgsqlLOBUnlink($oid); + +--EXPECT-- +Fetching: +int(1) +string(11) "Hello dude +" +Fetched! -- 2.40.0