From 6142b66e89bf384c319122717d034d814f4c84ea Mon Sep 17 00:00:00 2001 From: MARiA so cute <33935209+NathanFreeman@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:06:58 +0800 Subject: [PATCH] Compatible with PHP 8.4 (PDO) (#5483) --- config.m4 | 23 +- ext-src/php_swoole_odbc.h | 10 + ext-src/php_swoole_oracle.h | 11 + ext-src/php_swoole_pgsql.h | 13 + ext-src/php_swoole_sqlite.h | 11 + ext-src/swoole_pgsql.cc | 1 - thirdparty/php80/pdo_oci/oci_driver.c | 867 ++++++++++ thirdparty/php80/pdo_oci/oci_statement.c | 1029 ++++++++++++ thirdparty/php80/pdo_oci/php_pdo_oci_int.h | 105 ++ thirdparty/php80/pdo_odbc/odbc_driver.c | 485 ++++++ thirdparty/php80/pdo_odbc/odbc_stmt.c | 895 ++++++++++ thirdparty/php80/pdo_odbc/php_pdo_odbc_int.h | 183 +++ thirdparty/php80/pdo_pgsql/pgsql_driver.c | 1275 ++++++++++++++ .../php80/pdo_pgsql/pgsql_driver_arginfo.h | 71 + thirdparty/php80/pdo_pgsql/pgsql_statement.c | 727 ++++++++ .../php80/pdo_pgsql/php_pdo_pgsql_int.h | 111 ++ .../php80/pdo_sqlite/php_pdo_sqlite_int.h | 80 + thirdparty/php80/pdo_sqlite/sqlite_driver.c | 827 ++++++++++ .../php80/pdo_sqlite/sqlite_driver.stub.php | 15 + .../php80/pdo_sqlite/sqlite_driver_arginfo.h | 34 + .../php80/pdo_sqlite/sqlite_statement.c | 387 +++++ thirdparty/php81/pdo_oci/php_pdo_oci_int.h | 104 +- thirdparty/php83/pdo_oci/oci_driver.c | 2 +- thirdparty/php83/pdo_oci/oci_statement.c | 2 +- thirdparty/php83/pdo_oci/php_pdo_oci_int.h | 119 +- thirdparty/php83/pdo_odbc/odbc_driver.c | 2 +- thirdparty/php83/pdo_odbc/odbc_stmt.c | 3 +- thirdparty/php83/pdo_pgsql/pgsql_driver.c | 2 +- thirdparty/php83/pdo_pgsql/pgsql_statement.c | 2 +- thirdparty/php83/pdo_sqlite/sqlite_driver.c | 2 +- .../php83/pdo_sqlite/sqlite_statement.c | 2 +- thirdparty/php84/pdo_oci/LICENSE | 68 + thirdparty/php84/pdo_oci/oci_driver.c | 880 ++++++++++ thirdparty/php84/pdo_oci/oci_statement.c | 1041 ++++++++++++ thirdparty/php84/pdo_oci/php_pdo_oci_int.h | 105 ++ thirdparty/php84/pdo_odbc/odbc_driver.c | 638 ++++++++ thirdparty/php84/pdo_odbc/odbc_stmt.c | 865 ++++++++++ thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h | 179 ++ thirdparty/php84/pdo_pgsql/pgsql_driver.c | 1458 +++++++++++++++++ .../php84/pdo_pgsql/pgsql_driver_arginfo.h | 70 + thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c | 555 +++++++ thirdparty/php84/pdo_pgsql/pgsql_statement.c | 740 +++++++++ .../php84/pdo_pgsql/php_pdo_pgsql_int.h | 129 ++ .../php84/pdo_sqlite/php_pdo_sqlite_int.h | 84 + thirdparty/php84/pdo_sqlite/sqlite_driver.c | 825 ++++++++++ .../php84/pdo_sqlite/sqlite_driver.stub.php | 18 + .../php84/pdo_sqlite/sqlite_driver_arginfo.h | 32 + .../php84/pdo_sqlite/sqlite_sql_parser.c | 423 +++++ .../php84/pdo_sqlite/sqlite_statement.c | 381 +++++ 49 files changed, 15761 insertions(+), 130 deletions(-) create mode 100644 thirdparty/php80/pdo_oci/oci_driver.c create mode 100644 thirdparty/php80/pdo_oci/oci_statement.c create mode 100644 thirdparty/php80/pdo_oci/php_pdo_oci_int.h create mode 100644 thirdparty/php80/pdo_odbc/odbc_driver.c create mode 100644 thirdparty/php80/pdo_odbc/odbc_stmt.c create mode 100644 thirdparty/php80/pdo_odbc/php_pdo_odbc_int.h create mode 100644 thirdparty/php80/pdo_pgsql/pgsql_driver.c create mode 100644 thirdparty/php80/pdo_pgsql/pgsql_driver_arginfo.h create mode 100644 thirdparty/php80/pdo_pgsql/pgsql_statement.c create mode 100644 thirdparty/php80/pdo_pgsql/php_pdo_pgsql_int.h create mode 100644 thirdparty/php80/pdo_sqlite/php_pdo_sqlite_int.h create mode 100644 thirdparty/php80/pdo_sqlite/sqlite_driver.c create mode 100644 thirdparty/php80/pdo_sqlite/sqlite_driver.stub.php create mode 100644 thirdparty/php80/pdo_sqlite/sqlite_driver_arginfo.h create mode 100644 thirdparty/php80/pdo_sqlite/sqlite_statement.c create mode 100644 thirdparty/php84/pdo_oci/LICENSE create mode 100644 thirdparty/php84/pdo_oci/oci_driver.c create mode 100644 thirdparty/php84/pdo_oci/oci_statement.c create mode 100644 thirdparty/php84/pdo_oci/php_pdo_oci_int.h create mode 100644 thirdparty/php84/pdo_odbc/odbc_driver.c create mode 100644 thirdparty/php84/pdo_odbc/odbc_stmt.c create mode 100644 thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h create mode 100644 thirdparty/php84/pdo_pgsql/pgsql_driver.c create mode 100644 thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h create mode 100644 thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c create mode 100644 thirdparty/php84/pdo_pgsql/pgsql_statement.c create mode 100644 thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h create mode 100644 thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_driver.c create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_driver_arginfo.h create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_sql_parser.c create mode 100644 thirdparty/php84/pdo_sqlite/sqlite_statement.c diff --git a/config.m4 b/config.m4 index 1a8606e8e81..cdf236dad67 100644 --- a/config.m4 +++ b/config.m4 @@ -1070,7 +1070,11 @@ EOF thirdparty/php81/pdo_pgsql/pgsql_driver.c \ thirdparty/php81/pdo_pgsql/pgsql_statement.c \ thirdparty/php83/pdo_pgsql/pgsql_driver.c \ - thirdparty/php83/pdo_pgsql/pgsql_statement.c" + thirdparty/php83/pdo_pgsql/pgsql_statement.c \ + thirdparty/php83/pdo_pgsql/pgsql_statement.c \ + thirdparty/php84/pdo_pgsql/pgsql_driver.c \ + thirdparty/php84/pdo_pgsql/pgsql_statement.c \ + thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c" fi if test "$PHP_SWOOLE_ORACLE" != "no"; then @@ -1078,7 +1082,9 @@ EOF thirdparty/php81/pdo_oci/oci_driver.c \ thirdparty/php81/pdo_oci/oci_statement.c \ thirdparty/php83/pdo_oci/oci_driver.c \ - thirdparty/php83/pdo_oci/oci_statement.c" + thirdparty/php83/pdo_oci/oci_statement.c \ + thirdparty/php84/pdo_oci/oci_driver.c \ + thirdparty/php84/pdo_oci/oci_statement.c" fi if test "$PHP_SWOOLE_ODBC" != "no"; then @@ -1086,7 +1092,9 @@ EOF thirdparty/php81/pdo_odbc/odbc_driver.c \ thirdparty/php81/pdo_odbc/odbc_stmt.c \ thirdparty/php83/pdo_odbc/odbc_driver.c \ - thirdparty/php83/pdo_odbc/odbc_stmt.c" + thirdparty/php83/pdo_odbc/odbc_stmt.c \ + thirdparty/php84/pdo_odbc/odbc_driver.c \ + thirdparty/php84/pdo_odbc/odbc_stmt.c" fi if test "$PHP_SWOOLE_SQLITE" != "no"; then @@ -1094,7 +1102,10 @@ EOF thirdparty/php81/pdo_sqlite/sqlite_driver.c \ thirdparty/php81/pdo_sqlite/sqlite_statement.c \ thirdparty/php83/pdo_sqlite/sqlite_driver.c \ - thirdparty/php83/pdo_sqlite/sqlite_statement.c" + thirdparty/php83/pdo_sqlite/sqlite_statement.c \ + thirdparty/php84/pdo_sqlite/sqlite_driver.c \ + thirdparty/php84/pdo_sqlite/sqlite_statement.c \ + thirdparty/php84/pdo_sqlite/sqlite_sql_parser.c" fi SW_ASM_DIR="thirdparty/boost/asm/" @@ -1255,17 +1266,21 @@ EOF if test "$PHP_SWOOLE_PGSQL" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_pgsql) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_pgsql) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_pgsql) fi if test "$PHP_SWOOLE_ODBC" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_odbc) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_odbc) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_odbc) fi if test "$PHP_SWOOLE_ORACLE" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_oci) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_oci) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_oci) fi if test "$PHP_SWOOLE_SQLITE" != "no"; then PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php81/pdo_sqlite) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php83/pdo_sqlite) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_sqlite) fi fi diff --git a/ext-src/php_swoole_odbc.h b/ext-src/php_swoole_odbc.h index 3bfa253fc87..59e19c0f57b 100644 --- a/ext-src/php_swoole_odbc.h +++ b/ext-src/php_swoole_odbc.h @@ -24,7 +24,17 @@ BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" +#if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100 +#include "thirdparty/php80/pdo_odbc/php_pdo_odbc_int.h" +#elif PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80200 #include "thirdparty/php81/pdo_odbc/php_pdo_odbc_int.h" +#elif PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 +#include "thirdparty/php81/pdo_odbc/php_pdo_odbc_int.h" +#elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 +#include "thirdparty/php83/pdo_odbc/php_pdo_odbc_int.h" +#else +#include "thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h" +#endif extern const pdo_driver_t swoole_pdo_odbc_driver; diff --git a/ext-src/php_swoole_oracle.h b/ext-src/php_swoole_oracle.h index 94da4a531af..7ce78a99e3f 100644 --- a/ext-src/php_swoole_oracle.h +++ b/ext-src/php_swoole_oracle.h @@ -25,7 +25,18 @@ BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" + +#if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100 +#include "thirdparty/php80/pdo_oci/php_pdo_oci_int.h" +#elif PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80200 +#include "thirdparty/php81/pdo_oci/php_pdo_oci_int.h" +#elif PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 #include "thirdparty/php81/pdo_oci/php_pdo_oci_int.h" +#elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 +#include "thirdparty/php83/pdo_oci/php_pdo_oci_int.h" +#else +#include "thirdparty/php84/pdo_oci/php_pdo_oci_int.h" +#endif extern const pdo_driver_t swoole_pdo_oci_driver; diff --git a/ext-src/php_swoole_pgsql.h b/ext-src/php_swoole_pgsql.h index cdd267f020b..187c67d22af 100644 --- a/ext-src/php_swoole_pgsql.h +++ b/ext-src/php_swoole_pgsql.h @@ -25,6 +25,19 @@ BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" +#if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100 +#include "thirdparty/php80/pdo_pgsql/php_pdo_pgsql_int.h" +#elif PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80200 +#include "thirdparty/php81/pdo_pgsql/php_pdo_pgsql_int.h" +#elif PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 +#include "thirdparty/php81/pdo_pgsql/php_pdo_pgsql_int.h" +#elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 +#include "thirdparty/php83/pdo_pgsql/php_pdo_pgsql_int.h" +#else +#include "thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h" +#endif + + extern const pdo_driver_t swoole_pdo_pgsql_driver; #include diff --git a/ext-src/php_swoole_sqlite.h b/ext-src/php_swoole_sqlite.h index fa68529d4fb..209a4ad5565 100644 --- a/ext-src/php_swoole_sqlite.h +++ b/ext-src/php_swoole_sqlite.h @@ -24,7 +24,18 @@ BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" + +#if PHP_VERSION_ID >= 80000 && PHP_VERSION_ID < 80100 +#include "thirdparty/php80/pdo_sqlite/php_pdo_sqlite_int.h" +#elif PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80200 +#include "thirdparty/php81/pdo_sqlite/php_pdo_sqlite_int.h" +#elif PHP_VERSION_ID >= 80200 && PHP_VERSION_ID < 80300 #include "thirdparty/php81/pdo_sqlite/php_pdo_sqlite_int.h" +#elif PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 +#include "thirdparty/php83/pdo_sqlite/php_pdo_sqlite_int.h" +#else +#include "thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h" +#endif extern const pdo_driver_t swoole_pdo_sqlite_driver; void swoole_sqlite_set_blocking(bool blocking); diff --git a/ext-src/swoole_pgsql.cc b/ext-src/swoole_pgsql.cc index 6ef31c8c216..134ad17cce2 100644 --- a/ext-src/swoole_pgsql.cc +++ b/ext-src/swoole_pgsql.cc @@ -22,7 +22,6 @@ #include "swoole_coroutine_system.h" #ifdef SW_USE_PGSQL -#include "thirdparty/php81/pdo_pgsql/php_pdo_pgsql_int.h" using swoole::Reactor; using swoole::coroutine::Socket; diff --git a/thirdparty/php80/pdo_oci/oci_driver.c b/thirdparty/php80/pdo_oci/oci_driver.c new file mode 100644 index 00000000000..cba6c1bb865 --- /dev/null +++ b/thirdparty/php80/pdo_oci/oci_driver.c @@ -0,0 +1,867 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" + +#if PHP_VERSION_ID < 80100 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "Zend/zend_exceptions.h" + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch); + +static int pdo_oci_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + + einfo = &H->einfo; + + if (stmt) { + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + if (S->einfo.errmsg) { + einfo = &S->einfo; + } + } + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } + + return 1; +} +/* }}} */ + +ub4 _oci_error(OCIError *err, + pdo_dbh_t *dbh, + pdo_stmt_t *stmt, + char *what, + sword status, + int isinit, + const char *file, + int line) /* {{{ */ +{ + text errbuf[1024] = "<>"; + char tmp_buf[2048]; + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + pdo_oci_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_oci_stmt *) stmt->driver_data; + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } else { + einfo = &H->einfo; + } + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + + einfo->errmsg = NULL; + einfo->errcode = 0; + einfo->file = file; + einfo->line = line; + + if (isinit) { /* Initialization error */ + strcpy(*pdo_err, "HY000"); + slprintf(tmp_buf, sizeof(tmp_buf), "%s (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + } else { + switch (status) { + case OCI_SUCCESS: + strcpy(*pdo_err, "00000"); + break; + case OCI_ERROR: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_SUCCESS_WITH_INFO: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_SUCCESS_WITH_INFO: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NEED_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NEED_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NO_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NO_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_INVALID_HANDLE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_INVALID_HANDLE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_STILL_EXECUTING: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_STILL_EXECUTING (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_CONTINUE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_CONTINUE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + } + + if (einfo->errcode) { + switch (einfo->errcode) { + case 1013: /* user requested cancel of current operation */ + zend_bailout(); + break; + + case 12154: /* ORA-12154: TNS:could not resolve service name */ + strcpy(*pdo_err, "42S02"); + break; + + case 22: /* ORA-00022: invalid session id */ + case 378: + case 602: + case 603: + case 604: + case 609: + case 1012: /* ORA-01012: */ + case 1033: + case 1041: + case 1043: + case 1089: + case 1090: + case 1092: + case 3113: /* ORA-03133: end of file on communication channel */ + case 3114: + case 3122: + case 3135: + case 12153: + case 27146: + case 28511: + /* consider the connection closed */ + dbh->is_closed = 1; + H->attached = 0; + strcpy(*pdo_err, "01002"); /* FIXME */ + break; + + default: + strcpy(*pdo_err, "HY000"); + } + } + + if (stmt) { + /* always propagate the error code back up to the dbh, + * so that we can catch the error information when execute + * is called via query. See Bug #33707 */ + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + } + H->einfo = *einfo; + H->einfo.errmsg = einfo->errmsg ? pestrdup(einfo->errmsg, dbh->is_persistent) : NULL; + strcpy(dbh->error_code, stmt->error_code); + } + } + + /* little mini hack so that we can use this code from the dbh ctor */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), einfo->errcode, "SQLSTATE[%s]: %s", *pdo_err, einfo->errmsg); + } + + return einfo->errcode; +} +/* }}} */ + +static int oci_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + if (H->svc) { + /* rollback any outstanding work */ + OCITransRollback(H->svc, H->err, 0); + } + + if (H->session) { + OCIHandleFree(H->session, OCI_HTYPE_SESSION); + H->session = NULL; + } + + if (H->svc) { + OCIHandleFree(H->svc, OCI_HTYPE_SVCCTX); + H->svc = NULL; + } + + if (H->server && H->attached) { + H->last_err = OCIServerDetach(H->server, H->err, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCIServerDetach"); + } + H->attached = 0; + } + + if (H->server) { + OCIHandleFree(H->server, OCI_HTYPE_SERVER); + H->server = NULL; + } + + if (H->err) { + OCIHandleFree(H->err, OCI_HTYPE_ERROR); + H->err = NULL; + } + + if (H->charset && H->env) { + OCIHandleFree(H->env, OCI_HTYPE_ENV); + H->env = NULL; + } + + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + + pefree(H, dbh->is_persistent); + + return 0; +} +/* }}} */ + +static int oci_handle_preparer( + pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_stmt *S = ecalloc(1, sizeof(*S)); + ub4 prefetch; + char *nsql = NULL; + size_t nsql_len = 0; + int ret; + +#ifdef HAVE_OCISTMTFETCH2 + S->exec_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL + ? OCI_STMT_SCROLLABLE_READONLY + : OCI_DEFAULT; +#else + S->exec_type = OCI_DEFAULT; +#endif + + S->H = H; + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + ret = pdo_parse_params(stmt, (char *) sql, sql_len, &nsql, &nsql_len); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + sql_len = nsql_len; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return 0; + } + + /* create an OCI statement handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->stmt, OCI_HTYPE_STMT, 0, NULL); + + /* and our own private error handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->err, OCI_HTYPE_ERROR, 0, NULL); + + if (sql_len) { + H->last_err = OCIStmtPrepare(S->stmt, H->err, (text *) sql, (ub4) sql_len, OCI_NTV_SYNTAX, OCI_DEFAULT); + if (nsql) { + efree(nsql); + nsql = NULL; + } + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + efree(S); + return 0; + } + } + + prefetch = H->prefetch; /* Note 0 is allowed so in future REF CURSORs can be used & then passed with no row loss*/ + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_ROWS, H->err); + if (!H->last_err) { + prefetch *= PDO_OCI_PREFETCH_ROWSIZE; + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_MEMORY, H->err); + } + + stmt->driver_data = S; + stmt->methods = &swoole_oci_stmt_methods; + if (nsql) { + efree(nsql); + nsql = NULL; + } + + return 1; +} +/* }}} */ + +static zend_long oci_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + OCIStmt *stmt; + ub2 stmt_type; + ub4 rowcount; + int ret = -1; + + OCIHandleAlloc(H->env, (dvoid *) &stmt, OCI_HTYPE_STMT, 0, NULL); + + H->last_err = OCIStmtPrepare(stmt, H->err, (text *) sql, (ub4) sql_len, OCI_NTV_SYNTAX, OCI_DEFAULT); + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(stmt, OCI_HTYPE_STMT); + return -1; + } + + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &stmt_type, 0, OCI_ATTR_STMT_TYPE, H->err); + + if (stmt_type == OCI_STMT_SELECT) { + /* invalid usage; cancel it */ + OCIHandleFree(stmt, OCI_HTYPE_STMT); + php_error_docref(NULL, E_WARNING, "issuing a SELECT query here is invalid"); + return -1; + } + + /* now we are good to go */ + H->last_err = OCIStmtExecute(H->svc, + stmt, + H->err, + 1, + 0, + NULL, + NULL, + (dbh->auto_commit && !dbh->in_txn) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT); + + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtExecute"); + } else { + /* return the number of affected rows */ + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, H->err); + ret = rowcount; + } + + OCIHandleFree(stmt, OCI_HTYPE_STMT); + + return ret; +} +/* }}} */ + +static int oci_handle_quoter(pdo_dbh_t *dbh, + const char *unquoted, + size_t unquotedlen, + char **quoted, + size_t *quotedlen, + enum pdo_param_type paramtype) /* {{{ */ +{ + int qcount = 0; + char const *cu, *l, *r; + char *c; + + if (!unquotedlen) { + *quotedlen = 2; + *quoted = emalloc(*quotedlen + 1); + strcpy(*quoted, "''"); + return 1; + } + + /* count single quotes */ + for (cu = unquoted; (cu = strchr(cu, '\'')); qcount++, cu++) + ; /* empty loop */ + + *quotedlen = unquotedlen + qcount + 2; + *quoted = c = emalloc(*quotedlen + 1); + *c++ = '\''; + + /* foreach (chunk that ends in a quote) */ + for (l = unquoted; (r = strchr(l, '\'')); l = r + 1) { + strncpy(c, l, r - l + 1); + c += (r - l + 1); + *c++ = '\''; /* add second quote */ + } + + /* Copy remainder and add enclosing quote */ + strncpy(c, l, *quotedlen - (c - *quoted) - 1); + (*quoted)[*quotedlen - 1] = '\''; + (*quoted)[*quotedlen] = '\0'; + + return 1; +} +/* }}} */ + +static int oci_handle_begin(pdo_dbh_t *dbh) /* {{{ */ +{ + /* with Oracle, there is nothing special to be done */ + return 1; +} +/* }}} */ + +static int oci_handle_commit(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return 0; + } + return 1; +} +/* }}} */ + +static int oci_handle_rollback(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransRollback(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransRollback"); + return 0; + } + return 1; +} +/* }}} */ + +static int oci_handle_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + zend_long lval = zval_get_long(val); + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: { + if (dbh->in_txn) { + /* Assume they want to commit whatever is outstanding */ + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return 0; + } + dbh->in_txn = 0; + } + + dbh->auto_commit = (unsigned int) lval ? 1 : 0; + return 1; + } + case PDO_ATTR_PREFETCH: { + H->prefetch = pdo_oci_sanitize_prefetch(lval); + return 1; + } + case PDO_OCI_ATTR_ACTION: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *action = zval_try_get_string(val); + if (UNEXPECTED(!action)) { + return 0; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(action), (ub4) ZSTR_LEN(action), OCI_ATTR_ACTION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_ACTION"); + return 0; + } + return 1; +#else + oci_drv_error("Unsupported attribute type"); + return 0; +#endif + } + case PDO_OCI_ATTR_CLIENT_INFO: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *client_info = zval_try_get_string(val); + if (UNEXPECTED(!client_info)) { + return 0; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(client_info), + (ub4) ZSTR_LEN(client_info), + OCI_ATTR_CLIENT_INFO, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_INFO"); + return 0; + } + return 1; +#else + oci_drv_error("Unsupported attribute type"); + return 0; +#endif + } + case PDO_OCI_ATTR_CLIENT_IDENTIFIER: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *identifier = zval_try_get_string(val); + if (UNEXPECTED(!identifier)) { + return 0; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(identifier), + (ub4) ZSTR_LEN(identifier), + OCI_ATTR_CLIENT_IDENTIFIER, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_IDENTIFIER"); + return 0; + } + return 1; +#else + oci_drv_error("Unsupported attribute type"); + return 0; +#endif + } + case PDO_OCI_ATTR_MODULE: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *module = zval_try_get_string(val); + if (UNEXPECTED(!module)) { + return 0; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(module), (ub4) ZSTR_LEN(module), OCI_ATTR_MODULE, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_MODULE"); + return 0; + } + return 1; +#else + oci_drv_error("Unsupported attribute type"); + return 0; +#endif + } + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + ub4 timeout = (ub4) lval; + + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, (ub4) 0, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CALL_TIMEOUT"); + return 0; + } + return 1; +#else + oci_drv_error("Unsupported attribute type"); + return 0; +#endif + } + default: + return 0; + } +} +/* }}} */ + +static int oci_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: { + text infostr[512]; + char verstr[15]; + ub4 vernum; + + if (OCIServerRelease(H->svc, H->err, infostr, (ub4) sizeof(infostr), (ub1) OCI_HTYPE_SVCCTX, &vernum)) { + ZVAL_STRING(return_value, "<>"); + } else { + if (attr == PDO_ATTR_SERVER_INFO) { + ZVAL_STRING(return_value, (char *) infostr); + } else { + slprintf(verstr, + sizeof(verstr), + "%d.%d.%d.%d.%d", + (int) ((vernum >> 24) & 0xFF), /* version number */ + (int) ((vernum >> 20) & 0x0F), /* release number*/ + (int) ((vernum >> 12) & 0xFF), /* update number */ + (int) ((vernum >> 8) & 0x0F), /* port release number */ + (int) ((vernum >> 0) & 0xFF)); /* port update number */ + + ZVAL_STRING(return_value, verstr); + } + } + return TRUE; + } + + case PDO_ATTR_CLIENT_VERSION: { +#if OCI_MAJOR_VERSION > 10 || (OCI_MAJOR_VERSION == 10 && OCI_MINOR_VERSION >= 2) + /* Run time client version */ + sword major, minor, update, patch, port_update; + char verstr[15]; + + OCIClientVersion(&major, &minor, &update, &patch, &port_update); + slprintf(verstr, sizeof(verstr), "%d.%d.%d.%d.%d", major, minor, update, patch, port_update); + ZVAL_STRING(return_value, verstr); +#elif defined(SWOOLE_PDO_OCI_CLIENT_VERSION) + /* Compile time client version */ + ZVAL_STRING(return_value, SWOOLE_PDO_OCI_CLIENT_VERSION); +#else + return FALSE; + +#endif /* Check for OCIClientVersion() support */ + + return TRUE; + } + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(return_value, dbh->auto_commit); + return TRUE; + + case PDO_ATTR_PREFETCH: + ZVAL_LONG(return_value, H->prefetch); + return TRUE; + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + ub4 timeout; + + H->last_err = OCIAttrGet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, NULL, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrGet: OCI_ATTR_CALL_TIMEOUT"); + return FALSE; + } + + ZVAL_LONG(return_value, (zend_long) timeout); + return TRUE; +#else + oci_drv_error("Unsupported attribute type"); + return FALSE; +#endif + } + default: + return FALSE; + } + return FALSE; +} +/* }}} */ + +static int pdo_oci_check_liveness(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + sb4 error_code = 0; +#if (!((OCI_MAJOR_VERSION > 10) || ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2)))) + char version[256]; +#endif + + /* TODO move attached check to PDO level */ + if (H->attached == 0) { + return FAILURE; + } + /* TODO add persistent_timeout check at PDO level */ + + /* Use OCIPing instead of OCIServerVersion. If OCIPing returns ORA-1010 (invalid OCI operation) + * such as from Pre-10.1 servers, the error is still from the server and we would have + * successfully performed a roundtrip and validated the connection. Use OCIServerVersion for + * Pre-10.2 clients + */ +#if ((OCI_MAJOR_VERSION > 10) || \ + ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2))) /* OCIPing available 10.2 onwards */ + H->last_err = OCIPing(H->svc, H->err, OCI_DEFAULT); +#else + /* use good old OCIServerVersion() */ + H->last_err = OCIServerVersion(H->svc, H->err, (text *) version, sizeof(version), OCI_HTYPE_SVCCTX); +#endif + if (H->last_err == OCI_SUCCESS) { + return SUCCESS; + } + + OCIErrorGet(H->err, (ub4) 1, NULL, &error_code, NULL, 0, OCI_HTYPE_ERROR); + + if (error_code == 1010) { + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static const struct pdo_dbh_methods oci_methods = {oci_handle_closer, + oci_handle_preparer, + oci_handle_doer, + oci_handle_quoter, + oci_handle_begin, + oci_handle_commit, + oci_handle_rollback, + oci_handle_set_attribute, + NULL, + pdo_oci_fetch_error_func, + oci_handle_get_attribute, + pdo_oci_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, + NULL}; + +static int pdo_oci_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H; + int i, ret = 0; + struct pdo_data_src_parser vars[] = { + {"charset", NULL, 0}, {"dbname", "", 0}, {"user", NULL, 0}, {"password", NULL, 0}}; + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 4); + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = 1 << PDO_PARAM_EVT_FETCH_PRE | 1 << PDO_PARAM_EVT_FETCH_POST | 1 << PDO_PARAM_EVT_NORMALIZE; + + H->prefetch = PDO_OCI_PREFETCH_DEFAULT; + + /* allocate an environment */ +#ifdef HAVE_OCIENVNLSCREATE + if (vars[0].optval) { + H->charset = OCINlsCharSetNameToId(swoole_pdo_oci_Env, (const oratext *) vars[0].optval); + if (!H->charset) { + oci_init_error("OCINlsCharSetNameToId: unknown character set name"); + goto cleanup; + } else { + if (OCIEnvNlsCreate( + &H->env, SWOOLE_PDO_OCI_INIT_MODE, 0, NULL, NULL, NULL, 0, NULL, H->charset, H->charset) != + OCI_SUCCESS) { + oci_init_error("OCIEnvNlsCreate: Check the character set is valid and that PHP has access to Oracle " + "libraries and NLS data"); + goto cleanup; + } + } + } +#endif + if (H->env == NULL) { + /* use the global environment */ + H->env = swoole_pdo_oci_Env; + } + + /* something to hold errors */ + OCIHandleAlloc(H->env, (dvoid **) &H->err, OCI_HTYPE_ERROR, 0, NULL); + + /* handle for the server */ + OCIHandleAlloc(H->env, (dvoid **) &H->server, OCI_HTYPE_SERVER, 0, NULL); + + H->last_err = + OCIServerAttach(H->server, H->err, (text *) vars[1].optval, (sb4) strlen(vars[1].optval), OCI_DEFAULT); + + if (H->last_err) { + oci_drv_error("pdo_oci_handle_factory"); + goto cleanup; + } + + H->attached = 1; + + /* create a service context */ + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->svc, OCI_HTYPE_SVCCTX, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SVCCTX"); + goto cleanup; + } + + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->session, OCI_HTYPE_SESSION, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SESSION"); + goto cleanup; + } + + /* set server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->server, 0, OCI_ATTR_SERVER, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SERVER"); + goto cleanup; + } + + /* username */ + if (!dbh->username && vars[2].optval) { + dbh->username = pestrdup(vars[2].optval, dbh->is_persistent); + } + + if (dbh->username) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->username, (ub4) strlen(dbh->username), OCI_ATTR_USERNAME, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_USERNAME"); + goto cleanup; + } + } + + /* password */ + if (!dbh->password && vars[3].optval) { + dbh->password = pestrdup(vars[3].optval, dbh->is_persistent); + } + + if (dbh->password) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->password, (ub4) strlen(dbh->password), OCI_ATTR_PASSWORD, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_PASSWORD"); + goto cleanup; + } + } + + /* Now fire up the session */ + H->last_err = OCISessionBegin(H->svc, H->err, H->session, OCI_CRED_RDBMS, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCISessionBegin"); + goto cleanup; + } + + /* set the server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->session, 0, OCI_ATTR_SESSION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SESSION"); + goto cleanup; + } + + /* Get max character width */ + H->last_err = OCINlsNumericInfoGet(H->env, H->err, &H->max_char_width, OCI_NLS_CHARSET_MAXBYTESZ); + if (H->last_err) { + oci_drv_error("OCINlsNumericInfoGet: OCI_NLS_CHARSET_MAXBYTESZ"); + goto cleanup; + } + + dbh->methods = &oci_methods; + dbh->alloc_own_columns = 1; + dbh->native_case = PDO_CASE_UPPER; + + ret = 1; + +cleanup: + for (i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!ret) { + oci_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_oci_driver = {PDO_DRIVER_HEADER(oci), pdo_oci_handle_factory}; + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch) /* {{{ */ +{ + if (prefetch < 0) { + prefetch = 0; + } else if (prefetch > UB4MAXVAL / PDO_OCI_PREFETCH_ROWSIZE) { + prefetch = PDO_OCI_PREFETCH_DEFAULT; + } + return ((ub4) prefetch); +} +/* }}} */ +#endif diff --git a/thirdparty/php80/pdo_oci/oci_statement.c b/thirdparty/php80/pdo_oci/oci_statement.c new file mode 100644 index 00000000000..5e4ae72c12a --- /dev/null +++ b/thirdparty/php80/pdo_oci/oci_statement.c @@ -0,0 +1,1029 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" + +#if PHP_VERSION_ID < 80100 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "Zend/zend_extensions.h" + +#define PDO_OCI_LOBMAXSIZE (4294967295UL) /* OCI_LOBMAXSIZE */ + +#define STMT_CALL(name, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +#define STMT_CALL_MSG(name, msg, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name ": " #msg, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob); + +#define OCI_TEMPLOB_CLOSE(envhp, svchp, errhp, lob) \ + do { \ + boolean isTempLOB; \ + OCILobIsTemporary(envhp, errhp, lob, &isTempLOB); \ + if (isTempLOB) OCILobFreeTemporary(svchp, errhp, lob); \ + } while (0) + +static int oci_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + HashTable *BC = stmt->bound_columns; + HashTable *BP = stmt->bound_params; + + int i; + + if (S->stmt) { + /* cancel server side resources for the statement if we didn't + * fetch it all */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + + /* free the handle */ + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + S->stmt = NULL; + } + if (S->err) { + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + S->err = NULL; + } + + /* need to ensure these go away now */ + if (BC) { + zend_hash_destroy(BC); + FREE_HASHTABLE(stmt->bound_columns); + stmt->bound_columns = NULL; + } + + if (BP) { + zend_hash_destroy(BP); + FREE_HASHTABLE(stmt->bound_params); + stmt->bound_params = NULL; + } + + if (S->einfo.errmsg) { + pefree(S->einfo.errmsg, stmt->dbh->is_persistent); + S->einfo.errmsg = NULL; + } + + if (S->cols) { + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, (OCILobLocator *) S->cols[i].data); + OCIDescriptorFree(S->cols[i].data, OCI_DTYPE_LOB); + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + S->cols = NULL; + } + efree(S); + + stmt->driver_data = NULL; + + return 1; +} /* }}} */ + +static int oci_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + ub4 rowcount; + b4 mode; + + if (!S->stmt_type) { + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_STMT_TYPE", (S->stmt, OCI_HTYPE_STMT, &S->stmt_type, 0, OCI_ATTR_STMT_TYPE, S->err)); + } + + if (stmt->executed) { + /* ensure that we cancel the cursor from a previous fetch */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + } + +#ifdef OCI_STMT_SCROLLABLE_READONLY /* needed for oci8 ? */ + if (S->exec_type == OCI_STMT_SCROLLABLE_READONLY) { + mode = OCI_STMT_SCROLLABLE_READONLY; + } else +#endif + if (stmt->dbh->auto_commit && !stmt->dbh->in_txn) { + mode = OCI_COMMIT_ON_SUCCESS; + } else { + mode = OCI_DEFAULT; + } + + STMT_CALL( + OCIStmtExecute, + (S->H->svc, S->stmt, S->err, (S->stmt_type == OCI_STMT_SELECT && !S->have_blobs) ? 0 : 1, 0, NULL, NULL, mode)); + + if (!stmt->executed) { + ub4 colcount; + /* do first-time-only definition of bind/mapping stuff */ + + /* how many columns do we have ? */ + STMT_CALL_MSG( + OCIAttrGet, "ATTR_PARAM_COUNT", (S->stmt, OCI_HTYPE_STMT, &colcount, 0, OCI_ATTR_PARAM_COUNT, S->err)); + + stmt->column_count = (int) colcount; + + if (S->cols) { + int i; + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + /* do nothing */ + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + } + + S->cols = ecalloc(colcount, sizeof(pdo_oci_column)); + } + + STMT_CALL_MSG(OCIAttrGet, "ATTR_ROW_COUNT", (S->stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, S->err)); + stmt->row_count = (long) rowcount; + + return 1; +} /* }}} */ + +static sb4 oci_bind_input_cb( + dvoid *ctx, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 *alenp, ub1 *piecep, dvoid **indpp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + *indpp = &P->indicator; + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + if (P->thing) { + *bufpp = P->thing; + *alenp = sizeof(void *); + } else if (ZVAL_IS_NULL(parameter)) { + /* insert a NULL value into the column */ + P->indicator = -1; /* NULL */ + *bufpp = 0; + *alenp = -1; + } else if (!P->thing) { + /* regular string bind */ + if (!try_convert_to_string(parameter)) { + return OCI_ERROR; + } + *bufpp = Z_STRVAL_P(parameter); + *alenp = (ub4) Z_STRLEN_P(parameter); + } + + *piecep = OCI_ONE_PIECE; + return OCI_CONTINUE; +} /* }}} */ + +static sb4 oci_bind_output_cb(dvoid *ctx, + OCIBind *bindp, + ub4 iter, + ub4 index, + dvoid **bufpp, + ub4 **alenpp, + ub1 *piecep, + dvoid **indpp, + ub2 **rcodepp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + P->actual_len = sizeof(OCILobLocator *); + *bufpp = P->thing; + *alenpp = &P->actual_len; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + return OCI_CONTINUE; + } + + if (Z_TYPE_P(parameter) == IS_OBJECT || Z_TYPE_P(parameter) == IS_RESOURCE) { + return OCI_CONTINUE; + } + + zval_ptr_dtor(parameter); + + Z_STR_P(parameter) = zend_string_alloc(param->max_value_len, 1); + P->used_for_output = 1; + + P->actual_len = (ub4) Z_STRLEN_P(parameter); + *alenpp = &P->actual_len; + *bufpp = (Z_STR_P(parameter))->val; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + + return OCI_CONTINUE; +} /* }}} */ + +static int oci_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + pdo_oci_bound_param *P; + sb4 value_sz = -1; + zval *parameter; + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + P = (pdo_oci_bound_param *) param->driver_data; + + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P && P->thing) { + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + efree(P); + } else if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: + P = (pdo_oci_bound_param *) ecalloc(1, sizeof(pdo_oci_bound_param)); + param->driver_data = P; + + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_LOB: + /* P->thing is now an OCILobLocator * */ + P->oci_type = SQLT_BLOB; + value_sz = (sb4) sizeof(OCILobLocator *); + break; + + case PDO_PARAM_STR: + default: + P->oci_type = SQLT_CHR; + value_sz = (sb4) param->max_value_len; + if (param->max_value_len == 0) { + value_sz = (sb4) 1332; /* maximum size before value is interpreted as a LONG value */ + } + } + + if (param->name) { + STMT_CALL(OCIBindByName, + (S->stmt, + &P->bind, + S->err, + (text *) param->name->val, + (sb4) param->name->len, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } else { + STMT_CALL(OCIBindByPos, + (S->stmt, + &P->bind, + S->err, + ((ub4) param->paramno) + 1, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } + + STMT_CALL(OCIBindDynamic, (P->bind, S->err, param, oci_bind_input_cb, param, oci_bind_output_cb)); + + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + P->indicator = 0; + P->used_for_output = 0; + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + ub4 empty = 0; + STMT_CALL(OCIDescriptorAlloc, (S->H->env, &P->thing, OCI_DTYPE_LOB, 0, NULL)); + STMT_CALL(OCIAttrSet, (P->thing, OCI_DTYPE_LOB, &empty, 0, OCI_ATTR_LOBEMPTY, S->err)); + S->have_blobs = 1; + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + /* fixup stuff set in motion in oci_bind_output_cb */ + if (P->used_for_output) { + if (P->indicator == -1) { + /* set up a NULL value */ + if (Z_TYPE_P(parameter) == IS_STRING) { + /* OCI likes to stick non-terminated strings in things */ + *Z_STRVAL_P(parameter) = '\0'; + } + zval_ptr_dtor_str(parameter); + ZVAL_UNDEF(parameter); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + Z_STR_P(parameter) = zend_string_init(Z_STRVAL_P(parameter), P->actual_len, 1); + } + } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->thing) { + php_stream *stm; + + if (Z_TYPE_P(parameter) == IS_NULL) { + /* if the param is NULL, then we assume that they + * wanted to bind a lob locator into it from the query + * */ + + stm = oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator *) P->thing); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + php_stream_to_zval(stm, parameter); + } + } else { + /* we're a LOB being used for insert; transfer the data now */ + size_t n; + ub4 amt, offset = 1; + char *consume; + + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + do { + char buf[8192]; + n = php_stream_read(stm, buf, sizeof(buf)); + if ((int) n <= 0) { + break; + } + consume = buf; + do { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + offset += amt; + n -= amt; + consume += amt; + } while (n); + } while (1); + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + OCILobFlushBuffer(S->H->svc, S->err, (OCILobLocator *) P->thing, 0); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + /* stick the string into the LOB */ + consume = Z_STRVAL_P(parameter); + n = Z_STRLEN_P(parameter); + if (n) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + while (n) { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + consume += amt; + n -= amt; + } + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + } + } + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + } + } + + return 1; + } + } + + return 1; +} /* }}} */ + +static int oci_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) /* {{{ */ +{ +#ifdef HAVE_OCISTMTFETCH2 + ub4 ociori = OCI_FETCH_NEXT; +#endif + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + +#ifdef HAVE_OCISTMTFETCH2 + switch (ori) { + case PDO_FETCH_ORI_NEXT: + ociori = OCI_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + ociori = OCI_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + ociori = OCI_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + ociori = OCI_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + ociori = OCI_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + ociori = OCI_FETCH_RELATIVE; + break; + } + S->last_err = OCIStmtFetch2(S->stmt, S->err, 1, ociori, (sb4) offset, OCI_DEFAULT); +#else + S->last_err = OCIStmtFetch(S->stmt, S->err, 1, OCI_FETCH_NEXT, OCI_DEFAULT); +#endif + + if (S->last_err == OCI_NO_DATA) { + /* no (more) data */ + return 0; + } + + if (S->last_err == OCI_NEED_DATA) { + oci_stmt_error("OCI_NEED_DATA"); + return 0; + } + + if (S->last_err == OCI_SUCCESS_WITH_INFO || S->last_err == OCI_SUCCESS) { + return 1; + } + + oci_stmt_error("OCIStmtFetch"); + + return 0; +} /* }}} */ + +static sb4 oci_define_callback( + dvoid *octxp, OCIDefine *define, ub4 iter, dvoid **bufpp, ub4 **alenpp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp) { + pdo_oci_column *col = (pdo_oci_column *) octxp; + + switch (col->dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + *piecep = OCI_ONE_PIECE; + *bufpp = col->data; + *alenpp = &col->datalen; + *indpp = (dvoid *) &col->indicator; + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + + return OCI_CONTINUE; +} + +static int oci_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + text *colname; + ub2 dtype, data_size, precis; + ub4 namelen; + struct pdo_column_data *col = &stmt->columns[colno]; + zend_bool dyn = FALSE; + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* what type ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* how big ? */ + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_DATA_SIZE", (param, OCI_DTYPE_PARAM, &data_size, 0, OCI_ATTR_DATA_SIZE, S->err)); + + /* precision ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* name ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_NAME", (param, OCI_DTYPE_PARAM, &colname, &namelen, OCI_ATTR_NAME, S->err)); + + col->precision = precis; + col->maxlen = data_size; + col->name = zend_string_init((char *) colname, namelen, 0); + + S->cols[colno].dtype = dtype; + + /* how much room do we need to store the field */ + switch (dtype) { + case SQLT_LBI: + case SQLT_LNG: + if (dtype == SQLT_LBI) { + dtype = SQLT_BIN; + } else { + dtype = SQLT_CHR; + } + S->cols[colno].datalen = 512; /* XXX should be INT_MAX and fetched by pieces */ + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + col->param_type = PDO_PARAM_STR; + break; + + case SQLT_BLOB: + case SQLT_CLOB: + col->param_type = PDO_PARAM_LOB; + STMT_CALL(OCIDescriptorAlloc, (S->H->env, (dvoid **) &S->cols[colno].data, OCI_DTYPE_LOB, 0, NULL)); + S->cols[colno].datalen = sizeof(OCILobLocator *); + dyn = TRUE; + break; + + case SQLT_BIN: + default: + if (dtype == SQLT_DAT || dtype == SQLT_NUM || dtype == SQLT_RDD +#ifdef SQLT_TIMESTAMP + || dtype == SQLT_TIMESTAMP +#endif +#ifdef SQLT_TIMESTAMP_TZ + || dtype == SQLT_TIMESTAMP_TZ +#endif + ) { + /* should be big enough for most date formats and numbers */ + S->cols[colno].datalen = 512; +#if defined(SQLT_IBFLOAT) && defined(SQLT_IBDOUBLE) + } else if (dtype == SQLT_IBFLOAT || dtype == SQLT_IBDOUBLE) { + S->cols[colno].datalen = 1024; +#endif + } else if (dtype == SQLT_BIN) { + S->cols[colno].datalen = (ub4) col->maxlen * 2; /* raw characters to hex digits */ + } else { + S->cols[colno].datalen = (ub4) (col->maxlen * S->H->max_char_width); + } + + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + dtype = SQLT_CHR; + + /* returning data as a string */ + col->param_type = PDO_PARAM_STR; + } + + STMT_CALL(OCIDefineByPos, + (S->stmt, + &S->cols[colno].def, + S->err, + colno + 1, + S->cols[colno].data, + S->cols[colno].datalen, + dtype, + &S->cols[colno].indicator, + &S->cols[colno].fetched_len, + &S->cols[colno].retcode, + dyn ? OCI_DYNAMIC_FETCH : OCI_DEFAULT)); + + if (dyn) { + STMT_CALL(OCIDefineDynamic, (S->cols[colno].def, S->err, &S->cols[colno], oci_define_callback)); + } + + return 1; +} /* }}} */ + +struct _oci_lob_env { + OCISvcCtx *svc; + OCIError *err; +}; +typedef struct _oci_lob_env oci_lob_env; + +struct oci_lob_self { + zval dbh; + pdo_stmt_t *stmt; + pdo_oci_stmt *S; + OCILobLocator *lob; + oci_lob_env *E; + ub4 offset; +}; + +static ssize_t oci_blob_write(php_stream *stream, const char *buf, size_t count) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + ub4 amt; + sword r; + + amt = (ub4) count; + r = OCILobWrite(self->E->svc, + self->E->err, + self->lob, + &amt, + self->offset, + (char *) buf, + (ub4) count, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + + if (r != OCI_SUCCESS) { + return (ssize_t) -1; + } + + self->offset += amt; + return amt; +} + +static ssize_t oci_blob_read(php_stream *stream, char *buf, size_t count) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + ub4 amt; + sword r; + + amt = (ub4) count; + r = OCILobRead( + self->E->svc, self->E->err, self->lob, &amt, self->offset, buf, (ub4) count, NULL, NULL, 0, SQLCS_IMPLICIT); + + if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { + return (size_t) -1; + } + + self->offset += amt; + if (amt < count) { + stream->eof = 1; + } + return amt; +} + +static int oci_blob_close(php_stream *stream, int close_handle) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + pdo_stmt_t *stmt = self->stmt; + + if (close_handle) { + zend_object *obj = &stmt->std; + + OCILobClose(self->E->svc, self->E->err, self->lob); + zval_ptr_dtor(&self->dbh); + GC_DELREF(obj); + efree(self->E); + efree(self); + } + + /* php_pdo_free_statement(stmt); */ + return 0; +} + +static int oci_blob_flush(php_stream *stream) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + OCILobFlushBuffer(self->E->svc, self->E->err, self->lob, 0); + return 0; +} + +static int oci_blob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + + if (offset >= PDO_OCI_LOBMAXSIZE) { + return -1; + } else { + self->offset = (ub4) offset + 1; /* Oracle LOBS are 1-based, but PHP is 0-based */ + return 0; + } +} + +static const php_stream_ops oci_blob_stream_ops = {oci_blob_write, + oci_blob_read, + oci_blob_close, + oci_blob_flush, + "pdo_oci blob stream", + oci_blob_seek, + NULL, + NULL, + NULL}; + +static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob) { + php_stream *stm; + struct oci_lob_self *self = ecalloc(1, sizeof(*self)); + + ZVAL_COPY_VALUE(&self->dbh, dbh); + self->lob = lob; + self->offset = 1; /* 1-based */ + self->stmt = stmt; + self->S = (pdo_oci_stmt *) stmt->driver_data; + self->E = ecalloc(1, sizeof(oci_lob_env)); + self->E->svc = self->S->H->svc; + self->E->err = self->S->err; + + stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b"); + + if (stm) { + zend_object *obj; + obj = &stmt->std; + Z_ADDREF(self->dbh); + GC_ADDREF(obj); + return stm; + } + + efree(self); + return NULL; +} + +static int oci_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, size_t *len, int *caller_frees) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + pdo_oci_column *C = &S->cols[colno]; + + /* check the indicator to ensure that the data is intact */ + if (C->indicator == -1) { + /* A NULL value */ + *ptr = NULL; + *len = 0; + return 1; + } else if (C->indicator == 0) { + /* it was stored perfectly */ + + if (C->dtype == SQLT_BLOB || C->dtype == SQLT_CLOB) { + if (C->data) { + *ptr = (char *) oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator *) C->data); + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) C->data, OCI_LOB_READONLY); + } + *len = (size_t) 0; + return *ptr ? 1 : 0; + } + + *ptr = C->data; + *len = (size_t) C->fetched_len; + return 1; + } else { + /* it was truncated */ + php_error_docref(NULL, E_WARNING, "Column %d data was too large for buffer and was truncated to fit it", colno); + + *ptr = C->data; + *len = (size_t) C->fetched_len; + return 1; + } +} /* }}} */ + +static int oci_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + ub2 dtype, precis; + sb1 scale; + zval flags; + ub1 isnull, charset_form; + if (!S->stmt) { + return FAILURE; + } + if (colno >= stmt->column_count) { + /* error invalid column */ + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* column data type */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* column precision */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* column scale */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_SCALE", (param, OCI_DTYPE_PARAM, &scale, 0, OCI_ATTR_SCALE, S->err)); + + /* string column charset form */ + if (dtype == SQLT_CHR || dtype == SQLT_VCS || dtype == SQLT_AFC || dtype == SQLT_CLOB) { + STMT_CALL_MSG(OCIAttrGet, + "OCI_ATTR_CHARSET_FORM", + (param, OCI_DTYPE_PARAM, &charset_form, 0, OCI_ATTR_CHARSET_FORM, S->err)); + } + + if (dtype) { + /* if there is a declared type */ + switch (dtype) { +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP"); + add_assoc_string(return_value, "native_type", "TIMESTAMP"); + break; +#endif +#ifdef SQLT_TIMESTAMP_TZ + case SQLT_TIMESTAMP_TZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH TIMEZONE"); + break; +#endif +#ifdef SQLT_TIMESTAMP_LTZ + case SQLT_TIMESTAMP_LTZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + break; +#endif +#ifdef SQLT_INTERVAL_YM + case SQLT_INTERVAL_YM: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL YEAR TO MONTH"); + add_assoc_string(return_value, "native_type", "INTERVAL YEAR TO MONTH"); + break; +#endif +#ifdef SQLT_INTERVAL_DS + case SQLT_INTERVAL_DS: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL DAY TO SECOND"); + add_assoc_string(return_value, "native_type", "INTERVAL DAY TO SECOND"); + break; +#endif + case SQLT_DAT: + add_assoc_string(return_value, "oci:decl_type", "DATE"); + add_assoc_string(return_value, "native_type", "DATE"); + break; + case SQLT_FLT: + case SQLT_NUM: + /* if the precision is nonzero and scale is -127 then it is a FLOAT */ + if (scale == -127 && precis != 0) { + add_assoc_string(return_value, "oci:decl_type", "FLOAT"); + add_assoc_string(return_value, "native_type", "FLOAT"); + } else { + add_assoc_string(return_value, "oci:decl_type", "NUMBER"); + add_assoc_string(return_value, "native_type", "NUMBER"); + } + break; + case SQLT_LNG: + add_assoc_string(return_value, "oci:decl_type", "LONG"); + add_assoc_string(return_value, "native_type", "LONG"); + break; + case SQLT_BIN: + add_assoc_string(return_value, "oci:decl_type", "RAW"); + add_assoc_string(return_value, "native_type", "RAW"); + break; + case SQLT_LBI: + add_assoc_string(return_value, "oci:decl_type", "LONG RAW"); + add_assoc_string(return_value, "native_type", "LONG RAW"); + break; + case SQLT_CHR: + case SQLT_VCS: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NVARCHAR2"); + add_assoc_string(return_value, "native_type", "NVARCHAR2"); + } else { + add_assoc_string(return_value, "oci:decl_type", "VARCHAR2"); + add_assoc_string(return_value, "native_type", "VARCHAR2"); + } + break; + case SQLT_AFC: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCHAR"); + add_assoc_string(return_value, "native_type", "NCHAR"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CHAR"); + add_assoc_string(return_value, "native_type", "CHAR"); + } + break; + case SQLT_BLOB: + add_assoc_string(return_value, "oci:decl_type", "BLOB"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BLOB"); + break; + case SQLT_CLOB: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCLOB"); + add_assoc_string(return_value, "native_type", "NCLOB"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CLOB"); + add_assoc_string(return_value, "native_type", "CLOB"); + } + add_next_index_string(&flags, "blob"); + break; + case SQLT_BFILE: + add_assoc_string(return_value, "oci:decl_type", "BFILE"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BFILE"); + break; + case SQLT_RDD: + add_assoc_string(return_value, "oci:decl_type", "ROWID"); + add_assoc_string(return_value, "native_type", "ROWID"); + break; + case SQLT_BFLOAT: + case SQLT_IBFLOAT: + add_assoc_string(return_value, "oci:decl_type", "BINARY_FLOAT"); + add_assoc_string(return_value, "native_type", "BINARY_FLOAT"); + break; + case SQLT_BDOUBLE: + case SQLT_IBDOUBLE: + add_assoc_string(return_value, "oci:decl_type", "BINARY_DOUBLE"); + add_assoc_string(return_value, "native_type", "BINARY_DOUBLE"); + break; + default: + add_assoc_long(return_value, "oci:decl_type", dtype); + add_assoc_string(return_value, "native_type", "UNKNOWN"); + } + } else { + /* if the column is NULL */ + add_assoc_long(return_value, "oci:decl_type", 0); + add_assoc_string(return_value, "native_type", "NULL"); + } + + /* column can be null */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_IS_NULL", (param, OCI_DTYPE_PARAM, &isnull, 0, OCI_ATTR_IS_NULL, S->err)); + + if (isnull) { + add_next_index_string(&flags, "nullable"); + } else { + add_next_index_string(&flags, "not_null"); + } + + /* PDO type */ + switch (dtype) { + case SQLT_BFILE: + case SQLT_BLOB: + case SQLT_CLOB: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); + break; + default: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + } + + add_assoc_long(return_value, "scale", scale); + add_assoc_zval(return_value, "flags", &flags); + + OCIDescriptorFree(param, OCI_DTYPE_PARAM); + return SUCCESS; +} /* }}} */ + +const struct pdo_stmt_methods swoole_oci_stmt_methods = {oci_stmt_dtor, + oci_stmt_execute, + oci_stmt_fetch, + oci_stmt_describe, + oci_stmt_get_col, + oci_stmt_param_hook, + NULL, /* set_attr */ + NULL, /* get_attr */ + oci_stmt_col_meta, + NULL, + NULL}; +#endif diff --git a/thirdparty/php80/pdo_oci/php_pdo_oci_int.h b/thirdparty/php80/pdo_oci/php_pdo_oci_int.h new file mode 100644 index 00000000000..1aa178f8a64 --- /dev/null +++ b/thirdparty/php80/pdo_oci/php_pdo_oci_int.h @@ -0,0 +1,105 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#include + +typedef struct { + const char *file; + int line; + sb4 errcode; + char *errmsg; +} pdo_oci_error_info; + +/* stuff we use in an OCI database handle */ +typedef struct { + OCIServer *server; + OCISession *session; + OCIEnv *env; + OCIError *err; + OCISvcCtx *svc; + /* OCI9; 0 == use NLS_LANG */ + ub4 prefetch; + ub2 charset; + sword last_err; + sb4 max_char_width; + + unsigned attached : 1; + unsigned _reserved : 31; + + pdo_oci_error_info einfo; +} pdo_oci_db_handle; + +typedef struct { + OCIDefine *def; + ub2 fetched_len; + ub2 retcode; + sb2 indicator; + + char *data; + ub4 datalen; + + ub2 dtype; + +} pdo_oci_column; + +typedef struct { + pdo_oci_db_handle *H; + OCIStmt *stmt; + OCIError *err; + sword last_err; + ub2 stmt_type; + ub4 exec_type; + pdo_oci_column *cols; + pdo_oci_error_info einfo; + unsigned int have_blobs : 1; +} pdo_oci_stmt; + +typedef struct { + OCIBind *bind; /* allocated by OCI */ + sb2 oci_type; + sb2 indicator; + ub2 retcode; + + ub4 actual_len; + + dvoid *thing; /* for LOBS, REFCURSORS etc. */ + + unsigned used_for_output; +} pdo_oci_bound_param; + +extern const struct pdo_stmt_methods swoole_oci_stmt_methods; +extern const ub4 SWOOLE_PDO_OCI_INIT_MODE; +extern OCIEnv *swoole_pdo_oci_Env; + +ub4 _oci_error( + OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); +#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) +#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) +#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) + +/* Default prefetch size in number of rows */ +#define PDO_OCI_PREFETCH_DEFAULT 100 + +/* Arbitrary assumed row length for prefetch memory limit calcuation */ +#define PDO_OCI_PREFETCH_ROWSIZE 1024 + +enum { + PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, + PDO_OCI_ATTR_CLIENT_INFO, + PDO_OCI_ATTR_CLIENT_IDENTIFIER, + PDO_OCI_ATTR_MODULE, + PDO_OCI_ATTR_CALL_TIMEOUT +}; diff --git a/thirdparty/php80/pdo_odbc/odbc_driver.c b/thirdparty/php80/pdo_odbc/odbc_driver.c new file mode 100644 index 00000000000..2c666cc2104 --- /dev/null +++ b/thirdparty/php80/pdo_odbc/odbc_driver.c @@ -0,0 +1,485 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID < 80100 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "zend_exceptions.h" + +static int pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + zend_string *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt*)stmt->driver_data; + einfo = &S->einfo; + } + + message = strpprintf(0, "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, (long) einfo->last_error, + einfo->file, einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_str(info, message); + add_next_index_string(info, einfo->last_state); + + return 1; +} + + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt*)stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, eh, recno++, (SQLCHAR *) einfo->last_state, &einfo->last_error, + (SQLCHAR *) einfo->last_err_msg, sizeof(einfo->last_err_msg)-1, &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); +/* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), einfo->last_error, "SQLSTATE[%s] %s: %d %s", + *pdo_err, what, einfo->last_error, einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + SQLCHAR discard_state[6]; + SQLCHAR discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, + discard_buf, sizeof(discard_buf)-1, &errmsgsize); + } + +} +/* }}} */ + +static int odbc_handle_closer(pdo_dbh_t *dbh) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle*)dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + + return 0; +} + +static int odbc_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options) +{ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + char *nsql = NULL; + size_t nsql_len = 0; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return 0; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + efree(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return 0; + } + + stmt->driver_data = S; + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void*)SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + efree(nsql); + } + return 0; + } + } + + rc = SQLPrepare(S->stmt, (SQLCHAR *) sql, SQL_NTS); + if (nsql) { + efree(nsql); + } + + stmt->methods = &odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (SQLCHAR *) sql, sql_len); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +/* TODO: Do ODBC quoter +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type param_type ) +{ + // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + // TODO: figure it out + return 0; +} +*/ + +static int odbc_handle_begin(pdo_dbh_t *dbh) +{ + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return 0; + } + } + return 1; +} + +static int odbc_handle_commit(pdo_dbh_t *dbh) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return 0; + } + } + return 1; +} + +static int odbc_handle_rollback(pdo_dbh_t *dbh) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return 0; + } + } + + return 1; +} + +static int odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + H->assume_utf8 = zval_is_true(val); + return 1; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return 0; + } +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE); + return 1; + + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_SERVER_INFO: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8 ? 1 : 0); + return 1; + + } + return 0; +} + +static const struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + NULL, /* quoter */ + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + NULL, /* check_liveness */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + zend_ulong cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void*)pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle (DBC)"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER)(intptr_t)(dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void*)cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ + if (strchr(dbh->data_source, '=')) { + SQLCHAR dsnbuf[1024]; + SQLSMALLINT dsnbuflen; + + use_direct = 1; + + /* Force UID and PWD to be set in the DSN */ + if (dbh->username && *dbh->username && !strstr(dbh->data_source, "uid") + && !strstr(dbh->data_source, "UID")) { + char *dsn; + spprintf(&dsn, 0, "%s;UID=%s;PWD=%s", dbh->data_source, dbh->username, dbh->password); + pefree((char*)dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + } + + rc = SQLDriverConnect(H->dbc, NULL, (SQLCHAR *) dbh->data_source, strlen(dbh->data_source), + dsnbuf, sizeof(dsnbuf)-1, &dsnbuflen, SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, (SQLCHAR *) dbh->data_source, SQL_NTS, (SQLCHAR *) dbh->username, SQL_NTS, (SQLCHAR *) dbh->password, SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_odbc_driver = { + PDO_DRIVER_HEADER(odbc), + pdo_odbc_handle_factory +}; + +#endif diff --git a/thirdparty/php80/pdo_odbc/odbc_stmt.c b/thirdparty/php80/pdo_odbc/odbc_stmt.c new file mode 100644 index 00000000000..12a1bb203c8 --- /dev/null +++ b/thirdparty/php80/pdo_odbc/odbc_stmt.c @@ -0,0 +1,895 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID < 80100 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" + +enum pdo_odbc_conv_result { + PDO_ODBC_CONV_NOT_REQUIRED, + PDO_ODBC_CONV_OK, + PDO_ODBC_CONV_FAIL +}; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SWORD sqltype) +{ + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2(pdo_stmt_t *stmt, int is_unicode, const char *buf, + zend_ulong buflen, zend_ulong *outlen) +{ +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR)S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, const char *buf, + zend_ulong buflen, zend_ulong *outlen) +{ +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + if (S->convbufsize <= ret) { + S->convbufsize = ret + 1; + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)buf, buflen/sizeof(WCHAR), S->convbuf, S->convbufsize, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + *outlen = ret; + S->convbuf[*outlen] = '\0'; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) +{ + if (S->cols) { + int i; + + for (i = 0; i < S->col_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + S->col_count = 0; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt) +{ + RETCODE rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER*)¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + zval *parameter; + + P = (pdo_odbc_param*)param->driver_data; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) != IS_RESOURCE) { + /* they passed in a string */ + zend_ulong ulen; + convert_to_string(parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + SQLPutData(S->stmt, Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter)); + break; + case PDO_ODBC_CONV_OK: + SQLPutData(S->stmt, S->convbuf, ulen); + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + SQLPutData(S->stmt, buf, len); + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (S->cols == NULL) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = S->col_count = (int)colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + RETCODE rc; + SWORD sqltype = 0, ctype = 0, scale = 0, nullable = 0; + SQLULEN precision = 0; + pdo_odbc_param *P; + zval *parameter; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: + { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno+1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_INT: + sqltype = SQL_INTEGER; + break; + case PDO_PARAM_LOB: + sqltype = SQL_LONGVARBINARY; + break; + default: + sqltype = SQL_LONGVARCHAR; + } + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2:1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, (SQLUSMALLINT) param->paramno+1, + P->paramtype, ctype, sqltype, precision, scale, + P->paramtype == SQL_PARAM_INPUT ? + (SQLPOINTER)param : + P->outbuf, + P->len, + &P->len + ); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (!Z_ISREF(param->parameter)) { + parameter = ¶m->parameter; + } else { + parameter = Z_REFVAL(param->parameter); + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) + amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + } else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(parameter); + if (P->outbuf) { + zend_ulong ulen; + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + + if (P->outbuf) { + zend_ulong ulen; + char *srcbuf; + zend_ulong srclen = 0; + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + ZVAL_NULL(parameter); + + if (P->len >= 0) { + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, P->outbuf, P->len, &ulen)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + srcbuf = P->outbuf; + srclen = P->len; + break; + case PDO_ODBC_CONV_OK: + srcbuf = S->convbuf; + srclen = ulen; + break; + } + + ZVAL_NEW_STR(parameter, zend_string_alloc(srclen, 0)); + memcpy(Z_STRVAL_P(parameter), srcbuf, srclen); + Z_STRVAL_P(parameter)[Z_STRLEN_P(parameter)] = '\0'; + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: odbcori = SQL_FETCH_NEXT; break; + case PDO_FETCH_ORI_PRIOR: odbcori = SQL_FETCH_PRIOR; break; + case PDO_FETCH_ORI_FIRST: odbcori = SQL_FETCH_FIRST; break; + case PDO_FETCH_ORI_LAST: odbcori = SQL_FETCH_LAST; break; + case PDO_FETCH_ORI_ABS: odbcori = SQL_FETCH_ABSOLUTE; break; + case PDO_FETCH_ORI_REL: odbcori = SQL_FETCH_RELATIVE; break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + RETCODE rc; + SWORD colnamelen; + SQLULEN colsize; + SQLLEN displaysize = 0; + + rc = SQLDescribeCol(S->stmt, colno+1, (SQLCHAR *) S->cols[colno].colname, + sizeof(S->cols[colno].colname)-1, &colnamelen, + &S->cols[colno].coltype, &colsize, NULL, NULL); + + /* This fixes a known issue with SQL Server and (max) lengths, + may affect others as well. If we are SQL_VARCHAR, + SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations) + and zero is returned from colsize then consider it long */ + if (0 == colsize && + (S->cols[colno].coltype == SQL_VARCHAR || + S->cols[colno].coltype == SQL_LONGVARCHAR || +#ifdef SQL_WVARCHAR + S->cols[colno].coltype == SQL_WVARCHAR || +#endif +#ifdef SQL_WLONGVARCHAR + S->cols[colno].coltype == SQL_WLONGVARCHAR || +#endif + S->cols[colno].coltype == SQL_VARBINARY || + S->cols[colno].coltype == SQL_LONGVARBINARY)) { + S->going_long = 1; + } + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno+1, + SQL_DESC_DISPLAY_SIZE, + NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* returning data as a string */ + col->param_type = PDO_PARAM_STR; + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < 256 && !S->going_long) { + S->cols[colno].data = emalloc(colsize+1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, colno+1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen+1, &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(256); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, zend_ulong *len, int *caller_frees) +{ + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + zend_ulong ulen; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; + zend_ulong used = 0; + char *buf; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * of 256 bytes; if there is more to be had, we then allocate + * bigger buffer for the caller to free */ + + rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, + 256, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < 256) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks + in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert + more or less NUL bytes at the end; we cater to that later, if actual length information is available + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read + for that size would be slower except maybe for extremely long columns.*/ + char *buf2; + + buf2 = emalloc(256); + buf = estrndup(C->data, 256); + used = 255; /* not 256; the driver NUL terminated the buffer */ + + do { + C->fetched_len = 0; + /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ + rc = SQLGetData(S->stmt, colno+1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len); + + /* adjust `used` in case we have length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + ZEND_ASSERT(fixed_used <= used + 1); + used = fixed_used; + } + + /* resize output buffer and reassemble block */ + if (rc==SQL_SUCCESS_WITH_INFO || (rc==SQL_SUCCESS && C->fetched_len > 255)) { + /* point 5, in section "Retrieving Data with SQLGetData" in http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx + states that if SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) + (if a driver fails to follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ + buf = erealloc(buf, used + 255+1); + memcpy(buf + used, buf2, 255); + used = used + 255; + } else if (rc==SQL_SUCCESS) { + buf = erealloc(buf, used + C->fetched_len+1); + memcpy(buf + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + buf[used] = '\0'; + + *ptr = buf; + *caller_frees = 1; + *len = used; + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + *ptr = NULL; + *len = 0; + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + *ptr = NULL; + *len = 0; + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + *ptr = C->data; + *len = C->fetched_len; + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + *ptr = NULL; + *len = 0; + return 1; + } + + unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, *ptr, *len, &ulen)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + + case PDO_ODBC_CONV_OK: + if (*caller_frees) { + efree(*ptr); + } + *ptr = emalloc(ulen + 1); + *len = ulen; + memcpy(*ptr, S->convbuf, ulen+1); + *caller_frees = 1; + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt) +{ + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = S->col_count = (int)colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +static int odbc_stmt_close_cursor(pdo_stmt_t *stmt) +{ + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt*)stmt->driver_data; + + rc = SQLCloseCursor(S->stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +const struct pdo_stmt_methods odbc_stmt_methods = { + odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, /* get attr */ + NULL, /* get column meta */ + odbc_stmt_next_rowset, + odbc_stmt_close_cursor +}; + +#endif diff --git a/thirdparty/php80/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php80/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 00000000000..e5777943dbf --- /dev/null +++ b/thirdparty/php80/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,183 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifdef PHP_WIN32 +# define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +# warning Please fix configure to give your ODBC libraries a name +# define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#ifdef HAVE_SQLCLI1_H +# include +# if defined(DB268K) && HAVE_LIBRARYMANAGER_H +# include +# endif +#endif + +#ifdef HAVE_ODBC_H +# include +#endif + +#ifdef HAVE_IODBC_H +# include +#endif + +#if defined(HAVE_SQLUNIX_H) && !defined(PHP_WIN32) +# include +#endif + +#ifdef HAVE_SQLTYPES_H +# include +#endif + +#ifdef HAVE_SQLUCODE_H +# include +#endif + +#ifdef HAVE_SQL_H +# include +#endif + +#ifdef HAVE_ISQL_H +# include +#endif + +#ifdef HAVE_SQLEXT_H +# include +#endif + +#ifdef HAVE_ISQLEXT_H +# include +#endif + +#ifdef HAVE_UDBCEXT_H +# include +#endif + +#ifdef HAVE_CLI0CORE_H +# include +#endif + +#ifdef HAVE_CLI0EXT1_H +# include +#endif + +#ifdef HAVE_CLI0CLI_H +# include +#endif + +#ifdef HAVE_CLI0DEFS_H +# include +#endif + +#ifdef HAVE_CLI0ENV_H +# include +#endif + +#ifdef HAVE_ODBCSDK_H +# include +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +# define PDO_ODBC_HENV SQLHANDLE +# define PDO_ODBC_HDBC SQLHANDLE +# define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +# define PDO_ODBC_HENV SQLHENV +# define PDO_ODBC_HDBC SQLHDBC +# define PDO_ODBC_HSTMT SQLHSTMT +#else +# define PDO_ODBC_HENV HENV +# define PDO_ODBC_HDBC HDBC +# define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SDWORD last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8:1; + unsigned _spare:31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + zend_ulong datalen; + SQLLEN fetched_len; + SWORD coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + zend_ulong convbufsize; + unsigned going_long:1; + unsigned assume_utf8:1; + signed col_count:16; + unsigned _spare:14; +} pdo_odbc_stmt; + +typedef struct { + SQLLEN len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode:1; + unsigned _spare:31; +} pdo_odbc_param; + +extern const pdo_driver_t pdo_odbc_driver; +extern const struct pdo_stmt_methods odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern zend_ulong pdo_odbc_pool_on; +extern zend_ulong pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; diff --git a/thirdparty/php80/pdo_pgsql/pgsql_driver.c b/thirdparty/php80/pdo_pgsql/pgsql_driver.c new file mode 100644 index 00000000000..34661423741 --- /dev/null +++ b/thirdparty/php80/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1275 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID < 80100 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/standard/php_string.h" +#include "main/php_network.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "pdo/php_pdo_error.h" +#include "ext/standard/file.h" +#undef SIZEOF_OFF_T +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "pgsql_driver_arginfo.h" + +static char * _pdo_pgsql_trim_message(const char *message, int persistent) +{ + register int i = strlen(message)-1; + char *tmp; + + if (i>1 && (message[i-1] == '\r' || message[i-1] == '\n') && message[i] == '.') { + --i; + } + while (i>0 && (message[i] == '\r' || message[i] == '\n')) { + --i; + } + ++i; + tmp = pemalloc(i + 1, persistent); + memcpy(tmp, message, i); + tmp[i] = '\0'; + + return tmp; +} + +static zend_string* _pdo_pgsql_escape_credentials(char *str) +{ + if (str) { + return php_addcslashes_str(str, strlen(str), "\\'", sizeof("\\'")); + } + + return NULL; +} + +int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_pgsql_error_info *einfo = &H->einfo; + char *errmsg = PQerrorMessage(H->server); + + einfo->errcode = errcode; + einfo->file = file; + einfo->line = line; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + + if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) { + strcpy(*pdo_err, "HY000"); + } + else { + strcpy(*pdo_err, sqlstate); + } + + if (msg) { + einfo->errmsg = pestrdup(msg, dbh->is_persistent); + } + else if (errmsg) { + einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent); + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return errcode; +} +/* }}} */ + +static void _pdo_pgsql_notice(pdo_dbh_t *dbh, const char *message) /* {{{ */ +{ +/* pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */ +} +/* }}} */ + +static int pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + } else { + add_next_index_null(info); + } + if (einfo->errmsg) { + add_next_index_string(info, einfo->errmsg); + } + + return 1; +} +/* }}} */ + +/* {{{ pdo_pgsql_create_lob_stream */ +static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + return lo_write(self->conn, self->lfd, (char*)buf, count); +} + +static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count) +{ + 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) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + zval_ptr_dtor(&self->dbh); + efree(self); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream) +{ + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, zend_off_t offset, int whence, + zend_off_t *newoffset) +{ + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self*)stream->abstract; +#if defined(HAVE_PG_LO64) && defined(ZEND_ENABLE_ZVAL_LONG64) + zend_off_t pos = lo_lseek64(self->conn, self->lfd, offset, whence); +#else + zend_off_t pos = lo_lseek(self->conn, self->lfd, offset, whence); +#endif + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +const 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(zval *dbh, int lfd, Oid oid) +{ + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)(Z_PDO_DBH_P(dbh))->driver_data; + + ZVAL_COPY_VALUE(&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) { + Z_ADDREF_P(dbh); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + +static int pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (H) { + if (H->server) { + PQfinish(H->server); + H->server = NULL; + } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } + return 0; +} +/* }}} */ + +static int pgsql_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); + int scrollable; + int ret; + char *nsql = NULL; + size_t nsql_len = 0; + int emulate = 0; + int execute_only = 0; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_pgsql_stmt_methods; + + scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, + PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL; + + if (scrollable) { + if (S->cursor_name) { + efree(S->cursor_name); + } + spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter); + emulate = 1; + } else if (driver_options) { + if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) { + emulate = 1; + } + if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) { + execute_only = 1; + } + } else { + emulate = H->disable_native_prepares || H->emulate_prepares; + execute_only = H->disable_prepares; + } + + if (!emulate && PQprotocolVersion(H->server) <= 2) { + emulate = 1; + } + + if (emulate) { + stmt->supports_placeholders = PDO_PLACEHOLDER_NONE; + } else { + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + stmt->named_rewrite_template = "$%d"; + } + + ret = pdo_parse_params(stmt, (char*)sql, sql_len, &nsql, &nsql_len); + + if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + return 0; + } else if (ret == 1) { + /* query was re-written */ + S->query = nsql; + } else { + S->query = estrdup(sql); + } + + if (!emulate && !execute_only) { + /* prepared query: set the query name and defer the + actual prepare until the first execute call */ + spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter); + } + + return 1; +} + +static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + zend_long ret = 1; + ExecStatusType qs; + + if (!(res = PQexec(H->server, sql))) { + /* fatal error */ + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + return -1; + } + qs = PQresultStatus(res); + if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) { + pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res)); + PQclear(res); + return -1; + } + H->pgoid = PQoidValue(res); + if (qs == PGRES_COMMAND_OK) { + ZEND_ATOL(ret, PQcmdTuples(res)); + } else { + ret = Z_L(0); + } + PQclear(res); + + return ret; +} + +static int pgsql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type paramtype) +{ + unsigned char *escaped; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + size_t tmp_len; + + switch (paramtype) { + case PDO_PARAM_LOB: + /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + escaped = PQescapeByteaConn(H->server, (unsigned char *)unquoted, unquotedlen, &tmp_len); + *quotedlen = tmp_len + 1; + *quoted = emalloc(*quotedlen + 1); + memcpy((*quoted)+1, escaped, *quotedlen-2); + (*quoted)[0] = '\''; + (*quoted)[*quotedlen-1] = '\''; + (*quoted)[*quotedlen] = '\0'; + PQfreemem(escaped); + break; + default: + *quoted = safe_emalloc(2, unquotedlen, 3); + (*quoted)[0] = '\''; + *quotedlen = PQescapeStringConn(H->server, *quoted + 1, unquoted, unquotedlen, NULL); + (*quoted)[*quotedlen + 1] = '\''; + (*quoted)[*quotedlen + 2] = '\0'; + *quotedlen += 2; + } + return 1; +} + +static char *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const char *name, size_t *len) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + char *id = NULL; + PGresult *res; + ExecStatusType status; + + if (name == NULL) { + res = PQexec(H->server, "SELECT LASTVAL()"); + } else { + const char *q[1]; + q[0] = name; + + res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0); + } + status = PQresultStatus(res); + + if (res && (status == PGRES_TUPLES_OK)) { + id = estrdup((char *)PQgetvalue(res, 0, 0)); + *len = PQgetlength(res, 0, 0); + } else { + pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); + } + + if (res) { + PQclear(res); + } + + return id; +} + +void pdo_libpq_version(char *buf, size_t len) +{ + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + ZVAL_BOOL(return_value, H->emulate_prepares); + break; + + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + ZVAL_BOOL(return_value, H->disable_prepares); + break; + + case PDO_ATTR_CLIENT_VERSION: { + char buf[16]; + pdo_libpq_version(buf, sizeof(buf)); + ZVAL_STRING(return_value, buf); + break; + } + + case PDO_ATTR_SERVER_VERSION: + if (PQprotocolVersion(H->server) >= 3) { /* PostgreSQL 7.4 or later */ + ZVAL_STRING(return_value, (char*)PQparameterStatus(H->server, "server_version")); + } else /* emulate above via a query */ + { + PGresult *res = PQexec(H->server, "SELECT VERSION()"); + if (res && PQresultStatus(res) == PGRES_TUPLES_OK) { + ZVAL_STRING(return_value, (char *)PQgetvalue(res, 0, 0)); + } + + if (res) { + PQclear(res); + } + } + break; + + case PDO_ATTR_CONNECTION_STATUS: + switch (PQstatus(H->server)) { + case CONNECTION_STARTED: + ZVAL_STRINGL(return_value, "Waiting for connection to be made.", sizeof("Waiting for connection to be made.")-1); + break; + + case CONNECTION_MADE: + case CONNECTION_OK: + ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", sizeof("Connection OK; waiting to send.")-1); + break; + + case CONNECTION_AWAITING_RESPONSE: + ZVAL_STRINGL(return_value, "Waiting for a response from the server.", sizeof("Waiting for a response from the server.")-1); + break; + + case CONNECTION_AUTH_OK: + ZVAL_STRINGL(return_value, "Received authentication; waiting for backend start-up to finish.", sizeof("Received authentication; waiting for backend start-up to finish.")-1); + break; +#ifdef CONNECTION_SSL_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", sizeof("Negotiating SSL encryption.")-1); + break; +#endif + case CONNECTION_SETENV: + ZVAL_STRINGL(return_value, "Negotiating environment-driven parameter settings.", sizeof("Negotiating environment-driven parameter settings.")-1); + break; + + case CONNECTION_BAD: + default: + ZVAL_STRINGL(return_value, "Bad connection.", sizeof("Bad connection.")-1); + break; + } + break; + + case PDO_ATTR_SERVER_INFO: { + int spid = PQbackendPID(H->server); + + + zend_string *str_info = + strpprintf(0, + "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s", + spid, + (char*)PQparameterStatus(H->server, "client_encoding"), + (char*)PQparameterStatus(H->server, "is_superuser"), + (char*)PQparameterStatus(H->server, "session_authorization"), + (char*)PQparameterStatus(H->server, "DateStyle")); + + ZVAL_STR(return_value, str_info); + break; + } + + default: + return 0; + } + + return 1; +} + +/* {{{ */ +static int pdo_pgsql_check_liveness(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + if (!PQconsumeInput(H->server) || PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + +static int pgsql_handle_in_transaction(pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + return PQtransactionStatus(H->server) > PQTRANS_IDLE; +} + +static int pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh) +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + PGresult *res; + int ret = 1; + + res = PQexec(H->server, cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res)); + ret = 0; + } + + PQclear(res); + return ret; +} + +static int pgsql_handle_begin(pdo_dbh_t *dbh) +{ + return pdo_pgsql_transaction_cmd("BEGIN", dbh); +} + +static int pgsql_handle_commit(pdo_dbh_t *dbh) +{ + int ret = pdo_pgsql_transaction_cmd("COMMIT", dbh); + + /* When deferred constraints are used the commit could + fail, and a ROLLBACK implicitly ran. See bug #67462 */ + if (!ret) { + dbh->in_txn = pgsql_handle_in_transaction(dbh); + } + + return ret; +} + +static int pgsql_handle_rollback(pdo_dbh_t *dbh) +{ + return pdo_pgsql_transaction_cmd("ROLLBACK", dbh); +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + zval *pg_rows; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sa|sss!", + &table_name, &table_name_len, &pg_rows, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) { + zend_argument_value_error(2, "cannot be empty"); + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + /* Obtain db Handle */ + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + query = NULL; + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + int command_failed = 0; + size_t buffer_len = 0; + zval *tmp; + + PQclear(pgsql_result); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + size_t query_len; + if (!try_convert_to_string(tmp)) { + efree(query); + RETURN_THROWS(); + } + + if (buffer_len < Z_STRLEN_P(tmp)) { + buffer_len = Z_STRLEN_P(tmp); + query = erealloc(query, buffer_len + 2); /* room for \n\0 */ + } + memcpy(query, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp)); + query_len = Z_STRLEN_P(tmp); + if (query[query_len - 1] != '\n') { + query[query_len++] = '\n'; + } + query[query_len] = '\0'; + if (PQputCopyData(H->server, query, query_len) != 1) { + efree(query); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } ZEND_HASH_FOREACH_END(); + if (query) { + efree(query); + } + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + PGresult *pgsql_result; + ExecStatusType status; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + /* Obtain db Handler */ + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + stream = php_stream_open_wrapper_ex(filename, "rb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + char *buf; + int command_failed = 0; + size_t line_len = 0; + + PQclear(pgsql_result); + while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) { + if (PQputCopyData(H->server, buf, line_len) != 1) { + efree(buf); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + efree(buf); + } + php_stream_close(stream); + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sp|sss!", + &table_name, &table_name_len, &filename, &filename_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + stream = php_stream_open_wrapper_ex(filename, "wb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + + if (ret == -1) { + break; /* done */ + } else if (ret > 0) { + if (php_stream_write(stream, csv, ret) != (size_t)ret) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to write to file"); + PQfreemem(csv); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } else { + PQfreemem(csv); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + php_stream_close(stream); + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + RETURN_TRUE; + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|sss!", + &table_name, &table_name_len, + &pg_delim, &pg_delim_len, &pg_null_as, &pg_null_as_len, &pg_fields, &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, 0, "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, pg_fields, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, 0, "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", table_name, (pg_delim_len ? *pg_delim : '\t'), (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + array_init(return_value); + + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + if (ret == -1) { + break; /* copy done */ + } else if (ret > 0) { + add_next_index_stringl(return_value, csv, ret); + PQfreemem(csv); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} +/* }}} */ + + +/* {{{ Creates a new large object, returning its identifier. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + lfd = lo_creat(H->server, INV_READ|INV_WRITE); + + if (lfd != InvalidOid) { + zend_string *buf = strpprintf(0, ZEND_ULONG_FMT, (zend_long) lfd); + + RETURN_STR(buf); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Opens an existing large object stream. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + size_t oidstrlen; + char *modestr = "rb"; + size_t modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", + &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_THROWS(); + } + + 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 = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + 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(ZEND_THIS, lfd, oid); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Deletes the large object identified by oid. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + size_t oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s", + &oidstr, &oidlen)) { + RETURN_THROWS(); + } + + oid = (Oid)strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ Get asynchronous notification */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetNotify) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + zend_long result_type = PDO_FETCH_USE_DEFAULT; + zend_long ms_timeout = 0; + PGnotify *pgsql_notify; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", + &result_type, &ms_timeout)) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + if (result_type == PDO_FETCH_USE_DEFAULT) { + result_type = dbh->default_fetch_type; + } + + if (result_type != PDO_FETCH_BOTH && result_type != PDO_FETCH_ASSOC && result_type != PDO_FETCH_NUM) { + zend_argument_value_error(1, "must be one of PDO::FETCH_BOTH, PDO::FETCH_ASSOC, or PDO::FETCH_NUM"); + RETURN_THROWS(); + } + + if (ms_timeout < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); +#ifdef ZEND_ENABLE_ZVAL_LONG64 + } else if (ms_timeout > INT_MAX) { + php_error_docref(NULL, E_WARNING, "Timeout was shrunk to %d", INT_MAX); + ms_timeout = INT_MAX; +#endif + } + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + + if (ms_timeout && !pgsql_notify) { + php_pollfd_for_ms(PQsocket(H->server), PHP_POLLREADABLE, (int)ms_timeout); + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + } + + if (!pgsql_notify) { + RETURN_FALSE; + } + + array_init(return_value); + if (result_type == PDO_FETCH_NUM || result_type == PDO_FETCH_BOTH) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_index_string(return_value, 2, pgsql_notify->extra); + } + } + if (result_type == PDO_FETCH_ASSOC || result_type == PDO_FETCH_BOTH) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_assoc_string(return_value, "payload", pgsql_notify->extra); + } + } + + PQfreemem(pgsql_notify); +} +/* }}} */ + +/* {{{ Get backend(server) pid */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) +{ + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + RETURN_LONG(PQbackendPID(H->server)); +} +/* }}} */ + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) +{ + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_PGSql_Ext_methods; + default: + return NULL; + } +} + +static int pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + zend_bool bval = zval_get_long(val)? 1 : 0; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + H->emulate_prepares = bval; + return 1; + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + H->disable_prepares = bval; + return 1; + default: + return 0; + } +} + +static const struct pdo_dbh_methods pgsql_methods = { + pgsql_handle_closer, + pgsql_handle_preparer, + pgsql_handle_doer, + pgsql_handle_quoter, + pgsql_handle_begin, + pgsql_handle_commit, + pgsql_handle_rollback, + pdo_pgsql_set_attr, + pdo_pgsql_last_insert_id, + pdo_pgsql_fetch_error_func, + pdo_pgsql_get_attribute, + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods, /* get_driver_methods */ + NULL, + pgsql_handle_in_transaction, +}; + +static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_pgsql_db_handle *H; + int ret = 0; + char *conn_str, *p, *e; + zend_string *tmp_user, *tmp_pass; + zend_long connect_timeout = 30; + + H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = + 1 << PDO_PARAM_EVT_EXEC_POST | + 1 << PDO_PARAM_EVT_FETCH_PRE | + 1 << PDO_PARAM_EVT_FETCH_POST; + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + + /* PostgreSQL wants params in the connect string to be separated by spaces, + * if the PDO standard semicolons are used, we convert them to spaces + */ + e = (char *) dbh->data_source + strlen(dbh->data_source); + p = (char *) dbh->data_source; + while ((p = memchr(p, ';', (e - p)))) { + *p = ' '; + } + + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30); + } + + /* escape username and password, if provided */ + tmp_user = _pdo_pgsql_escape_credentials(dbh->username); + tmp_pass = _pdo_pgsql_escape_credentials(dbh->password); + + /* support both full connection string & connection string + login and/or password */ + if (tmp_user && tmp_pass) { + spprintf(&conn_str, 0, "%s user='%s' password='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_user), ZSTR_VAL(tmp_pass), connect_timeout); + } else if (tmp_user) { + spprintf(&conn_str, 0, "%s user='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_user), connect_timeout); + } else if (tmp_pass) { + spprintf(&conn_str, 0, "%s password='%s' connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, ZSTR_VAL(tmp_pass), connect_timeout); + } else { + spprintf(&conn_str, 0, "%s connect_timeout=" ZEND_LONG_FMT, (char *) dbh->data_source, connect_timeout); + } + + H->server = PQconnectdb(conn_str); + + if (tmp_user) { + zend_string_release_ex(tmp_user, 0); + } + if (tmp_pass) { + zend_string_release_ex(tmp_pass, 0); + } + + efree(conn_str); + + if (PQstatus(H->server) != CONNECTION_OK) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); + goto cleanup; + } + + PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)&dbh); + + H->attached = 1; + H->pgoid = -1; + + dbh->methods = &pgsql_methods; + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &pgsql_methods; + if (!ret) { + pgsql_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_pgsql_driver = { + PDO_DRIVER_HEADER(pgsql), + pdo_pgsql_handle_factory +}; +#endif diff --git a/thirdparty/php80/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php80/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 00000000000..d8d33450af8 --- /dev/null +++ b/thirdparty/php80/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,71 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 82be78f03c363afd795c59bcbb061ef0b2f9d08c */ + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 0, 2) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, 0, 0, 2) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_USE_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlGetPid arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate + + +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); + + +static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBCreate, arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBOpen, arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php80/pdo_pgsql/pgsql_statement.c b/thirdparty/php80/pdo_pgsql/pgsql_statement.c new file mode 100644 index 00000000000..6bb17aaad4f --- /dev/null +++ b/thirdparty/php80/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,727 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID < 80100 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "php_pdo_pgsql_int.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* from postgresql/src/include/catalog/pg_type.h */ +#define BOOLLABEL "bool" +#define BOOLOID 16 +#define BYTEALABEL "bytea" +#define BYTEAOID 17 +#define DATELABEL "date" +#define DATEOID 1082 +#define INT2LABEL "int2" +#define INT2OID 21 +#define INT4LABEL "int4" +#define INT4OID 23 +#define INT8LABEL "int8" +#define INT8OID 20 +#define OIDOID 26 +#define TEXTLABEL "text" +#define TEXTOID 25 +#define TIMESTAMPLABEL "timestamp" +#define TIMESTAMPOID 1114 +#define VARCHARLABEL "varchar" +#define VARCHAROID 1043 + + + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + zend_bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle) + && IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) + && !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->stmt_name) { + if (S->is_prepared && server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + 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; + } + if (S->param_lengths) { + efree(S->param_lengths); + S->param_lengths = NULL; + } + if (S->param_values) { + efree(S->param_values); + S->param_values = NULL; + } + if (S->param_formats) { + 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; + } + + if (S->cursor_name) { + if (server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + res = PQexec(H->server, q); + efree(q); + if (res) PQclear(res); + } + efree(S->cursor_name); + S->cursor_name = NULL; + } + + if(S->cols) { + efree(S->cols); + S->cols = NULL; + } + efree(S); + stmt->driver_data = NULL; + return 1; +} + +static int pgsql_stmt_execute(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + pdo_pgsql_db_handle *H = S->H; + ExecStatusType status; + + /* ensure that we free any previous unfetched results */ + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->current_row = 0; + + if (S->cursor_name) { + char *q = NULL; + + if (S->is_prepared) { + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + PQclear(PQexec(H->server, q)); + efree(q); + } + + spprintf(&q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, stmt->active_query_string); + S->result = PQexec(H->server, q); + efree(q); + + /* check if declare failed */ + status = PQresultStatus(S->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + PQclear(S->result); + + /* the cursor was declared correctly */ + S->is_prepared = 1; + + /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ + spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); + S->result = PQexec(H->server, q); + efree(q); + } else 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 chance 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 (sqlstate && !strcmp(sqlstate, "42P05")) { + char buf[100]; /* stmt_name == "pdo_crsr_%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, + S->param_formats, + 0); + } else if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED) { + /* execute query with parameters */ + S->result = PQexecParams(H->server, S->query, + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char**)S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + /* execute plain query (with embedded parameters) */ + S->result = PQexec(H->server, stmt->active_query_string); + } + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (!stmt->executed && (!stmt->column_count || S->cols == NULL)) { + stmt->column_count = (int) PQnfields(S->result); + S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column)); + } + + if (status == PGRES_COMMAND_OK) { + ZEND_ATOL(stmt->row_count, PQcmdTuples(S->result)); + H->pgoid = PQoidValue(S->result); + } else { + stmt->row_count = (zend_long)PQntuples(S->result); + } + + return 1; +} + +static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED && param->is_param) { + switch (event_type) { + 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 (ZSTR_VAL(param->name)[0] == '$') { + ZEND_ATOL(param->paramno, ZSTR_VAL(param->name) + 1); + } else { + /* resolve parameter name to rewritten name */ + char *namevar; + + if (stmt->bound_param_map && (namevar = zend_hash_find_ptr(stmt->bound_param_map, + param->name)) != NULL) { + ZEND_ATOL(param->paramno, namevar + 1); + param->paramno--; + } else { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", ZSTR_VAL(param->name)); + return 0; + } + } + } + break; + + case PDO_PARAM_EVT_ALLOC: + if (!stmt->bound_param_map) { + return 1; + } + if (!zend_hash_index_exists(stmt->bound_param_map, param->paramno)) { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", "parameter was not defined"); + return 0; + } + 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 1; + } + if (!S->param_values) { + S->param_values = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(char*)); + S->param_lengths = ecalloc( + zend_hash_num_elements(stmt->bound_param_map), + sizeof(int)); + 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) { + zval *parameter; + + /* + if (param->paramno >= zend_hash_num_elements(stmt->bound_params)) { + pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); + return 0; + } + */ + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && + Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, 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 { + zend_string *str = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + if (str != NULL) { + //??SEPARATE_ZVAL_IF_NOT_REF(¶m->parameter); + ZVAL_STR(parameter, str); + } else { + ZVAL_EMPTY_STRING(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(parameter) == IS_NULL) { + S->param_values[param->paramno] = NULL; + S->param_lengths[param->paramno] = 0; + } else if (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE) { + S->param_values[param->paramno] = Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f"; + S->param_lengths[param->paramno] = 1; + S->param_formats[param->paramno] = 0; + } else { + //SEPARATE_ZVAL_IF_NOT_REF(¶m->parameter); + convert_to_string_ex(parameter); + S->param_values[param->paramno] = Z_STRVAL_P(parameter); + S->param_lengths[param->paramno] = Z_STRLEN_P(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; + } + } else if (param->is_param && event_type == PDO_PARAM_EVT_NORMALIZE) { + /* We need to manually convert to a pg native boolean value */ + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) { + const char *s = zend_is_true(¶m->parameter) ? "t" : "f"; + param->param_type = PDO_PARAM_STR; + zval_ptr_dtor(¶m->parameter); + ZVAL_STRINGL(¶m->parameter, s, 1); + } + } + return 1; +} + +static int pgsql_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (S->cursor_name) { + char *ori_str = NULL; + char *q = NULL; + ExecStatusType status; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: spprintf(&ori_str, 0, "NEXT"); break; + case PDO_FETCH_ORI_PRIOR: spprintf(&ori_str, 0, "BACKWARD"); break; + case PDO_FETCH_ORI_FIRST: spprintf(&ori_str, 0, "FIRST"); break; + case PDO_FETCH_ORI_LAST: spprintf(&ori_str, 0, "LAST"); break; + case PDO_FETCH_ORI_ABS: spprintf(&ori_str, 0, "ABSOLUTE " ZEND_LONG_FMT, offset); break; + case PDO_FETCH_ORI_REL: spprintf(&ori_str, 0, "RELATIVE " ZEND_LONG_FMT, offset); break; + default: + return 0; + } + + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name); + efree(ori_str); + S->result = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (PQntuples(S->result)) { + S->current_row = 1; + return 1; + } else { + return 0; + } + } else { + if (S->current_row < stmt->row_count) { + S->current_row++; + return 1; + } else { + return 0; + } + } +} + +static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + struct pdo_bound_param_data *param; + char *str; + + if (!S->result) { + return 0; + } + + str = PQfname(S->result, colno); + cols[colno].name = zend_string_init(str, strlen(str), 0); + cols[colno].maxlen = PQfsize(S->result, colno); + cols[colno].precision = PQfmod(S->result, colno); + S->cols[colno].pgsql_type = PQftype(S->result, colno); + + switch (S->cols[colno].pgsql_type) { + + 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 && ( + (param = zend_hash_index_find_ptr(stmt->bound_columns, colno)) != NULL || + (param = zend_hash_find_ptr(stmt->bound_columns, cols[colno].name)) != NULL)) { + + 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: + cols[colno].param_type = PDO_PARAM_INT; + break; + + case INT8OID: + if (sizeof(zend_long)>=8) { + cols[colno].param_type = PDO_PARAM_INT; + } else { + cols[colno].param_type = PDO_PARAM_STR; + } + break; + + case BYTEAOID: + cols[colno].param_type = PDO_PARAM_LOB; + break; + + default: + cols[colno].param_type = PDO_PARAM_STR; + } + + return 1; +} + +static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, size_t *len, int *caller_frees ) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + size_t tmp_len; + + if (!S->result) { + return 0; + } + + /* We have already increased count by 1 in pgsql_stmt_fetch() */ + if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */ + *ptr = NULL; + *len = 0; + } else { + *ptr = PQgetvalue(S->result, S->current_row - 1, colno); + *len = PQgetlength(S->result, S->current_row - 1, colno); + + switch (cols[colno].param_type) { + + case PDO_PARAM_INT: + ZEND_ATOL(S->cols[colno].intval, *ptr); + *ptr = (char *) &(S->cols[colno].intval); + *len = sizeof(zend_long); + break; + + case PDO_PARAM_BOOL: + S->cols[colno].boolval = **ptr == 't'; + *ptr = (char *) &(S->cols[colno].boolval); + *len = sizeof(zend_bool); + break; + + case PDO_PARAM_LOB: + 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->database_object_handle, loid, oid); + *len = 0; + return *ptr ? 1 : 0; + } + *ptr = NULL; + *len = 0; + return 0; + } else { + char *tmp_ptr = (char *)PQunescapeBytea((unsigned char *)*ptr, &tmp_len); + if (!tmp_ptr) { + /* PQunescapeBytea returned an error */ + *len = 0; + return 0; + } + if (!tmp_len) { + /* Empty string, return as empty stream */ + *ptr = (char *)php_stream_memory_open(TEMP_STREAM_READONLY, "", 0); + PQfreemem(tmp_ptr); + *len = 0; + } else { + *ptr = estrndup(tmp_ptr, tmp_len); + PQfreemem(tmp_ptr); + *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; + } + } + + return 1; +} + +static zend_always_inline char * pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) +{ + char *table_name = NULL; + PGresult *tmp_res; + char *querystr = NULL; + + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); + + if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + efree(querystr); + return 0; + } + efree(querystr); + + if (1 == PQgetisnull(tmp_res, 0, 0) || (table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + return 0; + } + + table_name = estrdup(table_name); + + PQclear(tmp_res); + return table_name; +} + +static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + PGresult *res; + char *q=NULL; + ExecStatusType status; + Oid table_oid; + char *table_name=NULL; + + if (!S->result) { + return FAILURE; + } + + if (colno >= stmt->column_count) { + return FAILURE; + } + + array_init(return_value); + add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type); + + table_oid = PQftable(S->result, colno); + add_assoc_long(return_value, "pgsql:table_oid", table_oid); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + if (table_name) { + add_assoc_string(return_value, "table", table_name); + efree(table_name); + } + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + add_assoc_string(return_value, "native_type", BOOLLABEL); + break; + case BYTEAOID: + add_assoc_string(return_value, "native_type", BYTEALABEL); + break; + case INT8OID: + add_assoc_string(return_value, "native_type", INT8LABEL); + break; + case INT2OID: + add_assoc_string(return_value, "native_type", INT2LABEL); + break; + case INT4OID: + add_assoc_string(return_value, "native_type", INT4LABEL); + break; + case TEXTOID: + add_assoc_string(return_value, "native_type", TEXTLABEL); + break; + case VARCHAROID: + add_assoc_string(return_value, "native_type", VARCHARLABEL); + break; + case DATEOID: + add_assoc_string(return_value, "native_type", DATELABEL); + break; + case TIMESTAMPOID: + add_assoc_string(return_value, "native_type", TIMESTAMPLABEL); + break; + default: + /* Fetch metadata from Postgres system catalogue */ + spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); + res = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(res); + if (status == PGRES_TUPLES_OK && 1 == PQntuples(res)) { + add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0)); + } + PQclear(res); + } + return 1; +} + +static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + + if (S->cols != NULL){ + efree(S->cols); + S->cols = NULL; + } + return 1; +} + +const struct pdo_stmt_methods swoole_pgsql_stmt_methods = { + pgsql_stmt_dtor, + pgsql_stmt_execute, + pgsql_stmt_fetch, + pgsql_stmt_describe, + pgsql_stmt_get_col, + pgsql_stmt_param_hook, + NULL, /* set_attr */ + NULL, /* get_attr */ + pgsql_stmt_get_column_meta, + NULL, /* next_rowset */ + pdo_pgsql_stmt_cursor_closer +}; +#endif diff --git a/thirdparty/php80/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php80/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 00000000000..dc97c3ea3c3 --- /dev/null +++ b/thirdparty/php80/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,111 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_INT_H +#define PHP_PDO_PGSQL_INT_H + +#include +#include +#include + +#define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_pgsql_error_info; + +/* stuff we use in a pgsql database handle */ +typedef struct { + PGconn *server; + unsigned attached:1; + unsigned _reserved:31; + pdo_pgsql_error_info einfo; + Oid pgoid; + unsigned int stmt_counter; + /* The following two variables have the same purpose. Unfortunately we need + to keep track of two different attributes having the same effect. */ + zend_bool emulate_prepares; + zend_bool disable_native_prepares; /* deprecated since 5.6 */ + zend_bool disable_prepares; +} pdo_pgsql_db_handle; + +typedef struct { + char *def; + zend_long intval; + Oid pgsql_type; + zend_bool boolval; +} pdo_pgsql_column; + +typedef struct { + pdo_pgsql_db_handle *H; + PGresult *result; + pdo_pgsql_column *cols; + char *cursor_name; + char *stmt_name; + char *query; + char **param_values; + int *param_lengths; + int *param_formats; + Oid *param_types; + int current_row; + zend_bool is_prepared; +} pdo_pgsql_stmt; + +typedef struct { + Oid oid; +} pdo_pgsql_bound_param; + +extern int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); +#define pdo_pgsql_error(d,e,z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_msg(d,e,m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt(s,e,z) _pdo_pgsql_error(s->dbh, s, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt_msg(stmt, e, sqlstate, msg) \ + _pdo_pgsql_error(stmt->dbh, stmt, e, sqlstate, msg, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_pgsql_stmt_methods; + +#define pdo_pgsql_sqlstate(r) PQresultErrorField(r, PG_DIAG_SQLSTATE) + +enum { + PDO_PGSQL_ATTR_DISABLE_PREPARES = PDO_ATTR_DRIVER_SPECIFIC, +}; + +struct pdo_pgsql_lob_self { + zval dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + +enum pdo_pgsql_specific_constants { + PGSQL_TRANSACTION_IDLE = PQTRANS_IDLE, + PGSQL_TRANSACTION_ACTIVE = PQTRANS_ACTIVE, + PGSQL_TRANSACTION_INTRANS = PQTRANS_INTRANS, + PGSQL_TRANSACTION_INERROR = PQTRANS_INERROR, + PGSQL_TRANSACTION_UNKNOWN = PQTRANS_UNKNOWN +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid); +extern const php_stream_ops pdo_pgsql_lob_stream_ops; + +void pdo_libpq_version(char *buf, size_t len); + +#endif /* PHP_PDO_PGSQL_INT_H */ diff --git a/thirdparty/php80/pdo_sqlite/php_pdo_sqlite_int.h b/thirdparty/php80/pdo_sqlite/php_pdo_sqlite_int.h new file mode 100644 index 00000000000..b7da23d16d6 --- /dev/null +++ b/thirdparty/php80/pdo_sqlite/php_pdo_sqlite_int.h @@ -0,0 +1,80 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_SQLITE_INT_H +#define PHP_PDO_SQLITE_INT_H + +#include + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_sqlite_error_info; + +struct pdo_sqlite_fci { + zend_fcall_info fci; + zend_fcall_info_cache fcc; +}; + +struct pdo_sqlite_func { + struct pdo_sqlite_func *next; + + zval func, step, fini; + int argc; + const char *funcname; + + /* accelerated callback references */ + struct pdo_sqlite_fci afunc, astep, afini; +}; + +struct pdo_sqlite_collation { + struct pdo_sqlite_collation *next; + + const char *name; + zval callback; + struct pdo_sqlite_fci fc; +}; + +typedef struct { + sqlite3 *db; + pdo_sqlite_error_info einfo; + struct pdo_sqlite_func *funcs; + struct pdo_sqlite_collation *collations; +} pdo_sqlite_db_handle; + +typedef struct { + pdo_sqlite_db_handle *H; + sqlite3_stmt *stmt; + unsigned pre_fetched:1; + unsigned done:1; +} pdo_sqlite_stmt; + + +extern int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); +#define pdo_sqlite_error(s) _pdo_sqlite_error(s, NULL, __FILE__, __LINE__) +#define pdo_sqlite_error_stmt(s) _pdo_sqlite_error(stmt->dbh, stmt, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_sqlite_stmt_methods; + +enum { + PDO_SQLITE_ATTR_OPEN_FLAGS = PDO_ATTR_DRIVER_SPECIFIC, + PDO_SQLITE_ATTR_READONLY_STATEMENT, + PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES +}; + +#endif diff --git a/thirdparty/php80/pdo_sqlite/sqlite_driver.c b/thirdparty/php80/pdo_sqlite/sqlite_driver.c new file mode 100644 index 00000000000..21d66d0f7d3 --- /dev/null +++ b/thirdparty/php80/pdo_sqlite/sqlite_driver.c @@ -0,0 +1,827 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" + +#if PHP_VERSION_ID < 80100 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "zend_exceptions.h" +#include "sqlite_driver_arginfo.h" + +int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_sqlite_error_info *einfo = &H->einfo; + + einfo->errcode = sqlite3_errcode(H->db); + einfo->file = file; + einfo->line = line; + + if (einfo->errcode != SQLITE_OK) { + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + einfo->errmsg = pestrdup((char*)sqlite3_errmsg(H->db), dbh->is_persistent); + } else { /* no error */ + strncpy(*pdo_err, PDO_ERR_NONE, sizeof(*pdo_err)); + return 0; + } + switch (einfo->errcode) { + case SQLITE_NOTFOUND: + strncpy(*pdo_err, "42S02", sizeof(*pdo_err)); + break; + + case SQLITE_INTERRUPT: + strncpy(*pdo_err, "01002", sizeof(*pdo_err)); + break; + + case SQLITE_NOLFS: + strncpy(*pdo_err, "HYC00", sizeof(*pdo_err)); + break; + + case SQLITE_TOOBIG: + strncpy(*pdo_err, "22001", sizeof(*pdo_err)); + break; + + case SQLITE_CONSTRAINT: + strncpy(*pdo_err, "23000", sizeof(*pdo_err)); + break; + + case SQLITE_ERROR: + default: + strncpy(*pdo_err, "HY000", sizeof(*pdo_err)); + break; + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return einfo->errcode; +} +/* }}} */ + +static int pdo_sqlite_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + pdo_sqlite_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } + + return 1; +} + +static void pdo_sqlite_cleanup_callbacks(pdo_sqlite_db_handle *H) +{ + struct pdo_sqlite_func *func; + + while (H->funcs) { + func = H->funcs; + H->funcs = func->next; + + if (H->db) { + /* delete the function from the handle */ + sqlite3_create_function(H->db, + func->funcname, + func->argc, + SQLITE_UTF8, + func, + NULL, NULL, NULL); + } + + efree((char*)func->funcname); + if (!Z_ISUNDEF(func->func)) { + zval_ptr_dtor(&func->func); + } + if (!Z_ISUNDEF(func->step)) { + zval_ptr_dtor(&func->step); + } + if (!Z_ISUNDEF(func->fini)) { + zval_ptr_dtor(&func->fini); + } + efree(func); + } + + while (H->collations) { + struct pdo_sqlite_collation *collation; + collation = H->collations; + H->collations = collation->next; + + if (H->db) { + /* delete the collation from the handle */ + sqlite3_create_collation(H->db, + collation->name, + SQLITE_UTF8, + collation, + NULL); + } + + efree((char*)collation->name); + if (!Z_ISUNDEF(collation->callback)) { + zval_ptr_dtor(&collation->callback); + } + efree(collation); + } +} + +static int sqlite_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + if (H) { + pdo_sqlite_error_info *einfo = &H->einfo; + + pdo_sqlite_cleanup_callbacks(H); + if (H->db) { +#ifdef HAVE_SW_SQLITE3_CLOSE_V2 + sqlite3_close_v2(H->db); +#else + sqlite3_close(H->db); +#endif + H->db = NULL; + } + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } + return 0; +} +/* }}} */ + +static int sqlite_handle_preparer(pdo_dbh_t *dbh, const char *sql, size_t sql_len, pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + pdo_sqlite_stmt *S = ecalloc(1, sizeof(pdo_sqlite_stmt)); + int i; + const char *tail; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_sqlite_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL|PDO_PLACEHOLDER_NAMED; + + if (PDO_CURSOR_FWDONLY != pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY)) { + H->einfo.errcode = SQLITE_ERROR; + pdo_sqlite_error(dbh); + return 0; + } + + i = sqlite3_prepare_v2(H->db, sql, sql_len, &S->stmt, &tail); + if (i == SQLITE_OK) { + return 1; + } + + pdo_sqlite_error(dbh); + + return 0; +} + +static zend_long sqlite_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_len) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + char *errmsg = NULL; + + if (sqlite3_exec(H->db, sql, NULL, NULL, &errmsg) != SQLITE_OK) { + pdo_sqlite_error(dbh); + if (errmsg) + sqlite3_free(errmsg); + + return -1; + } else { + return sqlite3_changes(H->db); + } +} + +static char *pdo_sqlite_last_insert_id(pdo_dbh_t *dbh, const char *name, size_t *len) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + char *id; + + id = php_pdo_int64_to_str(sqlite3_last_insert_rowid(H->db)); + *len = strlen(id); + return id; +} + +/* NB: doesn't handle binary strings... use prepared stmts for that */ +static int sqlite_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type paramtype ) +{ + if (unquotedlen > (INT_MAX - 3) / 2) { + return 0; + } + *quoted = safe_emalloc(2, unquotedlen, 3); + sqlite3_snprintf(2*unquotedlen + 3, *quoted, "'%q'", unquoted); + *quotedlen = strlen(*quoted); + return 1; +} + +static int sqlite_handle_begin(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + char *errmsg = NULL; + + if (sqlite3_exec(H->db, "BEGIN", NULL, NULL, &errmsg) != SQLITE_OK) { + pdo_sqlite_error(dbh); + if (errmsg) + sqlite3_free(errmsg); + return 0; + } + return 1; +} + +static int sqlite_handle_commit(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + char *errmsg = NULL; + + if (sqlite3_exec(H->db, "COMMIT", NULL, NULL, &errmsg) != SQLITE_OK) { + pdo_sqlite_error(dbh); + if (errmsg) + sqlite3_free(errmsg); + return 0; + } + return 1; +} + +static int sqlite_handle_rollback(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + char *errmsg = NULL; + + if (sqlite3_exec(H->db, "ROLLBACK", NULL, NULL, &errmsg) != SQLITE_OK) { + pdo_sqlite_error(dbh); + if (errmsg) + sqlite3_free(errmsg); + return 0; + } + return 1; +} + +static int pdo_sqlite_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) +{ + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *)sqlite3_libversion()); + break; + + default: + return 0; + } + + return 1; +} + +static int pdo_sqlite_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_TIMEOUT: + sqlite3_busy_timeout(H->db, zval_get_long(val) * 1000); + return 1; + case PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES: + sqlite3_extended_result_codes(H->db, zval_get_long(val)); + return 1; + } + return 0; +} + +typedef struct { + zval val; + zend_long row; +} aggregate_context; + +static int do_callback(struct pdo_sqlite_fci *fc, zval *cb, + int argc, sqlite3_value **argv, sqlite3_context *context, + int is_agg) +{ + zval *zargs = NULL; + zval retval; + int i; + int ret; + int fake_argc; + aggregate_context *agg_context = NULL; + + if (is_agg) { + is_agg = 2; + } + + fake_argc = argc + is_agg; + + fc->fci.size = sizeof(fc->fci); + ZVAL_COPY_VALUE(&fc->fci.function_name, cb); + fc->fci.object = NULL; + fc->fci.retval = &retval; + fc->fci.param_count = fake_argc; + + /* build up the params */ + + if (fake_argc) { + zargs = safe_emalloc(fake_argc, sizeof(zval), 0); + } + + if (is_agg) { + agg_context = sqlite3_aggregate_context(context, sizeof(aggregate_context)); + if (!agg_context) { + efree(zargs); + return FAILURE; + } + if (Z_ISUNDEF(agg_context->val)) { + ZVAL_NEW_REF(&agg_context->val, &EG(uninitialized_zval)); + } + ZVAL_COPY_VALUE(&zargs[0], &agg_context->val); + ZVAL_LONG(&zargs[1], ++agg_context->row); + } + + for (i = 0; i < argc; i++) { + /* get the value */ + switch (sqlite3_value_type(argv[i])) { + case SQLITE_INTEGER: + ZVAL_LONG(&zargs[i + is_agg], sqlite3_value_int(argv[i])); + break; + + case SQLITE_FLOAT: + ZVAL_DOUBLE(&zargs[i + is_agg], sqlite3_value_double(argv[i])); + break; + + case SQLITE_NULL: + ZVAL_NULL(&zargs[i + is_agg]); + break; + + case SQLITE_BLOB: + case SQLITE3_TEXT: + default: + ZVAL_STRINGL(&zargs[i + is_agg], (char*)sqlite3_value_text(argv[i]), sqlite3_value_bytes(argv[i])); + break; + } + } + + fc->fci.params = zargs; + + if ((ret = zend_call_function(&fc->fci, &fc->fcc)) == FAILURE) { + php_error_docref(NULL, E_WARNING, "An error occurred while invoking the callback"); + } + + /* clean up the params */ + if (zargs) { + for (i = is_agg; i < fake_argc; i++) { + zval_ptr_dtor(&zargs[i]); + } + if (is_agg) { + zval_ptr_dtor(&zargs[1]); + } + efree(zargs); + } + + if (!is_agg || !argv) { + /* only set the sqlite return value if we are a scalar function, + * or if we are finalizing an aggregate */ + if (!Z_ISUNDEF(retval)) { + switch (Z_TYPE(retval)) { + case IS_LONG: + sqlite3_result_int(context, Z_LVAL(retval)); + break; + + case IS_NULL: + sqlite3_result_null(context); + break; + + case IS_DOUBLE: + sqlite3_result_double(context, Z_DVAL(retval)); + break; + + default: + if (!try_convert_to_string(&retval)) { + ret = FAILURE; + break; + } + sqlite3_result_text(context, Z_STRVAL(retval), Z_STRLEN(retval), SQLITE_TRANSIENT); + break; + } + } else { + sqlite3_result_error(context, "failed to invoke callback", 0); + } + + if (agg_context) { + zval_ptr_dtor(&agg_context->val); + } + } else { + /* we're stepping in an aggregate; the return value goes into + * the context */ + if (agg_context) { + if (Z_ISUNDEF(retval)) { + zval_ptr_dtor(&agg_context->val); + return FAILURE; + } + zval_ptr_dtor(Z_REFVAL(agg_context->val)); + ZVAL_COPY_VALUE(Z_REFVAL(agg_context->val), &retval); + ZVAL_UNDEF(&retval); + } + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + + return ret; +} + +static void php_sqlite3_func_callback(sqlite3_context *context, int argc, + sqlite3_value **argv) +{ + struct pdo_sqlite_func *func = (struct pdo_sqlite_func*)sqlite3_user_data(context); + + do_callback(&func->afunc, &func->func, argc, argv, context, 0); +} + +static void php_sqlite3_func_step_callback(sqlite3_context *context, int argc, + sqlite3_value **argv) +{ + struct pdo_sqlite_func *func = (struct pdo_sqlite_func*)sqlite3_user_data(context); + + do_callback(&func->astep, &func->step, argc, argv, context, 1); +} + +static void php_sqlite3_func_final_callback(sqlite3_context *context) +{ + struct pdo_sqlite_func *func = (struct pdo_sqlite_func*)sqlite3_user_data(context); + + do_callback(&func->afini, &func->fini, 0, NULL, context, 1); +} + +static int php_sqlite3_collation_callback(void *context, + int string1_len, const void *string1, + int string2_len, const void *string2) +{ + int ret; + zval zargs[2]; + zval retval; + struct pdo_sqlite_collation *collation = (struct pdo_sqlite_collation*) context; + + collation->fc.fci.size = sizeof(collation->fc.fci); + ZVAL_COPY_VALUE(&collation->fc.fci.function_name, &collation->callback); + collation->fc.fci.object = NULL; + collation->fc.fci.retval = &retval; + + // Prepare the arguments. + ZVAL_STRINGL(&zargs[0], (char *) string1, string1_len); + ZVAL_STRINGL(&zargs[1], (char *) string2, string2_len); + collation->fc.fci.param_count = 2; + collation->fc.fci.params = zargs; + + if ((ret = zend_call_function(&collation->fc.fci, &collation->fc.fcc)) == FAILURE) { + php_error_docref(NULL, E_WARNING, "An error occurred while invoking the callback"); + } else if (!Z_ISUNDEF(retval)) { + if (Z_TYPE(retval) != IS_LONG) { + convert_to_long_ex(&retval); + } + ret = 0; + if (Z_LVAL(retval) > 0) { + ret = 1; + } else if (Z_LVAL(retval) < 0) { + ret = -1; + } + zval_ptr_dtor(&retval); + } + + zval_ptr_dtor(&zargs[0]); + zval_ptr_dtor(&zargs[1]); + + return ret; +} + +/* {{{ bool SQLite::sqliteCreateFunction(string name, callable callback [, int argcount, int flags]) + Registers a UDF with the sqlite db handle */ +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateFunction) +{ + struct pdo_sqlite_func *func; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + char *func_name; + size_t func_name_len; + zend_long argc = -1; + zend_long flags = 0; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(func_name, func_name_len) + Z_PARAM_FUNC(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(argc) + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *)dbh->driver_data; + + func = (struct pdo_sqlite_func*)ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function(H->db, func_name, argc, flags | SQLITE_UTF8, + func, php_sqlite3_func_callback, NULL, NULL); + if (ret == SQLITE_OK) { + func->funcname = estrdup(func_name); + + ZVAL_COPY(&func->func, &fci.function_name); + + func->argc = argc; + + func->next = H->funcs; + H->funcs = func; + + RETURN_TRUE; + } + + efree(func); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ bool SQLite::sqliteCreateAggregate(string name, callable step, callable fini [, int argcount]) + Registers a UDF with the sqlite db handle */ + +/* The step function should have the prototype: + mixed step(mixed $context, int $rownumber, $value [, $value2 [, ...]]) + + $context will be null for the first row; on subsequent rows it will have + the value that was previously returned from the step function; you should + use this to maintain state for the aggregate. + + The fini function should have the prototype: + mixed fini(mixed $context, int $rownumber) + + $context will hold the return value from the very last call to the step function. + rownumber will hold the number of rows over which the aggregate was performed. + The return value of this function will be used as the return value for this + aggregate UDF. +*/ + +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateAggregate) +{ + struct pdo_sqlite_func *func; + zend_fcall_info step_fci, fini_fci; + zend_fcall_info_cache step_fcc, fini_fcc; + char *func_name; + size_t func_name_len; + zend_long argc = -1; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STRING(func_name, func_name_len) + Z_PARAM_FUNC(step_fci, step_fcc) + Z_PARAM_FUNC(fini_fci, fini_fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(argc) + ZEND_PARSE_PARAMETERS_END(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *)dbh->driver_data; + + func = (struct pdo_sqlite_func*)ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function(H->db, func_name, argc, SQLITE_UTF8, + func, NULL, php_sqlite3_func_step_callback, php_sqlite3_func_final_callback); + if (ret == SQLITE_OK) { + func->funcname = estrdup(func_name); + + ZVAL_COPY(&func->step, &step_fci.function_name); + + ZVAL_COPY(&func->fini, &fini_fci.function_name); + + func->argc = argc; + + func->next = H->funcs; + H->funcs = func; + + RETURN_TRUE; + } + + efree(func); + RETURN_FALSE; +} +/* }}} */ + +/* {{{ bool SQLite::sqliteCreateCollation(string name, callable callback) + Registers a collation with the sqlite db handle */ +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateCollation) +{ + struct pdo_sqlite_collation *collation; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + char *collation_name; + size_t collation_name_len; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(collation_name, collation_name_len) + Z_PARAM_FUNC(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *)dbh->driver_data; + + collation = (struct pdo_sqlite_collation*)ecalloc(1, sizeof(*collation)); + + ret = sqlite3_create_collation(H->db, collation_name, SQLITE_UTF8, collation, php_sqlite3_collation_callback); + if (ret == SQLITE_OK) { + collation->name = estrdup(collation_name); + + ZVAL_COPY(&collation->callback, &fci.function_name); + + collation->next = H->collations; + H->collations = collation; + + RETURN_TRUE; + } + + efree(collation); + RETURN_FALSE; +} +/* }}} */ + +static const zend_function_entry *get_driver_methods(pdo_dbh_t *dbh, int kind) +{ + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_SQLite_Ext_methods; + + default: + return NULL; + } +} + +static void pdo_sqlite_request_shutdown(pdo_dbh_t *dbh) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + /* unregister functions, so that they don't linger for the next + * request */ + if (H) { + pdo_sqlite_cleanup_callbacks(H); + } +} + +static const struct pdo_dbh_methods sqlite_methods = { + sqlite_handle_closer, + sqlite_handle_preparer, + sqlite_handle_doer, + sqlite_handle_quoter, + sqlite_handle_begin, + sqlite_handle_commit, + sqlite_handle_rollback, + pdo_sqlite_set_attr, + pdo_sqlite_last_insert_id, + pdo_sqlite_fetch_error_func, + pdo_sqlite_get_attribute, + NULL, /* check_liveness: not needed */ + get_driver_methods, + pdo_sqlite_request_shutdown, + NULL +}; + +static char *make_filename_safe(const char *filename) +{ + if (!filename) { + return NULL; + } + if (*filename && memcmp(filename, ":memory:", sizeof(":memory:"))) { + char *fullpath = expand_filepath(filename, NULL); + + if (!fullpath) { + return NULL; + } + + if (php_check_open_basedir(fullpath)) { + efree(fullpath); + return NULL; + } + return fullpath; + } + return estrdup(filename); +} + +static int authorizer(void *autharg, int access_type, const char *arg3, const char *arg4, + const char *arg5, const char *arg6) +{ + char *filename; + switch (access_type) { + case SQLITE_COPY: { + filename = make_filename_safe(arg4); + if (!filename) { + return SQLITE_DENY; + } + efree(filename); + return SQLITE_OK; + } + + case SQLITE_ATTACH: { + filename = make_filename_safe(arg3); + if (!filename) { + return SQLITE_DENY; + } + efree(filename); + return SQLITE_OK; + } + + default: + /* access allowed */ + return SQLITE_OK; + } +} + +static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_sqlite_db_handle *H; + int i, ret = 0; + zend_long timeout = 60, flags; + char *filename; + + H = pecalloc(1, sizeof(pdo_sqlite_db_handle), dbh->is_persistent); + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + dbh->driver_data = H; + + /* skip all but this one param event */ + dbh->skip_param_evt = 0x7F ^ (1 << PDO_PARAM_EVT_EXEC_PRE); + + filename = make_filename_safe(dbh->data_source); + + if (!filename) { + zend_throw_exception_ex(php_pdo_get_exception(), 0, + "open_basedir prohibits opening %s", + dbh->data_source); + goto cleanup; + } + + flags = pdo_attr_lval(driver_options, PDO_SQLITE_ATTR_OPEN_FLAGS, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + + i = sqlite3_open_v2(filename, &H->db, flags, NULL); + + efree(filename); + + if (i != SQLITE_OK) { + pdo_sqlite_error(dbh); + goto cleanup; + } + + if (PG(open_basedir) && *PG(open_basedir)) { + sqlite3_set_authorizer(H->db, authorizer, NULL); + } + + if (driver_options) { + timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, timeout); + } + sqlite3_busy_timeout(H->db, timeout * 1000); + + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &sqlite_methods; + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_sqlite_driver = { + PDO_DRIVER_HEADER(sqlite), + pdo_sqlite_handle_factory +}; +#endif diff --git a/thirdparty/php80/pdo_sqlite/sqlite_driver.stub.php b/thirdparty/php80/pdo_sqlite/sqlite_driver.stub.php new file mode 100644 index 00000000000..fc827cd381c --- /dev/null +++ b/thirdparty/php80/pdo_sqlite/sqlite_driver.stub.php @@ -0,0 +1,15 @@ + | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" + +#if PHP_VERSION_ID < 80100 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" + + +static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + + if (S->stmt) { + sqlite3_finalize(S->stmt); + S->stmt = NULL; + } + efree(S); + return 1; +} + +static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + } + + S->done = 0; + switch (sqlite3_step(S->stmt)) { + case SQLITE_ROW: + S->pre_fetched = 1; + php_pdo_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt)); + return 1; + + case SQLITE_DONE: + php_pdo_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt)); + stmt->row_count = sqlite3_changes(S->H->db); + sqlite3_reset(S->stmt); + S->done = 1; + return 1; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, + enum pdo_param_event event_type) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + zval *parameter; + + switch (event_type) { + case PDO_PARAM_EVT_EXEC_PRE: + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + S->done = 1; + } + + if (param->is_param) { + + if (param->paramno == -1) { + param->paramno = sqlite3_bind_parameter_index(S->stmt, ZSTR_VAL(param->name)) - 1; + } + + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_NULL: + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_INT: + case PDO_PARAM_BOOL: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + convert_to_long(parameter); +#if ZEND_LONG_MAX > 2147483647 + if (SQLITE_OK == sqlite3_bind_int64(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#else + if (SQLITE_OK == sqlite3_bind_int(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#endif + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_LOB: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm = NULL; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + zend_string *mem = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + zval_ptr_dtor(parameter); + ZVAL_STR(parameter, mem ? mem : ZSTR_EMPTY_ALLOC()); + } else { + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); + return 0; + } + } else if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + } + + if (SQLITE_OK == sqlite3_bind_blob(S->stmt, param->paramno + 1, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + SQLITE_STATIC)) { + return 1; + } + return 0; + + case PDO_PARAM_STR: + default: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + if (SQLITE_OK == sqlite3_bind_text(S->stmt, param->paramno + 1, + Z_STRVAL_P(parameter), + Z_STRLEN_P(parameter), + SQLITE_STATIC)) { + return 1; + } + } + pdo_sqlite_error_stmt(stmt); + return 0; + } + } + break; + + default: + ; + } + return 1; +} + +static int pdo_sqlite_stmt_fetch(pdo_stmt_t *stmt, + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + int i; + if (!S->stmt) { + return 0; + } + if (S->pre_fetched) { + S->pre_fetched = 0; + return 1; + } + if (S->done) { + return 0; + } + i = sqlite3_step(S->stmt); + switch (i) { + case SQLITE_ROW: + return 1; + + case SQLITE_DONE: + S->done = 1; + sqlite3_reset(S->stmt); + return 0; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_describe(pdo_stmt_t *stmt, int colno) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + const char *str; + + if(colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + + str = sqlite3_column_name(S->stmt, colno); + stmt->columns[colno].name = zend_string_init(str, strlen(str), 0); + stmt->columns[colno].maxlen = SIZE_MAX; + stmt->columns[colno].precision = 0; + + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_INTEGER: + case SQLITE_FLOAT: + case SQLITE3_TEXT: + case SQLITE_BLOB: + case SQLITE_NULL: + default: + stmt->columns[colno].param_type = PDO_PARAM_STR; + break; + } + + return 1; +} + +static int pdo_sqlite_stmt_get_col(pdo_stmt_t *stmt, int colno, char **ptr, size_t *len, int *caller_frees) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + if (!S->stmt) { + return 0; + } + if(colno >= sqlite3_data_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + *ptr = NULL; + *len = 0; + return 1; + + case SQLITE_BLOB: + *ptr = (char*)sqlite3_column_blob(S->stmt, colno); + *len = sqlite3_column_bytes(S->stmt, colno); + return 1; + + default: + *ptr = (char*)sqlite3_column_text(S->stmt, colno); + *len = sqlite3_column_bytes(S->stmt, colno); + return 1; + } +} + +static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + const char *str; + zval flags; + + if (!S->stmt) { + return FAILURE; + } + if(colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + add_assoc_string(return_value, "native_type", "null"); + break; + + case SQLITE_FLOAT: + add_assoc_string(return_value, "native_type", "double"); + break; + + case SQLITE_BLOB: + add_next_index_string(&flags, "blob"); + case SQLITE_TEXT: + add_assoc_string(return_value, "native_type", "string"); + break; + + case SQLITE_INTEGER: + add_assoc_string(return_value, "native_type", "integer"); + break; + } + + str = sqlite3_column_decltype(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "sqlite:decl_type", (char *)str); + } + +#ifdef HAVE_SW_SQLITE3_COLUMN_TABLE_NAME + str = sqlite3_column_table_name(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "table", (char *)str); + } +#endif + + add_assoc_zval(return_value, "flags", &flags); + + return SUCCESS; +} + +static int pdo_sqlite_stmt_cursor_closer(pdo_stmt_t *stmt) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + sqlite3_reset(S->stmt); + return 1; +} + +static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_SQLITE_ATTR_READONLY_STATEMENT: + ZVAL_FALSE(val); + +#if SQLITE_VERSION_NUMBER >= 3007004 + if (sqlite3_stmt_readonly(S->stmt)) { + ZVAL_TRUE(val); + } +#endif + break; + + default: + return 0; + } + + return 1; +} + +const struct pdo_stmt_methods swoole_sqlite_stmt_methods = { + pdo_sqlite_stmt_dtor, + pdo_sqlite_stmt_execute, + pdo_sqlite_stmt_fetch, + pdo_sqlite_stmt_describe, + pdo_sqlite_stmt_get_col, + pdo_sqlite_stmt_param_hook, + NULL, /* set_attr */ + pdo_sqlite_stmt_get_attribute, /* get_attr */ + pdo_sqlite_stmt_col_meta, + NULL, /* next_rowset */ + pdo_sqlite_stmt_cursor_closer +}; +#endif diff --git a/thirdparty/php81/pdo_oci/php_pdo_oci_int.h b/thirdparty/php81/pdo_oci/php_pdo_oci_int.h index 2d3db8f7309..6668c108d79 100644 --- a/thirdparty/php81/pdo_oci/php_pdo_oci_int.h +++ b/thirdparty/php81/pdo_oci/php_pdo_oci_int.h @@ -17,73 +17,74 @@ #include typedef struct { - const char *file; - int line; - sb4 errcode; - char *errmsg; + const char *file; + int line; + sb4 errcode; + char *errmsg; } pdo_oci_error_info; /* stuff we use in an OCI database handle */ typedef struct { - OCIServer *server; - OCISession *session; - OCIEnv *env; - OCIError *err; - OCISvcCtx *svc; - /* OCI9; 0 == use NLS_LANG */ - ub4 prefetch; - ub2 charset; - sword last_err; - sb4 max_char_width; - - unsigned attached:1; - unsigned _reserved:31; - - pdo_oci_error_info einfo; + OCIServer *server; + OCISession *session; + OCIEnv *env; + OCIError *err; + OCISvcCtx *svc; + /* OCI9; 0 == use NLS_LANG */ + ub4 prefetch; + ub2 charset; + sword last_err; + sb4 max_char_width; + + unsigned attached : 1; + unsigned _reserved : 31; + + pdo_oci_error_info einfo; } pdo_oci_db_handle; typedef struct { - OCIDefine *def; - ub2 fetched_len; - ub2 retcode; - sb2 indicator; + OCIDefine *def; + ub2 fetched_len; + ub2 retcode; + sb2 indicator; - char *data; - ub4 datalen; + char *data; + ub4 datalen; - ub2 dtype; + ub2 dtype; } pdo_oci_column; typedef struct { - pdo_oci_db_handle *H; - OCIStmt *stmt; - OCIError *err; - sword last_err; - ub2 stmt_type; - ub4 exec_type; - pdo_oci_column *cols; - pdo_oci_error_info einfo; - unsigned int have_blobs:1; + pdo_oci_db_handle *H; + OCIStmt *stmt; + OCIError *err; + sword last_err; + ub2 stmt_type; + ub4 exec_type; + pdo_oci_column *cols; + pdo_oci_error_info einfo; + unsigned int have_blobs : 1; } pdo_oci_stmt; typedef struct { - OCIBind *bind; /* allocated by OCI */ - sb2 oci_type; - sb2 indicator; - ub2 retcode; + OCIBind *bind; /* allocated by OCI */ + sb2 oci_type; + sb2 indicator; + ub2 retcode; - ub4 actual_len; + ub4 actual_len; - dvoid *thing; /* for LOBS, REFCURSORS etc. */ + dvoid *thing; /* for LOBS, REFCURSORS etc. */ - unsigned used_for_output; + unsigned used_for_output; } pdo_oci_bound_param; -ub4 _oci_error(OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); -#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) -#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) -#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) +ub4 _oci_error( + OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); +#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) +#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) +#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) extern const struct pdo_stmt_methods swoole_oci_stmt_methods; extern const ub4 SWOOLE_PDO_OCI_INIT_MODE; @@ -95,11 +96,10 @@ extern OCIEnv *swoole_pdo_oci_Env; /* Arbitrary assumed row length for prefetch memory limit calcuation */ #define PDO_OCI_PREFETCH_ROWSIZE 1024 - enum { - PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, - PDO_OCI_ATTR_CLIENT_INFO, - PDO_OCI_ATTR_CLIENT_IDENTIFIER, - PDO_OCI_ATTR_MODULE, - PDO_OCI_ATTR_CALL_TIMEOUT + PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, + PDO_OCI_ATTR_CLIENT_INFO, + PDO_OCI_ATTR_CLIENT_IDENTIFIER, + PDO_OCI_ATTR_MODULE, + PDO_OCI_ATTR_CALL_TIMEOUT }; diff --git a/thirdparty/php83/pdo_oci/oci_driver.c b/thirdparty/php83/pdo_oci/oci_driver.c index d1ca6d69c75..fb6aa6864fe 100644 --- a/thirdparty/php83/pdo_oci/oci_driver.c +++ b/thirdparty/php83/pdo_oci/oci_driver.c @@ -17,7 +17,7 @@ #define SW_USE_ORACLE_HOOK #include "php_swoole_oracle.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_oci/oci_statement.c b/thirdparty/php83/pdo_oci/oci_statement.c index d048a91217c..11e8e0c5630 100644 --- a/thirdparty/php83/pdo_oci/oci_statement.c +++ b/thirdparty/php83/pdo_oci/oci_statement.c @@ -17,7 +17,7 @@ #define SW_USE_ORACLE_HOOK #include "php_swoole_oracle.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_oci/php_pdo_oci_int.h b/thirdparty/php83/pdo_oci/php_pdo_oci_int.h index a455188c924..0e693c0e459 100644 --- a/thirdparty/php83/pdo_oci/php_pdo_oci_int.h +++ b/thirdparty/php83/pdo_oci/php_pdo_oci_int.h @@ -14,87 +14,81 @@ +----------------------------------------------------------------------+ */ -#ifndef PHP_PDO_OCI_INT_H -#define PHP_PDO_OCI_INT_H - -#include "zend_portability.h" - -ZEND_DIAGNOSTIC_IGNORED_START("-Wstrict-prototypes") #include -ZEND_DIAGNOSTIC_IGNORED_END typedef struct { - const char *file; - int line; - sb4 errcode; - char *errmsg; + const char *file; + int line; + sb4 errcode; + char *errmsg; } pdo_oci_error_info; /* stuff we use in an OCI database handle */ typedef struct { - OCIServer *server; - OCISession *session; - OCIEnv *env; - OCIError *err; - OCISvcCtx *svc; - /* OCI9; 0 == use NLS_LANG */ - ub4 prefetch; - ub2 charset; - sword last_err; - sb4 max_char_width; - - unsigned attached:1; - unsigned _reserved:31; - - pdo_oci_error_info einfo; + OCIServer *server; + OCISession *session; + OCIEnv *env; + OCIError *err; + OCISvcCtx *svc; + /* OCI9; 0 == use NLS_LANG */ + ub4 prefetch; + ub2 charset; + sword last_err; + sb4 max_char_width; + + unsigned attached : 1; + unsigned _reserved : 31; + + pdo_oci_error_info einfo; } pdo_oci_db_handle; typedef struct { - OCIDefine *def; - ub2 fetched_len; - ub2 retcode; - sb2 indicator; + OCIDefine *def; + ub2 fetched_len; + ub2 retcode; + sb2 indicator; - char *data; - ub4 datalen; + char *data; + ub4 datalen; - ub2 dtype; + ub2 dtype; } pdo_oci_column; typedef struct { - pdo_oci_db_handle *H; - OCIStmt *stmt; - OCIError *err; - sword last_err; - ub2 stmt_type; - ub4 exec_type; - pdo_oci_column *cols; - pdo_oci_error_info einfo; - unsigned int have_blobs:1; + pdo_oci_db_handle *H; + OCIStmt *stmt; + OCIError *err; + sword last_err; + ub2 stmt_type; + ub4 exec_type; + pdo_oci_column *cols; + pdo_oci_error_info einfo; + unsigned int have_blobs : 1; } pdo_oci_stmt; typedef struct { - OCIBind *bind; /* allocated by OCI */ - sb2 oci_type; - sb2 indicator; - ub2 retcode; + OCIBind *bind; /* allocated by OCI */ + sb2 oci_type; + sb2 indicator; + ub2 retcode; - ub4 actual_len; + ub4 actual_len; - dvoid *thing; /* for LOBS, REFCURSORS etc. */ + dvoid *thing; /* for LOBS, REFCURSORS etc. */ - unsigned used_for_output; + unsigned used_for_output; } pdo_oci_bound_param; -extern const ub4 PDO_OCI_INIT_MODE; -extern const pdo_driver_t pdo_oci_driver; -extern OCIEnv *pdo_oci_Env; +extern const ub4 SWOOLE_PDO_OCI_INIT_MODE; +extern const pdo_driver_t swoole_pdo_oci_driver; +extern OCIEnv *swoole_pdo_oci_Env; -ub4 _oci_error(OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); -#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) -#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) -#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) +ub4 _oci_error( + OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); +#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) +#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) +#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) extern const struct pdo_stmt_methods swoole_oci_stmt_methods; extern const ub4 SWOOLE_PDO_OCI_INIT_MODE; @@ -106,13 +100,10 @@ extern OCIEnv *swoole_pdo_oci_Env; /* Arbitrary assumed row length for prefetch memory limit calcuation */ #define PDO_OCI_PREFETCH_ROWSIZE 1024 - enum { - PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, - PDO_OCI_ATTR_CLIENT_INFO, - PDO_OCI_ATTR_CLIENT_IDENTIFIER, - PDO_OCI_ATTR_MODULE, - PDO_OCI_ATTR_CALL_TIMEOUT + PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, + PDO_OCI_ATTR_CLIENT_INFO, + PDO_OCI_ATTR_CLIENT_IDENTIFIER, + PDO_OCI_ATTR_MODULE, + PDO_OCI_ATTR_CALL_TIMEOUT }; - -#endif /* PHP_PDO_OCI_INT_H */ diff --git a/thirdparty/php83/pdo_odbc/odbc_driver.c b/thirdparty/php83/pdo_odbc/odbc_driver.c index a5e3a8b4f0c..5c7cbe76890 100644 --- a/thirdparty/php83/pdo_odbc/odbc_driver.c +++ b/thirdparty/php83/pdo_odbc/odbc_driver.c @@ -17,7 +17,7 @@ #define SW_USE_ODBC_HOOK #include "php_swoole_odbc.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_odbc/odbc_stmt.c b/thirdparty/php83/pdo_odbc/odbc_stmt.c index c8eef615784..da75f668415 100644 --- a/thirdparty/php83/pdo_odbc/odbc_stmt.c +++ b/thirdparty/php83/pdo_odbc/odbc_stmt.c @@ -17,8 +17,7 @@ #define SW_USE_ODBC_HOOK #include "php_swoole_odbc.h" -#if PHP_VERSION_ID >= 80300 - +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php83/pdo_pgsql/pgsql_driver.c b/thirdparty/php83/pdo_pgsql/pgsql_driver.c index 73e610674a4..86b05d37fc6 100644 --- a/thirdparty/php83/pdo_pgsql/pgsql_driver.c +++ b/thirdparty/php83/pdo_pgsql/pgsql_driver.c @@ -19,7 +19,7 @@ #define SW_USE_PGSQL_HOOK #include "php_swoole_pgsql.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_pgsql/pgsql_statement.c b/thirdparty/php83/pdo_pgsql/pgsql_statement.c index 13532fe3659..ae4e7e16fea 100644 --- a/thirdparty/php83/pdo_pgsql/pgsql_statement.c +++ b/thirdparty/php83/pdo_pgsql/pgsql_statement.c @@ -19,7 +19,7 @@ #define SW_USE_PGSQL_HOOK #include "php_swoole_pgsql.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php83/pdo_sqlite/sqlite_driver.c b/thirdparty/php83/pdo_sqlite/sqlite_driver.c index e130d779f9c..10f48849e53 100644 --- a/thirdparty/php83/pdo_sqlite/sqlite_driver.c +++ b/thirdparty/php83/pdo_sqlite/sqlite_driver.c @@ -17,7 +17,7 @@ #include "php_swoole_sqlite.h" #include "php_swoole_call_stack.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php83/pdo_sqlite/sqlite_statement.c b/thirdparty/php83/pdo_sqlite/sqlite_statement.c index 52fa33b2abb..bf8978f1134 100644 --- a/thirdparty/php83/pdo_sqlite/sqlite_statement.c +++ b/thirdparty/php83/pdo_sqlite/sqlite_statement.c @@ -17,7 +17,7 @@ #define SW_USE_SQLITE_HOOK #include "php_swoole_sqlite.h" -#if PHP_VERSION_ID >= 80300 +#if PHP_VERSION_ID >= 80300 && PHP_VERSION_ID < 80400 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php84/pdo_oci/LICENSE b/thirdparty/php84/pdo_oci/LICENSE new file mode 100644 index 00000000000..0815d7eb791 --- /dev/null +++ b/thirdparty/php84/pdo_oci/LICENSE @@ -0,0 +1,68 @@ +-------------------------------------------------------------------- + The PHP License, version 3.01 +Copyright (c) 1999 - 2024 The PHP Group. All rights reserved. +-------------------------------------------------------------------- + +Redistribution and use in source and binary forms, with or without +modification, is 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. + + 3. The name "PHP" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact group@php.net. + + 4. Products derived from this software may not be called "PHP", nor + may "PHP" appear in their name, without prior written permission + from group@php.net. You may indicate that your software works in + conjunction with PHP by saying "Foo for PHP" instead of calling + it "PHP Foo" or "phpfoo" + + 5. The PHP Group may publish revised and/or new versions of the + license from time to time. Each version will be given a + distinguishing version number. + Once covered code has been published under a particular version + of the license, you may always continue to use it under the terms + of that version. You may also choose to use such covered code + under the terms of any subsequent version of the license + published by the PHP Group. No one other than the PHP Group has + the right to modify the terms applicable to covered code created + under this License. + + 6. Redistributions of any form whatsoever must retain the following + acknowledgment: + "This product includes PHP software, freely available from + ". + +THIS SOFTWARE IS PROVIDED BY THE PHP DEVELOPMENT TEAM ``AS IS'' AND +ANY EXPRESSED 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 PHP +DEVELOPMENT TEAM OR ITS 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 consists of voluntary contributions made by many +individuals on behalf of the PHP Group. + +The PHP Group can be contacted via Email at group@php.net. + +For more information on the PHP Group and the PHP project, +please see . + +PHP includes the Zend Engine, freely available at +. diff --git a/thirdparty/php84/pdo_oci/oci_driver.c b/thirdparty/php84/pdo_oci/oci_driver.c new file mode 100644 index 00000000000..6ee73fd7110 --- /dev/null +++ b/thirdparty/php84/pdo_oci/oci_driver.c @@ -0,0 +1,880 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" + +#if PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "Zend/zend_exceptions.h" + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch); + +static void pdo_oci_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + + einfo = &H->einfo; + + if (stmt) { + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + if (S->einfo.errmsg) { + einfo = &S->einfo; + } + } + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +ub4 _oci_error(OCIError *err, + pdo_dbh_t *dbh, + pdo_stmt_t *stmt, + char *what, + sword status, + int isinit, + const char *file, + int line) /* {{{ */ +{ + text errbuf[1024] = "<>"; + char tmp_buf[2048]; + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_error_info *einfo; + pdo_oci_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_oci_stmt *) stmt->driver_data; + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } else { + einfo = &H->einfo; + } + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + + einfo->errmsg = NULL; + einfo->errcode = 0; + einfo->file = file; + einfo->line = line; + + if (isinit) { /* Initialization error */ + strcpy(*pdo_err, "HY000"); + slprintf(tmp_buf, sizeof(tmp_buf), "%s (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + } else { + switch (status) { + case OCI_SUCCESS: + strcpy(*pdo_err, "00000"); + break; + case OCI_ERROR: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_SUCCESS_WITH_INFO: + OCIErrorGet(err, (ub4) 1, NULL, &einfo->errcode, errbuf, (ub4) sizeof(errbuf), OCI_HTYPE_ERROR); + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_SUCCESS_WITH_INFO: %s (%s:%d)", what, errbuf, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NEED_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NEED_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_NO_DATA: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_NO_DATA (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_INVALID_HANDLE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_INVALID_HANDLE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_STILL_EXECUTING: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_STILL_EXECUTING (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + case OCI_CONTINUE: + slprintf(tmp_buf, sizeof(tmp_buf), "%s: OCI_CONTINUE (%s:%d)", what, file, line); + einfo->errmsg = pestrdup(tmp_buf, dbh->is_persistent); + break; + } + + if (einfo->errcode) { + switch (einfo->errcode) { + case 1013: /* user requested cancel of current operation */ + zend_bailout(); + break; + + case 12154: /* ORA-12154: TNS:could not resolve service name */ + strcpy(*pdo_err, "42S02"); + break; + + case 22: /* ORA-00022: invalid session id */ + case 378: + case 602: + case 603: + case 604: + case 609: + case 1012: /* ORA-01012: */ + case 1033: + case 1041: + case 1043: + case 1089: + case 1090: + case 1092: + case 3113: /* ORA-03133: end of file on communication channel */ + case 3114: + case 3122: + case 3135: + case 12153: + case 27146: + case 28511: + /* consider the connection closed */ + dbh->is_closed = 1; + H->attached = 0; + strcpy(*pdo_err, "01002"); /* FIXME */ + break; + + default: + strcpy(*pdo_err, "HY000"); + } + } + + if (stmt) { + /* always propagate the error code back up to the dbh, + * so that we can catch the error information when execute + * is called via query. See Bug #33707 */ + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + } + H->einfo = *einfo; + H->einfo.errmsg = einfo->errmsg ? pestrdup(einfo->errmsg, dbh->is_persistent) : NULL; + strcpy(dbh->error_code, stmt->error_code); + } + } + + /* little mini hack so that we can use this code from the dbh ctor */ + if (!dbh->methods && status != OCI_SUCCESS_WITH_INFO) { + zend_throw_exception_ex(php_pdo_get_exception(), einfo->errcode, "SQLSTATE[%s]: %s", *pdo_err, einfo->errmsg); + } + + return einfo->errcode; +} +/* }}} */ + +static void oci_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + if (H->svc) { + /* rollback any outstanding work */ + OCITransRollback(H->svc, H->err, 0); + } + + if (H->session) { + OCIHandleFree(H->session, OCI_HTYPE_SESSION); + H->session = NULL; + } + + if (H->svc) { + OCIHandleFree(H->svc, OCI_HTYPE_SVCCTX); + H->svc = NULL; + } + + if (H->server && H->attached) { + H->last_err = OCIServerDetach(H->server, H->err, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCIServerDetach"); + } + H->attached = 0; + } + + if (H->server) { + OCIHandleFree(H->server, OCI_HTYPE_SERVER); + H->server = NULL; + } + + if (H->err) { + OCIHandleFree(H->err, OCI_HTYPE_ERROR); + H->err = NULL; + } + + if (H->charset && H->env) { + OCIHandleFree(H->env, OCI_HTYPE_ENV); + H->env = NULL; + } + + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + + pefree(H, dbh->is_persistent); +} +/* }}} */ + +static bool oci_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + pdo_oci_stmt *S = ecalloc(1, sizeof(*S)); + ub4 prefetch; + zend_string *nsql = NULL; + int ret; + + S->exec_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL + ? OCI_STMT_SCROLLABLE_READONLY + : OCI_DEFAULT; + + S->H = H; + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + /* create an OCI statement handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->stmt, OCI_HTYPE_STMT, 0, NULL); + + /* and our own private error handle */ + OCIHandleAlloc(H->env, (dvoid *) &S->err, OCI_HTYPE_ERROR, 0, NULL); + + if (ZSTR_LEN(sql) != 0) { + H->last_err = + OCIStmtPrepare(S->stmt, H->err, (text *) ZSTR_VAL(sql), (ub4) ZSTR_LEN(sql), OCI_NTV_SYNTAX, OCI_DEFAULT); + if (nsql) { + zend_string_release(nsql); + nsql = NULL; + } + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + efree(S); + return false; + } + } + + prefetch = H->prefetch; /* Note 0 is allowed so in future REF CURSORs can be used & then passed with no row loss*/ + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_ROWS, H->err); + if (!H->last_err) { + prefetch *= PDO_OCI_PREFETCH_ROWSIZE; + H->last_err = OCIAttrSet(S->stmt, OCI_HTYPE_STMT, &prefetch, 0, OCI_ATTR_PREFETCH_MEMORY, H->err); + } + + stmt->driver_data = S; + stmt->methods = &swoole_oci_stmt_methods; + if (nsql) { + zend_string_release(nsql); + nsql = NULL; + } + + return true; +} +/* }}} */ + +static zend_long oci_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + OCIStmt *stmt; + ub2 stmt_type; + ub4 rowcount; + int ret = -1; + + OCIHandleAlloc(H->env, (dvoid *) &stmt, OCI_HTYPE_STMT, 0, NULL); + + H->last_err = + OCIStmtPrepare(stmt, H->err, (text *) ZSTR_VAL(sql), (ub4) ZSTR_LEN(sql), OCI_NTV_SYNTAX, OCI_DEFAULT); + if (H->last_err) { + H->last_err = oci_drv_error("OCIStmtPrepare"); + OCIHandleFree(stmt, OCI_HTYPE_STMT); + return -1; + } + + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &stmt_type, 0, OCI_ATTR_STMT_TYPE, H->err); + + if (stmt_type == OCI_STMT_SELECT) { + /* invalid usage; cancel it */ + OCIHandleFree(stmt, OCI_HTYPE_STMT); + php_error_docref(NULL, E_WARNING, "issuing a SELECT query here is invalid"); + return -1; + } + + /* now we are good to go */ + H->last_err = OCIStmtExecute(H->svc, + stmt, + H->err, + 1, + 0, + NULL, + NULL, + (dbh->auto_commit && !dbh->in_txn) ? OCI_COMMIT_ON_SUCCESS : OCI_DEFAULT); + + sword last_err = H->last_err; + + if (last_err) { + H->last_err = oci_drv_error("OCIStmtExecute"); + } + + if (!last_err || last_err == OCI_SUCCESS_WITH_INFO) { + /* return the number of affected rows */ + H->last_err = OCIAttrGet(stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, H->err); + ret = rowcount; + } + + OCIHandleFree(stmt, OCI_HTYPE_STMT); + + return ret; +} +/* }}} */ + +static zend_string *oci_handle_quoter(pdo_dbh_t *dbh, + const zend_string *unquoted, + enum pdo_param_type paramtype) /* {{{ */ +{ + int qcount = 0; + char const *cu, *l, *r; + char *c, *quoted; + size_t quotedlen; + zend_string *quoted_str; + + if (ZSTR_LEN(unquoted) == 0) { + return ZSTR_INIT_LITERAL("''", 0); + } + + /* count single quotes */ + for (cu = ZSTR_VAL(unquoted); (cu = strchr(cu, '\'')); qcount++, cu++) + ; /* empty loop */ + + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; + quoted = c = emalloc(quotedlen + 1); + *c++ = '\''; + + /* foreach (chunk that ends in a quote) */ + for (l = ZSTR_VAL(unquoted); (r = strchr(l, '\'')); l = r + 1) { + strncpy(c, l, r - l + 1); + c += (r - l + 1); + *c++ = '\''; /* add second quote */ + } + + /* Copy remainder and add enclosing quote */ + strncpy(c, l, quotedlen - (c - quoted) - 1); + quoted[quotedlen - 1] = '\''; + quoted[quotedlen] = '\0'; + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} +/* }}} */ + +static bool oci_handle_begin(pdo_dbh_t *dbh) /* {{{ */ +{ + /* with Oracle, there is nothing special to be done */ + return true; +} +/* }}} */ + +static bool oci_handle_commit(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return false; + } + return true; +} +/* }}} */ + +static bool oci_handle_rollback(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + H->last_err = OCITransRollback(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransRollback"); + return false; + } + return true; +} +/* }}} */ + +static bool oci_handle_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + zend_long lval; + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: { + bool bval; + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (dbh->in_txn) { + /* Assume they want to commit whatever is outstanding */ + H->last_err = OCITransCommit(H->svc, H->err, 0); + + if (H->last_err) { + H->last_err = oci_drv_error("OCITransCommit"); + return false; + } + dbh->in_txn = false; + } + + dbh->auto_commit = (unsigned int) bval; + return true; + } + case PDO_ATTR_PREFETCH: { + if (!pdo_get_long_param(&lval, val)) { + return false; + } + + H->prefetch = pdo_oci_sanitize_prefetch(lval); + return true; + } + case PDO_OCI_ATTR_ACTION: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *action = zval_try_get_string(val); + if (UNEXPECTED(!action)) { + return false; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(action), (ub4) ZSTR_LEN(action), OCI_ATTR_ACTION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_ACTION"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CLIENT_INFO: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *client_info = zval_try_get_string(val); + if (UNEXPECTED(!client_info)) { + return false; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(client_info), + (ub4) ZSTR_LEN(client_info), + OCI_ATTR_CLIENT_INFO, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_INFO"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CLIENT_IDENTIFIER: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *identifier = zval_try_get_string(val); + if (UNEXPECTED(!identifier)) { + return false; + } + + H->last_err = OCIAttrSet(H->session, + OCI_HTYPE_SESSION, + (dvoid *) ZSTR_VAL(identifier), + (ub4) ZSTR_LEN(identifier), + OCI_ATTR_CLIENT_IDENTIFIER, + H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CLIENT_IDENTIFIER"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_MODULE: { +#if (OCI_MAJOR_VERSION >= 10) + zend_string *module = zval_try_get_string(val); + if (UNEXPECTED(!module)) { + return false; + } + + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, (dvoid *) ZSTR_VAL(module), (ub4) ZSTR_LEN(module), OCI_ATTR_MODULE, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_MODULE"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + if (!pdo_get_long_param(&lval, val)) { + return false; + } + ub4 timeout = (ub4) lval; + + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, (ub4) 0, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_CALL_TIMEOUT"); + return false; + } + return true; +#else + oci_drv_error("Unsupported attribute type"); + return false; +#endif + } + default: + return false; + } +} +/* }}} */ + +static int oci_handle_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: { + text infostr[512]; + char verstr[15]; + ub4 vernum; + + if (OCIServerRelease(H->svc, H->err, infostr, (ub4) sizeof(infostr), (ub1) OCI_HTYPE_SVCCTX, &vernum)) { + ZVAL_STRING(return_value, "<>"); + } else { + if (attr == PDO_ATTR_SERVER_INFO) { + ZVAL_STRING(return_value, (char *) infostr); + } else { + slprintf(verstr, + sizeof(verstr), + "%d.%d.%d.%d.%d", + (int) ((vernum >> 24) & 0xFF), /* version number */ + (int) ((vernum >> 20) & 0x0F), /* release number*/ + (int) ((vernum >> 12) & 0xFF), /* update number */ + (int) ((vernum >> 8) & 0x0F), /* port release number */ + (int) ((vernum >> 0) & 0xFF)); /* port update number */ + + ZVAL_STRING(return_value, verstr); + } + } + return TRUE; + } + + case PDO_ATTR_CLIENT_VERSION: { +#if OCI_MAJOR_VERSION > 10 || (OCI_MAJOR_VERSION == 10 && OCI_MINOR_VERSION >= 2) + /* Run time client version */ + sword major, minor, update, patch, port_update; + char verstr[15]; + + OCIClientVersion(&major, &minor, &update, &patch, &port_update); + slprintf(verstr, sizeof(verstr), "%d.%d.%d.%d.%d", major, minor, update, patch, port_update); + ZVAL_STRING(return_value, verstr); +#elif defined(SWOOLE_PDO_OCI_CLIENT_VERSION) + /* Compile time client version */ + ZVAL_STRING(return_value, SWOOLE_PDO_OCI_CLIENT_VERSION); +#else + return FALSE; + +#endif /* Check for OCIClientVersion() support */ + + return TRUE; + } + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(return_value, dbh->auto_commit); + return TRUE; + + case PDO_ATTR_PREFETCH: + ZVAL_LONG(return_value, H->prefetch); + return TRUE; + case PDO_OCI_ATTR_CALL_TIMEOUT: { +#if (OCI_MAJOR_VERSION >= 18) + ub4 timeout; + + H->last_err = OCIAttrGet(H->svc, OCI_HTYPE_SVCCTX, (dvoid *) &timeout, NULL, OCI_ATTR_CALL_TIMEOUT, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrGet: OCI_ATTR_CALL_TIMEOUT"); + return FALSE; + } + + ZVAL_LONG(return_value, (zend_long) timeout); + return TRUE; +#else + oci_drv_error("Unsupported attribute type"); + return FALSE; +#endif + } + default: + return FALSE; + } + return FALSE; +} +/* }}} */ + +static zend_result pdo_oci_check_liveness(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_oci_db_handle *H = (pdo_oci_db_handle *) dbh->driver_data; + sb4 error_code = 0; +#if (!((OCI_MAJOR_VERSION > 10) || ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2)))) + char version[256]; +#endif + + /* TODO move attached check to PDO level */ + if (H->attached == 0) { + return FAILURE; + } + /* TODO add persistent_timeout check at PDO level */ + + /* Use OCIPing instead of OCIServerVersion. If OCIPing returns ORA-1010 (invalid OCI operation) + * such as from Pre-10.1 servers, the error is still from the server and we would have + * successfully performed a roundtrip and validated the connection. Use OCIServerVersion for + * Pre-10.2 clients + */ +#if ((OCI_MAJOR_VERSION > 10) || \ + ((OCI_MAJOR_VERSION == 10) && (OCI_MINOR_VERSION >= 2))) /* OCIPing available 10.2 onwards */ + H->last_err = OCIPing(H->svc, H->err, OCI_DEFAULT); +#else + /* use good old OCIServerVersion() */ + H->last_err = OCIServerVersion(H->svc, H->err, (text *) version, sizeof(version), OCI_HTYPE_SVCCTX); +#endif + if (H->last_err == OCI_SUCCESS) { + return SUCCESS; + } + + OCIErrorGet(H->err, (ub4) 1, NULL, &error_code, NULL, 0, OCI_HTYPE_ERROR); + + if (error_code == 1010) { + return SUCCESS; + } + return FAILURE; +} +/* }}} */ + +static const struct pdo_dbh_methods oci_methods = { + oci_handle_closer, + oci_handle_preparer, + oci_handle_doer, + oci_handle_quoter, + oci_handle_begin, + oci_handle_commit, + oci_handle_rollback, + oci_handle_set_attribute, + NULL, /* last_id not supported */ + pdo_oci_fetch_error_func, + oci_handle_get_attribute, + pdo_oci_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL /* get_gc */ +}; + +static int pdo_oci_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_oci_db_handle *H; + int i, ret = 0; + struct pdo_data_src_parser vars[] = { + {"charset", NULL, 0}, {"dbname", "", 0}, {"user", NULL, 0}, {"password", NULL, 0}}; + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 4); + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = 1 << PDO_PARAM_EVT_FETCH_PRE | 1 << PDO_PARAM_EVT_FETCH_POST | 1 << PDO_PARAM_EVT_NORMALIZE; + + H->prefetch = PDO_OCI_PREFETCH_DEFAULT; + + /* allocate an environment */ + if (vars[0].optval) { + H->charset = OCINlsCharSetNameToId(swoole_pdo_oci_Env, (const oratext *) vars[0].optval); + if (!H->charset) { + oci_init_error("OCINlsCharSetNameToId: unknown character set name"); + goto cleanup; + } else { + if (OCIEnvNlsCreate( + &H->env, SWOOLE_PDO_OCI_INIT_MODE, 0, NULL, NULL, NULL, 0, NULL, H->charset, H->charset) != + OCI_SUCCESS) { + oci_init_error("OCIEnvNlsCreate: Check the character set is valid and that PHP has access to Oracle " + "libraries and NLS data"); + goto cleanup; + } + } + } + + if (H->env == NULL) { + /* use the global environment */ + H->env = swoole_pdo_oci_Env; + } + + /* something to hold errors */ + OCIHandleAlloc(H->env, (dvoid **) &H->err, OCI_HTYPE_ERROR, 0, NULL); + + /* handle for the server */ + OCIHandleAlloc(H->env, (dvoid **) &H->server, OCI_HTYPE_SERVER, 0, NULL); + + H->last_err = + OCIServerAttach(H->server, H->err, (text *) vars[1].optval, (sb4) strlen(vars[1].optval), OCI_DEFAULT); + + if (H->last_err) { + oci_drv_error("pdo_oci_handle_factory"); + goto cleanup; + } + + H->attached = 1; + + /* create a service context */ + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->svc, OCI_HTYPE_SVCCTX, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SVCCTX"); + goto cleanup; + } + + H->last_err = OCIHandleAlloc(H->env, (dvoid **) &H->session, OCI_HTYPE_SESSION, 0, NULL); + if (H->last_err) { + oci_drv_error("OCIHandleAlloc: OCI_HTYPE_SESSION"); + goto cleanup; + } + + /* set server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->server, 0, OCI_ATTR_SERVER, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SERVER"); + goto cleanup; + } + + /* username */ + if (!dbh->username && vars[2].optval) { + dbh->username = pestrdup(vars[2].optval, dbh->is_persistent); + } + + if (dbh->username) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->username, (ub4) strlen(dbh->username), OCI_ATTR_USERNAME, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_USERNAME"); + goto cleanup; + } + } + + /* password */ + if (!dbh->password && vars[3].optval) { + dbh->password = pestrdup(vars[3].optval, dbh->is_persistent); + } + + if (dbh->password) { + H->last_err = OCIAttrSet( + H->session, OCI_HTYPE_SESSION, dbh->password, (ub4) strlen(dbh->password), OCI_ATTR_PASSWORD, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_PASSWORD"); + goto cleanup; + } + } + + /* Now fire up the session */ + H->last_err = OCISessionBegin(H->svc, H->err, H->session, OCI_CRED_RDBMS, OCI_DEFAULT); + if (H->last_err) { + oci_drv_error("OCISessionBegin"); + /* OCISessionBegin returns OCI_SUCCESS_WITH_INFO when + * user's password has expired, but is still usable. + */ + if (H->last_err != OCI_SUCCESS_WITH_INFO) { + goto cleanup; + } + } + + /* set the server handle into service handle */ + H->last_err = OCIAttrSet(H->svc, OCI_HTYPE_SVCCTX, H->session, 0, OCI_ATTR_SESSION, H->err); + if (H->last_err) { + oci_drv_error("OCIAttrSet: OCI_ATTR_SESSION"); + goto cleanup; + } + + /* Get max character width */ + H->last_err = OCINlsNumericInfoGet(H->env, H->err, &H->max_char_width, OCI_NLS_CHARSET_MAXBYTESZ); + if (H->last_err) { + oci_drv_error("OCINlsNumericInfoGet: OCI_NLS_CHARSET_MAXBYTESZ"); + goto cleanup; + } + + dbh->methods = &oci_methods; + dbh->alloc_own_columns = 1; + dbh->native_case = PDO_CASE_UPPER; + + ret = 1; + +cleanup: + for (i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!ret) { + oci_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_oci_driver = {PDO_DRIVER_HEADER(oci), pdo_oci_handle_factory}; + +static inline ub4 pdo_oci_sanitize_prefetch(long prefetch) /* {{{ */ +{ + if (prefetch < 0) { + prefetch = 0; + } else if (prefetch > UB4MAXVAL / PDO_OCI_PREFETCH_ROWSIZE) { + prefetch = PDO_OCI_PREFETCH_DEFAULT; + } + return ((ub4) prefetch); +} +/* }}} */ +#endif diff --git a/thirdparty/php84/pdo_oci/oci_statement.c b/thirdparty/php84/pdo_oci/oci_statement.c new file mode 100644 index 00000000000..cd91dbf7223 --- /dev/null +++ b/thirdparty/php84/pdo_oci/oci_statement.c @@ -0,0 +1,1041 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ORACLE_HOOK +#include "php_swoole_oracle.h" + +#if PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "Zend/zend_extensions.h" + +#define PDO_OCI_LOBMAXSIZE (4294967295UL) /* OCI_LOBMAXSIZE */ + +#define STMT_CALL(name, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +#define STMT_CALL_MSG(name, msg, params) \ + do { \ + S->last_err = name params; \ + S->last_err = _oci_error(S->err, stmt->dbh, stmt, #name ": " #msg, S->last_err, FALSE, __FILE__, __LINE__); \ + if (S->last_err) { \ + return 0; \ + } \ + } while (0) + +static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob); + +#define OCI_TEMPLOB_CLOSE(envhp, svchp, errhp, lob) \ + do { \ + boolean isTempLOB; \ + OCILobIsTemporary(envhp, errhp, lob, &isTempLOB); \ + if (isTempLOB) OCILobFreeTemporary(svchp, errhp, lob); \ + } while (0) + +static int oci_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + HashTable *BC = stmt->bound_columns; + HashTable *BP = stmt->bound_params; + + int i; + + if (S->stmt) { + /* cancel server side resources for the statement if we didn't + * fetch it all */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + + /* free the handle */ + OCIHandleFree(S->stmt, OCI_HTYPE_STMT); + S->stmt = NULL; + } + if (S->err) { + OCIHandleFree(S->err, OCI_HTYPE_ERROR); + S->err = NULL; + } + + /* need to ensure these go away now */ + if (BC) { + zend_hash_destroy(BC); + FREE_HASHTABLE(stmt->bound_columns); + stmt->bound_columns = NULL; + } + + if (BP) { + zend_hash_destroy(BP); + FREE_HASHTABLE(stmt->bound_params); + stmt->bound_params = NULL; + } + + if (S->einfo.errmsg) { + pefree(S->einfo.errmsg, stmt->dbh->is_persistent); + S->einfo.errmsg = NULL; + } + + if (S->cols) { + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, (OCILobLocator *) S->cols[i].data); + OCIDescriptorFree(S->cols[i].data, OCI_DTYPE_LOB); + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + S->cols = NULL; + } + efree(S); + + stmt->driver_data = NULL; + + return 1; +} /* }}} */ + +static int oci_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + ub4 rowcount; + b4 mode; + + if (!S->stmt_type) { + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_STMT_TYPE", (S->stmt, OCI_HTYPE_STMT, &S->stmt_type, 0, OCI_ATTR_STMT_TYPE, S->err)); + } + + if (stmt->executed) { + /* ensure that we cancel the cursor from a previous fetch */ + OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT); + } + +#ifdef OCI_STMT_SCROLLABLE_READONLY /* needed for oci8 ? */ + if (S->exec_type == OCI_STMT_SCROLLABLE_READONLY) { + mode = OCI_STMT_SCROLLABLE_READONLY; + } else +#endif + if (stmt->dbh->auto_commit && !stmt->dbh->in_txn) { + mode = OCI_COMMIT_ON_SUCCESS; + } else { + mode = OCI_DEFAULT; + } + + STMT_CALL( + OCIStmtExecute, + (S->H->svc, S->stmt, S->err, (S->stmt_type == OCI_STMT_SELECT && !S->have_blobs) ? 0 : 1, 0, NULL, NULL, mode)); + + if (!stmt->executed) { + ub4 colcount; + /* do first-time-only definition of bind/mapping stuff */ + + /* how many columns do we have ? */ + STMT_CALL_MSG( + OCIAttrGet, "ATTR_PARAM_COUNT", (S->stmt, OCI_HTYPE_STMT, &colcount, 0, OCI_ATTR_PARAM_COUNT, S->err)); + + stmt->column_count = (int) colcount; + + if (S->cols) { + int i; + for (i = 0; i < stmt->column_count; i++) { + if (S->cols[i].data) { + switch (S->cols[i].dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + /* do nothing */ + break; + default: + efree(S->cols[i].data); + } + } + } + efree(S->cols); + } + + S->cols = ecalloc(colcount, sizeof(pdo_oci_column)); + } + + STMT_CALL_MSG(OCIAttrGet, "ATTR_ROW_COUNT", (S->stmt, OCI_HTYPE_STMT, &rowcount, 0, OCI_ATTR_ROW_COUNT, S->err)); + stmt->row_count = (long) rowcount; + + return 1; +} /* }}} */ + +static sb4 oci_bind_input_cb( + dvoid *ctx, OCIBind *bindp, ub4 iter, ub4 index, dvoid **bufpp, ub4 *alenp, ub1 *piecep, dvoid **indpp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + *indpp = &P->indicator; + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + if (P->thing) { + *bufpp = P->thing; + *alenp = sizeof(void *); + } else if (ZVAL_IS_NULL(parameter)) { + /* insert a NULL value into the column */ + P->indicator = -1; /* NULL */ + *bufpp = 0; + *alenp = -1; + } else if (!P->thing) { + /* regular string bind */ + if (!try_convert_to_string(parameter)) { + return OCI_ERROR; + } + *bufpp = Z_STRVAL_P(parameter); + *alenp = (ub4) Z_STRLEN_P(parameter); + } + + *piecep = OCI_ONE_PIECE; + return OCI_CONTINUE; +} /* }}} */ + +static sb4 oci_bind_output_cb(dvoid *ctx, + OCIBind *bindp, + ub4 iter, + ub4 index, + dvoid **bufpp, + ub4 **alenpp, + ub1 *piecep, + dvoid **indpp, + ub2 **rcodepp) /* {{{ */ +{ + struct pdo_bound_param_data *param = (struct pdo_bound_param_data *) ctx; + pdo_oci_bound_param *P = (pdo_oci_bound_param *) param->driver_data; + zval *parameter; + + ZEND_ASSERT(param); + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + P->actual_len = sizeof(OCILobLocator *); + *bufpp = P->thing; + *alenpp = &P->actual_len; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + return OCI_CONTINUE; + } + + if (Z_TYPE_P(parameter) == IS_OBJECT || Z_TYPE_P(parameter) == IS_RESOURCE) { + return OCI_CONTINUE; + } + + zval_ptr_dtor(parameter); + + Z_STR_P(parameter) = zend_string_alloc(param->max_value_len, 1); + P->used_for_output = 1; + + P->actual_len = (ub4) Z_STRLEN_P(parameter); + *alenpp = &P->actual_len; + *bufpp = (Z_STR_P(parameter))->val; + *piecep = OCI_ONE_PIECE; + *rcodepp = &P->retcode; + *indpp = &P->indicator; + + return OCI_CONTINUE; +} /* }}} */ + +static int oci_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + pdo_oci_bound_param *P; + sb4 value_sz = -1; + zval *parameter; + + if (Z_ISREF(param->parameter)) + parameter = Z_REFVAL(param->parameter); + else + parameter = ¶m->parameter; + + P = (pdo_oci_bound_param *) param->driver_data; + + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P && P->thing) { + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + efree(P); + } else if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: + P = (pdo_oci_bound_param *) ecalloc(1, sizeof(pdo_oci_bound_param)); + param->driver_data = P; + + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_LOB: + /* P->thing is now an OCILobLocator * */ + P->oci_type = SQLT_BLOB; + value_sz = (sb4) sizeof(OCILobLocator *); + break; + + case PDO_PARAM_STR: + default: + P->oci_type = SQLT_CHR; + value_sz = (sb4) param->max_value_len; + if (param->max_value_len == 0) { + value_sz = (sb4) 1332; /* maximum size before value is interpreted as a LONG value */ + } + } + + if (param->name) { + STMT_CALL(OCIBindByName, + (S->stmt, + &P->bind, + S->err, + (text *) param->name->val, + (sb4) param->name->len, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } else { + STMT_CALL(OCIBindByPos, + (S->stmt, + &P->bind, + S->err, + ((ub4) param->paramno) + 1, + 0, + value_sz, + P->oci_type, + &P->indicator, + 0, + &P->retcode, + 0, + 0, + OCI_DATA_AT_EXEC)); + } + + STMT_CALL(OCIBindDynamic, (P->bind, S->err, param, oci_bind_input_cb, param, oci_bind_output_cb)); + + return 1; + + case PDO_PARAM_EVT_EXEC_PRE: + P->indicator = 0; + P->used_for_output = 0; + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + ub4 empty = 0; + STMT_CALL(OCIDescriptorAlloc, (S->H->env, &P->thing, OCI_DTYPE_LOB, 0, NULL)); + STMT_CALL(OCIAttrSet, (P->thing, OCI_DTYPE_LOB, &empty, 0, OCI_ATTR_LOBEMPTY, S->err)); + S->have_blobs = 1; + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + /* fixup stuff set in motion in oci_bind_output_cb */ + if (P->used_for_output) { + if (P->indicator == -1) { + /* set up a NULL value */ + if (Z_TYPE_P(parameter) == IS_STRING) { + /* OCI likes to stick non-terminated strings in things */ + *Z_STRVAL_P(parameter) = '\0'; + } + zval_ptr_dtor_str(parameter); + ZVAL_UNDEF(parameter); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + Z_STR_P(parameter) = zend_string_init(Z_STRVAL_P(parameter), P->actual_len, 1); + } + } else if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->thing) { + php_stream *stm; + + if (Z_TYPE_P(parameter) == IS_NULL) { + /* if the param is NULL, then we assume that they + * wanted to bind a lob locator into it from the query + * */ + + stm = oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator *) P->thing); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + php_stream_to_zval(stm, parameter); + } + } else { + /* we're a LOB being used for insert; transfer the data now */ + size_t n; + ub4 amt, offset = 1; + char *consume; + + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + do { + char buf[8192]; + n = php_stream_read(stm, buf, sizeof(buf)); + if ((int) n <= 0) { + break; + } + consume = buf; + do { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + offset += amt; + n -= amt; + consume += amt; + } while (n); + } while (1); + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + OCILobFlushBuffer(S->H->svc, S->err, (OCILobLocator *) P->thing, 0); + } else if (Z_TYPE_P(parameter) == IS_STRING) { + /* stick the string into the LOB */ + consume = Z_STRVAL_P(parameter); + n = Z_STRLEN_P(parameter); + if (n) { + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) P->thing, OCI_LOB_READWRITE); + while (n) { + amt = (ub4) n; + OCILobWrite(S->H->svc, + S->err, + (OCILobLocator *) P->thing, + &amt, + offset, + consume, + (ub4) n, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + consume += amt; + n -= amt; + } + OCILobClose(S->H->svc, S->err, (OCILobLocator *) P->thing); + } + } + OCI_TEMPLOB_CLOSE(S->H->env, S->H->svc, S->H->err, P->thing); + OCIDescriptorFree(P->thing, OCI_DTYPE_LOB); + P->thing = NULL; + } + } + + return 1; + } + } + + return 1; +} /* }}} */ + +static int oci_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) /* {{{ */ +{ + ub4 ociori = OCI_FETCH_NEXT; + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + ociori = OCI_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + ociori = OCI_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + ociori = OCI_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + ociori = OCI_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + ociori = OCI_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + ociori = OCI_FETCH_RELATIVE; + break; + } + S->last_err = OCIStmtFetch2(S->stmt, S->err, 1, ociori, (sb4) offset, OCI_DEFAULT); + + if (S->last_err == OCI_NO_DATA) { + /* no (more) data */ + return 0; + } + + if (S->last_err == OCI_NEED_DATA) { + oci_stmt_error("OCI_NEED_DATA"); + return 0; + } + + if (S->last_err == OCI_SUCCESS_WITH_INFO || S->last_err == OCI_SUCCESS) { + return 1; + } + + oci_stmt_error("OCIStmtFetch"); + + return 0; +} /* }}} */ + +static sb4 oci_define_callback( + dvoid *octxp, OCIDefine *define, ub4 iter, dvoid **bufpp, ub4 **alenpp, ub1 *piecep, dvoid **indpp, ub2 **rcodepp) { + pdo_oci_column *col = (pdo_oci_column *) octxp; + + switch (col->dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + *piecep = OCI_ONE_PIECE; + *bufpp = col->data; + *alenpp = &col->datalen; + *indpp = (dvoid *) &col->indicator; + break; + EMPTY_SWITCH_DEFAULT_CASE(); + } + + return OCI_CONTINUE; +} + +static int oci_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + text *colname; + ub2 dtype, data_size, precis; + ub4 namelen; + struct pdo_column_data *col = &stmt->columns[colno]; + bool dyn = FALSE; + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* what type ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* how big ? */ + STMT_CALL_MSG( + OCIAttrGet, "OCI_ATTR_DATA_SIZE", (param, OCI_DTYPE_PARAM, &data_size, 0, OCI_ATTR_DATA_SIZE, S->err)); + + /* precision ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* name ? */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_NAME", (param, OCI_DTYPE_PARAM, &colname, &namelen, OCI_ATTR_NAME, S->err)); + + col->precision = precis; + col->maxlen = data_size; + col->name = zend_string_init((char *) colname, namelen, 0); + + S->cols[colno].dtype = dtype; + + /* how much room do we need to store the field */ + switch (dtype) { + case SQLT_LBI: + case SQLT_LNG: + if (dtype == SQLT_LBI) { + dtype = SQLT_BIN; + } else { + dtype = SQLT_CHR; + } + S->cols[colno].datalen = 512; /* XXX should be INT_MAX and fetched by pieces */ + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + break; + + case SQLT_BLOB: + case SQLT_CLOB: + STMT_CALL(OCIDescriptorAlloc, (S->H->env, (dvoid **) &S->cols[colno].data, OCI_DTYPE_LOB, 0, NULL)); + S->cols[colno].datalen = sizeof(OCILobLocator *); + dyn = TRUE; + break; + + case SQLT_BIN: + default: + if (dtype == SQLT_DAT || dtype == SQLT_NUM || dtype == SQLT_RDD +#ifdef SQLT_TIMESTAMP + || dtype == SQLT_TIMESTAMP +#endif +#ifdef SQLT_TIMESTAMP_TZ + || dtype == SQLT_TIMESTAMP_TZ +#endif + ) { + /* should be big enough for most date formats and numbers */ + S->cols[colno].datalen = 512; +#if defined(SQLT_IBFLOAT) && defined(SQLT_IBDOUBLE) + } else if (dtype == SQLT_IBFLOAT || dtype == SQLT_IBDOUBLE) { + S->cols[colno].datalen = 1024; +#endif + } else if (dtype == SQLT_BIN) { + S->cols[colno].datalen = (ub4) col->maxlen * 2; /* raw characters to hex digits */ + } else { + S->cols[colno].datalen = (ub4) (col->maxlen * S->H->max_char_width); + } + + S->cols[colno].data = emalloc(S->cols[colno].datalen + 1); + dtype = SQLT_CHR; + } + + STMT_CALL(OCIDefineByPos, + (S->stmt, + &S->cols[colno].def, + S->err, + colno + 1, + S->cols[colno].data, + S->cols[colno].datalen, + dtype, + &S->cols[colno].indicator, + &S->cols[colno].fetched_len, + &S->cols[colno].retcode, + dyn ? OCI_DYNAMIC_FETCH : OCI_DEFAULT)); + + if (dyn) { + STMT_CALL(OCIDefineDynamic, (S->cols[colno].def, S->err, &S->cols[colno], oci_define_callback)); + } + + return 1; +} /* }}} */ + +struct _oci_lob_env { + OCISvcCtx *svc; + OCIError *err; +}; +typedef struct _oci_lob_env oci_lob_env; + +struct oci_lob_self { + zval dbh; + pdo_stmt_t *stmt; + pdo_oci_stmt *S; + OCILobLocator *lob; + oci_lob_env *E; + ub4 offset; + ub1 csfrm; +}; + +static ssize_t oci_blob_write(php_stream *stream, const char *buf, size_t count) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + ub4 amt; + sword r; + + amt = (ub4) count; + r = OCILobWrite(self->E->svc, + self->E->err, + self->lob, + &amt, + self->offset, + (char *) buf, + (ub4) count, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + SQLCS_IMPLICIT); + + if (r != OCI_SUCCESS) { + return (ssize_t) -1; + } + + self->offset += amt; + return amt; +} + +static ssize_t oci_blob_read(php_stream *stream, char *buf, size_t count) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + oraub8 byte_amt = (oraub8) count; + oraub8 char_amt = 0; + + sword r = OCILobRead2(self->E->svc, + self->E->err, + self->lob, + &byte_amt, + &char_amt, + (oraub8) self->offset, + buf, + (oraub8) count, + OCI_ONE_PIECE, + NULL, + NULL, + 0, + self->csfrm); + + if (r != OCI_SUCCESS && r != OCI_NEED_DATA) { + return (ssize_t) -1; + } + + self->offset += self->csfrm == 0 ? byte_amt : char_amt; + if (byte_amt < count) { + stream->eof = 1; + } + return byte_amt; +} + +static int oci_blob_close(php_stream *stream, int close_handle) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + pdo_stmt_t *stmt = self->stmt; + + if (close_handle) { + zend_object *obj = &stmt->std; + + OCILobClose(self->E->svc, self->E->err, self->lob); + zval_ptr_dtor(&self->dbh); + GC_DELREF(obj); + efree(self->E); + efree(self); + } + + /* php_pdo_free_statement(stmt); */ + return 0; +} + +static int oci_blob_flush(php_stream *stream) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + OCILobFlushBuffer(self->E->svc, self->E->err, self->lob, 0); + return 0; +} + +static int oci_blob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + struct oci_lob_self *self = (struct oci_lob_self *) stream->abstract; + + if (offset >= PDO_OCI_LOBMAXSIZE) { + return -1; + } else { + self->offset = (ub4) offset + 1; /* Oracle LOBS are 1-based, but PHP is 0-based */ + return 0; + } +} + +static const php_stream_ops oci_blob_stream_ops = {oci_blob_write, + oci_blob_read, + oci_blob_close, + oci_blob_flush, + "pdo_oci blob stream", + oci_blob_seek, + NULL, + NULL, + NULL}; + +static php_stream *oci_create_lob_stream(zval *dbh, pdo_stmt_t *stmt, OCILobLocator *lob) { + php_stream *stm; + struct oci_lob_self *self = ecalloc(1, sizeof(*self)); + + ZVAL_COPY_VALUE(&self->dbh, dbh); + self->lob = lob; + self->offset = 1; /* 1-based */ + self->stmt = stmt; + self->S = (pdo_oci_stmt *) stmt->driver_data; + self->E = ecalloc(1, sizeof(oci_lob_env)); + self->E->svc = self->S->H->svc; + self->E->err = self->S->err; + + OCILobCharSetForm(self->S->H->env, self->S->err, self->lob, &self->csfrm); + + stm = php_stream_alloc(&oci_blob_stream_ops, self, 0, "r+b"); + + if (stm) { + zend_object *obj; + obj = &stmt->std; + Z_ADDREF(self->dbh); + GC_ADDREF(obj); + return stm; + } + + efree(self); + return NULL; +} + +static int oci_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + pdo_oci_column *C = &S->cols[colno]; + + /* check the indicator to ensure that the data is intact */ + if (C->indicator == -1) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->indicator == 0) { + /* it was stored perfectly */ + + if (C->dtype == SQLT_BLOB || C->dtype == SQLT_CLOB) { + if (C->data) { + php_stream *stream = + oci_create_lob_stream(&stmt->database_object_handle, stmt, (OCILobLocator *) C->data); + OCILobOpen(S->H->svc, S->err, (OCILobLocator *) C->data, OCI_LOB_READONLY); + php_stream_to_zval(stream, result); + return 1; + } + return 0; + } + + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + return 1; + } else { + /* it was truncated */ + php_error_docref(NULL, E_WARNING, "Column %d data was too large for buffer and was truncated to fit it", colno); + ZVAL_STRINGL(result, C->data, C->fetched_len); + return 1; + } +} /* }}} */ + +static int oci_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) /* {{{ */ +{ + pdo_oci_stmt *S = (pdo_oci_stmt *) stmt->driver_data; + OCIParam *param = NULL; + ub2 dtype, precis; + sb1 scale; + zval flags; + ub1 isnull, charset_form; + if (!S->stmt) { + return FAILURE; + } + if (colno >= stmt->column_count) { + /* error invalid column */ + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + /* describe the column */ + STMT_CALL(OCIParamGet, (S->stmt, OCI_HTYPE_STMT, S->err, (dvoid *) ¶m, colno + 1)); + + /* column data type */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_DATA_TYPE", (param, OCI_DTYPE_PARAM, &dtype, 0, OCI_ATTR_DATA_TYPE, S->err)); + + /* column precision */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_PRECISION", (param, OCI_DTYPE_PARAM, &precis, 0, OCI_ATTR_PRECISION, S->err)); + + /* column scale */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_SCALE", (param, OCI_DTYPE_PARAM, &scale, 0, OCI_ATTR_SCALE, S->err)); + + /* string column charset form */ + if (dtype == SQLT_CHR || dtype == SQLT_VCS || dtype == SQLT_AFC || dtype == SQLT_CLOB) { + STMT_CALL_MSG(OCIAttrGet, + "OCI_ATTR_CHARSET_FORM", + (param, OCI_DTYPE_PARAM, &charset_form, 0, OCI_ATTR_CHARSET_FORM, S->err)); + } + + if (dtype) { + /* if there is a declared type */ + switch (dtype) { +#ifdef SQLT_TIMESTAMP + case SQLT_TIMESTAMP: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP"); + add_assoc_string(return_value, "native_type", "TIMESTAMP"); + break; +#endif +#ifdef SQLT_TIMESTAMP_TZ + case SQLT_TIMESTAMP_TZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH TIMEZONE"); + break; +#endif +#ifdef SQLT_TIMESTAMP_LTZ + case SQLT_TIMESTAMP_LTZ: + add_assoc_string(return_value, "oci:decl_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + add_assoc_string(return_value, "native_type", "TIMESTAMP WITH LOCAL TIMEZONE"); + break; +#endif +#ifdef SQLT_INTERVAL_YM + case SQLT_INTERVAL_YM: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL YEAR TO MONTH"); + add_assoc_string(return_value, "native_type", "INTERVAL YEAR TO MONTH"); + break; +#endif +#ifdef SQLT_INTERVAL_DS + case SQLT_INTERVAL_DS: + add_assoc_string(return_value, "oci:decl_type", "INTERVAL DAY TO SECOND"); + add_assoc_string(return_value, "native_type", "INTERVAL DAY TO SECOND"); + break; +#endif + case SQLT_DAT: + add_assoc_string(return_value, "oci:decl_type", "DATE"); + add_assoc_string(return_value, "native_type", "DATE"); + break; + case SQLT_FLT: + case SQLT_NUM: + /* if the precision is nonzero and scale is -127 then it is a FLOAT */ + if (scale == -127 && precis != 0) { + add_assoc_string(return_value, "oci:decl_type", "FLOAT"); + add_assoc_string(return_value, "native_type", "FLOAT"); + } else { + add_assoc_string(return_value, "oci:decl_type", "NUMBER"); + add_assoc_string(return_value, "native_type", "NUMBER"); + } + break; + case SQLT_LNG: + add_assoc_string(return_value, "oci:decl_type", "LONG"); + add_assoc_string(return_value, "native_type", "LONG"); + break; + case SQLT_BIN: + add_assoc_string(return_value, "oci:decl_type", "RAW"); + add_assoc_string(return_value, "native_type", "RAW"); + break; + case SQLT_LBI: + add_assoc_string(return_value, "oci:decl_type", "LONG RAW"); + add_assoc_string(return_value, "native_type", "LONG RAW"); + break; + case SQLT_CHR: + case SQLT_VCS: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NVARCHAR2"); + add_assoc_string(return_value, "native_type", "NVARCHAR2"); + } else { + add_assoc_string(return_value, "oci:decl_type", "VARCHAR2"); + add_assoc_string(return_value, "native_type", "VARCHAR2"); + } + break; + case SQLT_AFC: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCHAR"); + add_assoc_string(return_value, "native_type", "NCHAR"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CHAR"); + add_assoc_string(return_value, "native_type", "CHAR"); + } + break; + case SQLT_BLOB: + add_assoc_string(return_value, "oci:decl_type", "BLOB"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BLOB"); + break; + case SQLT_CLOB: + if (charset_form == SQLCS_NCHAR) { + add_assoc_string(return_value, "oci:decl_type", "NCLOB"); + add_assoc_string(return_value, "native_type", "NCLOB"); + } else { + add_assoc_string(return_value, "oci:decl_type", "CLOB"); + add_assoc_string(return_value, "native_type", "CLOB"); + } + add_next_index_string(&flags, "blob"); + break; + case SQLT_BFILE: + add_assoc_string(return_value, "oci:decl_type", "BFILE"); + add_next_index_string(&flags, "blob"); + add_assoc_string(return_value, "native_type", "BFILE"); + break; + case SQLT_RDD: + add_assoc_string(return_value, "oci:decl_type", "ROWID"); + add_assoc_string(return_value, "native_type", "ROWID"); + break; + case SQLT_BFLOAT: + case SQLT_IBFLOAT: + add_assoc_string(return_value, "oci:decl_type", "BINARY_FLOAT"); + add_assoc_string(return_value, "native_type", "BINARY_FLOAT"); + break; + case SQLT_BDOUBLE: + case SQLT_IBDOUBLE: + add_assoc_string(return_value, "oci:decl_type", "BINARY_DOUBLE"); + add_assoc_string(return_value, "native_type", "BINARY_DOUBLE"); + break; + default: + add_assoc_long(return_value, "oci:decl_type", dtype); + add_assoc_string(return_value, "native_type", "UNKNOWN"); + } + } else { + /* if the column is NULL */ + add_assoc_long(return_value, "oci:decl_type", 0); + add_assoc_string(return_value, "native_type", "NULL"); + } + + switch (dtype) { + case SQLT_BLOB: + case SQLT_CLOB: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); + break; + default: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + } + + /* column can be null */ + STMT_CALL_MSG(OCIAttrGet, "OCI_ATTR_IS_NULL", (param, OCI_DTYPE_PARAM, &isnull, 0, OCI_ATTR_IS_NULL, S->err)); + + if (isnull) { + add_next_index_string(&flags, "nullable"); + } else { + add_next_index_string(&flags, "not_null"); + } + + /* PDO type */ + switch (dtype) { + case SQLT_BFILE: + case SQLT_BLOB: + case SQLT_CLOB: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_LOB); + break; + default: + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + } + + add_assoc_long(return_value, "scale", scale); + add_assoc_zval(return_value, "flags", &flags); + + OCIDescriptorFree(param, OCI_DTYPE_PARAM); + return SUCCESS; +} /* }}} */ + +const struct pdo_stmt_methods swoole_oci_stmt_methods = {oci_stmt_dtor, + oci_stmt_execute, + oci_stmt_fetch, + oci_stmt_describe, + oci_stmt_get_col, + oci_stmt_param_hook, + NULL, /* set_attr */ + NULL, /* get_attr */ + oci_stmt_col_meta, + NULL, + NULL}; +#endif diff --git a/thirdparty/php84/pdo_oci/php_pdo_oci_int.h b/thirdparty/php84/pdo_oci/php_pdo_oci_int.h new file mode 100644 index 00000000000..de3b75d1777 --- /dev/null +++ b/thirdparty/php84/pdo_oci/php_pdo_oci_int.h @@ -0,0 +1,105 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#include + +typedef struct { + const char *file; + int line; + sb4 errcode; + char *errmsg; +} pdo_oci_error_info; + +/* stuff we use in an OCI database handle */ +typedef struct { + OCIServer *server; + OCISession *session; + OCIEnv *env; + OCIError *err; + OCISvcCtx *svc; + /* OCI9; 0 == use NLS_LANG */ + ub4 prefetch; + ub2 charset; + sword last_err; + sb4 max_char_width; + + unsigned attached : 1; + unsigned _reserved : 31; + + pdo_oci_error_info einfo; +} pdo_oci_db_handle; + +typedef struct { + OCIDefine *def; + ub2 fetched_len; + ub2 retcode; + sb2 indicator; + + char *data; + ub4 datalen; + + ub2 dtype; + +} pdo_oci_column; + +typedef struct { + pdo_oci_db_handle *H; + OCIStmt *stmt; + OCIError *err; + sword last_err; + ub2 stmt_type; + ub4 exec_type; + pdo_oci_column *cols; + pdo_oci_error_info einfo; + unsigned int have_blobs : 1; +} pdo_oci_stmt; + +typedef struct { + OCIBind *bind; /* allocated by OCI */ + sb2 oci_type; + sb2 indicator; + ub2 retcode; + + ub4 actual_len; + + dvoid *thing; /* for LOBS, REFCURSORS etc. */ + + unsigned used_for_output; +} pdo_oci_bound_param; + +extern const struct pdo_stmt_methods swoole_oci_stmt_methods; +extern const ub4 SWOOLE_PDO_OCI_INIT_MODE; +extern OCIEnv *swoole_pdo_oci_Env; + +ub4 _oci_error( + OCIError *err, pdo_dbh_t *dbh, pdo_stmt_t *stmt, char *what, sword status, int isinit, const char *file, int line); +#define oci_init_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, TRUE, __FILE__, __LINE__) +#define oci_drv_error(w) _oci_error(H->err, dbh, NULL, w, H->last_err, FALSE, __FILE__, __LINE__) +#define oci_stmt_error(w) _oci_error(S->err, stmt->dbh, stmt, w, S->last_err, FALSE, __FILE__, __LINE__) + +/* Default prefetch size in number of rows */ +#define PDO_OCI_PREFETCH_DEFAULT 100 + +/* Arbitrary assumed row length for prefetch memory limit calcuation */ +#define PDO_OCI_PREFETCH_ROWSIZE 1024 + +enum { + PDO_OCI_ATTR_ACTION = PDO_ATTR_DRIVER_SPECIFIC, + PDO_OCI_ATTR_CLIENT_INFO, + PDO_OCI_ATTR_CLIENT_IDENTIFIER, + PDO_OCI_ATTR_MODULE, + PDO_OCI_ATTR_CALL_TIMEOUT +}; diff --git a/thirdparty/php84/pdo_odbc/odbc_driver.c b/thirdparty/php84/pdo_odbc/odbc_driver.c new file mode 100644 index 00000000000..f410d42050c --- /dev/null +++ b/thirdparty/php84/pdo_odbc/odbc_driver.c @@ -0,0 +1,638 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" +#include "zend_exceptions.h" +#include + +static void pdo_odbc_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + zend_string *message = NULL; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + einfo = &S->einfo; + } + + message = strpprintf(0, + "%s (%s[%ld] at %s:%d)", + einfo->last_err_msg, + einfo->what, + (long) einfo->last_error, + einfo->file, + einfo->line); + + add_next_index_long(info, einfo->last_error); + add_next_index_str(info, message); + add_next_index_string(info, einfo->last_state); +} + +void pdo_odbc_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line) /* {{{ */ +{ + SQLRETURN rc; + SQLSMALLINT errmsgsize = 0; + SQLHANDLE eh; + SQLSMALLINT htype, recno = 1; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_errinfo *einfo = &H->einfo; + pdo_odbc_stmt *S = NULL; + pdo_error_type *pdo_err = &dbh->error_code; + + if (stmt) { + S = (pdo_odbc_stmt *) stmt->driver_data; + + einfo = &S->einfo; + pdo_err = &stmt->error_code; + } + + if (statement == SQL_NULL_HSTMT && S) { + statement = S->stmt; + } + + if (statement) { + htype = SQL_HANDLE_STMT; + eh = statement; + } else if (H->dbc) { + htype = SQL_HANDLE_DBC; + eh = H->dbc; + } else { + htype = SQL_HANDLE_ENV; + eh = H->env; + } + + rc = SQLGetDiagRec(htype, + eh, + recno++, + (SQLCHAR *) einfo->last_state, + &einfo->last_error, + (SQLCHAR *) einfo->last_err_msg, + sizeof(einfo->last_err_msg) - 1, + &errmsgsize); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + errmsgsize = 0; + } + + einfo->last_err_msg[errmsgsize] = '\0'; + einfo->file = file; + einfo->line = line; + einfo->what = what; + + strcpy(*pdo_err, einfo->last_state); + /* printf("@@ SQLSTATE[%s] %s\n", *pdo_err, einfo->last_err_msg); */ + if (!dbh->methods) { + zend_throw_exception_ex(php_pdo_get_exception(), + einfo->last_error, + "SQLSTATE[%s] %s: %d %s", + *pdo_err, + what, + einfo->last_error, + einfo->last_err_msg); + } + + /* just like a cursor, once you start pulling, you need to keep + * going until the end; SQL Server (at least) will mess with the + * actual cursor state if you don't finish retrieving all the + * diagnostic records (which can be generated by PRINT statements + * in the query, for instance). */ + while (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + SQLCHAR discard_state[6]; + SQLCHAR discard_buf[1024]; + SQLINTEGER code; + rc = SQLGetDiagRec(htype, eh, recno++, discard_state, &code, discard_buf, sizeof(discard_buf) - 1, &errmsgsize); + } +} +/* }}} */ + +static void odbc_handle_closer(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + if (H->dbc != SQL_NULL_HANDLE) { + SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + SQLDisconnect(H->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, H->dbc); + H->dbc = NULL; + } + SQLFreeHandle(SQL_HANDLE_ENV, H->env); + H->env = NULL; + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; +} + +static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + pdo_odbc_stmt *S = ecalloc(1, sizeof(*S)); + enum pdo_cursor_type cursor_type = PDO_CURSOR_FWDONLY; + int ret; + zend_string *nsql = NULL; + + S->H = H; + S->assume_utf8 = H->assume_utf8; + + /* before we prepare, we need to peek at the query; if it uses named parameters, + * we want PDO to rewrite them for us */ + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == 1) { + /* query was re-written */ + sql = nsql; + } else if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + efree(S); + return false; + } + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &S->stmt); + + if (rc == SQL_INVALID_HANDLE || rc == SQL_ERROR) { + efree(S); + if (nsql) { + zend_string_release(nsql); + } + pdo_odbc_drv_error("SQLAllocStmt"); + return false; + } + + stmt->driver_data = S; + + cursor_type = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY); + if (cursor_type != PDO_CURSOR_FWDONLY) { + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_CURSOR_SCROLLABLE, (void *) SQL_SCROLLABLE, 0); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_CURSOR_SCROLLABLE"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + zend_string_release(nsql); + } + return false; + } + } + + rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS); + if (nsql) { + zend_string_release(nsql); + } + + stmt->methods = &odbc_stmt_methods; + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLPrepare"); + if (rc != SQL_SUCCESS_WITH_INFO) { + /* clone error information into the db handle */ + strcpy(H->einfo.last_err_msg, S->einfo.last_err_msg); + H->einfo.file = S->einfo.file; + H->einfo.line = S->einfo.line; + H->einfo.what = S->einfo.what; + strcpy(dbh->error_code, stmt->error_code); + } + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + return true; +} + +static zend_long odbc_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + SQLLEN row_count = -1; + PDO_ODBC_HSTMT stmt; + + rc = SQLAllocHandle(SQL_HANDLE_STMT, H->dbc, &stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: STMT"); + return -1; + } + + rc = SQLExecDirect(stmt, (SQLCHAR *) ZSTR_VAL(sql), ZSTR_LEN(sql)); + + if (rc == SQL_NO_DATA) { + /* If SQLExecDirect executes a searched update or delete statement that + * does not affect any rows at the data source, the call to + * SQLExecDirect returns SQL_NO_DATA. */ + row_count = 0; + goto out; + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLExecDirect"); + goto out; + } + + rc = SQLRowCount(stmt, &row_count); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_doer_error("SQLRowCount"); + goto out; + } + if (row_count == -1) { + row_count = 0; + } +out: + SQLFreeHandle(SQL_HANDLE_STMT, stmt); + return row_count; +} + +/* TODO: Do ODBC quoter +static int odbc_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t +*quotedlen, enum pdo_param_type param_type ) +{ + // pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; + // TODO: figure it out + return 0; +} +*/ + +static bool odbc_handle_begin(pdo_dbh_t *dbh) { + if (dbh->auto_commit) { + /* we need to disable auto-commit now, to be able to initiate a transaction */ + RETCODE rc; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_OFF, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; +} + +static bool odbc_handle_commit(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_COMMIT); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Commit"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + + if (dbh->auto_commit) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + return true; +} + +static bool odbc_handle_rollback(pdo_dbh_t *dbh) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + RETCODE rc; + + rc = SQLEndTran(SQL_HANDLE_DBC, H->dbc, SQL_ROLLBACK); + + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLEndTran: Rollback"); + + if (rc != SQL_SUCCESS_WITH_INFO) { + return false; + } + } + if (dbh->auto_commit && H->dbc) { + /* turn auto-commit back on again */ + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT = ON"); + return false; + } + } + + return true; +} + +static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + bool bval; + + switch (attr) { + case PDO_ODBC_ATTR_ASSUME_UTF8: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->assume_utf8 = bval; + return true; + case PDO_ATTR_AUTOCOMMIT: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + if (dbh->in_txn) { + pdo_raise_impl_error( + dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + if (dbh->auto_commit ^ bval) { + dbh->auto_commit = bval; + RETCODE rc = + SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + dbh->auto_commit ? (SQLPOINTER) SQL_AUTOCOMMIT_ON : (SQLPOINTER) SQL_AUTOCOMMIT_OFF, + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error(dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" + : "SQLSetConnectAttr AUTOCOMMIT = OFF"); + return false; + } + } + return true; + default: + strcpy(H->einfo.last_err_msg, "Unknown Attribute"); + H->einfo.what = "setAttribute"; + strcpy(H->einfo.last_state, "IM001"); + return false; + } +} + +static int pdo_odbc_get_info_string(pdo_dbh_t *dbh, SQLUSMALLINT type, zval *val) { + RETCODE rc; + SQLSMALLINT out_len; + char buf[256]; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + rc = SQLGetInfo(H->dbc, type, (SQLPOINTER) buf, sizeof(buf), &out_len); + /* returning -1 is treated as an error, not as unsupported */ + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return -1; + } + ZVAL_STRINGL(val, buf, out_len); + return 1; +} + +static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + ZVAL_STRING(val, "ODBC-" PDO_ODBC_TYPE); + return 1; + + case PDO_ATTR_SERVER_VERSION: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val); + case PDO_ATTR_SERVER_INFO: + return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val); + case PDO_ATTR_PREFETCH: + case PDO_ATTR_TIMEOUT: + case PDO_ATTR_CONNECTION_STATUS: + break; + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, H->assume_utf8); + return 1; + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val, dbh->auto_commit); + return 1; + } + return 0; +} + +static zend_result odbc_handle_check_liveness(pdo_dbh_t *dbh) { + RETCODE ret; + UCHAR d_name[32]; + SQLSMALLINT len; + SQLUINTEGER dead = SQL_CD_FALSE; + pdo_odbc_db_handle *H = (pdo_odbc_db_handle *) dbh->driver_data; + + ret = SQLGetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_DEAD, &dead, 0, NULL); + if (ret == SQL_SUCCESS && dead == SQL_CD_TRUE) { + /* Bail early here, since we know it's gone */ + return FAILURE; + } + /* + * If the driver doesn't support SQL_ATTR_CONNECTION_DEAD, or if + * it returns false (which could be a false positive), fall back + * to using SQL_DATA_SOURCE_READ_ONLY, which isn't semantically + * correct, but works with many drivers. + */ + ret = SQLGetInfo(H->dbc, SQL_DATA_SOURCE_READ_ONLY, d_name, sizeof(d_name), &len); + + if (ret != SQL_SUCCESS || len == 0) { + return FAILURE; + } + return SUCCESS; +} + +static const struct pdo_dbh_methods odbc_methods = { + odbc_handle_closer, + odbc_handle_preparer, + odbc_handle_doer, + NULL, /* quoter */ + odbc_handle_begin, + odbc_handle_commit, + odbc_handle_rollback, + odbc_handle_set_attr, + NULL, /* last id */ + pdo_odbc_fetch_error_func, + odbc_handle_get_attr, /* get attr */ + odbc_handle_check_liveness, /* check_liveness */ + NULL, /* get_driver_methods */ + NULL, /* request_shutdown */ + NULL, /* in transaction, use PDO's internal tracking mechanism */ + NULL, /* get_gc */ + NULL /* scanner */ +}; + +static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_odbc_db_handle *H; + RETCODE rc; + int use_direct = 0; + zend_ulong cursor_lib; + + H = pecalloc(1, sizeof(*H), dbh->is_persistent); + + dbh->driver_data = H; + + rc = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &H->env); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: ENV"); + goto fail; + } + + rc = SQLSetEnvAttr(H->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLSetEnvAttr: ODBC3"); + goto fail; + } + +#ifdef SQL_ATTR_CONNECTION_POOLING + if (pdo_odbc_pool_on != SQL_CP_OFF) { + rc = SQLSetEnvAttr(H->env, SQL_ATTR_CP_MATCH, (void *) pdo_odbc_pool_mode, 0); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetEnvAttr: SQL_ATTR_CP_MATCH"); + goto fail; + } + } +#endif + + rc = SQLAllocHandle(SQL_HANDLE_DBC, H->env, &H->dbc); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error("SQLAllocHandle: DBC"); + goto fail; + } + + rc = SQLSetConnectAttr(H->dbc, + SQL_ATTR_AUTOCOMMIT, + (SQLPOINTER) (intptr_t) (dbh->auto_commit ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF), + SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr AUTOCOMMIT"); + goto fail; + } + + /* set up the cursor library, if needed, or if configured explicitly */ + cursor_lib = pdo_attr_lval(driver_options, PDO_ODBC_ATTR_USE_CURSOR_LIBRARY, SQL_CUR_USE_IF_NEEDED); + rc = SQLSetConnectAttr(H->dbc, SQL_ODBC_CURSORS, (void *) cursor_lib, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && cursor_lib != SQL_CUR_USE_IF_NEEDED) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ODBC_CURSORS"); + goto fail; + } + + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ + if (strchr(dbh->data_source, '=')) { + SQLCHAR dsnbuf[1024]; + SQLSMALLINT dsnbuflen; + + use_direct = 1; + + size_t db_len = strlen(dbh->data_source); + bool use_uid_arg = + dbh->username != NULL && !php_memnistr(dbh->data_source, "uid=", strlen("uid="), dbh->data_source + db_len); + bool use_pwd_arg = + dbh->password != NULL && !php_memnistr(dbh->data_source, "pwd=", strlen("pwd="), dbh->data_source + db_len); + + if (use_uid_arg || use_pwd_arg) { + char *db = (char *) emalloc(db_len + 1); + strcpy(db, dbh->data_source); + char *db_end = db + db_len; + db_end--; + if ((unsigned char) *(db_end) == ';') { + *db_end = '\0'; + } + + char *uid = NULL, *pwd = NULL, *dsn = NULL; + bool should_quote_uid, should_quote_pwd; + size_t new_dsn_size; + + if (use_uid_arg) { + should_quote_uid = + !php_odbc_connstr_is_quoted(dbh->username) && php_odbc_connstr_should_quote(dbh->username); + if (should_quote_uid) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->username); + uid = emalloc(estimated_length); + php_odbc_connstr_quote(uid, dbh->username, estimated_length); + } else { + uid = dbh->username; + } + + if (!use_pwd_arg) { + new_dsn_size = strlen(db) + strlen(uid) + strlen(";UID=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;", db, uid); + } + } + + if (use_pwd_arg) { + should_quote_pwd = + !php_odbc_connstr_is_quoted(dbh->password) && php_odbc_connstr_should_quote(dbh->password); + if (should_quote_pwd) { + size_t estimated_length = php_odbc_connstr_estimate_quote_length(dbh->password); + pwd = emalloc(estimated_length); + php_odbc_connstr_quote(pwd, dbh->password, estimated_length); + } else { + pwd = dbh->password; + } + + if (!use_uid_arg) { + new_dsn_size = strlen(db) + strlen(pwd) + strlen(";PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;PWD=%s;", db, pwd); + } + } + + if (use_uid_arg && use_pwd_arg) { + new_dsn_size = strlen(db) + strlen(uid) + strlen(pwd) + strlen(";UID=;PWD=;") + 1; + dsn = pemalloc(new_dsn_size, dbh->is_persistent); + snprintf(dsn, new_dsn_size, "%s;UID=%s;PWD=%s;", db, uid, pwd); + } + + pefree((char *) dbh->data_source, dbh->is_persistent); + dbh->data_source = dsn; + if (uid && should_quote_uid) { + efree(uid); + } + if (pwd && should_quote_pwd) { + efree(pwd); + } + efree(db); + } + + rc = SQLDriverConnect(H->dbc, + NULL, + (SQLCHAR *) dbh->data_source, + strlen(dbh->data_source), + dsnbuf, + sizeof(dsnbuf) - 1, + &dsnbuflen, + SQL_DRIVER_NOPROMPT); + } + if (!use_direct) { + rc = SQLConnect(H->dbc, + (SQLCHAR *) dbh->data_source, + SQL_NTS, + (SQLCHAR *) dbh->username, + SQL_NTS, + (SQLCHAR *) dbh->password, + SQL_NTS); + } + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_drv_error(use_direct ? "SQLDriverConnect" : "SQLConnect"); + goto fail; + } + + /* TODO: if we want to play nicely, we should check to see if the driver really supports ODBC v3 or not */ + + dbh->methods = &odbc_methods; + dbh->alloc_own_columns = 1; + + return 1; + +fail: + dbh->methods = &odbc_methods; + return 0; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_odbc_driver = {PDO_DRIVER_HEADER(odbc), pdo_odbc_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_odbc/odbc_stmt.c b/thirdparty/php84/pdo_odbc/odbc_stmt.c new file mode 100644 index 00000000000..8fe11cf2c6a --- /dev/null +++ b/thirdparty/php84/pdo_odbc/odbc_stmt.c @@ -0,0 +1,865 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_ODBC_HOOK +#include "php_swoole_odbc.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "pdo/php_pdo_driver.h" + +enum pdo_odbc_conv_result { PDO_ODBC_CONV_NOT_REQUIRED, PDO_ODBC_CONV_OK, PDO_ODBC_CONV_FAIL }; + +static int pdo_odbc_sqltype_is_unicode(pdo_odbc_stmt *S, SQLSMALLINT sqltype) { + if (!S->assume_utf8) return 0; + switch (sqltype) { +#ifdef SQL_WCHAR + case SQL_WCHAR: + return 1; +#endif +#ifdef SQL_WLONGVARCHAR + case SQL_WLONGVARCHAR: + return 1; +#endif +#ifdef SQL_WVARCHAR + case SQL_WVARCHAR: + return 1; +#endif + default: + return 0; + } +} + +static int pdo_odbc_utf82ucs2( + pdo_stmt_t *stmt, int is_unicode, const char *buf, zend_ulong buflen, zend_ulong *outlen) { +#ifdef PHP_WIN32 + if (is_unicode && buflen) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, NULL, 0); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + + if (S->convbufsize <= ret) { + S->convbufsize = ret + sizeof(WCHAR); + S->convbuf = erealloc(S->convbuf, S->convbufsize); + } + + ret = MultiByteToWideChar(CP_UTF8, 0, buf, buflen, (LPWSTR) S->convbuf, S->convbufsize / sizeof(WCHAR)); + if (ret == 0) { + /*printf("%s:%d %d [%d] %.*s\n", __FILE__, __LINE__, GetLastError(), buflen, buflen, buf);*/ + return PDO_ODBC_CONV_FAIL; + } + + ret *= sizeof(WCHAR); + *outlen = ret; + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static int pdo_odbc_ucs22utf8(pdo_stmt_t *stmt, int is_unicode, zval *result) { +#ifdef PHP_WIN32 + ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING); + if (is_unicode && Z_STRLEN_P(result) != 0) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + DWORD ret; + + ret = WideCharToMultiByte( + CP_UTF8, 0, (LPCWSTR) Z_STRVAL_P(result), Z_STRLEN_P(result) / sizeof(WCHAR), NULL, 0, NULL, NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + zend_string *str = zend_string_alloc(ret, 0); + ret = WideCharToMultiByte(CP_UTF8, + 0, + (LPCWSTR) Z_STRVAL_P(result), + Z_STRLEN_P(result) / sizeof(WCHAR), + ZSTR_VAL(str), + ZSTR_LEN(str), + NULL, + NULL); + if (ret == 0) { + return PDO_ODBC_CONV_FAIL; + } + + ZSTR_VAL(str)[ret] = '\0'; + zval_ptr_dtor_str(result); + ZVAL_STR(result, str); + return PDO_ODBC_CONV_OK; + } +#endif + return PDO_ODBC_CONV_NOT_REQUIRED; +} + +static void free_cols(pdo_stmt_t *stmt, pdo_odbc_stmt *S) { + if (S->cols) { + int i; + + for (i = 0; i < S->col_count; i++) { + if (S->cols[i].data) { + efree(S->cols[i].data); + } + } + efree(S->cols); + S->cols = NULL; + S->col_count = 0; + } +} + +static int odbc_stmt_dtor(pdo_stmt_t *stmt) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + if (S->stmt != SQL_NULL_HANDLE) { + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + S->stmt = SQL_NULL_HANDLE; + } + + free_cols(stmt, S); + if (S->convbuf) { + efree(S->convbuf); + } + efree(S); + + return 1; +} + +static int odbc_stmt_execute(pdo_stmt_t *stmt) { + RETCODE rc, rc1; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + char *buf = NULL; + SQLLEN row_count = -1; + + if (stmt->executed) { + SQLCloseCursor(S->stmt); + } + + rc = SQLExecute(S->stmt); + + while (rc == SQL_NEED_DATA) { + struct pdo_bound_param_data *param; + + rc = SQLParamData(S->stmt, (SQLPOINTER *) ¶m); + if (rc == SQL_NEED_DATA) { + php_stream *stm; + int len; + pdo_odbc_param *P; + zval *parameter; + + P = (pdo_odbc_param *) param->driver_data; + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) != IS_RESOURCE) { + /* they passed in a string */ + zend_ulong ulen; + convert_to_string(parameter); + + switch (pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_NOT_REQUIRED: + rc1 = SQLPutData(S->stmt, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter)); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_OK: + rc1 = SQLPutData(S->stmt, S->convbuf, ulen); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + break; + case PDO_ODBC_CONV_FAIL: + pdo_odbc_stmt_error("error converting input string"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + continue; + } + + /* we assume that LOBs are binary and don't need charset + * conversion */ + + php_stream_from_zval_no_verify(stm, parameter); + if (!stm) { + /* shouldn't happen either */ + pdo_odbc_stmt_error("input LOB is no longer a stream"); + SQLCloseCursor(S->stmt); + if (buf) { + efree(buf); + } + return 0; + } + + /* now suck data from the stream and stick it into the database */ + if (buf == NULL) { + buf = emalloc(8192); + } + + do { + len = php_stream_read(stm, buf, 8192); + if (len == 0) { + break; + } + rc1 = SQLPutData(S->stmt, buf, len); + if (rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO) { + rc = rc1; + } + } while (1); + } + } + + if (buf) { + efree(buf); + } + + switch (rc) { + case SQL_SUCCESS: + break; + case SQL_NO_DATA_FOUND: + case SQL_SUCCESS_WITH_INFO: + pdo_odbc_stmt_error("SQLExecute"); + break; + + default: + pdo_odbc_stmt_error("SQLExecute"); + return 0; + } + + SQLRowCount(S->stmt, &row_count); + stmt->row_count = row_count; + + if (S->cols == NULL) { + /* do first-time-only definition of bind/mapping stuff */ + SQLSMALLINT colcount; + + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + } + + return 1; +} + +static int odbc_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, enum pdo_param_event event_type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + RETCODE rc; + SQLSMALLINT sqltype = 0, ctype = 0, scale = 0, nullable = 0; + SQLULEN precision = 0; + pdo_odbc_param *P; + zval *parameter; + + /* we're only interested in parameters for prepared SQL right now */ + if (param->is_param) { + switch (event_type) { + case PDO_PARAM_EVT_FETCH_PRE: + case PDO_PARAM_EVT_FETCH_POST: + case PDO_PARAM_EVT_NORMALIZE: + /* Do nothing */ + break; + + case PDO_PARAM_EVT_FREE: + P = param->driver_data; + if (P) { + efree(P); + } + break; + + case PDO_PARAM_EVT_ALLOC: { + /* figure out what we're doing */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_LOB: + break; + + case PDO_PARAM_STMT: + return 0; + + default: + break; + } + + rc = SQLDescribeParam(S->stmt, (SQLUSMALLINT) param->paramno + 1, &sqltype, &precision, &scale, &nullable); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + /* MS Access, for instance, doesn't support SQLDescribeParam, + * so we need to guess */ + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_INT: + sqltype = SQL_INTEGER; + break; + case PDO_PARAM_LOB: + sqltype = SQL_LONGVARBINARY; + break; + default: + sqltype = SQL_LONGVARCHAR; + } + precision = 4000; + scale = 5; + nullable = 1; + + if (param->max_value_len > 0) { + precision = param->max_value_len; + } + } + if (sqltype == SQL_BINARY || sqltype == SQL_VARBINARY || sqltype == SQL_LONGVARBINARY) { + ctype = SQL_C_BINARY; + } else { + ctype = SQL_C_CHAR; + } + + P = emalloc(sizeof(*P)); + param->driver_data = P; + + P->len = 0; /* is re-populated each EXEC_PRE */ + P->outbuf = NULL; + + P->is_unicode = pdo_odbc_sqltype_is_unicode(S, sqltype); + if (P->is_unicode) { + /* avoid driver auto-translation: we'll do it ourselves */ + ctype = SQL_C_BINARY; + } + + if ((param->param_type & PDO_PARAM_INPUT_OUTPUT) == PDO_PARAM_INPUT_OUTPUT) { + P->paramtype = SQL_PARAM_INPUT_OUTPUT; + } else if (param->max_value_len <= 0) { + P->paramtype = SQL_PARAM_INPUT; + } else { + P->paramtype = SQL_PARAM_OUTPUT; + } + + if (P->paramtype != SQL_PARAM_INPUT) { + if (PDO_PARAM_TYPE(param->param_type) != PDO_PARAM_NULL) { + /* need an explicit buffer to hold result */ + P->len = param->max_value_len > 0 ? param->max_value_len : precision; + if (P->is_unicode) { + P->len *= 2; + } + P->outbuf = emalloc(P->len + (P->is_unicode ? 2 : 1)); + } + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && P->paramtype != SQL_PARAM_INPUT) { + pdo_odbc_stmt_error("Can't bind a lob for output"); + return 0; + } + + rc = SQLBindParameter(S->stmt, + (SQLUSMALLINT) param->paramno + 1, + P->paramtype, + ctype, + sqltype, + precision, + scale, + P->paramtype == SQL_PARAM_INPUT ? (SQLPOINTER) param : P->outbuf, + P->len, + &P->len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLBindParameter"); + return 0; + } + + case PDO_PARAM_EVT_EXEC_PRE: + P = param->driver_data; + if (!Z_ISREF(param->parameter)) { + parameter = ¶m->parameter; + } else { + parameter = Z_REFVAL(param->parameter); + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB) { + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_statbuf sb; + + php_stream_from_zval_no_verify(stm, parameter); + + if (!stm) { + return 0; + } + + if (0 == php_stream_stat(stm, &sb)) { + if (P->outbuf) { + int len, amount; + char *ptr = P->outbuf; + char *end = P->outbuf + P->len; + + P->len = 0; + do { + amount = end - ptr; + if (amount == 0) { + break; + } + if (amount > 8192) amount = 8192; + len = php_stream_read(stm, ptr, amount); + if (len == 0) { + break; + } + ptr += len; + P->len += len; + } while (1); + + } else { + P->len = SQL_LEN_DATA_AT_EXEC(sb.sb.st_size); + } + } else { + if (P->outbuf) { + P->len = 0; + } else { + P->len = SQL_LEN_DATA_AT_EXEC(0); + } + } + } else { + convert_to_string(parameter); + if (P->outbuf) { + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + } else if (Z_TYPE_P(parameter) == IS_NULL || PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_NULL) { + P->len = SQL_NULL_DATA; + } else { + convert_to_string(parameter); + if (P->outbuf) { + zend_ulong ulen; + switch ( + pdo_odbc_utf82ucs2(stmt, P->is_unicode, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &ulen)) { + case PDO_ODBC_CONV_FAIL: + case PDO_ODBC_CONV_NOT_REQUIRED: + P->len = Z_STRLEN_P(parameter); + memcpy(P->outbuf, Z_STRVAL_P(parameter), P->len); + break; + case PDO_ODBC_CONV_OK: + P->len = ulen; + memcpy(P->outbuf, S->convbuf, P->len); + break; + } + } else { + P->len = SQL_LEN_DATA_AT_EXEC(Z_STRLEN_P(parameter)); + } + } + return 1; + + case PDO_PARAM_EVT_EXEC_POST: + P = param->driver_data; + + if (P->outbuf) { + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + + if (P->len >= 0) { + ZVAL_STRINGL(parameter, P->outbuf, P->len); + switch (pdo_odbc_ucs22utf8(stmt, P->is_unicode, parameter)) { + case PDO_ODBC_CONV_FAIL: + /* something fishy, but allow it to come back as binary */ + case PDO_ODBC_CONV_NOT_REQUIRED: + break; + case PDO_ODBC_CONV_OK: + break; + } + } else { + ZVAL_NULL(parameter); + } + } + return 1; + } + } + return 1; +} + +static int odbc_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + RETCODE rc; + SQLSMALLINT odbcori; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + odbcori = SQL_FETCH_NEXT; + break; + case PDO_FETCH_ORI_PRIOR: + odbcori = SQL_FETCH_PRIOR; + break; + case PDO_FETCH_ORI_FIRST: + odbcori = SQL_FETCH_FIRST; + break; + case PDO_FETCH_ORI_LAST: + odbcori = SQL_FETCH_LAST; + break; + case PDO_FETCH_ORI_ABS: + odbcori = SQL_FETCH_ABSOLUTE; + break; + case PDO_FETCH_ORI_REL: + odbcori = SQL_FETCH_RELATIVE; + break; + default: + strcpy(stmt->error_code, "HY106"); + return 0; + } + rc = SQLFetchScroll(S->stmt, odbcori, offset); + + if (rc == SQL_SUCCESS) { + return 1; + } + if (rc == SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLFetchScroll"); + return 1; + } + + if (rc == SQL_NO_DATA) { + /* pdo_odbc_stmt_error("SQLFetchScroll"); */ + return 0; + } + + pdo_odbc_stmt_error("SQLFetchScroll"); + + return 0; +} + +static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + RETCODE rc; + SQLSMALLINT colnamelen; + SQLULEN colsize; + SQLLEN displaysize = 0; + + rc = SQLDescribeCol(S->stmt, + colno + 1, + (SQLCHAR *) S->cols[colno].colname, + sizeof(S->cols[colno].colname) - 1, + &colnamelen, + &S->cols[colno].coltype, + &colsize, + NULL, + NULL); + + /* This fixes a known issue with SQL Server and (max) lengths, + may affect others as well. If we are SQL_VARCHAR, + SQL_VARBINARY, or SQL_WVARCHAR (or any of the long variations) + and zero is returned from colsize then consider it long */ + if (0 == colsize && (S->cols[colno].coltype == SQL_VARCHAR || S->cols[colno].coltype == SQL_LONGVARCHAR || +#ifdef SQL_WVARCHAR + S->cols[colno].coltype == SQL_WVARCHAR || +#endif +#ifdef SQL_WLONGVARCHAR + S->cols[colno].coltype == SQL_WLONGVARCHAR || +#endif + S->cols[colno].coltype == SQL_VARBINARY || S->cols[colno].coltype == SQL_LONGVARBINARY)) { + S->going_long = 1; + } + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLDescribeCol"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + + rc = SQLColAttribute(S->stmt, colno + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &displaysize); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLColAttribute"); + if (rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + } + colsize = displaysize; + + col->maxlen = S->cols[colno].datalen = colsize; + col->name = zend_string_init(S->cols[colno].colname, colnamelen, 0); + S->cols[colno].is_unicode = pdo_odbc_sqltype_is_unicode(S, S->cols[colno].coltype); + + /* tell ODBC to put it straight into our buffer, but only if it + * isn't "long" data, and only if we haven't already bound a long + * column. */ + if (colsize < 256 && !S->going_long) { + S->cols[colno].data = emalloc(colsize + 1); + S->cols[colno].is_long = 0; + + rc = SQLBindCol(S->stmt, + colno + 1, + S->cols[colno].is_unicode ? SQL_C_BINARY : SQL_C_CHAR, + S->cols[colno].data, + S->cols[colno].datalen + 1, + &S->cols[colno].fetched_len); + + if (rc != SQL_SUCCESS) { + pdo_odbc_stmt_error("SQLBindCol"); + return 0; + } + } else { + /* allocate a smaller buffer to keep around for smaller + * "long" columns */ + S->cols[colno].data = emalloc(256); + S->going_long = 1; + S->cols[colno].is_long = 1; + } + + return 1; +} + +static int odbc_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + array_init(return_value); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + return 1; +} + +static int odbc_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + pdo_odbc_column *C = &S->cols[colno]; + + /* if it is a column containing "long" data, perform late binding now */ + if (C->is_long) { + SQLLEN orig_fetched_len = SQL_NULL_DATA; + RETCODE rc; + + /* fetch it into C->data, which is allocated with a length + * of 256 bytes; if there is more to be had, we then allocate + * bigger buffer for the caller to free */ + + rc = SQLGetData(S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, C->data, 256, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < 256) { + /* all the data fit into our little buffer; + * jump down to the generic bound data case */ + goto in_data; + } + + if (rc == SQL_SUCCESS_WITH_INFO || rc == SQL_SUCCESS) { + /* this is a 'long column' + + read the column in 255 byte blocks until the end of the column is reached, reassembling those blocks + in order into the output buffer; 255 bytes are an optimistic assumption, since the driver may assert + more or less NUL bytes at the end; we cater to that later, if actual length information is available + + this loop has to work whether or not SQLGetData() provides the total column length. + calling SQLDescribeCol() or other, specifically to get the column length, then doing a single read + for that size would be slower except maybe for extremely long columns.*/ + char *buf2 = emalloc(256); + zend_string *str = zend_string_init(C->data, 256, 0); + size_t used = 255; /* not 256; the driver NUL terminated the buffer */ + + do { + C->fetched_len = 0; + /* read block. 256 bytes => 255 bytes are actually read, the last 1 is NULL */ + rc = SQLGetData( + S->stmt, colno + 1, C->is_unicode ? SQL_C_BINARY : SQL_C_CHAR, buf2, 256, &C->fetched_len); + + /* adjust `used` in case we have length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + ZEND_ASSERT(fixed_used <= used + 1); + used = fixed_used; + } + + /* resize output buffer and reassemble block */ + if (rc == SQL_SUCCESS_WITH_INFO || (rc == SQL_SUCCESS && C->fetched_len > 255)) { + /* point 5, in section "Retrieving Data with SQLGetData" in + http://msdn.microsoft.com/en-us/library/windows/desktop/ms715441(v=vs.85).aspx states that if + SQL_SUCCESS_WITH_INFO, fetched_len will be > 255 (greater than buf2's size) (if a driver fails to + follow that and wrote less than 255 bytes to buf2, this will AV or read garbage into buf) */ + str = zend_string_realloc(str, used + 256, 0); + memcpy(ZSTR_VAL(str) + used, buf2, 256); + used = used + 255; + } else if (rc == SQL_SUCCESS) { + str = zend_string_realloc(str, used + C->fetched_len, 0); + memcpy(ZSTR_VAL(str) + used, buf2, C->fetched_len); + used = used + C->fetched_len; + } else { + /* includes SQL_NO_DATA */ + break; + } + + } while (1); + + efree(buf2); + + /* NULL terminate the buffer once, when finished, for use with the rest of PHP */ + ZSTR_VAL(str)[used] = '\0'; + ZVAL_STR(result, str); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } + + /* something went caca */ + return 1; + } + +in_data: + /* check the indicator to ensure that the data is intact */ + if (C->fetched_len == SQL_NULL_DATA) { + /* A NULL value */ + ZVAL_NULL(result); + return 1; + } else if (C->fetched_len >= 0) { + /* it was stored perfectly */ + ZVAL_STRINGL_FAST(result, C->data, C->fetched_len); + if (C->is_unicode) { + goto unicode_conv; + } + return 1; + } else { + /* no data? */ + ZVAL_NULL(result); + return 1; + } + +unicode_conv: + switch (pdo_odbc_ucs22utf8(stmt, C->is_unicode, result)) { + case PDO_ODBC_CONV_FAIL: + /* oh well. They can have the binary version of it */ + case PDO_ODBC_CONV_NOT_REQUIRED: + /* shouldn't happen... */ + return 1; + case PDO_ODBC_CONV_OK: + return 1; + } + return 1; +} + +static int odbc_stmt_set_param(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: + convert_to_string(val); + rc = SQLSetCursorName(S->stmt, (SQLCHAR *) Z_STRVAL_P(val), Z_STRLEN_P(val)); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + return 1; + } + pdo_odbc_stmt_error("SQLSetCursorName"); + return 0; + + case PDO_ODBC_ATTR_ASSUME_UTF8: + S->assume_utf8 = zval_is_true(val); + return 0; + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "setAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_ATTR_CURSOR_NAME: { + char buf[256]; + SQLSMALLINT len = 0; + rc = SQLGetCursorName(S->stmt, (SQLCHAR *) buf, sizeof(buf), &len); + + if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) { + ZVAL_STRINGL(val, buf, len); + return 1; + } + pdo_odbc_stmt_error("SQLGetCursorName"); + return 0; + } + + case PDO_ODBC_ATTR_ASSUME_UTF8: + ZVAL_BOOL(val, S->assume_utf8 ? 1 : 0); + return 0; + + default: + strcpy(S->einfo.last_err_msg, "Unknown Attribute"); + S->einfo.what = "getAttribute"; + strcpy(S->einfo.last_state, "IM001"); + return -1; + } +} + +static int odbc_stmt_next_rowset(pdo_stmt_t *stmt) { + SQLRETURN rc; + SQLSMALLINT colcount; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + /* NOTE: can't guarantee that output or input/output parameters + * are set until this fella returns SQL_NO_DATA, according to + * MSDN ODBC docs */ + rc = SQLMoreResults(S->stmt); + + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + + free_cols(stmt, S); + /* how many columns do we have ? */ + SQLNumResultCols(S->stmt, &colcount); + stmt->column_count = S->col_count = (int) colcount; + S->cols = ecalloc(colcount, sizeof(pdo_odbc_column)); + S->going_long = 0; + + return 1; +} + +static int odbc_stmt_close_cursor(pdo_stmt_t *stmt) { + SQLRETURN rc; + pdo_odbc_stmt *S = (pdo_odbc_stmt *) stmt->driver_data; + + rc = SQLCloseCursor(S->stmt); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + return 0; + } + return 1; +} + +const struct pdo_stmt_methods odbc_stmt_methods = {odbc_stmt_dtor, + odbc_stmt_execute, + odbc_stmt_fetch, + odbc_stmt_describe, + odbc_stmt_get_col, + odbc_stmt_param_hook, + odbc_stmt_set_param, + odbc_stmt_get_attr, + odbc_stmt_get_column_meta, + odbc_stmt_next_rowset, + odbc_stmt_close_cursor}; +#endif diff --git a/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 00000000000..0893f4b824b --- /dev/null +++ b/thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,179 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifdef PHP_WIN32 +#define PDO_ODBC_TYPE "Win32" +#endif + +#ifndef PDO_ODBC_TYPE +#warning Please fix configure to give your ODBC libraries a name +#define PDO_ODBC_TYPE "Unknown" +#endif + +/* {{{ Roll a dice, pick a header at random... */ +#ifdef HAVE_SQLCLI1_H +#include +#if defined(DB268K) && HAVE_LIBRARYMANAGER_H +#include +#endif +#endif + +#ifdef HAVE_ODBC_H +#include +#endif + +#ifdef HAVE_IODBC_H +#include +#endif + +#if defined(HAVE_SQLUNIX_H) && !defined(PHP_WIN32) +#include +#endif + +#ifdef HAVE_SQLTYPES_H +#include +#endif + +#ifdef HAVE_SQLUCODE_H +#include +#endif + +#ifdef HAVE_SQL_H +#include +#endif + +#ifdef HAVE_ISQL_H +#include +#endif + +#ifdef HAVE_SQLEXT_H +#include +#endif + +#ifdef HAVE_ISQLEXT_H +#include +#endif + +#ifdef HAVE_UDBCEXT_H +#include +#endif + +#ifdef HAVE_CLI0CORE_H +#include +#endif + +#ifdef HAVE_CLI0EXT1_H +#include +#endif + +#ifdef HAVE_CLI0CLI_H +#include +#endif + +#ifdef HAVE_CLI0DEFS_H +#include +#endif + +#ifdef HAVE_CLI0ENV_H +#include +#endif + +/* }}} */ + +/* {{{ Figure out the type for handles */ +#if !defined(HENV) && !defined(SQLHENV) && defined(SQLHANDLE) +#define PDO_ODBC_HENV SQLHANDLE +#define PDO_ODBC_HDBC SQLHANDLE +#define PDO_ODBC_HSTMT SQLHANDLE +#elif !defined(HENV) && (defined(SQLHENV) || defined(DB2CLI_VER)) +#define PDO_ODBC_HENV SQLHENV +#define PDO_ODBC_HDBC SQLHDBC +#define PDO_ODBC_HSTMT SQLHSTMT +#else +#define PDO_ODBC_HENV HENV +#define PDO_ODBC_HDBC HDBC +#define PDO_ODBC_HSTMT HSTMT +#endif +/* }}} */ + +typedef struct { + char last_state[6]; + char last_err_msg[SQL_MAX_MESSAGE_LENGTH]; + SQLINTEGER last_error; + const char *file, *what; + int line; +} pdo_odbc_errinfo; + +typedef struct { + PDO_ODBC_HENV env; + PDO_ODBC_HDBC dbc; + pdo_odbc_errinfo einfo; + unsigned assume_utf8 : 1; + unsigned _spare : 31; +} pdo_odbc_db_handle; + +typedef struct { + char *data; + zend_ulong datalen; + SQLLEN fetched_len; + SQLSMALLINT coltype; + char colname[128]; + unsigned is_long; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_column; + +typedef struct { + PDO_ODBC_HSTMT stmt; + pdo_odbc_column *cols; + pdo_odbc_db_handle *H; + pdo_odbc_errinfo einfo; + char *convbuf; + zend_ulong convbufsize; + unsigned going_long : 1; + unsigned assume_utf8 : 1; + signed col_count : 16; + unsigned _spare : 14; +} pdo_odbc_stmt; + +typedef struct { + SQLLEN len; + SQLSMALLINT paramtype; + char *outbuf; + unsigned is_unicode : 1; + unsigned _spare : 31; +} pdo_odbc_param; + +extern const pdo_driver_t pdo_odbc_driver; +extern const struct pdo_stmt_methods odbc_stmt_methods; + +void pdo_odbc_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, PDO_ODBC_HSTMT statement, char *what, const char *file, int line); +#define pdo_odbc_drv_error(what) pdo_odbc_error(dbh, NULL, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_stmt_error(what) pdo_odbc_error(stmt->dbh, stmt, SQL_NULL_HSTMT, what, __FILE__, __LINE__) +#define pdo_odbc_doer_error(what) pdo_odbc_error(dbh, NULL, stmt, what, __FILE__, __LINE__) + +void pdo_odbc_init_error_table(void); +void pdo_odbc_fini_error_table(void); + +#ifdef SQL_ATTR_CONNECTION_POOLING +extern zend_ulong pdo_odbc_pool_on; +extern zend_ulong pdo_odbc_pool_mode; +#endif + +enum { + PDO_ODBC_ATTR_USE_CURSOR_LIBRARY = PDO_ATTR_DRIVER_SPECIFIC, + PDO_ODBC_ATTR_ASSUME_UTF8 /* assume that input strings are UTF-8 when feeding data to unicode columns */ +}; diff --git a/thirdparty/php84/pdo_pgsql/pgsql_driver.c b/thirdparty/php84/pdo_pgsql/pgsql_driver.c new file mode 100644 index 00000000000..cf98f5f45f9 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1458 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/php_string.h" /* For php_addcslashes_str() in _pdo_pgsql_escape_credentials() */ +#include "main/php_network.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_error.h" +#include "ext/standard/file.h" +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "zend_smart_str.h" +#include "pgsql_driver_arginfo.h" + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh); + +static char *_pdo_pgsql_trim_message(const char *message, int persistent) { + size_t i = strlen(message) - 1; + char *tmp; + + if (i > 1 && (message[i - 1] == '\r' || message[i - 1] == '\n') && message[i] == '.') { + --i; + } + while (i > 0 && (message[i] == '\r' || message[i] == '\n')) { + --i; + } + ++i; + tmp = pemalloc(i + 1, persistent); + memcpy(tmp, message, i); + tmp[i] = '\0'; + + return tmp; +} + +static zend_string *_pdo_pgsql_escape_credentials(char *str) { + if (str) { + return php_addcslashes_str(str, strlen(str), "\\'", sizeof("\\'")); + } + + return NULL; +} + +int _pdo_pgsql_error(pdo_dbh_t *dbh, + pdo_stmt_t *stmt, + int errcode, + const char *sqlstate, + const char *msg, + const char *file, + int line) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_pgsql_error_info *einfo = &H->einfo; + char *errmsg = PQerrorMessage(H->server); + + einfo->errcode = errcode; + einfo->file = file; + einfo->line = line; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + + if (sqlstate == NULL || strlen(sqlstate) >= sizeof(pdo_error_type)) { + strcpy(*pdo_err, "HY000"); + } else { + strcpy(*pdo_err, sqlstate); + } + + if (msg) { + einfo->errmsg = pestrdup(msg, dbh->is_persistent); + } else if (errmsg) { + einfo->errmsg = _pdo_pgsql_trim_message(errmsg, dbh->is_persistent); + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return errcode; +} +/* }}} */ + +static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ +{ + pdo_dbh_t *dbh = (pdo_dbh_t *) context; + zend_fcall_info_cache *fc = ((pdo_pgsql_db_handle *) dbh->driver_data)->notice_callback; + if (fc) { + zval zarg; + ZVAL_STRING(&zarg, message); + zend_call_known_fcc(fc, NULL, 1, &zarg, NULL); + zval_ptr_dtor_str(&zarg); + } +} +/* }}} */ + +static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_pgsql_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + } else { + /* Add null to respect expected info array structure */ + add_next_index_null(info); + } + if (einfo->errmsg) { + add_next_index_string(info, einfo->errmsg); + } +} +/* }}} */ + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ +{ + if (H->notice_callback) { + zend_fcc_dtor(H->notice_callback); + efree(H->notice_callback); + H->notice_callback = NULL; + } +} +/* }}} */ + +/* {{{ pdo_pgsql_create_lob_stream */ +static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + return lo_write(self->conn, self->lfd, (char *) buf, count); +} + +static ssize_t pgsql_lob_read(php_stream *stream, char *buf, size_t count) { + 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) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) (Z_PDO_DBH_P(&self->dbh))->driver_data; + + if (close_handle) { + lo_close(self->conn, self->lfd); + } + zend_hash_index_del(H->lob_streams, php_stream_get_resource_id(stream)); + zval_ptr_dtor(&self->dbh); + efree(self); + return 0; +} + +static int pgsql_lob_flush(php_stream *stream) { + return 0; +} + +static int pgsql_lob_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset) { + struct pdo_pgsql_lob_self *self = (struct pdo_pgsql_lob_self *) stream->abstract; +#ifdef ZEND_ENABLE_ZVAL_LONG64 + zend_off_t pos = lo_lseek64(self->conn, self->lfd, offset, whence); +#else + zend_off_t pos = lo_lseek(self->conn, self->lfd, offset, whence); +#endif + *newoffset = pos; + return pos >= 0 ? 0 : -1; +} + +const 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(zval *dbh, int lfd, Oid oid) { + php_stream *stm; + struct pdo_pgsql_lob_self *self = ecalloc(1, sizeof(*self)); + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) (Z_PDO_DBH_P(dbh))->driver_data; + + ZVAL_COPY_VALUE(&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) { + Z_ADDREF_P(dbh); + zend_hash_index_add_ptr(H->lob_streams, php_stream_get_resource_id(stm), stm->res); + return stm; + } + + efree(self); + return NULL; +} +/* }}} */ + +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh) { + zend_resource *res; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (H->lob_streams) { + ZEND_HASH_REVERSE_FOREACH_PTR(H->lob_streams, res) { + if (res->type >= 0) { + zend_list_close(res); + } + } + ZEND_HASH_FOREACH_END(); + } +} + +static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (H) { + if (H->lob_streams) { + pdo_pgsql_close_lob_streams(dbh); + zend_hash_destroy(H->lob_streams); + pefree(H->lob_streams, dbh->is_persistent); + H->lob_streams = NULL; + } + pdo_pgsql_cleanup_notice_callback(H); + if (H->server) { + PQfinish(H->server); + H->server = NULL; + } + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + pdo_pgsql_stmt *S = ecalloc(1, sizeof(pdo_pgsql_stmt)); + int scrollable; + int ret; + zend_string *nsql = NULL; + int emulate = 0; + int execute_only = 0; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_pgsql_stmt_methods; + + scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL; + + if (scrollable) { + if (S->cursor_name) { + efree(S->cursor_name); + } + spprintf(&S->cursor_name, 0, "pdo_crsr_%08x", ++H->stmt_counter); + emulate = 1; + } else if (driver_options) { + if (pdo_attr_lval(driver_options, PDO_ATTR_EMULATE_PREPARES, H->emulate_prepares) == 1) { + emulate = 1; + } + if (pdo_attr_lval(driver_options, PDO_PGSQL_ATTR_DISABLE_PREPARES, H->disable_prepares) == 1) { + execute_only = 1; + } + } else { + emulate = H->disable_native_prepares || H->emulate_prepares; + execute_only = H->disable_prepares; + } + + if (emulate) { + stmt->supports_placeholders = PDO_PLACEHOLDER_NONE; + } else { + stmt->supports_placeholders = PDO_PLACEHOLDER_NAMED; + stmt->named_rewrite_template = "$%d"; + } + + ret = pdo_parse_params(stmt, sql, &nsql); + + if (ret == -1) { + /* couldn't grok it */ + strcpy(dbh->error_code, stmt->error_code); + return false; + } else if (ret == 1) { + /* query was re-written */ + S->query = nsql; + } else { + S->query = zend_string_copy(sql); + } + + if (!emulate && !execute_only) { + /* prepared query: set the query name and defer the + actual prepare until the first execute call */ + spprintf(&S->stmt_name, 0, "pdo_stmt_%08x", ++H->stmt_counter); + } + + return true; +} + +static zend_long pgsql_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + PGresult *res; + zend_long ret = 1; + ExecStatusType qs; + + bool in_trans = pgsql_handle_in_transaction(dbh); + + if (!(res = PQexec(H->server, ZSTR_VAL(sql)))) { + /* fatal error */ + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + return -1; + } + qs = PQresultStatus(res); + if (qs != PGRES_COMMAND_OK && qs != PGRES_TUPLES_OK) { + pdo_pgsql_error(dbh, qs, pdo_pgsql_sqlstate(res)); + PQclear(res); + return -1; + } + H->pgoid = PQoidValue(res); + if (qs == PGRES_COMMAND_OK) { + ret = ZEND_ATOL(PQcmdTuples(res)); + } else { + ret = Z_L(0); + } + PQclear(res); + if (in_trans && !pgsql_handle_in_transaction(dbh)) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +static zend_string *pgsql_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { + unsigned char *escaped; + char *quoted; + size_t quotedlen; + zend_string *quoted_str; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + size_t tmp_len; + + switch (paramtype) { + case PDO_PARAM_LOB: + /* escapedlen returned by PQescapeBytea() accounts for trailing 0 */ + escaped = PQescapeByteaConn(H->server, (unsigned char *) ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), &tmp_len); + quotedlen = tmp_len + 1; + quoted = emalloc(quotedlen + 1); + memcpy(quoted + 1, escaped, quotedlen - 2); + quoted[0] = '\''; + quoted[quotedlen - 1] = '\''; + quoted[quotedlen] = '\0'; + PQfreemem(escaped); + break; + default: + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + quoted[0] = '\''; + quotedlen = PQescapeStringConn(H->server, quoted + 1, ZSTR_VAL(unquoted), ZSTR_LEN(unquoted), NULL); + quoted[quotedlen + 1] = '\''; + quoted[quotedlen + 2] = '\0'; + quotedlen += 2; + } + + quoted_str = zend_string_init(quoted, quotedlen, 0); + efree(quoted); + return quoted_str; +} + +static zend_string *pdo_pgsql_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + zend_string *id = NULL; + PGresult *res; + ExecStatusType status; + + if (name == NULL) { + res = PQexec(H->server, "SELECT LASTVAL()"); + } else { + const char *q[1]; + q[0] = ZSTR_VAL(name); + + res = PQexecParams(H->server, "SELECT CURRVAL($1)", 1, NULL, q, NULL, NULL, 0); + } + status = PQresultStatus(res); + + if (res && (status == PGRES_TUPLES_OK)) { + id = zend_string_init((char *) PQgetvalue(res, 0, 0), PQgetlength(res, 0, 0), 0); + } else { + pdo_pgsql_error(dbh, status, pdo_pgsql_sqlstate(res)); + } + + if (res) { + PQclear(res); + } + + return id; +} + +void pdo_libpq_version(char *buf, size_t len) { + int version = PQlibVersion(); + int major = version / 10000; + if (major >= 10) { + int minor = version % 10000; + snprintf(buf, len, "%d.%d", major, minor); + } else { + int minor = version / 100 % 100; + int revision = version % 100; + snprintf(buf, len, "%d.%d.%d", major, minor, revision); + } +} + +static int pdo_pgsql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + ZVAL_BOOL(return_value, H->emulate_prepares); + break; + + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + ZVAL_BOOL(return_value, H->disable_prepares); + break; + + case PDO_ATTR_CLIENT_VERSION: { + char buf[16]; + pdo_libpq_version(buf, sizeof(buf)); + ZVAL_STRING(return_value, buf); + break; + } + + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *) PQparameterStatus(H->server, "server_version")); + break; + + case PDO_ATTR_CONNECTION_STATUS: + switch (PQstatus(H->server)) { + case CONNECTION_STARTED: + ZVAL_STRINGL( + return_value, "Waiting for connection to be made.", strlen("Waiting for connection to be made.")); + break; + + case CONNECTION_MADE: + case CONNECTION_OK: + ZVAL_STRINGL(return_value, "Connection OK; waiting to send.", strlen("Connection OK; waiting to send.")); + break; + + case CONNECTION_AWAITING_RESPONSE: + ZVAL_STRINGL(return_value, + "Waiting for a response from the server.", + strlen("Waiting for a response from the server.")); + break; + + case CONNECTION_AUTH_OK: + ZVAL_STRINGL(return_value, + "Received authentication; waiting for backend start-up to finish.", + strlen("Received authentication; waiting for backend start-up to finish.")); + break; +#ifdef CONNECTION_SSL_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating SSL encryption.", strlen("Negotiating SSL encryption.")); + break; +#endif + case CONNECTION_SETENV: + ZVAL_STRINGL(return_value, + "Negotiating environment-driven parameter settings.", + strlen("Negotiating environment-driven parameter settings.")); + break; + +#ifdef CONNECTION_CONSUME + case CONNECTION_CONSUME: + ZVAL_STRINGL(return_value, + "Flushing send queue/consuming extra data.", + strlen("Flushing send queue/consuming extra data.")); + break; +#endif +#ifdef CONNECTION_GSS_STARTUP + case CONNECTION_SSL_STARTUP: + ZVAL_STRINGL(return_value, "Negotiating GSSAPI.", strlen("Negotiating GSSAPI.")); + break; +#endif +#ifdef CONNECTION_CHECK_TARGET + case CONNECTION_CHECK_TARGET: + ZVAL_STRINGL(return_value, + "Connection OK; checking target server properties.", + strlen("Connection OK; checking target server properties.")); + break; +#endif +#ifdef CONNECTION_CHECK_STANDBY + case CONNECTION_CHECK_STANDBY: + ZVAL_STRINGL(return_value, + "Connection OK; checking if server in standby.", + strlen("Connection OK; checking if server in standby.")); + break; +#endif + case CONNECTION_BAD: + default: + ZVAL_STRINGL(return_value, "Bad connection.", strlen("Bad connection.")); + break; + } + break; + + case PDO_ATTR_SERVER_INFO: { + int spid = PQbackendPID(H->server); + + zend_string *str_info = + strpprintf(0, + "PID: %d; Client Encoding: %s; Is Superuser: %s; Session Authorization: %s; Date Style: %s", + spid, + (char *) PQparameterStatus(H->server, "client_encoding"), + (char *) PQparameterStatus(H->server, "is_superuser"), + (char *) PQparameterStatus(H->server, "session_authorization"), + (char *) PQparameterStatus(H->server, "DateStyle")); + + ZVAL_STR(return_value, str_info); + break; + } + + default: + return 0; + } + + return 1; +} + +/* {{{ */ +static zend_result pdo_pgsql_check_liveness(pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + if (!PQconsumeInput(H->server) || PQstatus(H->server) == CONNECTION_BAD) { + PQreset(H->server); + } + return (PQstatus(H->server) == CONNECTION_OK) ? SUCCESS : FAILURE; +} +/* }}} */ + +static bool pgsql_handle_in_transaction(pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + return PQtransactionStatus(H->server) > PQTRANS_IDLE; +} + +static bool pdo_pgsql_transaction_cmd(const char *cmd, pdo_dbh_t *dbh) { + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + PGresult *res; + bool ret = true; + + res = PQexec(H->server, cmd); + + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + pdo_pgsql_error(dbh, PQresultStatus(res), pdo_pgsql_sqlstate(res)); + ret = false; + } + + PQclear(res); + return ret; +} + +static bool pgsql_handle_begin(pdo_dbh_t *dbh) { + return pdo_pgsql_transaction_cmd("BEGIN", dbh); +} + +static bool pgsql_handle_commit(pdo_dbh_t *dbh) { + bool ret = pdo_pgsql_transaction_cmd("COMMIT", dbh); + + /* When deferred constraints are used the commit could + fail, and a ROLLBACK implicitly ran. See bug #67462 */ + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } else { + dbh->in_txn = pgsql_handle_in_transaction(dbh); + } + + return ret; +} + +static bool pgsql_handle_rollback(pdo_dbh_t *dbh) { + int ret = pdo_pgsql_transaction_cmd("ROLLBACK", dbh); + + if (ret) { + pdo_pgsql_close_lob_streams(dbh); + } + + return ret; +} + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + zval *pg_rows; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sa|sss!", + &table_name, + &table_name_len, + &pg_rows, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + if (!zend_hash_num_elements(Z_ARRVAL_P(pg_rows))) { + zend_argument_value_error(2, "cannot be empty"); + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + /* Obtain db Handle */ + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + query = NULL; + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + int command_failed = 0; + size_t buffer_len = 0; + zval *tmp; + + PQclear(pgsql_result); + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + size_t query_len; + if (!try_convert_to_string(tmp)) { + efree(query); + RETURN_THROWS(); + } + + if (buffer_len < Z_STRLEN_P(tmp)) { + buffer_len = Z_STRLEN_P(tmp); + query = erealloc(query, buffer_len + 2); /* room for \n\0 */ + } + query_len = Z_STRLEN_P(tmp); + memcpy(query, Z_STRVAL_P(tmp), query_len); + if (query[query_len - 1] != '\n') { + query[query_len++] = '\n'; + } + query[query_len] = '\0'; + if (PQputCopyData(H->server, query, query_len) != 1) { + efree(query); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + ZEND_HASH_FOREACH_END(); + if (query) { + efree(query); + } + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray) { + pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *filename, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, filename_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + PGresult *pgsql_result; + ExecStatusType status; + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sp|sss!", + &table_name, + &table_name_len, + &filename, + &filename_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + /* Obtain db Handler */ + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + stream = php_stream_open_wrapper_ex(filename, "rb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s FROM STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + pgsql_result = PQexec(H->server, query); + + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_IN && pgsql_result) { + char *buf; + int command_failed = 0; + size_t line_len = 0; + + PQclear(pgsql_result); + while ((buf = php_stream_get_line(stream, NULL, 0, &line_len)) != NULL) { + if (PQputCopyData(H->server, buf, line_len) != 1) { + efree(buf); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + efree(buf); + } + php_stream_close(stream); + + if (PQputCopyEnd(H->server, NULL) != 1) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + if (PGRES_COMMAND_OK != PQresultStatus(pgsql_result)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + command_failed = 1; + } + PQclear(pgsql_result); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_BOOL(!command_failed); + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile) { + pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL, *filename = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len, filename_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + php_stream *stream; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "sp|sss!", + &table_name, + &table_name_len, + &filename, + &filename_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + stream = php_stream_open_wrapper_ex(filename, "wb", 0, NULL, FG(default_context)); + if (!stream) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to open the file for writing"); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + + if (ret == -1) { + break; /* done */ + } else if (ret > 0) { + if (php_stream_write(stream, csv, ret) != (size_t) ret) { + pdo_pgsql_error_msg(dbh, PGRES_FATAL_ERROR, "Unable to write to file"); + PQfreemem(csv); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } else { + PQfreemem(csv); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + php_stream_close(stream); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + php_stream_close(stream); + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + RETURN_TRUE; + } else { + php_stream_close(stream); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile) { + pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + char *table_name, *pg_delim = NULL, *pg_null_as = NULL, *pg_fields = NULL; + size_t table_name_len, pg_delim_len = 0, pg_null_as_len = 0, pg_fields_len; + char *query; + + PGresult *pgsql_result; + ExecStatusType status; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), + "s|sss!", + &table_name, + &table_name_len, + &pg_delim, + &pg_delim_len, + &pg_null_as, + &pg_null_as_len, + &pg_fields, + &pg_fields_len) == FAILURE) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + + /* using pre-9.0 syntax as PDO_pgsql is 7.4+ compatible */ + if (pg_fields) { + spprintf(&query, + 0, + "COPY %s (%s) TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + pg_fields, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } else { + spprintf(&query, + 0, + "COPY %s TO STDIN WITH DELIMITER E'%c' NULL AS E'%s'", + table_name, + (pg_delim_len ? *pg_delim : '\t'), + (pg_null_as_len ? pg_null_as : "\\\\N")); + } + pgsql_result = PQexec(H->server, query); + efree(query); + + if (pgsql_result) { + status = PQresultStatus(pgsql_result); + } else { + status = (ExecStatusType) PQstatus(H->server); + } + + if (status == PGRES_COPY_OUT && pgsql_result) { + PQclear(pgsql_result); + array_init(return_value); + + while (1) { + char *csv = NULL; + int ret = PQgetCopyData(H->server, &csv, 0); + if (ret == -1) { + break; /* copy done */ + } else if (ret > 0) { + add_next_index_stringl(return_value, csv, ret); + PQfreemem(csv); + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + + while ((pgsql_result = PQgetResult(H->server))) { + PQclear(pgsql_result); + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, pdo_pgsql_sqlstate(pgsql_result)); + PQclear(pgsql_result); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } +} + +/* {{{ Returns true if the copy worked fine or false if error */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray) { + pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid lfd; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + lfd = lo_creat(H->server, INV_READ | INV_WRITE); + + if (lfd != InvalidOid) { + zend_string *buf = strpprintf(0, ZEND_ULONG_FMT, (zend_long) lfd); + + RETURN_STR(buf); + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Creates a new large object, returning its identifier. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate) { + pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + int lfd; + char *oidstr; + size_t oidstrlen; + char *modestr = "rb"; + size_t modestrlen; + int mode = INV_READ; + char *end_ptr; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &oidstr, &oidstrlen, &modestr, &modestrlen)) { + RETURN_THROWS(); + } + + 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 = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + 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(ZEND_THIS, lfd, oid); + if (stream) { + php_stream_to_zval(stream, return_value); + return; + } + } else { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + } + + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Opens an existing large object stream. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen) { + pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + Oid oid; + char *oidstr, *end_ptr; + size_t oidlen; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "s", &oidstr, &oidlen)) { + RETURN_THROWS(); + } + + oid = (Oid) strtoul(oidstr, &end_ptr, 10); + if (oid == 0 && (errno == ERANGE || errno == EINVAL)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + PDO_DBH_CLEAR_ERR(); + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + if (1 == lo_unlink(H->server, oid)) { + RETURN_TRUE; + } + + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; +} + +/* {{{ Deletes the large object identified by oid. Must be called inside a transaction. */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink) { + pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + zend_long result_type = PDO_FETCH_USE_DEFAULT; + zend_long ms_timeout = 0; + PGnotify *pgsql_notify; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "|ll", &result_type, &ms_timeout)) { + RETURN_THROWS(); + } + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + if (result_type == PDO_FETCH_USE_DEFAULT) { + result_type = dbh->default_fetch_type; + } + + if (result_type != PDO_FETCH_BOTH && result_type != PDO_FETCH_ASSOC && result_type != PDO_FETCH_NUM) { + zend_argument_value_error(1, "must be one of PDO::FETCH_BOTH, PDO::FETCH_ASSOC, or PDO::FETCH_NUM"); + RETURN_THROWS(); + } + + if (ms_timeout < 0) { + zend_argument_value_error(2, "must be greater than or equal to 0"); + RETURN_THROWS(); +#ifdef ZEND_ENABLE_ZVAL_LONG64 + } else if (ms_timeout > INT_MAX) { + php_error_docref(NULL, E_WARNING, "Timeout was shrunk to %d", INT_MAX); + ms_timeout = INT_MAX; +#endif + } + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + + if (ms_timeout && !pgsql_notify) { + php_pollfd_for_ms(PQsocket(H->server), PHP_POLLREADABLE, (int) ms_timeout); + + if (!PQconsumeInput(H->server)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + pgsql_notify = PQnotifies(H->server); + } + + if (!pgsql_notify) { + RETURN_FALSE; + } + + array_init(return_value); + if (result_type == PDO_FETCH_NUM || result_type == PDO_FETCH_BOTH) { + add_index_string(return_value, 0, pgsql_notify->relname); + add_index_long(return_value, 1, pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_index_string(return_value, 2, pgsql_notify->extra); + } + } + if (result_type == PDO_FETCH_ASSOC || result_type == PDO_FETCH_BOTH) { + add_assoc_string(return_value, "message", pgsql_notify->relname); + add_assoc_long(return_value, "pid", pgsql_notify->be_pid); + if (pgsql_notify->extra && pgsql_notify->extra[0]) { + add_assoc_string(return_value, "payload", pgsql_notify->extra); + } + } + + PQfreemem(pgsql_notify); +} + +/* {{{ Get asynchronous notification */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetNotify) { + pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS) { + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + + ZEND_PARSE_PARAMETERS_NONE(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *) dbh->driver_data; + + RETURN_LONG(PQbackendPID(H->server)); +} + +/* {{{ Get backend(server) pid */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) { + pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +/* {{{ Sets a callback to receive DB notices (after client_min_messages has been set) */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) { + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { + RETURN_THROWS(); + } + + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(cleanup); + + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + pdo_pgsql_cleanup_notice_callback(H); + + if (ZEND_FCC_INITIALIZED(fcc)) { + H->notice_callback = emalloc(sizeof(zend_fcall_info_cache)); + zend_fcc_dup(H->notice_callback, &fcc); + } + + return; + +cleanup: + if (ZEND_FCC_INITIALIZED(fcc)) { + zend_fcc_dtor(&fcc); + } + RETURN_THROWS(); +} +/* }}} */ + +static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) { + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_PGSql_Ext_methods; + default: + return NULL; + } +} + +static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + bool bval; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *) dbh->driver_data; + + switch (attr) { + case PDO_ATTR_EMULATE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->emulate_prepares = bval; + return true; + case PDO_PGSQL_ATTR_DISABLE_PREPARES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->disable_prepares = bval; + return true; + default: + return false; + } +} + +static const struct pdo_dbh_methods pgsql_methods = {pgsql_handle_closer, + pgsql_handle_preparer, + pgsql_handle_doer, + pgsql_handle_quoter, + pgsql_handle_begin, + pgsql_handle_commit, + pgsql_handle_rollback, + pdo_pgsql_set_attr, + pdo_pgsql_last_insert_id, + pdo_pgsql_fetch_error_func, + pdo_pgsql_get_attribute, + pdo_pgsql_check_liveness, /* check_liveness */ + pdo_pgsql_get_driver_methods, /* get_driver_methods */ + NULL, + pgsql_handle_in_transaction, + NULL, /* get_gc */ + pdo_pgsql_scanner}; + +static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_pgsql_db_handle *H; + int ret = 0; + char *p, *e; + zend_string *tmp_user, *tmp_pass; + smart_str conn_str = {0}; + zend_long connect_timeout = 30; + + H = pecalloc(1, sizeof(pdo_pgsql_db_handle), dbh->is_persistent); + dbh->driver_data = H; + + dbh->skip_param_evt = 1 << PDO_PARAM_EVT_EXEC_POST | 1 << PDO_PARAM_EVT_FETCH_PRE | 1 << PDO_PARAM_EVT_FETCH_POST; + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + + /* PostgreSQL wants params in the connect string to be separated by spaces, + * if the PDO standard semicolons are used, we convert them to spaces + */ + e = (char *) dbh->data_source + strlen(dbh->data_source); + p = (char *) dbh->data_source; + while ((p = memchr(p, ';', (e - p)))) { + *p = ' '; + } + + if (driver_options) { + connect_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 30); + } + + /* escape username and password, if provided */ + tmp_user = !strstr((char *) dbh->data_source, "user=") ? _pdo_pgsql_escape_credentials(dbh->username) : NULL; + tmp_pass = !strstr((char *) dbh->data_source, "password=") ? _pdo_pgsql_escape_credentials(dbh->password) : NULL; + + smart_str_appends(&conn_str, dbh->data_source); + smart_str_append_printf(&conn_str, " connect_timeout=" ZEND_LONG_FMT, connect_timeout); + + /* support both full connection string & connection string + login and/or password */ + if (tmp_user) { + smart_str_append_printf(&conn_str, " user='%s'", ZSTR_VAL(tmp_user)); + } + + if (tmp_pass) { + smart_str_append_printf(&conn_str, " password='%s'", ZSTR_VAL(tmp_pass)); + } + smart_str_0(&conn_str); + + H->server = PQconnectdb(ZSTR_VAL(conn_str.s)); + H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent); + zend_hash_init(H->lob_streams, 0, NULL, NULL, 1); + + if (tmp_user) { + zend_string_release_ex(tmp_user, 0); + } + if (tmp_pass) { + zend_string_release_ex(tmp_pass, 0); + } + + smart_str_free(&conn_str); + + if (PQstatus(H->server) != CONNECTION_OK) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE); + goto cleanup; + } + + PQsetNoticeProcessor(H->server, _pdo_pgsql_notice, (void *) dbh); + + H->attached = 1; + H->pgoid = -1; + + dbh->methods = &pgsql_methods; + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &pgsql_methods; + if (!ret) { + pgsql_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_pgsql_driver = {PDO_DRIVER_HEADER(pgsql), pdo_pgsql_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 00000000000..5bdea01edc2 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,70 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: dd20abc5d8580d72b25bfb3c598b1ca54a501fcc */ + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, rows, IS_ARRAY, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, 0, 1, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, separator, IS_STRING, 0, "\"\\t\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, nullAs, IS_STRING, 0, "\"\\\\\\\\N\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fields, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + +#define arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, 0, 0, 1) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_STRING, 0, "\"rb\"") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, fetchMode, IS_LONG, 0, "PDO::FETCH_DEFAULT") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeoutMilliseconds, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToFile); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBCreate); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); + +static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlCopyToFile, arginfo_class_PDO_PGSql_Ext_pgsqlCopyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBCreate, arginfo_class_PDO_PGSql_Ext_pgsqlLOBCreate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBOpen, arginfo_class_PDO_PGSql_Ext_pgsqlLOBOpen, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c b/thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c new file mode 100644 index 00000000000..c030abc318b --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_sql_parser.c @@ -0,0 +1,555 @@ +/* Generated by re2c 3.0 */ +#line 1 "ext/pdo_pgsql/pgsql_sql_parser.re" +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole.h" +#if defined(SW_USE_PGSQL) && PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_pgsql_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + #line 38 "ext/pdo_pgsql/pgsql_sql_parser.re" + + + +#line 35 "ext/pdo_pgsql/pgsql_sql_parser.c" +{ + YYCTYPE yych; + unsigned int yyaccept = 0; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy4; + case '$': goto yy6; + case '\'': goto yy7; + case '-': goto yy8; + case '/': goto yy9; + case ':': goto yy10; + case '?': goto yy11; + case 'E': + case 'e': goto yy13; + default: goto yy2; + } +yy1: + YYCURSOR = YYMARKER; + switch (yyaccept) { + case 0: goto yy5; + case 1: goto yy17; + case 2: goto yy23; + default: goto yy35; + } +yy2: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: + case '"': + case '$': + case '\'': + case '-': + case '/': + case ':': + case '?': + case 'E': + case 'e': goto yy3; + default: goto yy2; + } +yy3: +#line 51 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 82 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy4: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych >= 0x01) goto yy15; +yy5: +#line 49 "ext/pdo_pgsql/pgsql_sql_parser.re" + { SKIP_ONE(PDO_PARSER_TEXT); } +#line 90 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy6: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + switch (yych) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case ' ': + case '!': + case '"': + case '#': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case '\\': + case ']': + case '^': + case '`': + case '{': + case '|': + case '}': + case '~': + case 0x7F: goto yy5; + case '$': goto yy18; + default: goto yy19; + } +yy7: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy21; +yy8: + yych = *++YYCURSOR; + switch (yych) { + case '-': goto yy24; + default: goto yy5; + } +yy9: + yych = *++YYCURSOR; + switch (yych) { + case '*': goto yy26; + default: goto yy5; + } +yy10: + yych = *++YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy27; + case ':': goto yy29; + default: goto yy5; + } +yy11: + yych = *++YYCURSOR; + switch (yych) { + case '?': goto yy31; + default: goto yy12; + } +yy12: +#line 48 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_BIND_POS); } +#line 267 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy13: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + switch (yych) { + case '\'': goto yy32; + default: goto yy5; + } +yy14: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy15: + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy16; + default: goto yy14; + } +yy16: + yyaccept = 1; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '"': goto yy14; + default: goto yy17; + } +yy17: +#line 42 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 297 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy18: + ++YYCURSOR; +#line 44 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_CUSTOM_QUOTE); } +#line 302 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy19: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case '\t': + case '\n': + case '\v': + case '\f': + case '\r': + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1A: + case 0x1B: + case 0x1C: + case 0x1D: + case 0x1E: + case 0x1F: + case ' ': + case '!': + case '"': + case '#': + case '%': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case '-': + case '.': + case '/': + case ':': + case ';': + case '<': + case '=': + case '>': + case '?': + case '@': + case '[': + case '\\': + case ']': + case '^': + case '`': + case '{': + case '|': + case '}': + case '~': + case 0x7F: goto yy1; + case '$': goto yy18; + default: goto yy19; + } +yy20: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy21: + switch (yych) { + case 0x00: goto yy1; + case '\'': goto yy22; + default: goto yy20; + } +yy22: + yyaccept = 2; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\'': goto yy20; + default: goto yy23; + } +yy23: +#line 43 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 397 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy24: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\n': goto yy25; + default: goto yy24; + } +yy25: +#line 50 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 409 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy26: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy33; + default: goto yy26; + } +yy27: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy27; + default: goto yy28; + } +yy28: +#line 47 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_BIND); } +#line 491 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy29: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case ':': goto yy29; + default: goto yy30; + } +yy30: +#line 45 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 503 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy31: + ++YYCURSOR; +#line 46 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_ESCAPED_QUESTION); } +#line 508 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy32: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy1; + case '\'': goto yy34; + case '\\': goto yy36; + default: goto yy32; + } +yy33: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy33; + case '/': goto yy37; + default: goto yy26; + } +yy34: + yyaccept = 3; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\'': goto yy32; + default: goto yy35; + } +yy35: +#line 41 "ext/pdo_pgsql/pgsql_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 540 "ext/pdo_pgsql/pgsql_sql_parser.c" +yy36: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 0x00) goto yy1; + goto yy32; +yy37: + ++YYCURSOR; + goto yy25; +} +#line 52 "ext/pdo_pgsql/pgsql_sql_parser.re" + +} +#endif diff --git a/thirdparty/php84/pdo_pgsql/pgsql_statement.c b/thirdparty/php84/pdo_pgsql/pgsql_statement.c new file mode 100644 index 00000000000..acb21384814 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,740 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_PGSQL_HOOK +#include "php_swoole_pgsql.h" + +#if PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "ext/pdo/php_pdo.h" +#include "ext/pdo/php_pdo_driver.h" +#include "php_pdo_pgsql_int.h" +#ifdef HAVE_NETINET_IN_H +#include +#endif + +/* from postgresql/src/include/catalog/pg_type.h */ +#define BOOLLABEL "bool" +#define BOOLOID 16 +#define BYTEALABEL "bytea" +#define BYTEAOID 17 +#define DATELABEL "date" +#define DATEOID 1082 +#define INT2LABEL "int2" +#define INT2OID 21 +#define INT4LABEL "int4" +#define INT4OID 23 +#define INT8LABEL "int8" +#define INT8OID 20 +#define OIDOID 26 +#define TEXTLABEL "text" +#define TEXTOID 25 +#define TIMESTAMPLABEL "timestamp" +#define TIMESTAMPOID 1114 +#define VARCHARLABEL "varchar" +#define VARCHAROID 1043 +#define FLOAT4LABEL "float4" +#define FLOAT4OID 700 +#define FLOAT8LABEL "float8" +#define FLOAT8OID 701 + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + bool server_obj_usable = + !Z_ISUNDEF(stmt->database_object_handle) && + IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)]) && + !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED); + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->stmt_name) { + if (S->is_prepared && server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + 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; + } + if (S->param_lengths) { + efree(S->param_lengths); + S->param_lengths = NULL; + } + if (S->param_values) { + efree(S->param_values); + S->param_values = NULL; + } + if (S->param_formats) { + efree(S->param_formats); + S->param_formats = NULL; + } + if (S->param_types) { + efree(S->param_types); + S->param_types = NULL; + } + if (S->query) { + zend_string_release(S->query); + S->query = NULL; + } + + if (S->cursor_name) { + if (server_obj_usable) { + pdo_pgsql_db_handle *H = S->H; + char *q = NULL; + PGresult *res; + + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + res = PQexec(H->server, q); + efree(q); + if (res) PQclear(res); + } + efree(S->cursor_name); + S->cursor_name = NULL; + } + + if (S->cols) { + efree(S->cols); + S->cols = NULL; + } + efree(S); + stmt->driver_data = NULL; + return 1; +} + +static int pgsql_stmt_execute(pdo_stmt_t *stmt) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + pdo_pgsql_db_handle *H = S->H; + ExecStatusType status; + + bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh); + + /* ensure that we free any previous unfetched results */ + if (S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->current_row = 0; + + if (S->cursor_name) { + char *q = NULL; + + if (S->is_prepared) { + spprintf(&q, 0, "CLOSE %s", S->cursor_name); + PQclear(PQexec(H->server, q)); + efree(q); + } + + spprintf( + &q, 0, "DECLARE %s SCROLL CURSOR WITH HOLD FOR %s", S->cursor_name, ZSTR_VAL(stmt->active_query_string)); + S->result = PQexec(H->server, q); + efree(q); + + /* check if declare failed */ + status = PQresultStatus(S->result); + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + PQclear(S->result); + + /* the cursor was declared correctly */ + S->is_prepared = 1; + + /* fetch to be able to get the number of tuples later, but don't advance the cursor pointer */ + spprintf(&q, 0, "FETCH FORWARD 0 FROM %s", S->cursor_name); + S->result = PQexec(H->server, q); + efree(q); + } else 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, + ZSTR_VAL(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 chance 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 (sqlstate && !strcmp(sqlstate, "42P05")) { + char buf[100]; /* stmt_name == "pdo_crsr_%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, + S->param_formats, + 0); + } else if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED) { + /* execute query with parameters */ + S->result = PQexecParams(H->server, + ZSTR_VAL(S->query), + stmt->bound_params ? zend_hash_num_elements(stmt->bound_params) : 0, + S->param_types, + (const char **) S->param_values, + S->param_lengths, + S->param_formats, + 0); + } else { + /* execute plain query (with embedded parameters) */ + S->result = PQexec(H->server, ZSTR_VAL(stmt->active_query_string)); + } + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->column_count = (int) PQnfields(S->result); + if (S->cols == NULL) { + S->cols = ecalloc(stmt->column_count, sizeof(pdo_pgsql_column)); + } + + if (status == PGRES_COMMAND_OK) { + stmt->row_count = ZEND_ATOL(PQcmdTuples(S->result)); + H->pgoid = PQoidValue(S->result); + } else { + stmt->row_count = (zend_long) PQntuples(S->result); + } + + if (in_trans && !stmt->dbh->methods->in_transaction(stmt->dbh)) { + pdo_pgsql_close_lob_streams(stmt->dbh); + } + + return 1; +} + +static int pgsql_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + if (stmt->supports_placeholders == PDO_PLACEHOLDER_NAMED && param->is_param) { + switch (event_type) { + 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 (ZSTR_VAL(param->name)[0] == '$') { + param->paramno = ZEND_ATOL(ZSTR_VAL(param->name) + 1); + } else { + /* resolve parameter name to rewritten name */ + zend_string *namevar; + + if (stmt->bound_param_map && + (namevar = zend_hash_find_ptr(stmt->bound_param_map, param->name)) != NULL) { + param->paramno = ZEND_ATOL(ZSTR_VAL(namevar) + 1); + param->paramno--; + } else { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", ZSTR_VAL(param->name)); + return 0; + } + } + } + break; + + case PDO_PARAM_EVT_ALLOC: + if (!stmt->bound_param_map) { + return 1; + } + if (!zend_hash_index_exists(stmt->bound_param_map, param->paramno)) { + pdo_pgsql_error_stmt_msg(stmt, 0, "HY093", "parameter was not defined"); + return 0; + } + ZEND_FALLTHROUGH; + 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 1; + } + if (!S->param_values) { + S->param_values = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(char *)); + S->param_lengths = ecalloc(zend_hash_num_elements(stmt->bound_param_map), sizeof(int)); + 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) { + zval *parameter; + + /* + if (param->paramno >= zend_hash_num_elements(stmt->bound_params)) { + pdo_raise_impl_error(stmt->dbh, stmt, "HY093", "parameter was not defined"); + return 0; + } + */ + + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_LOB && Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm; + php_stream_from_zval_no_verify(stm, 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 { + zend_string *str = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + if (str != NULL) { + ZVAL_STR(parameter, str); + } else { + ZVAL_EMPTY_STRING(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(parameter) == IS_NULL) { + S->param_values[param->paramno] = NULL; + S->param_lengths[param->paramno] = 0; + } else if (Z_TYPE_P(parameter) == IS_FALSE || Z_TYPE_P(parameter) == IS_TRUE) { + S->param_values[param->paramno] = Z_TYPE_P(parameter) == IS_TRUE ? "t" : "f"; + S->param_lengths[param->paramno] = 1; + S->param_formats[param->paramno] = 0; + } else { + convert_to_string(parameter); + S->param_values[param->paramno] = Z_STRVAL_P(parameter); + S->param_lengths[param->paramno] = Z_STRLEN_P(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; + } + } else if (param->is_param && event_type == PDO_PARAM_EVT_NORMALIZE) { + /* We need to manually convert to a pg native boolean value */ + if (PDO_PARAM_TYPE(param->param_type) == PDO_PARAM_BOOL && + ((param->param_type & PDO_PARAM_INPUT_OUTPUT) != PDO_PARAM_INPUT_OUTPUT)) { + const char *s = zend_is_true(¶m->parameter) ? "t" : "f"; + param->param_type = PDO_PARAM_STR; + zval_ptr_dtor(¶m->parameter); + ZVAL_STRINGL(¶m->parameter, s, 1); + } + } + return 1; +} + +static int pgsql_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + if (S->cursor_name) { + char *ori_str = NULL; + char *q = NULL; + ExecStatusType status; + + switch (ori) { + case PDO_FETCH_ORI_NEXT: + spprintf(&ori_str, 0, "NEXT"); + break; + case PDO_FETCH_ORI_PRIOR: + spprintf(&ori_str, 0, "BACKWARD"); + break; + case PDO_FETCH_ORI_FIRST: + spprintf(&ori_str, 0, "FIRST"); + break; + case PDO_FETCH_ORI_LAST: + spprintf(&ori_str, 0, "LAST"); + break; + case PDO_FETCH_ORI_ABS: + spprintf(&ori_str, 0, "ABSOLUTE " ZEND_LONG_FMT, offset); + break; + case PDO_FETCH_ORI_REL: + spprintf(&ori_str, 0, "RELATIVE " ZEND_LONG_FMT, offset); + break; + default: + return 0; + } + + if (S->result) { + PQclear(S->result); + S->result = NULL; + } + + spprintf(&q, 0, "FETCH %s FROM %s", ori_str, S->cursor_name); + efree(ori_str); + S->result = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + if (PQntuples(S->result)) { + S->current_row = 1; + return 1; + } else { + return 0; + } + } else { + if (S->current_row < stmt->row_count) { + S->current_row++; + return 1; + } else { + return 0; + } + } +} + +static int pgsql_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + struct pdo_column_data *cols = stmt->columns; + char *str; + + if (!S->result) { + return 0; + } + + str = PQfname(S->result, colno); + cols[colno].name = zend_string_init(str, strlen(str), 0); + cols[colno].maxlen = PQfsize(S->result, colno); + cols[colno].precision = PQfmod(S->result, colno); + S->cols[colno].pgsql_type = PQftype(S->result, colno); + + return 1; +} + +static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + if (!S->result) { + return 0; + } + + /* We have already increased count by 1 in pgsql_stmt_fetch() */ + if (PQgetisnull(S->result, S->current_row - 1, colno)) { /* Check if we got NULL */ + ZVAL_NULL(result); + } else { + char *ptr = PQgetvalue(S->result, S->current_row - 1, colno); + size_t len = PQgetlength(S->result, S->current_row - 1, colno); + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + ZVAL_BOOL(result, *ptr == 't'); + break; + + case INT2OID: + case INT4OID: +#if SIZEOF_ZEND_LONG >= 8 + case INT8OID: +#endif + ZVAL_LONG(result, ZEND_ATOL(ptr)); + break; + case FLOAT4OID: + case FLOAT8OID: + if (strncmp(ptr, "Infinity", len) == 0) { + ZVAL_DOUBLE(result, ZEND_INFINITY); + } else if (strncmp(ptr, "-Infinity", len) == 0) { + ZVAL_DOUBLE(result, -ZEND_INFINITY); + } else if (strncmp(ptr, "NaN", len) == 0) { + ZVAL_DOUBLE(result, ZEND_NAN); + } else { + ZVAL_DOUBLE(result, zend_strtod(ptr, NULL)); + } + break; + + case OIDOID: { + char *end_ptr; + Oid oid = (Oid) strtoul(ptr, &end_ptr, 10); + if (type && *type == PDO_PARAM_LOB) { + /* If column was bound as LOB, return a stream. */ + int loid = lo_open(S->H->server, oid, INV_READ); + if (loid >= 0) { + php_stream *stream = pdo_pgsql_create_lob_stream(&stmt->database_object_handle, loid, oid); + if (stream) { + php_stream_to_zval(stream, result); + return 1; + } + } + return 0; + } else { + /* Otherwise return OID as integer. */ + ZVAL_LONG(result, oid); + } + break; + } + + case BYTEAOID: { + size_t tmp_len; + char *tmp_ptr = (char *) PQunescapeBytea((unsigned char *) ptr, &tmp_len); + if (!tmp_ptr) { + /* PQunescapeBytea returned an error */ + return 0; + } + + zend_string *str = zend_string_init(tmp_ptr, tmp_len, 0); + php_stream *stream = php_stream_memory_open(TEMP_STREAM_READONLY, str); + php_stream_to_zval(stream, result); + zend_string_release(str); + PQfreemem(tmp_ptr); + break; + } + + default: + ZVAL_STRINGL_FAST(result, ptr, len); + break; + } + } + + return 1; +} + +static zend_always_inline char *pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) { + char *table_name = NULL; + PGresult *tmp_res; + char *querystr = NULL; + + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); + + if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { + if (tmp_res) { + PQclear(tmp_res); + } + efree(querystr); + return 0; + } + efree(querystr); + + if (1 == PQgetisnull(tmp_res, 0, 0) || (table_name = PQgetvalue(tmp_res, 0, 0)) == NULL) { + PQclear(tmp_res); + return 0; + } + + table_name = estrdup(table_name); + + PQclear(tmp_res); + return table_name; +} + +static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + PGresult *res; + char *q = NULL; + ExecStatusType status; + Oid table_oid; + char *table_name = NULL; + + if (!S->result) { + return FAILURE; + } + + if (colno >= stmt->column_count) { + return FAILURE; + } + + array_init(return_value); + add_assoc_long(return_value, "pgsql:oid", S->cols[colno].pgsql_type); + + table_oid = PQftable(S->result, colno); + add_assoc_long(return_value, "pgsql:table_oid", table_oid); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + if (table_name) { + add_assoc_string(return_value, "table", table_name); + efree(table_name); + } + + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + add_assoc_string(return_value, "native_type", BOOLLABEL); + break; + case BYTEAOID: + add_assoc_string(return_value, "native_type", BYTEALABEL); + break; + case INT8OID: + add_assoc_string(return_value, "native_type", INT8LABEL); + break; + case INT2OID: + add_assoc_string(return_value, "native_type", INT2LABEL); + break; + case INT4OID: + add_assoc_string(return_value, "native_type", INT4LABEL); + break; + case FLOAT4OID: + add_assoc_string(return_value, "native_type", FLOAT4LABEL); + break; + case FLOAT8OID: + add_assoc_string(return_value, "native_type", FLOAT8LABEL); + break; + case TEXTOID: + add_assoc_string(return_value, "native_type", TEXTLABEL); + break; + case VARCHAROID: + add_assoc_string(return_value, "native_type", VARCHARLABEL); + break; + case DATEOID: + add_assoc_string(return_value, "native_type", DATELABEL); + break; + case TIMESTAMPOID: + add_assoc_string(return_value, "native_type", TIMESTAMPLABEL); + break; + default: + /* Fetch metadata from Postgres system catalogue */ + spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); + res = PQexec(S->H->server, q); + efree(q); + status = PQresultStatus(res); + if (status == PGRES_TUPLES_OK && 1 == PQntuples(res)) { + add_assoc_string(return_value, "native_type", PQgetvalue(res, 0, 0)); + } + PQclear(res); + } + + enum pdo_param_type param_type; + switch (S->cols[colno].pgsql_type) { + case BOOLOID: + param_type = PDO_PARAM_BOOL; + break; + case INT2OID: + case INT4OID: + case INT8OID: + param_type = PDO_PARAM_INT; + break; + case OIDOID: + case BYTEAOID: + param_type = PDO_PARAM_LOB; + break; + default: + param_type = PDO_PARAM_STR; + } + add_assoc_long(return_value, "pdo_type", param_type); + + return 1; +} + +static int pdo_pgsql_stmt_cursor_closer(pdo_stmt_t *stmt) { + return 1; +} + +static int pgsql_stmt_get_attr(pdo_stmt_t *stmt, zend_long attr, zval *val) { + pdo_pgsql_stmt *S = (pdo_pgsql_stmt *) stmt->driver_data; + + switch (attr) { +#ifdef HAVE_PG_RESULT_MEMORY_SIZE + case PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE: + if (stmt->executed) { + ZVAL_LONG(val, PQresultMemorySize(S->result)); + } else { + char *tmp; + spprintf(&tmp, 0, "statement '%s' has not been executed yet", S->stmt_name); + + pdo_pgsql_error_stmt_msg(stmt, 0, "HY000", tmp); + efree(tmp); + + ZVAL_NULL(val); + } + return 1; +#endif + + default: + (void) S; + return 0; + } +} + +const struct pdo_stmt_methods swoole_pgsql_stmt_methods = {pgsql_stmt_dtor, + pgsql_stmt_execute, + pgsql_stmt_fetch, + pgsql_stmt_describe, + pgsql_stmt_get_col, + pgsql_stmt_param_hook, + NULL, /* set_attr */ + pgsql_stmt_get_attr, + pgsql_stmt_get_column_meta, + NULL, /* next_rowset */ + pdo_pgsql_stmt_cursor_closer}; +#endif diff --git a/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 00000000000..c48a6bd9278 --- /dev/null +++ b/thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,129 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Edin Kadribasic | + | Ilia Alshanestsky | + | Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_INT_H +#define PHP_PDO_PGSQL_INT_H + +#include +#include +#include + +#define PHP_PDO_PGSQL_CONNECTION_FAILURE_SQLSTATE "08006" + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_pgsql_error_info; + +/* stuff we use in a pgsql database handle */ +typedef struct { + PGconn *server; + unsigned attached : 1; + unsigned _reserved : 31; + pdo_pgsql_error_info einfo; + Oid pgoid; + unsigned int stmt_counter; + /* The following two variables have the same purpose. Unfortunately we need + to keep track of two different attributes having the same effect. */ + bool emulate_prepares; + bool disable_native_prepares; /* deprecated since 5.6 */ + bool disable_prepares; + HashTable *lob_streams; + zend_fcall_info_cache *notice_callback; +} pdo_pgsql_db_handle; + +typedef struct { + Oid pgsql_type; +} pdo_pgsql_column; + +typedef struct { + pdo_pgsql_db_handle *H; + PGresult *result; + pdo_pgsql_column *cols; + char *cursor_name; + char *stmt_name; + zend_string *query; + char **param_values; + int *param_lengths; + int *param_formats; + Oid *param_types; + int current_row; + bool is_prepared; +} pdo_pgsql_stmt; + +typedef struct { + Oid oid; +} pdo_pgsql_bound_param; + +extern const pdo_driver_t pdo_pgsql_driver; + +extern int pdo_pgsql_scanner(pdo_scanner_t *s); + +extern int _pdo_pgsql_error( + pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); +#define pdo_pgsql_error(d, e, z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_msg(d, e, m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt(s, e, z) _pdo_pgsql_error(s->dbh, s, e, z, NULL, __FILE__, __LINE__) +#define pdo_pgsql_error_stmt_msg(stmt, e, sqlstate, msg) \ + _pdo_pgsql_error(stmt->dbh, stmt, e, sqlstate, msg, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_pgsql_stmt_methods; + +#define pdo_pgsql_sqlstate(r) PQresultErrorField(r, PG_DIAG_SQLSTATE) + +enum { + PDO_PGSQL_ATTR_DISABLE_PREPARES = PDO_ATTR_DRIVER_SPECIFIC, + PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE, +}; + +struct pdo_pgsql_lob_self { + zval dbh; + PGconn *conn; + int lfd; + Oid oid; +}; + +enum pdo_pgsql_specific_constants { + PGSQL_TRANSACTION_IDLE = PQTRANS_IDLE, + PGSQL_TRANSACTION_ACTIVE = PQTRANS_ACTIVE, + PGSQL_TRANSACTION_INTRANS = PQTRANS_INTRANS, + PGSQL_TRANSACTION_INERROR = PQTRANS_INERROR, + PGSQL_TRANSACTION_UNKNOWN = PQTRANS_UNKNOWN +}; + +php_stream *pdo_pgsql_create_lob_stream(zval *pdh, int lfd, Oid oid); +extern const php_stream_ops pdo_pgsql_lob_stream_ops; + +void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H); + +void pdo_libpq_version(char *buf, size_t len); +void pdo_pgsql_close_lob_streams(pdo_dbh_t *dbh); + +void pgsqlCopyFromArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyFromFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToArray_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlCopyToFile_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBCreate_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBOpen_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlLOBUnlink_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetNotify_internal(INTERNAL_FUNCTION_PARAMETERS); +void pgsqlGetPid_internal(INTERNAL_FUNCTION_PARAMETERS); + +#endif /* PHP_PDO_PGSQL_INT_H */ diff --git a/thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h b/thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h new file mode 100644 index 00000000000..b04f31ec7f0 --- /dev/null +++ b/thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h @@ -0,0 +1,84 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_SQLITE_INT_H +#define PHP_PDO_SQLITE_INT_H + +#include + +typedef struct { + const char *file; + int line; + unsigned int errcode; + char *errmsg; +} pdo_sqlite_error_info; + +struct pdo_sqlite_func { + struct pdo_sqlite_func *next; + + int argc; + const char *funcname; + + /* accelerated callback references */ + zend_fcall_info_cache func; + zend_fcall_info_cache step; + zend_fcall_info_cache fini; +}; + +struct pdo_sqlite_collation { + struct pdo_sqlite_collation *next; + + const char *name; + zend_fcall_info_cache callback; +}; + +typedef struct { + sqlite3 *db; + pdo_sqlite_error_info einfo; + struct pdo_sqlite_func *funcs; + struct pdo_sqlite_collation *collations; +} pdo_sqlite_db_handle; + +typedef struct { + pdo_sqlite_db_handle *H; + sqlite3_stmt *stmt; + unsigned pre_fetched : 1; + unsigned done : 1; +} pdo_sqlite_stmt; + +extern const pdo_driver_t pdo_sqlite_driver; + +extern int pdo_sqlite_scanner(pdo_scanner_t *s); + +extern int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); +#define pdo_sqlite_error(s) _pdo_sqlite_error(s, NULL, __FILE__, __LINE__) +#define pdo_sqlite_error_stmt(s) _pdo_sqlite_error(stmt->dbh, stmt, __FILE__, __LINE__) + +extern const struct pdo_stmt_methods swoole_sqlite_stmt_methods; + +enum { + PDO_SQLITE_ATTR_OPEN_FLAGS = PDO_ATTR_DRIVER_SPECIFIC, + PDO_SQLITE_ATTR_READONLY_STATEMENT, + PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES +}; + +typedef int pdo_sqlite_create_collation_callback(void *, int, const void *, int, const void *); + +void pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAMETERS); +void pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAMETERS); +void pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAMETERS, pdo_sqlite_create_collation_callback callback); + +#endif diff --git a/thirdparty/php84/pdo_sqlite/sqlite_driver.c b/thirdparty/php84/pdo_sqlite/sqlite_driver.c new file mode 100644 index 00000000000..27d53bbe991 --- /dev/null +++ b/thirdparty/php84/pdo_sqlite/sqlite_driver.c @@ -0,0 +1,825 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" +#include "php_swoole_call_stack.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" +#include "zend_exceptions.h" +#include "sqlite_driver_arginfo.h" + +int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + pdo_error_type *pdo_err = stmt ? &stmt->error_code : &dbh->error_code; + pdo_sqlite_error_info *einfo = &H->einfo; + + einfo->errcode = sqlite3_errcode(H->db); + einfo->file = file; + einfo->line = line; + + if (einfo->errcode != SQLITE_OK) { + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + } + einfo->errmsg = pestrdup((char *) sqlite3_errmsg(H->db), dbh->is_persistent); + } else { /* no error */ + strncpy(*pdo_err, PDO_ERR_NONE, sizeof(*pdo_err)); + return 0; + } + switch (einfo->errcode) { + case SQLITE_NOTFOUND: + strncpy(*pdo_err, "42S02", sizeof(*pdo_err)); + break; + + case SQLITE_INTERRUPT: + strncpy(*pdo_err, "01002", sizeof(*pdo_err)); + break; + + case SQLITE_NOLFS: + strncpy(*pdo_err, "HYC00", sizeof(*pdo_err)); + break; + + case SQLITE_TOOBIG: + strncpy(*pdo_err, "22001", sizeof(*pdo_err)); + break; + + case SQLITE_CONSTRAINT: + strncpy(*pdo_err, "23000", sizeof(*pdo_err)); + break; + + case SQLITE_ERROR: + default: + strncpy(*pdo_err, "HY000", sizeof(*pdo_err)); + break; + } + + if (!dbh->methods) { + pdo_throw_exception(einfo->errcode, einfo->errmsg, pdo_err); + } + + return einfo->errcode; +} +/* }}} */ + +static void pdo_sqlite_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + pdo_sqlite_error_info *einfo = &H->einfo; + + if (einfo->errcode) { + add_next_index_long(info, einfo->errcode); + add_next_index_string(info, einfo->errmsg); + } +} + +static void pdo_sqlite_cleanup_callbacks(pdo_sqlite_db_handle *H) { + struct pdo_sqlite_func *func; + + while (H->funcs) { + func = H->funcs; + H->funcs = func->next; + + if (H->db) { + /* delete the function from the handle */ + sqlite3_create_function(H->db, func->funcname, func->argc, SQLITE_UTF8, func, NULL, NULL, NULL); + } + + efree((char *) func->funcname); + if (ZEND_FCC_INITIALIZED(func->func)) { + zend_fcc_dtor(&func->func); + } + if (ZEND_FCC_INITIALIZED(func->step)) { + zend_fcc_dtor(&func->step); + } + if (ZEND_FCC_INITIALIZED(func->fini)) { + zend_fcc_dtor(&func->fini); + } + efree(func); + } + + while (H->collations) { + struct pdo_sqlite_collation *collation; + collation = H->collations; + H->collations = collation->next; + + if (H->db) { + /* delete the collation from the handle */ + sqlite3_create_collation(H->db, collation->name, SQLITE_UTF8, collation, NULL); + } + + efree((char *) collation->name); + if (ZEND_FCC_INITIALIZED(collation->callback)) { + zend_fcc_dtor(&collation->callback); + } + efree(collation); + } +} + +static void sqlite_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (H) { + pdo_sqlite_error_info *einfo = &H->einfo; + + pdo_sqlite_cleanup_callbacks(H); + if (H->db) { +#ifdef HAVE_SQLITE3_CLOSE_V2 + sqlite3_close_v2(H->db); +#else + sqlite3_close(H->db); +#endif + H->db = NULL; + } + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + } + pefree(H, dbh->is_persistent); + dbh->driver_data = NULL; + } +} +/* }}} */ + +static bool sqlite_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *stmt, zval *driver_options) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + pdo_sqlite_stmt *S = ecalloc(1, sizeof(pdo_sqlite_stmt)); + int i; + const char *tail; + + S->H = H; + stmt->driver_data = S; + stmt->methods = &swoole_sqlite_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL | PDO_PLACEHOLDER_NAMED; + + if (PDO_CURSOR_FWDONLY != pdo_attr_lval(driver_options, PDO_ATTR_CURSOR, PDO_CURSOR_FWDONLY)) { + H->einfo.errcode = SQLITE_ERROR; + pdo_sqlite_error(dbh); + return false; + } + + i = sqlite3_prepare_v2(H->db, ZSTR_VAL(sql), ZSTR_LEN(sql), &S->stmt, &tail); + if (i == SQLITE_OK) { + return true; + } + + pdo_sqlite_error(dbh); + + return false; +} + +static zend_long sqlite_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, ZSTR_VAL(sql), NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return -1; + } else { + return sqlite3_changes(H->db); + } +} + +static zend_string *pdo_sqlite_last_insert_id(pdo_dbh_t *dbh, const zend_string *name) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + return zend_i64_to_str(sqlite3_last_insert_rowid(H->db)); +} + +/* NB: doesn't handle binary strings... use prepared stmts for that */ +static zend_string *sqlite_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { + char *quoted; + if (ZSTR_LEN(unquoted) > (INT_MAX - 3) / 2) { + return NULL; + } + quoted = safe_emalloc(2, ZSTR_LEN(unquoted), 3); + /* TODO use %Q format? */ + sqlite3_snprintf(2 * ZSTR_LEN(unquoted) + 3, quoted, "'%q'", ZSTR_VAL(unquoted)); + zend_string *quoted_str = zend_string_init(quoted, strlen(quoted), 0); + efree(quoted); + return quoted_str; +} + +static bool sqlite_handle_begin(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, "BEGIN", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static bool sqlite_handle_commit(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, "COMMIT", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static bool sqlite_handle_rollback(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + + if (sqlite3_exec(H->db, "ROLLBACK", NULL, NULL, NULL) != SQLITE_OK) { + pdo_sqlite_error(dbh); + return false; + } + return true; +} + +static int pdo_sqlite_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value) { + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *) sqlite3_libversion()); + break; + + default: + return 0; + } + + return 1; +} + +static bool pdo_sqlite_in_transaction(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + /* It's not possible in sqlite3 to explicitly turn autocommit off other + * than manually starting a transaction. Manual transactions always are + * the mode of operation when autocommit is off. */ + return H->db && sqlite3_get_autocommit(H->db) == 0; +} + +static bool pdo_sqlite_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + zend_long lval; + + switch (attr) { + case PDO_ATTR_TIMEOUT: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + sqlite3_busy_timeout(H->db, lval * 1000); + return true; + case PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + sqlite3_extended_result_codes(H->db, lval); + return true; + } + return false; +} + +typedef struct { + zval val; + zend_long row; +} aggregate_context; + +static int do_callback( + zend_fcall_info_cache *fcc, int argc, sqlite3_value **argv, sqlite3_context *context, int is_agg) { + zval *zargs = NULL; + zval retval; + int i; + int ret = SUCCESS; + int fake_argc; + aggregate_context *agg_context = NULL; + + if (is_agg) { + is_agg = 2; + } + + fake_argc = argc + is_agg; + + /* build up the params */ + if (fake_argc) { + zargs = safe_emalloc(fake_argc, sizeof(zval), 0); + } + + if (is_agg) { + agg_context = sqlite3_aggregate_context(context, sizeof(aggregate_context)); + if (!agg_context) { + efree(zargs); + return FAILURE; + } + if (Z_ISUNDEF(agg_context->val)) { + ZVAL_NEW_REF(&agg_context->val, &EG(uninitialized_zval)); + } + ZVAL_COPY_VALUE(&zargs[0], &agg_context->val); + ZVAL_LONG(&zargs[1], ++agg_context->row); + } + + for (i = 0; i < argc; i++) { + /* get the value */ + switch (sqlite3_value_type(argv[i])) { + case SQLITE_INTEGER: + ZVAL_LONG(&zargs[i + is_agg], sqlite3_value_int(argv[i])); + break; + + case SQLITE_FLOAT: + ZVAL_DOUBLE(&zargs[i + is_agg], sqlite3_value_double(argv[i])); + break; + + case SQLITE_NULL: + ZVAL_NULL(&zargs[i + is_agg]); + break; + + case SQLITE_BLOB: + case SQLITE3_TEXT: + default: + ZVAL_STRINGL(&zargs[i + is_agg], (char *) sqlite3_value_text(argv[i]), sqlite3_value_bytes(argv[i])); + break; + } + } + + HOOK_PHP_CALL_STACK(zend_call_known_fcc(fcc, &retval, fake_argc, zargs, /* named_params */ NULL);); + + /* clean up the params */ + if (zargs) { + for (i = is_agg; i < fake_argc; i++) { + zval_ptr_dtor(&zargs[i]); + } + if (is_agg) { + zval_ptr_dtor(&zargs[1]); + } + efree(zargs); + } + + if (!is_agg || !argv) { + /* only set the sqlite return value if we are a scalar function, + * or if we are finalizing an aggregate */ + if (!Z_ISUNDEF(retval)) { + switch (Z_TYPE(retval)) { + case IS_LONG: + sqlite3_result_int(context, Z_LVAL(retval)); + break; + + case IS_NULL: + sqlite3_result_null(context); + break; + + case IS_DOUBLE: + sqlite3_result_double(context, Z_DVAL(retval)); + break; + + default: + if (!try_convert_to_string(&retval)) { + ret = FAILURE; + break; + } + sqlite3_result_text(context, Z_STRVAL(retval), Z_STRLEN(retval), SQLITE_TRANSIENT); + break; + } + } else { + sqlite3_result_error(context, "failed to invoke callback", 0); + } + + if (agg_context) { + zval_ptr_dtor(&agg_context->val); + } + } else { + /* we're stepping in an aggregate; the return value goes into + * the context */ + if (agg_context) { + if (Z_ISUNDEF(retval)) { + return FAILURE; + } + zval_ptr_dtor(Z_REFVAL(agg_context->val)); + ZVAL_COPY_VALUE(Z_REFVAL(agg_context->val), &retval); + ZVAL_UNDEF(&retval); + } + } + + if (!Z_ISUNDEF(retval)) { + zval_ptr_dtor(&retval); + } + + return ret; +} + +static void php_sqlite3_func_step_callback(sqlite3_context *context, int argc, sqlite3_value **argv) { + struct pdo_sqlite_func *func = (struct pdo_sqlite_func *) sqlite3_user_data(context); + + do_callback(&func->step, argc, argv, context, 1); +} + +static void php_sqlite3_func_final_callback(sqlite3_context *context) { + struct pdo_sqlite_func *func = (struct pdo_sqlite_func *) sqlite3_user_data(context); + + do_callback(&func->fini, 0, NULL, context, 1); +} + +static int php_sqlite3_collation_callback( + void *context, int string1_len, const void *string1, int string2_len, const void *string2) { + int ret = 0; + zval zargs[2]; + zval retval; + struct pdo_sqlite_collation *collation = (struct pdo_sqlite_collation *) context; + + /* Prepare the arguments. */ + ZVAL_STRINGL(&zargs[0], (char *) string1, string1_len); + ZVAL_STRINGL(&zargs[1], (char *) string2, string2_len); + + HOOK_PHP_CALL_STACK( + zend_call_known_fcc(&collation->callback, &retval, /* argc */ 2, zargs, /* named_params */ NULL);); + + if (!Z_ISUNDEF(retval)) { + if (Z_TYPE(retval) != IS_LONG) { + convert_to_long(&retval); + } + if (Z_LVAL(retval) > 0) { + ret = 1; + } else if (Z_LVAL(retval) < 0) { + ret = -1; + } + zval_ptr_dtor(&retval); + } + + zval_ptr_dtor(&zargs[0]); + zval_ptr_dtor(&zargs[1]); + + return ret; +} + +static void php_sqlite3_func_callback(sqlite3_context *context, int argc, sqlite3_value **argv) { + struct pdo_sqlite_func *func = (struct pdo_sqlite_func *) sqlite3_user_data(context); + + do_callback(&func->func, argc, argv, context, 0); +} + +void pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAMETERS) { + struct pdo_sqlite_func *func; + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + char *func_name; + size_t func_name_len; + zend_long argc = -1; + zend_long flags = 0; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 4) + Z_PARAM_STRING(func_name, func_name_len) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(argc) + Z_PARAM_LONG(flags) + ZEND_PARSE_PARAMETERS_END_EX(goto error;); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *) dbh->driver_data; + + func = (struct pdo_sqlite_func *) ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function( + H->db, func_name, argc, flags | SQLITE_UTF8, func, php_sqlite3_func_callback, NULL, NULL); + if (ret == SQLITE_OK) { + func->funcname = estrdup(func_name); + + zend_fcc_dup(&func->func, &fcc); + + func->argc = argc; + + func->next = H->funcs; + H->funcs = func; + + RETURN_TRUE; + } + + efree(func); + +error: + zend_release_fcall_info_cache(&fcc); + RETURN_FALSE; +} + +/* {{{ bool SQLite::sqliteCreateFunction(string name, callable callback [, int argcount, int flags]) + Registers a UDF with the sqlite db handle */ +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateFunction) { + pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAMETERS) { + struct pdo_sqlite_func *func; + zend_fcall_info step_fci = empty_fcall_info; + zend_fcall_info fini_fci = empty_fcall_info; + zend_fcall_info_cache step_fcc = empty_fcall_info_cache; + zend_fcall_info_cache fini_fcc = empty_fcall_info_cache; + char *func_name; + size_t func_name_len; + zend_long argc = -1; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STRING(func_name, func_name_len) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(step_fci, step_fcc) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fini_fci, fini_fcc) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(argc) + ZEND_PARSE_PARAMETERS_END_EX(goto error;); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *) dbh->driver_data; + + func = (struct pdo_sqlite_func *) ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function(H->db, + func_name, + argc, + SQLITE_UTF8, + func, + NULL, + php_sqlite3_func_step_callback, + php_sqlite3_func_final_callback); + if (ret == SQLITE_OK) { + func->funcname = estrdup(func_name); + + zend_fcc_dup(&func->step, &step_fcc); + zend_fcc_dup(&func->fini, &fini_fcc); + + func->argc = argc; + + func->next = H->funcs; + H->funcs = func; + + RETURN_TRUE; + } + + efree(func); + +error: + zend_release_fcall_info_cache(&step_fcc); + zend_release_fcall_info_cache(&fini_fcc); + RETURN_FALSE; +} + +/* {{{ bool SQLite::sqliteCreateAggregate(string name, callable step, callable fini [, int argcount]) + Registers a UDF with the sqlite db handle */ + +/* The step function should have the prototype: + mixed step(mixed $context, int $rownumber, $value [, $value2 [, ...]]) + + $context will be null for the first row; on subsequent rows it will have + the value that was previously returned from the step function; you should + use this to maintain state for the aggregate. + + The fini function should have the prototype: + mixed fini(mixed $context, int $rownumber) + + $context will hold the return value from the very last call to the step function. + rownumber will hold the number of rows over which the aggregate was performed. + The return value of this function will be used as the return value for this + aggregate UDF. +*/ + +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateAggregate) { + pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU); +} +/* }}} */ + +void pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAMETERS, pdo_sqlite_create_collation_callback callback) { + struct pdo_sqlite_collation *collation; + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; + char *collation_name; + size_t collation_name_len; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STRING(collation_name, collation_name_len) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + + H = (pdo_sqlite_db_handle *) dbh->driver_data; + + collation = (struct pdo_sqlite_collation *) ecalloc(1, sizeof(*collation)); + + ret = sqlite3_create_collation(H->db, collation_name, SQLITE_UTF8, collation, callback); + if (ret == SQLITE_OK) { + collation->name = estrdup(collation_name); + + zend_fcc_dup(&collation->callback, &fcc); + + collation->next = H->collations; + H->collations = collation; + + RETURN_TRUE; + } + + zend_release_fcall_info_cache(&fcc); + + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } + + efree(collation); + RETURN_FALSE; +} + +/* {{{ bool SQLite::sqliteCreateCollation(string name, callable callback) + Registers a collation with the sqlite db handle */ +PHP_METHOD(PDO_SQLite_Ext, sqliteCreateCollation) { + pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAM_PASSTHRU, php_sqlite3_collation_callback); +} +/* }}} */ + +static const zend_function_entry *get_driver_methods(pdo_dbh_t *dbh, int kind) { + switch (kind) { + case PDO_DBH_DRIVER_METHOD_KIND_DBH: + return class_PDO_SQLite_Ext_methods; + + default: + return NULL; + } +} + +static void pdo_sqlite_request_shutdown(pdo_dbh_t *dbh) { + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *) dbh->driver_data; + /* unregister functions, so that they don't linger for the next + * request */ + if (H) { + pdo_sqlite_cleanup_callbacks(H); + } +} + +static void pdo_sqlite_get_gc(pdo_dbh_t *dbh, zend_get_gc_buffer *gc_buffer) { + pdo_sqlite_db_handle *H = dbh->driver_data; + + struct pdo_sqlite_func *func = H->funcs; + while (func) { + if (ZEND_FCC_INITIALIZED(func->func)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &func->func); + } + if (ZEND_FCC_INITIALIZED(func->step)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &func->step); + } + if (ZEND_FCC_INITIALIZED(func->fini)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &func->fini); + } + func = func->next; + } + + struct pdo_sqlite_collation *collation = H->collations; + while (collation) { + if (ZEND_FCC_INITIALIZED(collation->callback)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &collation->callback); + } + collation = collation->next; + } +} + +static const struct pdo_dbh_methods sqlite_methods = {sqlite_handle_closer, + sqlite_handle_preparer, + sqlite_handle_doer, + sqlite_handle_quoter, + sqlite_handle_begin, + sqlite_handle_commit, + sqlite_handle_rollback, + pdo_sqlite_set_attr, + pdo_sqlite_last_insert_id, + pdo_sqlite_fetch_error_func, + pdo_sqlite_get_attribute, + NULL, /* check_liveness: not needed */ + get_driver_methods, + pdo_sqlite_request_shutdown, + pdo_sqlite_in_transaction, + pdo_sqlite_get_gc, + pdo_sqlite_scanner}; + +static char *make_filename_safe(const char *filename) { + if (!filename) { + return NULL; + } + if (*filename && strncasecmp(filename, "file:", 5) == 0) { + if (PG(open_basedir) && *PG(open_basedir)) { + return NULL; + } + return estrdup(filename); + } + if (*filename && strcmp(filename, ":memory:")) { + char *fullpath = expand_filepath(filename, NULL); + + if (!fullpath) { + return NULL; + } + + if (php_check_open_basedir(fullpath)) { + efree(fullpath); + return NULL; + } + return fullpath; + } + return estrdup(filename); +} + +static int authorizer( + void *autharg, int access_type, const char *arg3, const char *arg4, const char *arg5, const char *arg6) { + char *filename; + switch (access_type) { + case SQLITE_ATTACH: { + filename = make_filename_safe(arg3); + if (!filename) { + return SQLITE_DENY; + } + efree(filename); + return SQLITE_OK; + } + + default: + /* access allowed */ + return SQLITE_OK; + } +} + +static int pdo_sqlite_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + pdo_sqlite_db_handle *H; + int i, ret = 0; + zend_long timeout = 60, flags; + char *filename; + + H = pecalloc(1, sizeof(pdo_sqlite_db_handle), dbh->is_persistent); + + H->einfo.errcode = 0; + H->einfo.errmsg = NULL; + dbh->driver_data = H; + + /* skip all but this one param event */ + dbh->skip_param_evt = 0x7F ^ (1 << PDO_PARAM_EVT_EXEC_PRE); + + filename = make_filename_safe(dbh->data_source); + + if (!filename) { + zend_throw_exception_ex(php_pdo_get_exception(), 0, "open_basedir prohibits opening %s", dbh->data_source); + goto cleanup; + } + + flags = pdo_attr_lval(driver_options, PDO_SQLITE_ATTR_OPEN_FLAGS, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); + + if (!(PG(open_basedir) && *PG(open_basedir))) { + flags |= SQLITE_OPEN_URI; + } + i = sqlite3_open_v2(filename, &H->db, flags, NULL); + + efree(filename); + + if (i != SQLITE_OK) { + pdo_sqlite_error(dbh); + goto cleanup; + } + + if (PG(open_basedir) && *PG(open_basedir)) { + sqlite3_set_authorizer(H->db, authorizer, NULL); + } + + if (driver_options) { + timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, timeout); + } + sqlite3_busy_timeout(H->db, timeout * 1000); + + dbh->alloc_own_columns = 1; + dbh->max_escaped_char_length = 2; + + ret = 1; + +cleanup: + dbh->methods = &sqlite_methods; + + return ret; +} +/* }}} */ + +const pdo_driver_t swoole_pdo_sqlite_driver = {PDO_DRIVER_HEADER(sqlite), pdo_sqlite_handle_factory}; +#endif diff --git a/thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php b/thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php new file mode 100644 index 00000000000..add395f2b99 --- /dev/null +++ b/thirdparty/php84/pdo_sqlite/sqlite_driver.stub.php @@ -0,0 +1,18 @@ + | + +----------------------------------------------------------------------+ +*/ + +#include "php_swoole.h" +#if defined(SW_USE_SQLITE) && PHP_VERSION_ID >= 80400 + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_sqlite_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + #line 35 "ext/pdo_sqlite/sqlite_sql_parser.re" + + + +#line 35 "ext/pdo_sqlite/sqlite_sql_parser.c" +{ + YYCTYPE yych; + unsigned int yyaccept = 0; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy4; + case '\'': goto yy6; + case '-': goto yy7; + case '/': goto yy8; + case ':': goto yy9; + case '?': goto yy10; + case '[': goto yy12; + case '`': goto yy13; + default: goto yy2; + } +yy1: + YYCURSOR = YYMARKER; + switch (yyaccept) { + case 0: goto yy5; + case 1: goto yy17; + case 2: goto yy21; + case 3: goto yy33; + default: goto yy37; + } +yy2: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: + case '"': + case '\'': + case '-': + case '/': + case ':': + case '?': + case '[': + case '`': goto yy3; + default: goto yy2; + } +yy3: +#line 47 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 81 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy4: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych >= 0x01) goto yy15; +yy5: +#line 45 "ext/pdo_sqlite/sqlite_sql_parser.re" + { SKIP_ONE(PDO_PARSER_TEXT); } +#line 89 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy6: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy19; +yy7: + yych = *++YYCURSOR; + switch (yych) { + case '-': goto yy22; + default: goto yy5; + } +yy8: + yych = *++YYCURSOR; + switch (yych) { + case '*': goto yy24; + default: goto yy5; + } +yy9: + yych = *++YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy25; + case ':': goto yy27; + default: goto yy5; + } +yy10: + yych = *++YYCURSOR; + switch (yych) { + case '?': goto yy29; + default: goto yy11; + } +yy11: +#line 44 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_BIND_POS); } +#line 185 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy12: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy31; +yy13: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy35; +yy14: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy15: + switch (yych) { + case 0x00: goto yy1; + case '"': goto yy16; + default: goto yy14; + } +yy16: + yyaccept = 1; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy17; + case '"': goto yy16; + default: goto yy14; + } +yy17: +#line 38 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 219 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy18: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy19: + switch (yych) { + case 0x00: goto yy1; + case '\'': goto yy20; + default: goto yy18; + } +yy20: + yyaccept = 2; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy21; + case '\'': goto yy20; + default: goto yy18; + } +yy21: +#line 39 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 243 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy22: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '\n': goto yy23; + default: goto yy22; + } +yy23: +#line 46 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 255 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy24: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy38; + default: goto yy24; + } +yy25: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': goto yy25; + default: goto yy26; + } +yy26: +#line 43 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_BIND); } +#line 337 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy27: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case ':': goto yy27; + default: goto yy28; + } +yy28: +#line 42 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 349 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy29: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '?': goto yy29; + default: goto yy28; + } +yy30: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy31: + switch (yych) { + case 0x00: goto yy1; + case ']': goto yy32; + default: goto yy30; + } +yy32: + yyaccept = 3; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy33; + case ']': goto yy32; + default: goto yy30; + } +yy33: +#line 41 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 381 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy34: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy35: + switch (yych) { + case 0x00: goto yy1; + case '`': goto yy36; + default: goto yy34; + } +yy36: + yyaccept = 4; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case 0x00: goto yy37; + case '`': goto yy36; + default: goto yy34; + } +yy37: +#line 40 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 405 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy38: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + switch (yych) { + case '*': goto yy38; + case '/': goto yy39; + default: goto yy24; + } +yy39: + ++YYCURSOR; + goto yy23; +} +#line 48 "ext/pdo_sqlite/sqlite_sql_parser.re" + +} +#endif diff --git a/thirdparty/php84/pdo_sqlite/sqlite_statement.c b/thirdparty/php84/pdo_sqlite/sqlite_statement.c new file mode 100644 index 00000000000..5f9793e4617 --- /dev/null +++ b/thirdparty/php84/pdo_sqlite/sqlite_statement.c @@ -0,0 +1,381 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Wez Furlong | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_SQLITE_HOOK +#include "php_swoole_sqlite.h" + +#if PHP_VERSION_ID >= 80400 +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "pdo/php_pdo.h" + +static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + + if (S->stmt) { + sqlite3_finalize(S->stmt); + S->stmt = NULL; + } + efree(S); + return 1; +} + +static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + } + + S->done = 0; + switch (sqlite3_step(S->stmt)) { + case SQLITE_ROW: + S->pre_fetched = 1; + php_pdo_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt)); + return 1; + + case SQLITE_DONE: + php_pdo_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt)); + stmt->row_count = sqlite3_changes(S->H->db); + sqlite3_reset(S->stmt); + S->done = 1; + return 1; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + ZEND_FALLTHROUGH; + case SQLITE_MISUSE: + case SQLITE_BUSY: + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_param_hook(pdo_stmt_t *stmt, + struct pdo_bound_param_data *param, + enum pdo_param_event event_type) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + zval *parameter; + + switch (event_type) { + case PDO_PARAM_EVT_EXEC_PRE: + if (stmt->executed && !S->done) { + sqlite3_reset(S->stmt); + S->done = 1; + } + + if (param->is_param) { + if (param->paramno == -1) { + param->paramno = sqlite3_bind_parameter_index(S->stmt, ZSTR_VAL(param->name)) - 1; + } + + switch (PDO_PARAM_TYPE(param->param_type)) { + case PDO_PARAM_STMT: + return 0; + + case PDO_PARAM_NULL: + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_INT: + case PDO_PARAM_BOOL: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + convert_to_long(parameter); +#if ZEND_LONG_MAX > 2147483647 + if (SQLITE_OK == sqlite3_bind_int64(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#else + if (SQLITE_OK == sqlite3_bind_int(S->stmt, param->paramno + 1, Z_LVAL_P(parameter))) { + return 1; + } +#endif + } + pdo_sqlite_error_stmt(stmt); + return 0; + + case PDO_PARAM_LOB: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_RESOURCE) { + php_stream *stm = NULL; + php_stream_from_zval_no_verify(stm, parameter); + if (stm) { + zend_string *mem = php_stream_copy_to_mem(stm, PHP_STREAM_COPY_ALL, 0); + zval_ptr_dtor(parameter); + ZVAL_STR(parameter, mem ? mem : ZSTR_EMPTY_ALLOC()); + } else { + pdo_raise_impl_error(stmt->dbh, stmt, "HY105", "Expected a stream resource"); + return 0; + } + } else if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + pdo_sqlite_error_stmt(stmt); + return 0; + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + } + + if (SQLITE_OK == + sqlite3_bind_blob( + S->stmt, param->paramno + 1, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), SQLITE_STATIC)) { + return 1; + } + return 0; + + case PDO_PARAM_STR: + default: + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + if (Z_TYPE_P(parameter) == IS_NULL) { + if (sqlite3_bind_null(S->stmt, param->paramno + 1) == SQLITE_OK) { + return 1; + } + } else { + if (!try_convert_to_string(parameter)) { + return 0; + } + if (SQLITE_OK == + sqlite3_bind_text( + S->stmt, param->paramno + 1, Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), SQLITE_STATIC)) { + return 1; + } + } + pdo_sqlite_error_stmt(stmt); + return 0; + } + } + break; + + default:; + } + return 1; +} + +static int pdo_sqlite_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + int i; + if (!S->stmt) { + return 0; + } + if (S->pre_fetched) { + S->pre_fetched = 0; + return 1; + } + if (S->done) { + return 0; + } + i = sqlite3_step(S->stmt); + switch (i) { + case SQLITE_ROW: + return 1; + + case SQLITE_DONE: + S->done = 1; + sqlite3_reset(S->stmt); + return 0; + + case SQLITE_ERROR: + sqlite3_reset(S->stmt); + ZEND_FALLTHROUGH; + default: + pdo_sqlite_error_stmt(stmt); + return 0; + } +} + +static int pdo_sqlite_stmt_describe(pdo_stmt_t *stmt, int colno) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + const char *str; + + if (colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + + str = sqlite3_column_name(S->stmt, colno); + stmt->columns[colno].name = zend_string_init(str, strlen(str), 0); + stmt->columns[colno].maxlen = SIZE_MAX; + stmt->columns[colno].precision = 0; + + return 1; +} + +static int pdo_sqlite_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + if (!S->stmt) { + return 0; + } + if (colno >= sqlite3_data_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return 0; + } + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + ZVAL_NULL(result); + return 1; + + case SQLITE_INTEGER: { + int64_t i = sqlite3_column_int64(S->stmt, colno); +#if SIZEOF_ZEND_LONG < 8 + if (i > ZEND_LONG_MAX || i < ZEND_LONG_MIN) { + ZVAL_STRINGL(result, (char *) sqlite3_column_text(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + } +#endif + ZVAL_LONG(result, i); + return 1; + } + + case SQLITE_FLOAT: + ZVAL_DOUBLE(result, sqlite3_column_double(S->stmt, colno)); + return 1; + + case SQLITE_BLOB: + ZVAL_STRINGL_FAST(result, sqlite3_column_blob(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + + default: + ZVAL_STRINGL_FAST(result, (char *) sqlite3_column_text(S->stmt, colno), sqlite3_column_bytes(S->stmt, colno)); + return 1; + } +} + +static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + const char *str; + zval flags; + + if (!S->stmt) { + return FAILURE; + } + if (colno >= sqlite3_column_count(S->stmt)) { + /* error invalid column */ + pdo_sqlite_error_stmt(stmt); + return FAILURE; + } + + array_init(return_value); + array_init(&flags); + + switch (sqlite3_column_type(S->stmt, colno)) { + case SQLITE_NULL: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_NULL_LOWERCASE)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_NULL); + break; + + case SQLITE_FLOAT: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_DOUBLE)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + + case SQLITE_BLOB: + add_next_index_string(&flags, "blob"); + /* TODO Check this is correct */ + ZEND_FALLTHROUGH; + case SQLITE_TEXT: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_STRING)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_STR); + break; + + case SQLITE_INTEGER: + add_assoc_str(return_value, "native_type", ZSTR_KNOWN(ZEND_STR_INTEGER)); + add_assoc_long(return_value, "pdo_type", PDO_PARAM_INT); + break; + } + + str = sqlite3_column_decltype(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "sqlite:decl_type", (char *) str); + } + +#ifdef HAVE_SQLITE3_COLUMN_TABLE_NAME + str = sqlite3_column_table_name(S->stmt, colno); + if (str) { + add_assoc_string(return_value, "table", (char *) str); + } +#endif + + add_assoc_zval(return_value, "flags", &flags); + + return SUCCESS; +} + +static int pdo_sqlite_stmt_cursor_closer(pdo_stmt_t *stmt) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + sqlite3_reset(S->stmt); + return 1; +} + +static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) { + pdo_sqlite_stmt *S = (pdo_sqlite_stmt *) stmt->driver_data; + + switch (attr) { + case PDO_SQLITE_ATTR_READONLY_STATEMENT: + ZVAL_FALSE(val); + +#if SQLITE_VERSION_NUMBER >= 3007004 + if (sqlite3_stmt_readonly(S->stmt)) { + ZVAL_TRUE(val); + } +#endif + break; + + default: + return 0; + } + + return 1; +} + +const struct pdo_stmt_methods swoole_sqlite_stmt_methods = {pdo_sqlite_stmt_dtor, + pdo_sqlite_stmt_execute, + pdo_sqlite_stmt_fetch, + pdo_sqlite_stmt_describe, + pdo_sqlite_stmt_get_col, + pdo_sqlite_stmt_param_hook, + NULL, /* set_attr */ + pdo_sqlite_stmt_get_attribute, /* get_attr */ + pdo_sqlite_stmt_col_meta, + NULL, /* next_rowset */ + pdo_sqlite_stmt_cursor_closer}; +#endif