diff --git a/.github/workflows/ext.yml b/.github/workflows/ext.yml index 9329310b63f..9b24ab0ef52 100644 --- a/.github/workflows/ext.yml +++ b/.github/workflows/ext.yml @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php-version: [ '8.1', '8.2', '8.3', '8.4' ] + php-version: [ '8.1', '8.2', '8.3', '8.4', '8.5' ] max-parallel: 8 fail-fast: false steps: diff --git a/.github/workflows/framework.yml b/.github/workflows/framework.yml index d29f01c19fa..800266b0db1 100644 --- a/.github/workflows/framework.yml +++ b/.github/workflows/framework.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: [ '8.1', '8.2', '8.3', '8.4' ] + php-version: [ '8.1', '8.2', '8.3', '8.4', '8.5' ] framework: [ 'Laravel Octane', 'Hyperf', 'Simps' ] name: ${{ matrix.framework }} - PHP ${{ matrix.php-version }} steps: diff --git a/.github/workflows/iouring.yml b/.github/workflows/iouring.yml index 48cd1673a49..cbdeaca9487 100644 --- a/.github/workflows/iouring.yml +++ b/.github/workflows/iouring.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - php: [ '8.1', '8.2', '8.3', '8.4' ] + php: [ '8.1', '8.2', '8.3', '8.4', '8.5' ] os: [ ubuntu-24.04, ubuntu-24.04-arm ] name: ${{ matrix.php }}-${{ matrix.os }}-iouring runs-on: ${{ matrix.os }} diff --git a/.github/workflows/thread.yml b/.github/workflows/thread.yml index 93b696fed29..f1a0b49b67a 100644 --- a/.github/workflows/thread.yml +++ b/.github/workflows/thread.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.1-zts', '8.2-zts', '8.3-zts', '8.4-zts'] + php: ['8.1-zts', '8.2-zts', '8.3-zts', '8.4-zts', '8.5-zts'] os: [ ubuntu-24.04, ubuntu-24.04-arm ] name: ${{ matrix.php }}-thread-${{ matrix.os }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 9c9b2f74e45..7b8b413fd8c 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['8.1', '8.2', '8.3', '8.4'] + php: ['8.1', '8.2', '8.3', '8.4', '8.5'] os: [ ubuntu-24.04, ubuntu-24.04-arm ] name: ${{ matrix.php }}-${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -50,7 +50,7 @@ jobs: strategy: fail-fast: false matrix: -# php-version: [ '8.1', '8.2', '8.3', '8.4' ] +# php-version: [ '8.1', '8.2', '8.3', '8.4', '8.5' ] php-version: [ '8.1' ] name: ${{ matrix.php-version }} - macOS steps: diff --git a/config.m4 b/config.m4 index c93948d5132..38ed9b76b73 100644 --- a/config.m4 +++ b/config.m4 @@ -1552,6 +1552,7 @@ EOF PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php/ssh2) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/curl) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php84/pdo_firebird) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php85/pdo_firebird) PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/llhttp) if test "$PHP_NGHTTP2_DIR" = "no"; then @@ -1561,20 +1562,24 @@ EOF 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) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php85/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) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php85/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) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php85/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) + PHP_ADD_BUILD_DIR($ext_builddir/thirdparty/php85/pdo_sqlite) fi fi diff --git a/ext-src/php_swoole.cc b/ext-src/php_swoole.cc index 6d76d03942a..66b593fdf94 100644 --- a/ext-src/php_swoole.cc +++ b/ext-src/php_swoole.cc @@ -37,6 +37,7 @@ BEGIN_EXTERN_C() #endif END_EXTERN_C() +#include "swoole_coroutine.h" #include "swoole_mime_type.h" #include "swoole_server.h" #include "swoole_util.h" @@ -74,6 +75,7 @@ END_EXTERN_C() #endif using swoole::Server; +using swoole::Coroutine; using swoole::network::Socket; #ifdef SW_USE_IOURING using swoole::Iouring; diff --git a/ext-src/php_swoole_firebird.h b/ext-src/php_swoole_firebird.h index 54cba9bb41d..9f24b874237 100644 --- a/ext-src/php_swoole_firebird.h +++ b/ext-src/php_swoole_firebird.h @@ -26,7 +26,11 @@ BEGIN_EXTERN_C() #include "ext/pdo/php_pdo_driver.h" +#if PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_firebird/php_pdo_firebird_int.h" +#else +#include "thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h" +#endif extern const pdo_driver_t swoole_pdo_firebird_driver; void swoole_firebird_set_blocking(bool blocking); diff --git a/ext-src/php_swoole_odbc.h b/ext-src/php_swoole_odbc.h index 8e77c9cf674..d05219f1539 100644 --- a/ext-src/php_swoole_odbc.h +++ b/ext-src/php_swoole_odbc.h @@ -30,8 +30,10 @@ BEGIN_EXTERN_C() #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 +#elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_odbc/php_pdo_odbc_int.h" +#else +#include "thirdparty/php85/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 7ce78a99e3f..02acdd3c5ae 100644 --- a/ext-src/php_swoole_oracle.h +++ b/ext-src/php_swoole_oracle.h @@ -34,8 +34,10 @@ BEGIN_EXTERN_C() #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 +#elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_oci/php_pdo_oci_int.h" +#else +#include "thirdparty/php85/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 51f1a0b5b16..1d91c9fd068 100644 --- a/ext-src/php_swoole_pgsql.h +++ b/ext-src/php_swoole_pgsql.h @@ -31,8 +31,10 @@ BEGIN_EXTERN_C() #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 +#elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_pgsql/php_pdo_pgsql_int.h" +#else +#include "thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h" #endif diff --git a/ext-src/php_swoole_sqlite.h b/ext-src/php_swoole_sqlite.h index e5c25e4712e..a2aa062c877 100644 --- a/ext-src/php_swoole_sqlite.h +++ b/ext-src/php_swoole_sqlite.h @@ -31,8 +31,10 @@ BEGIN_EXTERN_C() #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 +#elif PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "thirdparty/php84/pdo_sqlite/php_pdo_sqlite_int.h" +#else +#include "thirdparty/php85/pdo_sqlite/php_pdo_sqlite_int.h" #endif extern const pdo_driver_t swoole_pdo_sqlite_driver; diff --git a/ext-src/swoole_firebird.cc b/ext-src/swoole_firebird.cc index 67c31475f87..838672d1047 100644 --- a/ext-src/swoole_firebird.cc +++ b/ext-src/swoole_firebird.cc @@ -248,9 +248,15 @@ int swoole_isc_version(isc_db_handle *_0, ISC_VERSION_CALLBACK _1, void *_2) { void php_swoole_firebird_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("FB_ATTR_DATE_FORMAT")) == nullptr) { +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT); +#else REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_DATE_FORMAT", (zend_long) PDO_FB_ATTR_DATE_FORMAT); REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIME_FORMAT", (zend_long) PDO_FB_ATTR_TIME_FORMAT); REGISTER_PDO_CLASS_CONST_LONG("FB_ATTR_TIMESTAMP_FORMAT", (zend_long) PDO_FB_ATTR_TIMESTAMP_FORMAT); +#endif } php_pdo_unregister_driver(&swoole_pdo_firebird_driver); diff --git a/ext-src/swoole_odbc.cc b/ext-src/swoole_odbc.cc index 42a82a69b50..678bcbd3efa 100644 --- a/ext-src/swoole_odbc.cc +++ b/ext-src/swoole_odbc.cc @@ -238,11 +238,19 @@ int php_swoole_odbc_minit(int module_id) { } #endif +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); +#else REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_USE_CURSOR_LIBRARY", PDO_ODBC_ATTR_USE_CURSOR_LIBRARY); REGISTER_PDO_CLASS_CONST_LONG("ODBC_ATTR_ASSUME_UTF8", PDO_ODBC_ATTR_ASSUME_UTF8); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_IF_NEEDED", SQL_CUR_USE_IF_NEEDED); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_DRIVER", SQL_CUR_USE_DRIVER); REGISTER_PDO_CLASS_CONST_LONG("ODBC_SQL_USE_ODBC", SQL_CUR_USE_ODBC); +#endif } php_pdo_unregister_driver(&swoole_pdo_odbc_driver); diff --git a/ext-src/swoole_oracle.cc b/ext-src/swoole_oracle.cc index a83c72e0f04..e9d665422f7 100644 --- a/ext-src/swoole_oracle.cc +++ b/ext-src/swoole_oracle.cc @@ -132,11 +132,19 @@ void php_swoole_oracle_rinit() { void php_swoole_oracle_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("OCI_ATTR_ACTION")) == nullptr) { +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_ACTION", (zend_long) PDO_OCI_ATTR_ACTION); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CLIENT_INFO", (zend_long) PDO_OCI_ATTR_CLIENT_INFO); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CLIENT_IDENTIFIER", (zend_long) PDO_OCI_ATTR_CLIENT_IDENTIFIER); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_MODULE", (zend_long) PDO_OCI_ATTR_MODULE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("OCI_ATTR_CALL_TIMEOUT", (zend_long) PDO_OCI_ATTR_CALL_TIMEOUT); +#else REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_ACTION", (zend_long) PDO_OCI_ATTR_ACTION); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_INFO", (zend_long) PDO_OCI_ATTR_CLIENT_INFO); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CLIENT_IDENTIFIER", (zend_long) PDO_OCI_ATTR_CLIENT_IDENTIFIER); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_MODULE", (zend_long) PDO_OCI_ATTR_MODULE); REGISTER_PDO_CLASS_CONST_LONG("OCI_ATTR_CALL_TIMEOUT", (zend_long) PDO_OCI_ATTR_CALL_TIMEOUT); +#endif } php_pdo_unregister_driver(&swoole_pdo_oci_driver); diff --git a/ext-src/swoole_pgsql.cc b/ext-src/swoole_pgsql.cc index 902f0e767d7..ca85249f650 100644 --- a/ext-src/swoole_pgsql.cc +++ b/ext-src/swoole_pgsql.cc @@ -219,12 +219,21 @@ void swoole_pgsql_set_blocking(bool blocking) { void php_swoole_pgsql_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("PGSQL_ATTR_DISABLE_PREPARES")) == nullptr) { +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_ATTR_DISABLE_PREPARES", PDO_PGSQL_ATTR_DISABLE_PREPARES); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_IDLE", (zend_long) PGSQL_TRANSACTION_IDLE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_ACTIVE", (zend_long) PGSQL_TRANSACTION_ACTIVE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_INTRANS", (zend_long) PGSQL_TRANSACTION_INTRANS); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_INERROR", (zend_long) PGSQL_TRANSACTION_INERROR); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("PGSQL_TRANSACTION_UNKNOWN", (zend_long) PGSQL_TRANSACTION_UNKNOWN); +#else REGISTER_PDO_CLASS_CONST_LONG("PGSQL_ATTR_DISABLE_PREPARES", PDO_PGSQL_ATTR_DISABLE_PREPARES); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_IDLE", (zend_long) PGSQL_TRANSACTION_IDLE); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_ACTIVE", (zend_long) PGSQL_TRANSACTION_ACTIVE); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_INTRANS", (zend_long) PGSQL_TRANSACTION_INTRANS); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_INERROR", (zend_long) PGSQL_TRANSACTION_INERROR); REGISTER_PDO_CLASS_CONST_LONG("PGSQL_TRANSACTION_UNKNOWN", (zend_long) PGSQL_TRANSACTION_UNKNOWN); +#endif } php_pdo_unregister_driver(&swoole_pdo_pgsql_driver); php_pdo_register_driver(&swoole_pdo_pgsql_driver); diff --git a/ext-src/swoole_runtime.cc b/ext-src/swoole_runtime.cc index 231d9299285..3bec5532e3e 100644 --- a/ext-src/swoole_runtime.cc +++ b/ext-src/swoole_runtime.cc @@ -205,12 +205,13 @@ static zend_internal_arg_info *copy_arginfo(const zend_internal_function *functi memcpy(new_list, old_list, ZEND_TYPE_LIST_SIZE(old_list->num_types)); ZEND_TYPE_SET_PTR(new_arg_info[i].type, new_list); - zend_type *list_type; + const zend_type *list_type; ZEND_TYPE_LIST_FOREACH(new_list, list_type) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(*list_type), true); - ZEND_TYPE_SET_PTR(*list_type, name); - } - ZEND_TYPE_LIST_FOREACH_END(); + + zend_type *mutable_type = const_cast(list_type); + ZEND_TYPE_SET_PTR(*mutable_type, name); + } ZEND_TYPE_LIST_FOREACH_END(); } else if (ZEND_TYPE_HAS_NAME(arg_info[i].type)) { zend_string *name = zend_string_dup(ZEND_TYPE_NAME(arg_info[i].type), true); ZEND_TYPE_SET_PTR(new_arg_info[i].type, name); diff --git a/ext-src/swoole_sqlite.cc b/ext-src/swoole_sqlite.cc index 02ce7035fca..83d75bc6246 100644 --- a/ext-src/swoole_sqlite.cc +++ b/ext-src/swoole_sqlite.cc @@ -96,9 +96,22 @@ int swoole_sqlite3_step(sqlite3_stmt *stmt) { void php_swoole_sqlite_minit(int module_id) { if (zend_hash_str_find(&php_pdo_get_dbh_ce()->constants_table, ZEND_STRL("SQLITE_ATTR_OPEN_FLAGS")) == nullptr) { #ifdef SQLITE_DETERMINISTIC +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_DETERMINISTIC", (zend_long) SQLITE_DETERMINISTIC); +#else REGISTER_PDO_CLASS_CONST_LONG("SQLITE_DETERMINISTIC", (zend_long) SQLITE_DETERMINISTIC); #endif +#endif +#if PHP_VERSION_ID >= 80500 + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_OPEN_FLAGS", (zend_long) PDO_SQLITE_ATTR_OPEN_FLAGS); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_READONLY", (zend_long) SQLITE_OPEN_READONLY); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_READWRITE", (zend_long) SQLITE_OPEN_READWRITE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_OPEN_CREATE", (zend_long) SQLITE_OPEN_CREATE); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_READONLY_STATEMENT", (zend_long) PDO_SQLITE_ATTR_READONLY_STATEMENT); + REGISTER_PDO_CLASS_CONST_LONG_DEPRECATED_85("SQLITE_ATTR_EXTENDED_RESULT_CODES", + (zend_long) PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES); +#else REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_OPEN_FLAGS", (zend_long) PDO_SQLITE_ATTR_OPEN_FLAGS); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READONLY", (zend_long) SQLITE_OPEN_READONLY); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_OPEN_READWRITE", (zend_long) SQLITE_OPEN_READWRITE); @@ -106,6 +119,7 @@ void php_swoole_sqlite_minit(int module_id) { REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_READONLY_STATEMENT", (zend_long) PDO_SQLITE_ATTR_READONLY_STATEMENT); REGISTER_PDO_CLASS_CONST_LONG("SQLITE_ATTR_EXTENDED_RESULT_CODES", (zend_long) PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES); +#endif } php_pdo_unregister_driver(&swoole_pdo_sqlite_driver); diff --git a/package.xml b/package.xml index a9e290dd0e1..99ce045d96b 100644 --- a/package.xml +++ b/package.xml @@ -2758,6 +2758,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/php/main/SAPI.h b/thirdparty/php/main/SAPI.h index 03634219b02..f434ad1dd0e 100644 --- a/thirdparty/php/main/SAPI.h +++ b/thirdparty/php/main/SAPI.h @@ -38,7 +38,11 @@ static void swoole_php_treat_data(int arg, char *str, zval *destArray) { switch (arg) { case PARSE_STRING: +#if PHP_VERSION_ID >= 80500 + separator = ZSTR_VAL(PG(arg_separator).input); +#else separator = PG(arg_separator).input; +#endif break; case PARSE_COOKIE: separator = (char *) ";\0"; diff --git a/thirdparty/php84/pdo_firebird/firebird_driver.c b/thirdparty/php84/pdo_firebird/firebird_driver.c index a27eb146063..77051cdff08 100644 --- a/thirdparty/php84/pdo_firebird/firebird_driver.c +++ b/thirdparty/php84/pdo_firebird/firebird_driver.c @@ -17,6 +17,8 @@ #define SW_USE_FIREBIRD_HOOK #include "php_swoole_firebird.h" +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 + #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif @@ -1434,3 +1436,5 @@ const pdo_driver_t swoole_pdo_firebird_driver = { /* {{{ */ pdo_firebird_handle_factory }; /* }}} */ + +#endif \ No newline at end of file diff --git a/thirdparty/php84/pdo_firebird/firebird_statement.c b/thirdparty/php84/pdo_firebird/firebird_statement.c index 69c9ce5c693..8ea31633247 100644 --- a/thirdparty/php84/pdo_firebird/firebird_statement.c +++ b/thirdparty/php84/pdo_firebird/firebird_statement.c @@ -17,6 +17,8 @@ #define SW_USE_FIREBIRD_HOOK #include "php_swoole_firebird.h" +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 + #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" @@ -956,3 +958,5 @@ const struct pdo_stmt_methods firebird_stmt_methods = { /* {{{ */ pdo_firebird_stmt_cursor_closer }; /* }}} */ + +#endif \ No newline at end of file diff --git a/thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp b/thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp index 715963eec17..1b76d4399cc 100644 --- a/thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp +++ b/thirdparty/php84/pdo_firebird/pdo_firebird_utils.cpp @@ -18,6 +18,7 @@ #include #include +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 /* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */ extern "C" unsigned fb_get_client_version(void) { @@ -89,3 +90,4 @@ extern "C" ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_T } #endif +#endif diff --git a/thirdparty/php84/pdo_oci/oci_driver.c b/thirdparty/php84/pdo_oci/oci_driver.c index 6ee73fd7110..b03c14c5296 100644 --- a/thirdparty/php84/pdo_oci/oci_driver.c +++ b/thirdparty/php84/pdo_oci/oci_driver.c @@ -17,7 +17,7 @@ #define SW_USE_ORACLE_HOOK #include "php_swoole_oracle.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php84/pdo_oci/oci_statement.c b/thirdparty/php84/pdo_oci/oci_statement.c index cd91dbf7223..8e97cb415df 100644 --- a/thirdparty/php84/pdo_oci/oci_statement.c +++ b/thirdparty/php84/pdo_oci/oci_statement.c @@ -17,7 +17,7 @@ #define SW_USE_ORACLE_HOOK #include "php_swoole_oracle.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php84/pdo_odbc/odbc_driver.c b/thirdparty/php84/pdo_odbc/odbc_driver.c index f410d42050c..9978af9e353 100644 --- a/thirdparty/php84/pdo_odbc/odbc_driver.c +++ b/thirdparty/php84/pdo_odbc/odbc_driver.c @@ -16,7 +16,7 @@ #define SW_USE_ODBC_HOOK #include "php_swoole_odbc.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php84/pdo_odbc/odbc_stmt.c b/thirdparty/php84/pdo_odbc/odbc_stmt.c index 8fe11cf2c6a..6bd65d205f9 100644 --- a/thirdparty/php84/pdo_odbc/odbc_stmt.c +++ b/thirdparty/php84/pdo_odbc/odbc_stmt.c @@ -17,7 +17,7 @@ #define SW_USE_ODBC_HOOK #include "php_swoole_odbc.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php84/pdo_pgsql/pgsql_driver.c b/thirdparty/php84/pdo_pgsql/pgsql_driver.c index cf98f5f45f9..565f59d91ec 100644 --- a/thirdparty/php84/pdo_pgsql/pgsql_driver.c +++ b/thirdparty/php84/pdo_pgsql/pgsql_driver.c @@ -19,7 +19,7 @@ #define SW_USE_PGSQL_HOOK #include "php_swoole_pgsql.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" #include "ext/standard/php_string.h" /* For php_addcslashes_str() in _pdo_pgsql_escape_credentials() */ diff --git a/thirdparty/php84/pdo_pgsql/pgsql_statement.c b/thirdparty/php84/pdo_pgsql/pgsql_statement.c index acb21384814..0987b9825be 100644 --- a/thirdparty/php84/pdo_pgsql/pgsql_statement.c +++ b/thirdparty/php84/pdo_pgsql/pgsql_statement.c @@ -19,7 +19,7 @@ #define SW_USE_PGSQL_HOOK #include "php_swoole_pgsql.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" diff --git a/thirdparty/php84/pdo_sqlite/sqlite_driver.c b/thirdparty/php84/pdo_sqlite/sqlite_driver.c index 33e0c08852b..199c403a38c 100644 --- a/thirdparty/php84/pdo_sqlite/sqlite_driver.c +++ b/thirdparty/php84/pdo_sqlite/sqlite_driver.c @@ -18,7 +18,7 @@ #include "php_swoole_sqlite.h" #include "php_swoole_call_stack.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php84/pdo_sqlite/sqlite_statement.c b/thirdparty/php84/pdo_sqlite/sqlite_statement.c index 434a91d15c5..6b7f18cd72e 100644 --- a/thirdparty/php84/pdo_sqlite/sqlite_statement.c +++ b/thirdparty/php84/pdo_sqlite/sqlite_statement.c @@ -17,7 +17,7 @@ #define SW_USE_SQLITE_HOOK #include "php_swoole_sqlite.h" -#if PHP_VERSION_ID >= 80400 +#if PHP_VERSION_ID >= 80400 && PHP_VERSION_ID < 80500 #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" diff --git a/thirdparty/php85/pdo_firebird/CREDITS b/thirdparty/php85/pdo_firebird/CREDITS new file mode 100644 index 00000000000..60b917415db --- /dev/null +++ b/thirdparty/php85/pdo_firebird/CREDITS @@ -0,0 +1,2 @@ +Firebird driver for PDO +Ard Biesheuvel diff --git a/thirdparty/php85/pdo_firebird/firebird_driver.c b/thirdparty/php85/pdo_firebird/firebird_driver.c new file mode 100644 index 00000000000..78818c220a2 --- /dev/null +++ b/thirdparty/php85/pdo_firebird/firebird_driver.c @@ -0,0 +1,1440 @@ +/* + +----------------------------------------------------------------------+ + | 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: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID >= 80500 + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include "php.h" +#include "zend_exceptions.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_firebird.h" +#include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" + +static int php_firebird_alloc_prepare_stmt(pdo_dbh_t*, const zend_string*, XSQLDA*, isc_stmt_handle*, + HashTable*); +static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh); + +const char CHR_LETTER = 1; +const char CHR_DIGIT = 2; +const char CHR_IDENT = 4; +const char CHR_QUOTE = 8; +const char CHR_WHITE = 16; +const char CHR_HEX = 32; +const char CHR_INTRODUCER = 64; + +static const char classes_array[] = { + /* 000 */ 0, + /* 001 */ 0, + /* 002 */ 0, + /* 003 */ 0, + /* 004 */ 0, + /* 005 */ 0, + /* 006 */ 0, + /* 007 */ 0, + /* 008 */ 0, + /* 009 */ 16, /* CHR_WHITE */ + /* 010 */ 16, /* CHR_WHITE */ + /* 011 */ 0, + /* 012 */ 0, + /* 013 */ 16, /* CHR_WHITE */ + /* 014 */ 0, + /* 015 */ 0, + /* 016 */ 0, + /* 017 */ 0, + /* 018 */ 0, + /* 019 */ 0, + /* 020 */ 0, + /* 021 */ 0, + /* 022 */ 0, + /* 023 */ 0, + /* 024 */ 0, + /* 025 */ 0, + /* 026 */ 0, + /* 027 */ 0, + /* 028 */ 0, + /* 029 */ 0, + /* 030 */ 0, + /* 031 */ 0, + /* 032 */ 16, /* CHR_WHITE */ + /* 033 ! */ 0, + /* 034 " */ 8, /* CHR_QUOTE */ + /* 035 # */ 0, + /* 036 $ */ 4, /* CHR_IDENT */ + /* 037 % */ 0, + /* 038 & */ 0, + /* 039 ' */ 8, /* CHR_QUOTE */ + /* 040 ( */ 0, + /* 041 ) */ 0, + /* 042 * */ 0, + /* 043 + */ 0, + /* 044 , */ 0, + /* 045 - */ 0, + /* 046 . */ 0, + /* 047 / */ 0, + /* 048 0 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 049 1 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 050 2 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 051 3 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 052 4 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 053 5 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 054 6 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 055 7 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 056 8 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 057 9 */ 38, /* CHR_DIGIT | CHR_IDENT | CHR_HEX */ + /* 058 : */ 0, + /* 059 ; */ 0, + /* 060 < */ 0, + /* 061 = */ 0, + /* 062 > */ 0, + /* 063 ? */ 0, + /* 064 @ */ 0, + /* 065 A */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 066 B */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 067 C */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 068 D */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 069 E */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 070 F */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 071 G */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 072 H */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 073 I */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 074 J */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 075 K */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 076 L */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 077 M */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 078 N */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 079 O */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 080 P */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 081 Q */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 082 R */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 083 S */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 084 T */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 085 U */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 086 V */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 087 W */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 088 X */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 089 Y */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 090 Z */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 091 [ */ 0, + /* 092 \ */ 0, + /* 093 ] */ 0, + /* 094 ^ */ 0, + /* 095 _ */ 68, /* CHR_IDENT | CHR_INTRODUCER */ + /* 096 ` */ 0, + /* 097 a */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 098 b */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 099 c */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 100 d */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 101 e */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 102 f */ 37, /* CHR_LETTER | CHR_IDENT | CHR_HEX */ + /* 103 g */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 104 h */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 105 i */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 106 j */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 107 k */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 108 l */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 109 m */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 110 n */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 111 o */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 112 p */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 113 q */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 114 r */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 115 s */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 116 t */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 117 u */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 118 v */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 119 w */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 120 x */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 121 y */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 122 z */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 123 { */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 124 | */ 0, + /* 125 } */ 5, /* CHR_LETTER | CHR_IDENT */ + /* 126 ~ */ 0, + /* 127 */ 0 +}; + +static inline char php_firebird_classes(char idx) +{ + unsigned char uidx = (unsigned char) idx; + if (uidx > 127) return 0; + return classes_array[uidx]; +} + +typedef enum { + ttNone, + ttWhite, + ttComment, + ttBrokenComment, + ttString, + ttParamMark, + ttIdent, + ttOther +} FbTokenType; + +static FbTokenType php_firebird_get_token(const char** begin, const char* end) +{ + FbTokenType ret = ttNone; + const char* p = *begin; + + char c = *p++; + switch (c) + { + case ':': + case '?': + ret = ttParamMark; + break; + + case '\'': + case '"': + while (p < end) + { + if (*p++ == c) + { + ret = ttString; + break; + } + } + break; + + case '/': + if (p < end && *p == '*') + { + ret = ttBrokenComment; + p++; + while (p < end) + { + if (*p++ == '*' && p < end && *p == '/') + { + p++; + ret = ttComment; + break; + } + } + } + else { + ret = ttOther; + } + break; + + case '-': + if (p < end && *p == '-') + { + while (++p < end) + { + if (*p == '\r') + { + p++; + if (p < end && *p == '\n') + p++; + break; + } + else if (*p == '\n') + break; + } + + ret = ttComment; + } + else + ret = ttOther; + break; + + default: + if (php_firebird_classes(c) & CHR_DIGIT) + { + while (p < end && (php_firebird_classes(*p) & CHR_DIGIT)) + p++; + ret = ttOther; + } + else if (php_firebird_classes(c) & CHR_IDENT) + { + while (p < end && (php_firebird_classes(*p) & CHR_IDENT)) + p++; + ret = ttIdent; + } + else if (php_firebird_classes(c) & CHR_WHITE) + { + while (p < end && (php_firebird_classes(*p) & CHR_WHITE)) + p++; + ret = ttWhite; + } + else + { + while (p < end && !(php_firebird_classes(*p) & (CHR_DIGIT | CHR_IDENT | CHR_WHITE)) && + (*p != '/') && (*p != '-') && (*p != ':') && (*p != '?') && + (*p != '\'') && (*p != '"')) + { + p++; + } + ret = ttOther; + } + } + + *begin = p; + return ret; +} + +static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTable* named_params) +{ + bool passAsIs = true, execBlock = false; + zend_long pindex = -1; + char pname[254], ident[253], ident2[253]; + unsigned int l; + const char* p = ZSTR_VAL(sql), * end = ZSTR_VAL(sql) + ZSTR_LEN(sql); + const char* start = p; + FbTokenType tok = php_firebird_get_token(&p, end); + + const char* i = start; + while (p < end && (tok == ttComment || tok == ttWhite)) + { + i = p; + tok = php_firebird_get_token(&p, end); + } + + if (p >= end || tok != ttIdent) + { + /* Execute statement preprocess SQL error */ + /* Statement expected */ + return 0; + } + /* skip leading comments ?? */ + start = i; + l = p - i; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident, i, l); + ident[l] = '\0'; + if (!strcasecmp(ident, "EXECUTE")) + { + /* For EXECUTE PROCEDURE and EXECUTE BLOCK statements, named parameters must be processed. */ + /* However, in EXECUTE BLOCK this is done in a special way. */ + const char* i2 = p; + tok = php_firebird_get_token(&p, end); + while (p < end && (tok == ttComment || tok == ttWhite)) + { + i2 = p; + tok = php_firebird_get_token(&p, end); + } + if (p >= end || tok != ttIdent) + { + /* Execute statement preprocess SQL error */ + /* Statement expected */ + return 0; + } + l = p - i2; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident2, i2, l); + ident2[l] = '\0'; + execBlock = !strcasecmp(ident2, "BLOCK"); + passAsIs = false; + } + else + { + /* Named parameters must be processed in the INSERT, UPDATE, DELETE, MERGE statements. */ + /* If CTEs are present in the query, they begin with the WITH keyword. */ + passAsIs = strcasecmp(ident, "INSERT") && strcasecmp(ident, "UPDATE") && + strcasecmp(ident, "DELETE") && strcasecmp(ident, "MERGE") && + strcasecmp(ident, "SELECT") && strcasecmp(ident, "WITH"); + } + + if (passAsIs) + { + strcpy(sql_out, ZSTR_VAL(sql)); + return 1; + } + + strncat(sql_out, start, p - start); + + while (p < end) + { + start = p; + tok = php_firebird_get_token(&p, end); + switch (tok) + { + case ttParamMark: + tok = php_firebird_get_token(&p, end); + if (tok == ttIdent /*|| tok == ttString*/) + { + ++pindex; + l = p - start; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + /* + symbol ":" */ + if (l > 253) { + return 0; + } + strncpy(pname, start, l); + pname[l] = '\0'; + + if (named_params) { + zval tmp; + ZVAL_LONG(&tmp, pindex); + zend_hash_str_update(named_params, pname, l, &tmp); + } + + strcat(sql_out, "?"); + } + else + { + if (strncmp(start, "?", 1)) { + /* Execute statement preprocess SQL error */ + /* Parameter name expected */ + return 0; + } + ++pindex; + strncat(sql_out, start, p - start); + } + break; + + case ttIdent: + if (execBlock) + { + /* In the EXECUTE BLOCK statement, processing must be */ + /* carried out up to the keyword AS. */ + l = p - start; + /* check the length of the identifier */ + /* in Firebird 4.0 it is 63 characters, in previous versions 31 bytes */ + if (l > 252) { + return 0; + } + strncpy(ident, start, l); + ident[l] = '\0'; + if (!strcasecmp(ident, "AS")) + { + strncat(sql_out, start, end - start); + return 1; + } + } + /* TODO Check this is correct? */ + ZEND_FALLTHROUGH; + + case ttWhite: + case ttComment: + case ttString: + case ttOther: + strncat(sql_out, start, p - start); + break; + + case ttBrokenComment: + { + /* Execute statement preprocess SQL error */ + /* Unclosed comment found near ''@1'' */ + return 0; + } + break; + + + case ttNone: + /* Execute statement preprocess SQL error */ + return 0; + break; + } + } + return 1; +} + +#if FB_API_VER >= 40 +/* set coercing a data type */ +static void set_coercing_output_data_types(XSQLDA* sqlda) +{ + /* Data types introduced in Firebird 4.0 are difficult to process using the Firebird Legacy API. */ + /* These data types include DECFLOAT(16), DECFLOAT(34), INT128 (NUMERIC/DECIMAL(38, x)). */ + /* In any case, at this data types can only be mapped to strings. */ + /* This function allows you to ensure minimal performance of queries if they contain columns of the above types. */ + unsigned int i; + short dtype; + short nullable; + XSQLVAR* var; + unsigned fb_client_version = fb_get_client_version(); + unsigned fb_client_major_version = (fb_client_version >> 8) & 0xFF; + for (i=0, var = sqlda->sqlvar; i < sqlda->sqld; i++, var++) { + dtype = (var->sqltype & ~1); /* drop flag bit */ + nullable = (var->sqltype & 1); + switch(dtype) { + case SQL_INT128: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 46; + var->sqlscale = 0; + break; + + case SQL_DEC16: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 24; + break; + + case SQL_DEC34: + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 43; + break; + + case SQL_TIMESTAMP_TZ: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 58; + } + break; + + case SQL_TIME_TZ: + if (fb_client_major_version < 4) { + /* If the client version is below 4.0, then it is impossible to handle time zones natively, */ + /* so we convert these types to a string. */ + var->sqltype = SQL_VARYING + nullable; + var->sqllen = 46; + } + break; + + default: + break; + } + } +} +#endif + +/* map driver specific error message to PDO error */ +void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len) /* {{{ */ +{ + pdo_error_type *const error_code = stmt ? &stmt->error_code : &dbh->error_code; + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_error_info *einfo = &H->einfo; + int sqlcode = -999; + + if (einfo->errmsg) { + pefree(einfo->errmsg, dbh->is_persistent); + einfo->errmsg = NULL; + einfo->errmsg_length = 0; + } + + if (H->isc_status[0] == 1 && H->isc_status[1] > 0) { + char buf[512]; + size_t buf_size = sizeof(buf), read_len = 0; + ssize_t tmp_len; + const ISC_STATUS *s = H->isc_status; + sqlcode = isc_sqlcode(s); + + while ((buf_size > (read_len + 1)) && (tmp_len = fb_interpret(&buf[read_len], (buf_size - read_len - 1), &s)) && tmp_len > 0) { + read_len += tmp_len; + buf[read_len++] = ' '; + } + + /* remove last space */ + if (read_len) { + buf[read_len--] = '\0'; + } + + einfo->errmsg_length = read_len; + einfo->errmsg = pestrndup(buf, read_len, dbh->is_persistent); + + char sqlstate[sizeof(pdo_error_type)]; + fb_sqlstate(sqlstate, H->isc_status); + if (strlen(sqlstate) < sizeof(pdo_error_type)) { + strcpy(*error_code, sqlstate); + goto end; + } + } else if (msg && msg_len) { + einfo->errmsg_length = msg_len; + einfo->errmsg = pestrndup(msg, einfo->errmsg_length, dbh->is_persistent); + } + + if (state && state_len && state_len < sizeof(pdo_error_type)) { + memcpy(*error_code, state, state_len + 1); + } else { + memcpy(*error_code, "HY000", sizeof("HY000")); + } + +end: + einfo->sqlcode = sqlcode; + if (!dbh->methods) { + pdo_throw_exception(0, einfo->errmsg, error_code); + } +} +/* }}} */ + +/* called by PDO to close a db handle */ +static void firebird_handle_closer(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (H->tr) { + if (dbh->auto_commit) { + php_firebird_commit_transaction(dbh, /* retain */ false); + } else { + php_firebird_rollback_transaction(dbh); + } + } + H->in_manually_txn = false; + + /* isc_detach_database returns 0 on success, 1 on failure. */ + if (H->db && isc_detach_database(H->isc_status, &H->db)) { + php_firebird_error(dbh); + } + + if (H->date_format) { + pefree(H->date_format, dbh->is_persistent); + } + if (H->time_format) { + pefree(H->time_format, dbh->is_persistent); + } + if (H->timestamp_format) { + pefree(H->timestamp_format, dbh->is_persistent); + } + + if (H->einfo.errmsg) { + pefree(H->einfo.errmsg, dbh->is_persistent); + H->einfo.errmsg = NULL; + } + + pefree(H, dbh->is_persistent); +} +/* }}} */ + +/* called by PDO to prepare an SQL query */ +static bool firebird_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, /* {{{ */ + pdo_stmt_t *stmt, zval *driver_options) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + pdo_firebird_stmt *S = NULL; + HashTable *np; + + do { + isc_stmt_handle s = PDO_FIREBIRD_HANDLE_INITIALIZER; + XSQLDA num_sqlda; + static char const info[] = { isc_info_sql_stmt_type }; + char result[8]; + + num_sqlda.version = PDO_FB_SQLDA_VERSION; + num_sqlda.sqln = 1; + + ALLOC_HASHTABLE(np); + zend_hash_init(np, 8, NULL, NULL, 0); + + /* allocate and prepare statement */ + if (!php_firebird_alloc_prepare_stmt(dbh, sql, &num_sqlda, &s, np)) { + break; + } + + /* allocate a statement handle struct of the right size (struct out_sqlda is inlined) */ + S = ecalloc(1, sizeof(*S)-sizeof(XSQLDA) + XSQLDA_LENGTH(num_sqlda.sqld)); + S->H = H; + S->stmt = s; + S->out_sqlda.version = PDO_FB_SQLDA_VERSION; + S->out_sqlda.sqln = stmt->column_count = num_sqlda.sqld; + S->named_params = np; + + /* determine the statement type */ + if (isc_dsql_sql_info(H->isc_status, &s, sizeof(info), const_cast(info), sizeof(result), + result)) { + break; + } + S->statement_type = result[3]; + + /* fill the output sqlda with information about the prepared query */ + if (isc_dsql_describe(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + php_firebird_error(dbh); + break; + } + +#if FB_API_VER >= 40 + /* set coercing a data type */ + set_coercing_output_data_types(&S->out_sqlda); +#endif + + /* allocate the input descriptors */ + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, &num_sqlda)) { + break; + } + + if (num_sqlda.sqld) { + S->in_sqlda = ecalloc(1,XSQLDA_LENGTH(num_sqlda.sqld)); + S->in_sqlda->version = PDO_FB_SQLDA_VERSION; + S->in_sqlda->sqln = num_sqlda.sqld; + + if (isc_dsql_describe_bind(H->isc_status, &s, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + + /* make all parameters nullable */ + unsigned int i; + XSQLVAR* var; + for (i = 0, var = S->in_sqlda->sqlvar; i < S->in_sqlda->sqld; i++, var++) { + /* The low bit of sqltype indicates that the parameter can take a NULL value */ + var->sqltype |= 1; + } + } + + stmt->driver_data = S; + stmt->methods = &swoole_firebird_stmt_methods; + stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; + + return true; + + } while (0); + + php_firebird_error(dbh); + + zend_hash_destroy(np); + FREE_HASHTABLE(np); + + if (S) { + if (S->in_sqlda) { + efree(S->in_sqlda); + } + efree(S); + } + + return false; +} +/* }}} */ + +/* called by PDO to execute a statement that doesn't produce a result set */ +static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + isc_stmt_handle stmt = PDO_FIREBIRD_HANDLE_INITIALIZER; + static char const info_count[] = { isc_info_sql_records }; + char result[64]; + int ret = 0; + XSQLDA in_sqlda, out_sqlda; + + /* TODO no placeholders in exec() for now */ + in_sqlda.version = out_sqlda.version = PDO_FB_SQLDA_VERSION; + in_sqlda.sqld = out_sqlda.sqld = 0; + out_sqlda.sqln = 1; + + /* allocate and prepare statement */ + if (!php_firebird_alloc_prepare_stmt(dbh, sql, &out_sqlda, &stmt, 0)) { + return -1; + } + + /* execute the statement */ + if (isc_dsql_execute2(H->isc_status, &H->tr, &stmt, PDO_FB_SQLDA_VERSION, &in_sqlda, &out_sqlda)) { + php_firebird_error(dbh); + ret = -1; + goto free_statement; + } + + /* find out how many rows were affected */ + if (isc_dsql_sql_info(H->isc_status, &stmt, sizeof(info_count), const_cast(info_count), + sizeof(result), result)) { + php_firebird_error(dbh); + ret = -1; + goto free_statement; + } + + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1],2); + + if (result_size > sizeof(result)) { + ret = -1; + goto free_statement; + } + while (result[i] != isc_info_end && i < result_size) { + short len = (short)isc_vax_integer(&result[i+1],2); + /* bail out on bad len */ + if (len != 1 && len != 2 && len != 4) { + ret = -1; + goto free_statement; + } + if (result[i] != isc_info_req_select_count) { + ret += isc_vax_integer(&result[i+3],len); + } + i += len+3; + } + } + + if (dbh->auto_commit && !H->in_manually_txn) { + if (!php_firebird_commit_transaction(dbh, /* retain */ true)) { + ret = -1; + } + } + +free_statement: + + if (isc_dsql_free_statement(H->isc_status, &stmt, DSQL_drop)) { + php_firebird_error(dbh); + } + + return ret; +} +/* }}} */ + +/* called by the PDO SQL parser to add quotes to values that are copied into SQL */ +static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) +{ + size_t qcount = 0; + char const *co, *l, *r; + char *c; + size_t quotedlen; + zend_string *quoted_str; + + if (ZSTR_LEN(unquoted) == 0) { + return ZSTR_INIT_LITERAL("''", 0); + } + + /* Firebird only requires single quotes to be doubled if string lengths are used */ + /* count the number of ' characters */ + for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++); + + if (UNEXPECTED(ZSTR_LEN(unquoted) + 2 > ZSTR_MAX_LEN - qcount)) { + return NULL; + } + + quotedlen = ZSTR_LEN(unquoted) + qcount + 2; + quoted_str = zend_string_alloc(quotedlen, 0); + c = ZSTR_VAL(quoted_str); + *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); + /* add the second quote */ + *c++ = '\''; + } + + /* copy the remainder */ + strncpy(c, l, quotedlen-(c-ZSTR_VAL(quoted_str))-1); + ZSTR_VAL(quoted_str)[quotedlen-1] = '\''; + ZSTR_VAL(quoted_str)[quotedlen] = '\0'; + + return quoted_str; +} +/* }}} */ + +/* php_firebird_begin_transaction */ +static bool php_firebird_begin_transaction(pdo_dbh_t *dbh, bool is_auto_commit_txn) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /* isc_xxx are all 1 byte. */ + char tpb[4] = { isc_tpb_version3 }; + size_t tpb_size; + + /* access mode. writable or readonly */ + tpb[1] = H->is_writable_txn ? isc_tpb_write : isc_tpb_read; + + if (is_auto_commit_txn) { + /* + * In autocommit mode, we need to always read the latest information, so we set `read committed`. + */ + tpb[2] = isc_tpb_read_committed; + /* Ignore indeterminate data from other transactions. This option only required with `read committed`. */ + tpb[3] = isc_tpb_rec_version; + tpb_size = 4; + } else { + switch (H->txn_isolation_level) { + /* + * firebird's `read committed` has the option to wait until other transactions + * commit or rollback if there is indeterminate data. + * Introducing too many configuration values at once can cause confusion, so + * we don't support in PDO that feature yet. + */ + case PDO_FB_READ_COMMITTED: + tpb[2] = isc_tpb_read_committed; + /* Ignore indeterminate data from other transactions. This option only required with `read committed`. */ + tpb[3] = isc_tpb_rec_version; + tpb_size = 4; + break; + + case PDO_FB_SERIALIZABLE: + tpb[2] = isc_tpb_consistency; + tpb_size = 3; + break; + + case PDO_FB_REPEATABLE_READ: + default: + tpb[2] = isc_tpb_concurrency; + tpb_size = 3; + break; + } + } + + if (isc_start_transaction(H->isc_status, &H->tr, 1, &H->db, tpb_size, tpb)) { + php_firebird_error(dbh); + return false; + } + return true; +} +/* }}} */ + +/* called by PDO to start a transaction */ +static bool firebird_handle_manually_begin(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /** + * If in autocommit mode and in transaction, we will need to close the transaction once. + */ + if (dbh->auto_commit && H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + return false; + } + } + + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ false)) { + return false; + } + H->in_manually_txn = true; + return true; +} +/* }}} */ + +/* php_firebird_commit_transaction */ +bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /** + * `retaining` keeps the transaction open without closing it. + * + * firebird needs to always have a transaction open to emulate autocommit mode, + * and in autocommit mode it keeps the transaction open. + * + * Same as close and then begin again, but use retain to save overhead. + */ + if (retain) { + if (isc_commit_retaining(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + } else { + if (isc_commit_transaction(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + } + return true; +} +/* }}} */ + +/* called by PDO to commit a transaction */ +static bool firebird_handle_manually_commit(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + if (!php_firebird_commit_transaction(dbh, /*release*/ false)) { + return false; + } + + /** + * If in autocommit mode, begin the transaction again + * Reopen instead of retain because isolation level may change + */ + if (dbh->auto_commit) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + H->in_manually_txn = false; + return true; +} +/* }}} */ + +/* php_firebird_rollback_transaction */ +static bool php_firebird_rollback_transaction(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (isc_rollback_transaction(H->isc_status, &H->tr)) { + php_firebird_error(dbh); + return false; + } + return true; +} +/* }}} */ + +/* called by PDO to rollback a transaction */ +static bool firebird_handle_manually_rollback(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + if (!php_firebird_rollback_transaction(dbh)) { + return false; + } + + /** + * If in autocommit mode, begin the transaction again + * Reopen instead of retain because isolation level may change + */ + if (dbh->auto_commit) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + H->in_manually_txn = false; + return true; +} +/* }}} */ + +/* used by prepare and exec to allocate a statement handle and prepare the SQL */ +static int php_firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sql, + XSQLDA *out_sqlda, isc_stmt_handle *s, HashTable *named_params) +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + char *new_sql; + + /* allocate the statement */ + if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) { + php_firebird_error(dbh); + return 0; + } + + /* in order to support named params, which Firebird itself doesn't, + we need to replace :foo by ?, and store the name we just replaced */ + new_sql = emalloc(ZSTR_LEN(sql)+1); + new_sql[0] = '\0'; + if (!php_firebird_preprocess(sql, new_sql, named_params)) { + php_firebird_error_with_info(dbh, "07000", strlen("07000"), NULL, 0); + efree(new_sql); + return 0; + } + + /* prepare the statement */ + if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) { + php_firebird_error(dbh); + efree(new_sql); + return 0; + } + + efree(new_sql); + return 1; +} + +/* called by PDO to set a driver-specific dbh attribute */ +static bool pdo_firebird_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + bool bval; + zend_long lval; + + switch (attr) { + case PDO_ATTR_AUTOCOMMIT: + { + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (H->in_manually_txn) { + /* change auto commit mode with an open transaction is illegal, because + we won't know what to do with it */ + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change autocommit mode while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (dbh->auto_commit ^ bval) { + if (bval) { + /* + * change to auto commit mode. + * If the transaction is not started, start it. + */ + if (!H->tr) { + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + return false; + } + } + } else { + /* + * change to not auto commit mode. + * close the transaction if exists. + */ + if (H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + return false; + } + } + } + dbh->auto_commit = bval; + } + } + return true; + + case PDO_ATTR_FETCH_TABLE_NAMES: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->fetch_table_names = bval; + return true; + + case PDO_FB_ATTR_DATE_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->date_format) { + pefree(H->date_format, dbh->is_persistent); + H->date_format = NULL; + } + H->date_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_ATTR_TIME_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->time_format) { + pefree(H->time_format, dbh->is_persistent); + H->time_format = NULL; + } + H->time_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_ATTR_TIMESTAMP_FORMAT: + { + zend_string *str = zval_try_get_string(val); + if (UNEXPECTED(!str)) { + return false; + } + if (H->timestamp_format) { + pefree(H->timestamp_format, dbh->is_persistent); + H->timestamp_format = NULL; + } + H->timestamp_format = pestrndup(ZSTR_VAL(str), ZSTR_LEN(str),dbh->is_persistent); + zend_string_release_ex(str, 0); + } + return true; + + case PDO_FB_TRANSACTION_ISOLATION_LEVEL: + { + if (!pdo_get_long_param(&lval, val)) { + return false; + } + + if (H->in_manually_txn) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change transaction isolation level while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (H->txn_isolation_level != lval) { + if (lval == PDO_FB_READ_COMMITTED || + lval == PDO_FB_REPEATABLE_READ || + lval == PDO_FB_SERIALIZABLE + ) { + /* + * Autocommit mode is always read-committed, so this setting is used the next time + * a manual transaction starts. Therefore, there is no need to immediately reopen the transaction. + */ + H->txn_isolation_level = lval; + } else { + zend_value_error("Pdo\\Firebird::TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level " + "(Pdo\\Firebird::READ_COMMITTED, Pdo\\Firebird::REPEATABLE_READ, or Pdo\\Firebird::SERIALIZABLE)"); + return false; + } + } + } + return true; + + case PDO_FB_WRITABLE_TRANSACTION: + { + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + + if (H->in_manually_txn) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Cannot change access mode while a transaction is already open"); + return false; + } + + /* ignore if the new value equals the old one */ + if (H->is_writable_txn != bval) { + H->is_writable_txn = bval; + if (dbh->auto_commit) { + if (H->tr) { + if (!php_firebird_commit_transaction(dbh, /* retain */ false)) { + /* In case of error, revert the setting */ + H->is_writable_txn = !bval; + return false; + } + } + if (!php_firebird_begin_transaction(dbh, /* auto commit mode */ true)) { + /* In case of error, revert the setting */ + H->is_writable_txn = !bval; + return false; + } + } + } + } + return true; + } + return false; +} +/* }}} */ + +#define INFO_BUF_LEN 512 + +/* callback to used to report database server info */ +static void php_firebird_info_cb(void *arg, char const *s) /* {{{ */ +{ + if (arg) { + if (*(char*)arg) { /* second call */ + strlcat(arg, " ", INFO_BUF_LEN); + } + strlcat(arg, s, INFO_BUF_LEN); + } +} +/* }}} */ + +/* called by PDO to get a driver-specific dbh attribute */ +static int pdo_firebird_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + switch (attr) { + char tmp[INFO_BUF_LEN]; + + case PDO_ATTR_AUTOCOMMIT: + ZVAL_BOOL(val,dbh->auto_commit); + return 1; + + case PDO_ATTR_CONNECTION_STATUS: + ZVAL_BOOL(val, !isc_version(&H->db, php_firebird_info_cb, NULL)); + return 1; + + case PDO_ATTR_CLIENT_VERSION: + isc_get_client_version(tmp); + ZVAL_STRING(val, tmp); + return 1; + + case PDO_ATTR_SERVER_VERSION: + case PDO_ATTR_SERVER_INFO: + *tmp = 0; + + if (!isc_version(&H->db, php_firebird_info_cb, (void*)tmp)) { + ZVAL_STRING(val, tmp); + return 1; + } + return -1; + + case PDO_ATTR_FETCH_TABLE_NAMES: + ZVAL_BOOL(val, H->fetch_table_names); + return 1; + + case PDO_FB_ATTR_DATE_FORMAT: + ZVAL_STRING(val, H->date_format ? H->date_format : PDO_FB_DEF_DATE_FMT); + return 1; + + case PDO_FB_ATTR_TIME_FORMAT: + ZVAL_STRING(val, H->time_format ? H->time_format : PDO_FB_DEF_TIME_FMT); + return 1; + + case PDO_FB_ATTR_TIMESTAMP_FORMAT: + ZVAL_STRING(val, H->timestamp_format ? H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT); + return 1; + + case PDO_FB_TRANSACTION_ISOLATION_LEVEL: + ZVAL_LONG(val, H->txn_isolation_level); + return 1; + + case PDO_FB_WRITABLE_TRANSACTION: + ZVAL_BOOL(val, H->is_writable_txn); + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to check liveness */ +static zend_result pdo_firebird_check_liveness(pdo_dbh_t *dbh) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + + /* fb_ping return 0 if the connection is alive */ + return fb_ping(H->isc_status, &H->db) ? FAILURE : SUCCESS; +} +/* }}} */ + +/* called by PDO to retrieve driver-specific information about an error that has occurred */ +static void pdo_firebird_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *info) /* {{{ */ +{ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + if (H->einfo.sqlcode != IS_NULL) { + add_next_index_long(info, H->einfo.sqlcode); + } + if (H->einfo.errmsg && H->einfo.errmsg_length) { + add_next_index_stringl(info, H->einfo.errmsg, H->einfo.errmsg_length); + } +} +/* }}} */ + +/* {{{ firebird_in_manually_transaction */ +static bool pdo_firebird_in_manually_transaction(pdo_dbh_t *dbh) +{ + /** + * we can tell if a transaction exists now by checking H->tr, + * but which will always be true in autocommit mode. + * So this function checks if there is currently a "manually begun transaction". + */ + pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; + return H->in_manually_txn; +} +/* }}} */ + +static const struct pdo_dbh_methods firebird_methods = { /* {{{ */ + firebird_handle_closer, + firebird_handle_preparer, + firebird_handle_doer, + firebird_handle_quoter, + firebird_handle_manually_begin, + firebird_handle_manually_commit, + firebird_handle_manually_rollback, + pdo_firebird_set_attribute, + NULL, /* last_id not supported */ + pdo_firebird_fetch_error_func, + pdo_firebird_get_attribute, + pdo_firebird_check_liveness, + NULL, /* get driver methods */ + NULL, /* request shutdown */ + pdo_firebird_in_manually_transaction, + NULL, /* get gc */ + NULL /* scanner */ +}; +/* }}} */ + +/* the driver-specific PDO handle constructor */ +static int pdo_firebird_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ +{ + struct pdo_data_src_parser vars[] = { + { "dbname", NULL, 0 }, + { "charset", NULL, 0 }, + { "role", NULL, 0 }, + { "dialect", "3", 0 }, + { "user", NULL, 0 }, + { "password", NULL, 0 } + }; + int i, ret = 0; + short buf_len = 256, dpb_len; + + pdo_firebird_db_handle *H = dbh->driver_data = pecalloc(1,sizeof(*H),dbh->is_persistent); + + php_pdo_parse_data_source(dbh->data_source, dbh->data_source_len, vars, 6); + + if (!dbh->username && vars[4].optval) { + dbh->username = pestrdup(vars[4].optval, dbh->is_persistent); + } + + if (!dbh->password && vars[5].optval) { + dbh->password = pestrdup(vars[5].optval, dbh->is_persistent); + } + + H->in_manually_txn = false; + H->is_writable_txn = pdo_attr_lval(driver_options, PDO_FB_WRITABLE_TRANSACTION, 1); + zend_long txn_isolation_level = pdo_attr_lval(driver_options, PDO_FB_TRANSACTION_ISOLATION_LEVEL, PDO_FB_REPEATABLE_READ); + if (txn_isolation_level == PDO_FB_READ_COMMITTED || + txn_isolation_level == PDO_FB_REPEATABLE_READ || + txn_isolation_level == PDO_FB_SERIALIZABLE + ) { + H->txn_isolation_level = txn_isolation_level; + } else { + zend_value_error("Pdo\\Firebird::TRANSACTION_ISOLATION_LEVEL must be a valid transaction isolation level " + "(Pdo\\Firebird::READ_COMMITTED, Pdo\\Firebird::REPEATABLE_READ, or Pdo\\Firebird::SERIALIZABLE)"); + ret = 0; + } + + do { + static char const dpb_flags[] = { + isc_dpb_user_name, isc_dpb_password, isc_dpb_lc_ctype, isc_dpb_sql_role_name }; + char const *dpb_values[] = { dbh->username, dbh->password, vars[1].optval, vars[2].optval }; + char dpb_buffer[256] = { isc_dpb_version1 }, *dpb; + + dpb = dpb_buffer + 1; + + /* loop through all the provided arguments and set dpb fields accordingly */ + for (i = 0; i < sizeof(dpb_flags); ++i) { + if (dpb_values[i] && buf_len > 0) { + dpb_len = slprintf(dpb, buf_len, "%c%c%s", dpb_flags[i], (unsigned char)strlen(dpb_values[i]), + dpb_values[i]); + dpb += dpb_len; + buf_len -= dpb_len; + } + } + + H->sql_dialect = PDO_FB_DIALECT; + if (vars[3].optval) { + H->sql_dialect = atoi(vars[3].optval); + } + + /* fire it up baby! */ + if (isc_attach_database(H->isc_status, 0, vars[0].optval, &H->db,(short)(dpb-dpb_buffer), dpb_buffer)) { + break; + } + + dbh->methods = &firebird_methods; + dbh->native_case = PDO_CASE_UPPER; + dbh->alloc_own_columns = 1; + + ret = 1; + + } while (0); + + for (i = 0; i < sizeof(vars)/sizeof(vars[0]); ++i) { + if (vars[i].freeme) { + efree(vars[i].optval); + } + } + + if (!dbh->methods) { + char errmsg[512]; + const ISC_STATUS *s = H->isc_status; + fb_interpret(errmsg, sizeof(errmsg),&s); + zend_throw_exception_ex(php_pdo_get_exception(), H->isc_status[1], "SQLSTATE[%s] [%" PRIiPTR "] %s", + "HY000", H->isc_status[1], errmsg); + } + + if (ret && dbh->auto_commit && !H->tr) { + ret = php_firebird_begin_transaction(dbh, /* auto commit mode */ true); + } + + if (!ret) { + firebird_handle_closer(dbh); + } + + return ret; +} +/* }}} */ + + +const pdo_driver_t swoole_pdo_firebird_driver = { /* {{{ */ + PDO_DRIVER_HEADER(firebird), + pdo_firebird_handle_factory +}; +/* }}} */ + +#endif \ No newline at end of file diff --git a/thirdparty/php85/pdo_firebird/firebird_statement.c b/thirdparty/php85/pdo_firebird/firebird_statement.c new file mode 100644 index 00000000000..f48327f9218 --- /dev/null +++ b/thirdparty/php85/pdo_firebird/firebird_statement.c @@ -0,0 +1,966 @@ +/* + +----------------------------------------------------------------------+ + | 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: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +#define SW_USE_FIREBIRD_HOOK +#include "php_swoole_firebird.h" + +#if PHP_VERSION_ID >= 80500 + +#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_firebird.h" +#include "php_pdo_firebird_int.h" +#include "pdo_firebird_utils.h" + +#include + +#define READ_AND_RETURN_USING_MEMCPY(type, sqldata) do { \ + type ret; \ + memcpy(&ret, sqldata, sizeof(ret)); \ + return ret; \ + } while (0); + +static zend_always_inline ISC_INT64 php_get_isc_int64_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_INT64, sqldata); +} + +static zend_always_inline ISC_LONG php_get_isc_long_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_LONG, sqldata); +} + +static zend_always_inline double php_get_double_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(double, sqldata); +} + +static zend_always_inline float php_get_float_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(float, sqldata); +} + +static zend_always_inline ISC_TIMESTAMP php_get_isc_timestamp_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP, sqldata); +} + +static zend_always_inline ISC_QUAD php_get_isc_quad_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_QUAD, sqldata); +} + +#if FB_API_VER >= 40 + +static zend_always_inline ISC_TIME_TZ php_get_isc_time_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIME_TZ, sqldata); +} + +static zend_always_inline ISC_TIMESTAMP_TZ php_get_isc_timestamp_tz_from_sqldata(const ISC_SCHAR *sqldata) +{ + READ_AND_RETURN_USING_MEMCPY(ISC_TIMESTAMP_TZ, sqldata); +} + +/* fetch formatted time with time zone */ +static int get_formatted_time_tz(pdo_stmt_t *stmt, const ISC_TIME_TZ* timeTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned hours = 0, minutes = 0, seconds = 0, fractions = 0; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIME time; + char timeBuf[80] = {0}; + if (fb_decode_time_tz(S->H->isc_status, timeTz, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_sql_time(&time, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + + size_t len = strftime(timeBuf, sizeof(timeBuf), fmt, &t); + if (len == 0) { + return 1; + } + + zend_string *time_tz_str = zend_strpprintf(0, "%s %s", timeBuf, timeZoneBuffer); + ZVAL_NEW_STR(result, time_tz_str); + return 0; +} + +/* fetch formatted timestamp with time zone */ +static int get_formatted_timestamp_tz(pdo_stmt_t *stmt, const ISC_TIMESTAMP_TZ* timestampTz, zval *result) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + unsigned year, month, day, hours, minutes, seconds, fractions; + char timeZoneBuffer[40] = {0}; + char *fmt; + struct tm t; + ISC_TIMESTAMP ts; + char timestampBuf[80] = {0}; + if (fb_decode_timestamp_tz(S->H->isc_status, timestampTz, &year, &month, &day, &hours, &minutes, &seconds, &fractions, sizeof(timeZoneBuffer), timeZoneBuffer)) { + return 1; + } + ts.timestamp_date = fb_encode_date(year, month, day); + ts.timestamp_time = fb_encode_time(hours, minutes, seconds, fractions); + isc_decode_timestamp(&ts, &t); + + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + + size_t len = strftime(timestampBuf, sizeof(timestampBuf), fmt, &t); + if (len == 0) { + return 1; + } + + zend_string *timestamp_tz_str = zend_strpprintf(0, "%s %s", timestampBuf, timeZoneBuffer); + ZVAL_NEW_STR(result, timestamp_tz_str); + return 0; +} + +#endif + +/* free the allocated space for passing field values to the db and back */ +static void php_firebird_free_sqlda(XSQLDA const *sqlda) /* {{{ */ +{ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR const *var = &sqlda->sqlvar[i]; + + if (var->sqlind) { + efree(var->sqlind); + } + } +} +/* }}} */ + +/* called by PDO to clean up a statement handle */ +static int pdo_firebird_stmt_dtor(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + int result = 1; + + /* release the statement. + * Note: if the server object is already gone then the statement was closed already as well. */ + if (php_pdo_stmt_valid_db_obj_handle(stmt) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_drop)) { + php_firebird_error_stmt(stmt); + result = 0; + } + + zend_hash_destroy(S->named_params); + FREE_HASHTABLE(S->named_params); + + /* clean up the input descriptor */ + if (S->in_sqlda) { + php_firebird_free_sqlda(S->in_sqlda); + efree(S->in_sqlda); + } + + php_firebird_free_sqlda(&S->out_sqlda); + efree(S); + + return result; +} +/* }}} */ + +/* called by PDO to execute a prepared query */ +static int pdo_firebird_stmt_execute(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + zend_ulong affected_rows = 0; + static char info_count[] = {isc_info_sql_records}; + char result[64]; + + do { + /* named or open cursors should be closed first */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(H->isc_status, &S->stmt, DSQL_close)) { + break; + } + S->cursor_open = 0; + + /* allocate storage for the output data */ + if (S->out_sqlda.sqld) { + unsigned int i; + for (i = 0; i < S->out_sqlda.sqld; i++) { + XSQLVAR *var = &S->out_sqlda.sqlvar[i]; + if (var->sqlind) { + efree(var->sqlind); + } + var->sqlind = (void*)ecalloc(1, var->sqllen + 2 * sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + } + } + + if (S->statement_type == isc_info_sql_stmt_exec_procedure) { + if (isc_dsql_execute2(H->isc_status, &H->tr, &S->stmt, PDO_FB_SQLDA_VERSION, S->in_sqlda, &S->out_sqlda)) { + break; + } + } else if (isc_dsql_execute(H->isc_status, &H->tr, &S->stmt, PDO_FB_SQLDA_VERSION, S->in_sqlda)) { + break; + } + + /* Determine how many rows have changed. In this case we are + * only interested in rows changed, not rows retrieved. That + * should be handled by the client when fetching. */ + stmt->row_count = affected_rows; + + switch (S->statement_type) { + case isc_info_sql_stmt_insert: + case isc_info_sql_stmt_update: + case isc_info_sql_stmt_delete: + case isc_info_sql_stmt_exec_procedure: + if (isc_dsql_sql_info(H->isc_status, &S->stmt, sizeof ( info_count), + info_count, sizeof(result), result)) { + break; + } + if (result[0] == isc_info_sql_records) { + unsigned i = 3, result_size = isc_vax_integer(&result[1], 2); + if (result_size > sizeof(result)) { + goto error; + } + while (result[i] != isc_info_end && i < result_size) { + short len = (short) isc_vax_integer(&result[i + 1], 2); + if (len != 1 && len != 2 && len != 4) { + goto error; + } + if (result[i] != isc_info_req_select_count) { + affected_rows += isc_vax_integer(&result[i + 3], len); + } + i += len + 3; + } + stmt->row_count = affected_rows; + } + /* TODO Dead code or assert one of the previous cases are hit? */ + default: + ; + } + + if (stmt->dbh->auto_commit && !S->H->in_manually_txn && !php_firebird_commit_transaction(stmt->dbh, /* retain */ true)) { + break; + } + + *S->name = 0; + S->cursor_open = S->out_sqlda.sqln && (S->statement_type != isc_info_sql_stmt_exec_procedure); + S->exhausted = !S->out_sqlda.sqln; /* There are data to fetch */ + + return 1; + } while (0); + +error: + php_firebird_error_stmt(stmt); + + return 0; +} +/* }}} */ + +/* called by PDO to fetch the next row from a statement */ +static int pdo_firebird_stmt_fetch(pdo_stmt_t *stmt, /* {{{ */ + enum pdo_fetch_orientation ori, zend_long offset) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + + if (!stmt->executed) { + const char *msg = "Cannot fetch from a closed cursor"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + } else if (!S->exhausted) { + if (S->statement_type == isc_info_sql_stmt_exec_procedure) { + stmt->row_count = 1; + S->exhausted = 1; + return 1; + } + if (isc_dsql_fetch(H->isc_status, &S->stmt, PDO_FB_SQLDA_VERSION, &S->out_sqlda)) { + if (H->isc_status[0] && H->isc_status[1]) { + php_firebird_error_stmt(stmt); + } + S->exhausted = 1; + return 0; + } + stmt->row_count++; + return 1; + } + return 0; +} +/* }}} */ + +/* called by PDO to retrieve information about the fields being returned */ +static int pdo_firebird_stmt_describe(pdo_stmt_t *stmt, int colno) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + struct pdo_column_data *col = &stmt->columns[colno]; + XSQLVAR *var = &S->out_sqlda.sqlvar[colno]; + int colname_len; + char *cp; + + if ((var->sqltype & ~1) == SQL_TEXT) { + var->sqltype = SQL_VARYING | (var->sqltype & 1); + } + colname_len = (S->H->fetch_table_names && var->relname_length) + ? (var->aliasname_length + var->relname_length + 1) + : (var->aliasname_length); + col->precision = -var->sqlscale; + col->maxlen = var->sqllen; + col->name = zend_string_alloc(colname_len, 0); + cp = ZSTR_VAL(col->name); + if (colname_len > var->aliasname_length) { + memmove(cp, var->relname, var->relname_length); + cp += var->relname_length; + *cp++ = '.'; + } + memmove(cp, var->aliasname, var->aliasname_length); + *(cp+var->aliasname_length) = '\0'; + + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_value) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt *) stmt->driver_data; + XSQLVAR *var = &S->out_sqlda.sqlvar[colno]; + + enum pdo_param_type param_type; + if (var->sqlscale < 0) { + param_type = PDO_PARAM_STR; + } else { + switch (var->sqltype & ~1) { + case SQL_SHORT: + case SQL_LONG: +#if SIZEOF_ZEND_LONG >= 8 + case SQL_INT64: +#endif + param_type = PDO_PARAM_INT; + break; + case SQL_BOOLEAN: + param_type = PDO_PARAM_BOOL; + break; + default: + param_type = PDO_PARAM_STR; + break; + } + } + + array_init(return_value); + add_assoc_long(return_value, "pdo_type", param_type); + return 1; +} + +/* fetch a blob into a fetch buffer */ +static int php_firebird_fetch_blob(pdo_stmt_t *stmt, int colno, zval *result, ISC_QUAD *blob_id) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle blobh = PDO_FIREBIRD_HANDLE_INITIALIZER; + char const bl_item = isc_info_blob_total_length; + char bl_info[20]; + unsigned short i; + int retval = 0; + size_t len = 0; + + if (isc_open_blob(H->isc_status, &H->db, &H->tr, &blobh, blob_id)) { + php_firebird_error_stmt(stmt); + return 0; + } + + if (isc_blob_info(H->isc_status, &blobh, 1, const_cast(&bl_item), + sizeof(bl_info), bl_info)) { + php_firebird_error_stmt(stmt); + goto fetch_blob_end; + } + + /* find total length of blob's data */ + for (i = 0; i < sizeof(bl_info); ) { + unsigned short item_len; + char item = bl_info[i++]; + + if (item == isc_info_end || item == isc_info_truncated || item == isc_info_error + || i >= sizeof(bl_info)) { + const char *msg = "Couldn't determine BLOB size"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + goto fetch_blob_end; + } + + item_len = (unsigned short) isc_vax_integer(&bl_info[i], 2); + + if (item == isc_info_blob_total_length) { + len = isc_vax_integer(&bl_info[i+2], item_len); + break; + } + i += item_len+2; + } + + /* we've found the blob's length, now fetch! */ + + if (len) { + zend_ulong cur_len; + unsigned short seg_len; + ISC_STATUS stat; + zend_string *str; + + /* prevent overflow */ + if (len > ZSTR_MAX_LEN) { + result = 0; + goto fetch_blob_end; + } + + str = zend_string_alloc(len, 0); + + for (cur_len = stat = 0; (!stat || stat == isc_segment) && cur_len < len; cur_len += seg_len) { + + unsigned short chunk_size = (len - cur_len) > USHRT_MAX ? USHRT_MAX + : (unsigned short)(len - cur_len); + + stat = isc_get_segment(H->isc_status, &blobh, &seg_len, chunk_size, ZSTR_VAL(str) + cur_len); + } + + ZSTR_VAL(str)[len] = '\0'; + ZVAL_STR(result, str); + + if (H->isc_status[0] == 1 && (stat != 0 && stat != isc_segstr_eof && stat != isc_segment)) { + const char *msg = "Error reading from BLOB"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + goto fetch_blob_end; + } + } + retval = 1; + +fetch_blob_end: + if (isc_close_blob(H->isc_status, &blobh)) { + php_firebird_error_stmt(stmt); + return 0; + } + return retval; +} +/* }}} */ + +static int pdo_firebird_stmt_get_col( + pdo_stmt_t *stmt, int colno, zval *result, enum pdo_param_type *type) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLVAR const *var = &S->out_sqlda.sqlvar[colno]; + + if (*var->sqlind == -1) { + ZVAL_NULL(result); + } else { + if (var->sqlscale < 0) { + static ISC_INT64 const scales[] = { 1, 10, 100, 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + LL_LIT(10000000000), + LL_LIT(100000000000), + LL_LIT(1000000000000), + LL_LIT(10000000000000), + LL_LIT(100000000000000), + LL_LIT(1000000000000000), + LL_LIT(10000000000000000), + LL_LIT(100000000000000000), + LL_LIT(1000000000000000000) + }; + ISC_INT64 n, f = scales[-var->sqlscale]; + zend_string *str; + + switch (var->sqltype & ~1) { + case SQL_SHORT: + n = *(short*)var->sqldata; + break; + case SQL_LONG: + n = php_get_isc_long_from_sqldata(var->sqldata); + break; + case SQL_INT64: + n = php_get_isc_int64_from_sqldata(var->sqldata); + break; + case SQL_DOUBLE: + break; + EMPTY_SWITCH_DEFAULT_CASE() + } + + if ((var->sqltype & ~1) == SQL_DOUBLE) { + str = zend_strpprintf(0, "%.*F", -var->sqlscale, php_get_double_from_sqldata(var->sqldata)); + } else if (n >= 0) { + str = zend_strpprintf(0, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, n % f); + } else if (n <= -f) { + str = zend_strpprintf(0, "%" LL_MASK "d.%0*" LL_MASK "d", + n / f, -var->sqlscale, -n % f); + } else { + str = zend_strpprintf(0, "-0.%0*" LL_MASK "d", -var->sqlscale, -n % f); + } + ZVAL_STR(result, str); + } else { + switch (var->sqltype & ~1) { + struct tm t; + char *fmt; + + case SQL_VARYING: + ZVAL_STRINGL_FAST(result, &var->sqldata[2], *(short*)var->sqldata); + break; + case SQL_TEXT: + ZVAL_STRINGL_FAST(result, var->sqldata, var->sqllen); + break; + case SQL_SHORT: + ZVAL_LONG(result, *(short*)var->sqldata); + break; + case SQL_LONG: + ZVAL_LONG(result, php_get_isc_long_from_sqldata(var->sqldata)); + break; + case SQL_INT64: +#if SIZEOF_ZEND_LONG >= 8 + ZVAL_LONG(result, php_get_isc_int64_from_sqldata(var->sqldata)); +#else + ZVAL_STR(result, zend_strpprintf(0, "%" LL_MASK "d", php_get_isc_int64_from_sqldata(var->sqldata))); +#endif + break; + case SQL_FLOAT: + /* TODO: Why is this not returned as the native type? */ + ZVAL_STR(result, zend_strpprintf_unchecked(0, "%.8H", php_get_float_from_sqldata(var->sqldata))); + break; + case SQL_DOUBLE: + /* TODO: Why is this not returned as the native type? */ + ZVAL_STR(result, zend_strpprintf_unchecked(0, "%.16H", php_get_double_from_sqldata(var->sqldata))); + break; + case SQL_BOOLEAN: + ZVAL_BOOL(result, *(FB_BOOLEAN*)var->sqldata); + break; + case SQL_TYPE_DATE: + isc_decode_sql_date((ISC_DATE*)var->sqldata, &t); + fmt = S->H->date_format ? S->H->date_format : PDO_FB_DEF_DATE_FMT; + if (0) { + case SQL_TYPE_TIME: + isc_decode_sql_time((ISC_TIME*)var->sqldata, &t); + fmt = S->H->time_format ? S->H->time_format : PDO_FB_DEF_TIME_FMT; + } else if (0) { + case SQL_TIMESTAMP: + { + ISC_TIMESTAMP timestamp = php_get_isc_timestamp_from_sqldata(var->sqldata); + isc_decode_timestamp(×tamp, &t); + } + fmt = S->H->timestamp_format ? S->H->timestamp_format : PDO_FB_DEF_TIMESTAMP_FMT; + } + /* convert the timestamp into a string */ + char buf[80]; + size_t len = strftime(buf, sizeof(buf), fmt, &t); + ZVAL_STRINGL(result, buf, len); + break; +#if FB_API_VER >= 40 + case SQL_TIME_TZ: { + ISC_TIME_TZ time = php_get_isc_time_tz_from_sqldata(var->sqldata); + return get_formatted_time_tz(stmt, &time, result); + } + case SQL_TIMESTAMP_TZ: { + ISC_TIMESTAMP_TZ ts = php_get_isc_timestamp_tz_from_sqldata(var->sqldata); + return get_formatted_timestamp_tz(stmt, &ts, result); + } +#endif + case SQL_BLOB: { + ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata); + return php_firebird_fetch_blob(stmt, colno, result, &quad); + } + } + } + } + return 1; +} + +static int php_firebird_bind_blob(pdo_stmt_t *stmt, ISC_QUAD *blob_id, zval *param) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + pdo_firebird_db_handle *H = S->H; + isc_blob_handle h = PDO_FIREBIRD_HANDLE_INITIALIZER; + zval data; + zend_ulong put_cnt = 0, rem_cnt; + unsigned short chunk_size; + int result = 1; + + if (isc_create_blob(H->isc_status, &H->db, &H->tr, &h, blob_id)) { + php_firebird_error_stmt(stmt); + return 0; + } + + if (Z_TYPE_P(param) != IS_STRING) { + ZVAL_STR(&data, zval_get_string_func(param)); + } else { + ZVAL_COPY_VALUE(&data, param); + } + + for (rem_cnt = Z_STRLEN(data); rem_cnt > 0; rem_cnt -= chunk_size) { + chunk_size = rem_cnt > USHRT_MAX ? USHRT_MAX : (unsigned short)rem_cnt; + if (isc_put_segment(H->isc_status, &h, chunk_size, &Z_STRVAL(data)[put_cnt])) { + php_firebird_error_stmt(stmt); + result = 0; + break; + } + put_cnt += chunk_size; + } + + if (Z_TYPE_P(param) != IS_STRING) { + zval_ptr_dtor_str(&data); + } + + if (isc_close_blob(H->isc_status, &h)) { + php_firebird_error_stmt(stmt); + return 0; + } + return result; +} + +static int pdo_firebird_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_data *param, /* {{{ */ + enum pdo_param_event event_type) +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + XSQLDA *sqlda = param->is_param ? S->in_sqlda : &S->out_sqlda; + XSQLVAR *var; + + if (event_type == PDO_PARAM_EVT_FREE) { /* not used */ + return 1; + } + + if (!sqlda || param->paramno >= sqlda->sqld) { + const char *msg = "Invalid parameter index"; + php_firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); + return 0; + } + if (param->is_param && param->paramno == -1) { + zval *index; + + /* try to determine the index by looking in the named_params hash */ + if ((index = zend_hash_find(S->named_params, param->name)) != NULL) { + param->paramno = Z_LVAL_P(index); + } else { + /* ... or by looking in the input descriptor */ + int i; + + for (i = 0; i < sqlda->sqld; ++i) { + XSQLVAR *var = &sqlda->sqlvar[i]; + + if ((var->aliasname_length && !strncasecmp(ZSTR_VAL(param->name), var->aliasname, + min(ZSTR_LEN(param->name), var->aliasname_length))) + || (var->sqlname_length && !strncasecmp(ZSTR_VAL(param->name), var->sqlname, + min(ZSTR_LEN(param->name), var->sqlname_length)))) { + param->paramno = i; + break; + } + } + if (i >= sqlda->sqld) { + const char *msg = "Invalid parameter name"; + php_firebird_error_stmt_with_info(stmt, "HY093", strlen("HY093"), msg, strlen(msg)); + return 0; + } + } + } + + var = &sqlda->sqlvar[param->paramno]; + + switch (event_type) { + zval *parameter; + + case PDO_PARAM_EVT_ALLOC: + if (param->is_param) { + /* allocate the parameter */ + if (var->sqlind) { + efree(var->sqlind); + } + var->sqlind = (void*)emalloc(var->sqllen + 2*sizeof(short)); + var->sqldata = &((char*)var->sqlind)[sizeof(short)]; + } + break; + + case PDO_PARAM_EVT_EXEC_PRE: + if (!param->is_param) { + break; + } + + *var->sqlind = 0; + 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; + } + } + + switch (var->sqltype & ~1) { + case SQL_ARRAY: + { + const char *msg = "Cannot bind to array field"; + php_firebird_error_stmt_with_info(stmt, "HY000", strlen("HY000"), msg, strlen(msg)); + } + return 0; + + case SQL_BLOB: { + if (Z_TYPE_P(parameter) == IS_NULL) { + /* Check if field allow NULL values */ + if (~var->sqltype & 1) { + const char *msg = "Parameter requires non-null value"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + *var->sqlind = -1; + return 1; + } + ISC_QUAD quad = php_get_isc_quad_from_sqldata(var->sqldata); + if (php_firebird_bind_blob(stmt, &quad, parameter) != 0) { + memcpy(var->sqldata, &quad, sizeof(quad)); + return 1; + } + return 0; + } + } + + /* keep native BOOLEAN type */ + if ((var->sqltype & ~1) == SQL_BOOLEAN) { + switch (Z_TYPE_P(parameter)) { + case IS_LONG: + case IS_DOUBLE: + case IS_TRUE: + case IS_FALSE: + *(FB_BOOLEAN*)var->sqldata = zend_is_true(parameter) ? FB_TRUE : FB_FALSE; + break; + case IS_STRING: + { + zend_long lval; + double dval; + + if (Z_STRLEN_P(parameter) == 0) { + *(FB_BOOLEAN*)var->sqldata = FB_FALSE; + break; + } + + switch (is_numeric_string(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), &lval, &dval, 0)) { + case IS_LONG: + *(FB_BOOLEAN*)var->sqldata = (lval != 0) ? FB_TRUE : FB_FALSE; + break; + case IS_DOUBLE: + *(FB_BOOLEAN*)var->sqldata = (dval != 0) ? FB_TRUE : FB_FALSE; + break; + default: + if (!zend_binary_strncasecmp(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), "true", 4, 4)) { + *(FB_BOOLEAN*)var->sqldata = FB_TRUE; + } else if (!zend_binary_strncasecmp(Z_STRVAL_P(parameter), Z_STRLEN_P(parameter), "false", 5, 5)) { + *(FB_BOOLEAN*)var->sqldata = FB_FALSE; + } else { + const char *msg = "Cannot convert string to boolean"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + + } + } + break; + case IS_NULL: + *var->sqlind = -1; + break; + default: + { + const char *msg = "Binding arrays/objects is not supported"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } + return 0; + } + break; + } + + /* check if a NULL should be inserted */ + switch (Z_TYPE_P(parameter)) { + int force_null; + + case IS_LONG: + /* keep the allow-NULL flag */ + var->sqltype = (sizeof(zend_long) == 8 ? SQL_INT64 : SQL_LONG) | (var->sqltype & 1); + var->sqldata = (void*)&Z_LVAL_P(parameter); + var->sqllen = sizeof(zend_long); + break; + case IS_DOUBLE: + /* keep the allow-NULL flag */ + var->sqltype = SQL_DOUBLE | (var->sqltype & 1); + var->sqldata = (void*)&Z_DVAL_P(parameter); + var->sqllen = sizeof(double); + break; + case IS_STRING: + force_null = 0; + + /* for these types, an empty string can be handled like a NULL value */ + switch (var->sqltype & ~1) { + case SQL_SHORT: + case SQL_LONG: + case SQL_INT64: + case SQL_FLOAT: + case SQL_DOUBLE: + case SQL_TIMESTAMP: + case SQL_TYPE_DATE: + case SQL_TYPE_TIME: +#if FB_API_VER >= 40 + case SQL_INT128: + case SQL_DEC16: + case SQL_DEC34: + case SQL_TIMESTAMP_TZ: + case SQL_TIME_TZ: +#endif + force_null = (Z_STRLEN_P(parameter) == 0); + } + if (!force_null) { + /* keep the allow-NULL flag */ + var->sqltype = SQL_TEXT | (var->sqltype & 1); + var->sqldata = Z_STRVAL_P(parameter); + var->sqllen = Z_STRLEN_P(parameter); + break; + } + ZEND_FALLTHROUGH; + case IS_NULL: + /* complain if this field doesn't allow NULL values */ + if (~var->sqltype & 1) { + const char *msg = "Parameter requires non-null value"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + return 0; + } + *var->sqlind = -1; + break; + default: + { + const char *msg = "Binding arrays/objects is not supported"; + php_firebird_error_stmt_with_info(stmt, "HY105", strlen("HY105"), msg, strlen(msg)); + } + return 0; + } + break; + + case PDO_PARAM_EVT_FETCH_POST: + if (param->paramno == -1) { + return 0; + } + if (param->is_param) { + break; + } + if (Z_ISREF(param->parameter)) { + parameter = Z_REFVAL(param->parameter); + } else { + parameter = ¶m->parameter; + } + zval_ptr_dtor(parameter); + ZVAL_NULL(parameter); + return pdo_firebird_stmt_get_col(stmt, param->paramno, parameter, NULL); + default: + ; + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: { + zend_string *str_val = zval_try_get_string(val); + if (str_val == NULL) { + return 0; + } + // TODO Check cursor name does not have null bytes? + if (ZSTR_LEN(str_val) >= sizeof(S->name)) { + zend_value_error("Cursor name must not be longer than %zu bytes", sizeof(S->name) - 1); + zend_string_release(str_val); + return 0; + } + + if (isc_dsql_set_cursor_name(S->H->isc_status, &S->stmt, ZSTR_VAL(str_val), 0)) { + php_firebird_error_stmt(stmt); + zend_string_release(str_val); + return 0; + } + /* Include trailing nul byte */ + memcpy(S->name, ZSTR_VAL(str_val), ZSTR_LEN(str_val) + 1); + zend_string_release(str_val); + break; + } + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval *val) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + switch (attr) { + default: + return 0; + case PDO_ATTR_CURSOR_NAME: + if (*S->name) { + ZVAL_STRING(val, S->name); + } else { + ZVAL_NULL(val); + } + break; + } + return 1; +} +/* }}} */ + +static int pdo_firebird_stmt_cursor_closer(pdo_stmt_t *stmt) /* {{{ */ +{ + pdo_firebird_stmt *S = (pdo_firebird_stmt*)stmt->driver_data; + + /* close the statement handle */ + if ((*S->name || S->cursor_open) && isc_dsql_free_statement(S->H->isc_status, &S->stmt, DSQL_close)) { + php_firebird_error_stmt(stmt); + return 0; + } + *S->name = 0; + S->cursor_open = 0; + return 1; +} +/* }}} */ + + +const struct pdo_stmt_methods swoole_firebird_stmt_methods = { /* {{{ */ + pdo_firebird_stmt_dtor, + pdo_firebird_stmt_execute, + pdo_firebird_stmt_fetch, + pdo_firebird_stmt_describe, + pdo_firebird_stmt_get_col, + pdo_firebird_stmt_param_hook, + pdo_firebird_stmt_set_attribute, + pdo_firebird_stmt_get_attribute, + pdo_firebird_stmt_get_column_meta, + NULL, /* next_rowset_func */ + pdo_firebird_stmt_cursor_closer +}; +/* }}} */ + +#endif \ No newline at end of file diff --git a/thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp new file mode 100644 index 00000000000..1398b422604 --- /dev/null +++ b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.cpp @@ -0,0 +1,94 @@ +/* + +----------------------------------------------------------------------+ + | 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: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#include "pdo_firebird_utils.h" +#include +#include + +#if PHP_VERSION_ID >= 80500 + +/* Returns the client version. 0 bytes are minor version, 1 bytes are major version. */ +extern "C" unsigned fb_get_client_version(void) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->getClientVersion(); +} + +extern "C" ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeTime(hours, minutes, seconds, fractions); +} + +extern "C" ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + return util->encodeDate(year, month, day); +} + +#if FB_API_VER >= 40 +static void fb_copy_status(const ISC_STATUS* from, ISC_STATUS* to, size_t maxLength) +{ + for(size_t i=0; i < maxLength; ++i) { + memcpy(to + i, from + i, sizeof(ISC_STATUS)); + if (from[i] == isc_arg_end) { + break; + } + } +} + +/* Decodes a time with time zone into its time components. */ +extern "C" ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeTz(&st, timeTz, hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +/* Decodes a timestamp with time zone into its date and time components */ +extern "C" ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer) +{ + Firebird::IMaster* master = Firebird::fb_get_master_interface(); + Firebird::IUtil* util = master->getUtilInterface(); + Firebird::IStatus* status = master->getStatus(); + Firebird::CheckStatusWrapper st(status); + util->decodeTimeStampTz(&st, timestampTz, year, month, day, + hours, minutes, seconds, fractions, + timeZoneBufferLength, timeZoneBuffer); + if (st.hasData()) { + fb_copy_status((const ISC_STATUS*)st.getErrors(), isc_status, 20); + } + status->dispose(); + return isc_status[1]; +} + +#endif +#endif diff --git a/thirdparty/php85/pdo_firebird/pdo_firebird_utils.h b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.h new file mode 100644 index 00000000000..19b0d0ab4ba --- /dev/null +++ b/thirdparty/php85/pdo_firebird/pdo_firebird_utils.h @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | 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: Simonov Denis | + +----------------------------------------------------------------------+ +*/ + +#ifndef PDO_FIREBIRD_UTILS_H +#define PDO_FIREBIRD_UTILS_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +unsigned fb_get_client_version(void); + +ISC_TIME fb_encode_time(unsigned hours, unsigned minutes, unsigned seconds, unsigned fractions); + +ISC_DATE fb_encode_date(unsigned year, unsigned month, unsigned day); + +#if FB_API_VER >= 40 + +ISC_STATUS fb_decode_time_tz(ISC_STATUS* isc_status, const ISC_TIME_TZ* timeTz, unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +ISC_STATUS fb_decode_timestamp_tz(ISC_STATUS* isc_status, const ISC_TIMESTAMP_TZ* timestampTz, + unsigned* year, unsigned* month, unsigned* day, + unsigned* hours, unsigned* minutes, unsigned* seconds, unsigned* fractions, + unsigned timeZoneBufferLength, char* timeZoneBuffer); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* PDO_FIREBIRD_UTILS_H */ diff --git a/thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h b/thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h new file mode 100644 index 00000000000..f1dc9f625f8 --- /dev/null +++ b/thirdparty/php85/pdo_firebird/php_pdo_firebird_int.h @@ -0,0 +1,153 @@ +/* + +----------------------------------------------------------------------+ + | 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: Ard Biesheuvel | + +----------------------------------------------------------------------+ +*/ + +/* internal header; not supposed to be installed */ + +#ifndef PHP_PDO_FIREBIRD_INT_H +#define PHP_PDO_FIREBIRD_INT_H + +#include + +#ifdef SQLDA_VERSION +#define PDO_FB_SQLDA_VERSION SQLDA_VERSION +#else +#define PDO_FB_SQLDA_VERSION 1 +#endif + +#define PDO_FB_DIALECT 3 + +#define PDO_FB_DEF_DATE_FMT "%Y-%m-%d" +#define PDO_FB_DEF_TIME_FMT "%H:%M:%S" +#define PDO_FB_DEF_TIMESTAMP_FMT PDO_FB_DEF_DATE_FMT " " PDO_FB_DEF_TIME_FMT + +#if SIZEOF_ZEND_LONG == 8 && !defined(PHP_WIN32) +# define LL_LIT(lit) lit ## L +#else +# define LL_LIT(lit) lit ## LL +#endif +#define LL_MASK "ll" + +/* Firebird API has a couple of missing const decls in its API */ +#define const_cast(s) ((char*)(s)) + +#ifndef min +#define min(a,b) ((a)<(b)?(a):(b)) +#endif + +#if defined(_LP64) || defined(__LP64__) || defined(__arch64__) || defined(_WIN64) +# define PDO_FIREBIRD_HANDLE_INITIALIZER 0U +#else +# define PDO_FIREBIRD_HANDLE_INITIALIZER NULL +#endif + +typedef struct { + int sqlcode; + char *errmsg; + size_t errmsg_length; +} pdo_firebird_error_info; + +typedef struct { + /* the result of the last API call */ + ISC_STATUS isc_status[20]; + + /* the connection handle */ + isc_db_handle db; + + /* the transaction handle */ + isc_tr_handle tr; + bool in_manually_txn; + bool is_writable_txn; + zend_ulong txn_isolation_level; + + /* date and time format strings, can be set by the set_attribute method */ + char *date_format; + char *time_format; + char *timestamp_format; + + unsigned sql_dialect:2; + + /* prepend table names on column names in fetch */ + unsigned fetch_table_names:1; + + unsigned _reserved:29; + + pdo_firebird_error_info einfo; +} pdo_firebird_db_handle; + + +typedef struct { + /* the link that owns this statement */ + pdo_firebird_db_handle *H; + + /* the statement handle */ + isc_stmt_handle stmt; + + /* the name of the cursor (if it has one) */ + char name[32]; + + /* the type of statement that was issued */ + char statement_type:8; + + /* whether EOF was reached for this statement */ + unsigned exhausted:1; + + /* successful isc_dsql_execute opens a cursor */ + unsigned cursor_open:1; + + unsigned _reserved:22; + + /* the named params that were converted to ?'s by the driver */ + HashTable *named_params; + + /* the input SQLDA */ + XSQLDA *in_sqlda; + + /* the output SQLDA */ + XSQLDA out_sqlda; /* last member */ +} pdo_firebird_stmt; + +extern const pdo_driver_t pdo_firebird_driver; + +extern const struct pdo_stmt_methods swoole_firebird_stmt_methods; + +extern void php_firebird_set_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *state, const size_t state_len, + const char *msg, const size_t msg_len); +#define php_firebird_error(d) php_firebird_set_error(d, NULL, NULL, 0, NULL, 0) +#define php_firebird_error_stmt(s) php_firebird_set_error(s->dbh, s, NULL, 0, NULL, 0) +#define php_firebird_error_with_info(d,e,el,m,ml) php_firebird_set_error(d, NULL, e, el, m, ml) +#define php_firebird_error_stmt_with_info(s,e,el,m,ml) php_firebird_set_error(s->dbh, s, e, el, m, ml) + +extern bool php_firebird_commit_transaction(pdo_dbh_t *dbh, bool retain); + +enum { + PDO_FB_ATTR_DATE_FORMAT = PDO_ATTR_DRIVER_SPECIFIC, + PDO_FB_ATTR_TIME_FORMAT, + PDO_FB_ATTR_TIMESTAMP_FORMAT, + + /* + * transaction isolation level + * firebird does not have a level equivalent to read uncommited. + */ + PDO_FB_TRANSACTION_ISOLATION_LEVEL, + PDO_FB_READ_COMMITTED, + PDO_FB_REPEATABLE_READ, + PDO_FB_SERIALIZABLE, + + /* transaction access mode */ + PDO_FB_WRITABLE_TRANSACTION, +}; + +#endif /* PHP_PDO_FIREBIRD_INT_H */ diff --git a/thirdparty/php85/pdo_oci/LICENSE b/thirdparty/php85/pdo_oci/LICENSE new file mode 100644 index 00000000000..0815d7eb791 --- /dev/null +++ b/thirdparty/php85/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/php85/pdo_oci/oci_driver.c b/thirdparty/php85/pdo_oci/oci_driver.c new file mode 100644 index 00000000000..5e2577b60e1 --- /dev/null +++ b/thirdparty/php85/pdo_oci/oci_driver.c @@ -0,0 +1,892 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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_oci.h" +//#include "php_pdo_oci_int.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 = &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(PHP_PDO_OCI_CLIENT_VERSION) + /* Compile time client version */ + ZVAL_STRING(return_value, PHP_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 */ +#ifdef HAVE_OCIENVNLSCREATE + if (vars[0].optval) { + H->charset = OCINlsCharSetNameToId(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, 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 = 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/php85/pdo_oci/oci_statement.c b/thirdparty/php85/pdo_oci/oci_statement.c new file mode 100644 index 00000000000..90ab13f4efb --- /dev/null +++ b/thirdparty/php85/pdo_oci/oci_statement.c @@ -0,0 +1,1000 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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_oci.h" +//#include "php_pdo_oci_int.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(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 PHP_API_VERSION < 20240925 + 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); +#else + bool server_obj_usable = php_pdo_stmt_valid_db_obj_handle(stmt); +#endif + + 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: + if (server_obj_usable) { + 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, (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(pdo_stmt_t *stmt, OCILobLocator *lob) +{ + php_stream *stm; + struct oci_lob_self *self = ecalloc(1, sizeof(*self)); + +#if PHP_API_VERSION < 20240925 + ZVAL_COPY_VALUE(&self->dbh, &stmt->database_object_handle); +#else + ZVAL_OBJ(&self->dbh, stmt->database_object_handle); +#endif + 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, (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 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 \ No newline at end of file diff --git a/thirdparty/php85/pdo_oci/php_pdo_oci.h b/thirdparty/php85/pdo_oci/php_pdo_oci.h new file mode 100644 index 00000000000..7096823ad2d --- /dev/null +++ b/thirdparty/php85/pdo_oci/php_pdo_oci.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | 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_OCI_H +#define PHP_PDO_OCI_H + +extern zend_module_entry pdo_oci_module_entry; +#define phpext_pdo_oci_ptr &pdo_oci_module_entry + +#include "php_version.h" +#define PHP_PDO_OCI_VERSION "1.1.0" + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(pdo_oci); +PHP_MSHUTDOWN_FUNCTION(pdo_oci); +PHP_RINIT_FUNCTION(pdo_oci); +PHP_RSHUTDOWN_FUNCTION(pdo_oci); +PHP_MINFO_FUNCTION(pdo_oci); + +#endif /* PHP_PDO_OCI_H */ diff --git a/thirdparty/php85/pdo_oci/php_pdo_oci_int.h b/thirdparty/php85/pdo_oci/php_pdo_oci_int.h new file mode 100644 index 00000000000..dd513ff94e0 --- /dev/null +++ b/thirdparty/php85/pdo_oci/php_pdo_oci_int.h @@ -0,0 +1,116 @@ +/* + +----------------------------------------------------------------------+ + | 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_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; +} 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 ub4 PDO_OCI_INIT_MODE; +extern const pdo_driver_t pdo_oci_driver; +extern OCIEnv *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__) + +extern const struct pdo_stmt_methods oci_stmt_methods; + +/* 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 +}; + +#endif /* PHP_PDO_OCI_INT_H */ diff --git a/thirdparty/php85/pdo_odbc/odbc_driver.c b/thirdparty/php85/pdo_odbc/odbc_driver.c new file mode 100644 index 00000000000..6a8cfc5e347 --- /dev/null +++ b/thirdparty/php85/pdo_odbc/odbc_driver.c @@ -0,0 +1,632 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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" +/* this file actually lives in main/ */ +#include "php_odbc_utils.h" +#include "php_pdo_odbc.h" +//#include "php_pdo_odbc_int.h" +#include "zend_exceptions.h" + +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; + } + + /* If we don't have a driver error do not populate the info array */ + if (strlen(einfo->last_err_msg) == 0) { + return; + } + + 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 = &swoole_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; + + bool use_uid_arg = dbh->username != NULL && !php_memnistr(dbh->data_source, "uid=", strlen("uid="), dbh->data_source + dbh->data_source_len); + bool use_pwd_arg = dbh->password != NULL && !php_memnistr(dbh->data_source, "pwd=", strlen("pwd="), dbh->data_source + dbh->data_source_len); + + if (use_uid_arg || use_pwd_arg) { + char *db = (char*) estrndup(dbh->data_source, dbh->data_source_len); + char *db_end = db + dbh->data_source_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; + dbh->data_source_len = strlen(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, dbh->data_source_len, + 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 \ No newline at end of file diff --git a/thirdparty/php85/pdo_odbc/odbc_stmt.c b/thirdparty/php85/pdo_odbc/odbc_stmt.c new file mode 100644 index 00000000000..ae7a5066093 --- /dev/null +++ b/thirdparty/php85/pdo_odbc/odbc_stmt.c @@ -0,0 +1,898 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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_odbc.h" +//#include "php_pdo_odbc_int.h" + +/* Buffer size; bigger columns than this become a "long column" */ +#define LONG_COLUMN_BUFFER_SIZE (ZEND_MM_PAGE_SIZE- ZSTR_MAX_OVERHEAD) + +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(int is_unicode, zval *result) +{ +#ifdef PHP_WIN32 + ZEND_ASSERT(Z_TYPE_P(result) == IS_STRING); + if (is_unicode && Z_STRLEN_P(result) != 0) { + 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) { + zend_string_efree(str); + 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 && php_pdo_stmt_valid_db_obj_handle(stmt)) { + 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(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 < LONG_COLUMN_BUFFER_SIZE && !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(LONG_COLUMN_BUFFER_SIZE); + 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 the page size minus zend_string overhead (LONG_COLUMN_BUFFER_SIZE); + * 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, + LONG_COLUMN_BUFFER_SIZE, &C->fetched_len); + orig_fetched_len = C->fetched_len; + + if (rc == SQL_SUCCESS && C->fetched_len < LONG_COLUMN_BUFFER_SIZE) { + /* 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. + * + * Try to get as much as we can at once. If the + * driver somehow has more for us, get more. We'll + * assemble it into one big buffer at the end. + * + * N.B. with n and n+1 mentioned in the comments: + * n is the size returned without null terminator. + * + * The extension previously tried getting it in 256 + * byte blocks, but this could have created trouble + * with some drivers. + * + * However, depending on the driver, fetched_len may + * not contain the number of bytes and SQL_NO_TOTAL + * may be passed. + * The behavior in this case is the same as before, + * dividing the data into blocks. However, it has been + * changed from 256 byte to LONG_COLUMN_BUFFER_SIZE. + */ + ssize_t to_fetch_len; + if (orig_fetched_len == SQL_NO_TOTAL) { + to_fetch_len = C->datalen > (LONG_COLUMN_BUFFER_SIZE - 1) ? (LONG_COLUMN_BUFFER_SIZE - 1) : C->datalen; + } else { + to_fetch_len = orig_fetched_len; + } + ssize_t to_fetch_byte = to_fetch_len + 1; + char *buf2 = emalloc(to_fetch_byte); + zend_string *str = zend_string_init(C->data, to_fetch_byte, 0); + size_t used = to_fetch_len; + + do { + C->fetched_len = 0; + /* read block. n + 1 bytes => n 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, to_fetch_byte, &C->fetched_len); + + /* adjust `used` in case we have proper length info from the driver */ + if (orig_fetched_len >= 0 && C->fetched_len >= 0) { + SQLLEN fixed_used = orig_fetched_len - C->fetched_len; + if (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 > to_fetch_len)) { + /* 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 > n (greater than buf2's size) + (if a driver fails to follow that and wrote less than n bytes to buf2, this will AV or read garbage into buf) */ + str = zend_string_realloc(str, used + to_fetch_byte, 0); + memcpy(ZSTR_VAL(str) + used, buf2, to_fetch_byte); + used = used + to_fetch_len; + } 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(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 = zend_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 swoole_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 \ No newline at end of file diff --git a/thirdparty/php85/pdo_odbc/php_pdo_odbc.h b/thirdparty/php85/pdo_odbc/php_pdo_odbc.h new file mode 100644 index 00000000000..3f041702978 --- /dev/null +++ b/thirdparty/php85/pdo_odbc/php_pdo_odbc.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | 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_ODBC_H +#define PHP_PDO_ODBC_H + +extern zend_module_entry pdo_odbc_module_entry; +#define phpext_pdo_odbc_ptr &pdo_odbc_module_entry + +#include "php_version.h" +#define PHP_PDO_ODBC_VERSION PHP_VERSION + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(pdo_odbc); +PHP_MSHUTDOWN_FUNCTION(pdo_odbc); +PHP_RINIT_FUNCTION(pdo_odbc); +PHP_RSHUTDOWN_FUNCTION(pdo_odbc); +PHP_MINFO_FUNCTION(pdo_odbc); + +#endif /* PHP_PDO_ODBC_H */ diff --git a/thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h b/thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h new file mode 100644 index 00000000000..8b0ce31efa9 --- /dev/null +++ b/thirdparty/php85/pdo_odbc/php_pdo_odbc_int.h @@ -0,0 +1,181 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +/* internal header; not supposed to be installed */ + +#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 swoole_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/php85/pdo_pgsql/pdo_pgsql_arginfo.h b/thirdparty/php85/pdo_pgsql/pdo_pgsql_arginfo.h new file mode 100644 index 00000000000..296207471a1 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pdo_pgsql_arginfo.h @@ -0,0 +1,168 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 0ea21010467d661416f0858f2bda095583ea3a36 */ + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_escapeIdentifier, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, input, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_copyFromArray, 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_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_copyFromFile, 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_RETURN_TYPE_MASK_EX(arginfo_class_Pdo_Pgsql_copyToArray, 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_copyToFile arginfo_class_Pdo_Pgsql_copyFromFile + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Pdo_Pgsql_lobCreate, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Pdo_Pgsql_lobOpen, 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_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_lobUnlink, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, oid, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_Pdo_Pgsql_getNotify, 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_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_getPid, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Pgsql_setNoticeCallback, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1) +ZEND_END_ARG_INFO() + +ZEND_METHOD(Pdo_Pgsql, escapeIdentifier); +ZEND_METHOD(Pdo_Pgsql, copyFromArray); +ZEND_METHOD(Pdo_Pgsql, copyFromFile); +ZEND_METHOD(Pdo_Pgsql, copyToArray); +ZEND_METHOD(Pdo_Pgsql, copyToFile); +ZEND_METHOD(Pdo_Pgsql, lobCreate); +ZEND_METHOD(Pdo_Pgsql, lobOpen); +ZEND_METHOD(Pdo_Pgsql, lobUnlink); +ZEND_METHOD(Pdo_Pgsql, getNotify); +ZEND_METHOD(Pdo_Pgsql, getPid); +ZEND_METHOD(Pdo_Pgsql, setNoticeCallback); + +static const zend_function_entry class_Pdo_Pgsql_methods[] = { + ZEND_ME(Pdo_Pgsql, escapeIdentifier, arginfo_class_Pdo_Pgsql_escapeIdentifier, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyFromArray, arginfo_class_Pdo_Pgsql_copyFromArray, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyFromFile, arginfo_class_Pdo_Pgsql_copyFromFile, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyToArray, arginfo_class_Pdo_Pgsql_copyToArray, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, copyToFile, arginfo_class_Pdo_Pgsql_copyToFile, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, lobCreate, arginfo_class_Pdo_Pgsql_lobCreate, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, lobOpen, arginfo_class_Pdo_Pgsql_lobOpen, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, lobUnlink, arginfo_class_Pdo_Pgsql_lobUnlink, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, getNotify, arginfo_class_Pdo_Pgsql_getNotify, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, getPid, arginfo_class_Pdo_Pgsql_getPid, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Pgsql, setNoticeCallback, arginfo_class_Pdo_Pgsql_setNoticeCallback, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + +static zend_class_entry *register_class_Pdo_Pgsql(zend_class_entry *class_entry_PDO) +{ + zend_class_entry ce, *class_entry; + + INIT_NS_CLASS_ENTRY(ce, "Pdo", "Pgsql", class_Pdo_Pgsql_methods); + class_entry = zend_register_internal_class_with_flags(&ce, class_entry_PDO, ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE); + + zval const_ATTR_DISABLE_PREPARES_value; + ZVAL_LONG(&const_ATTR_DISABLE_PREPARES_value, PDO_PGSQL_ATTR_DISABLE_PREPARES); + zend_string *const_ATTR_DISABLE_PREPARES_name = zend_string_init_interned("ATTR_DISABLE_PREPARES", sizeof("ATTR_DISABLE_PREPARES") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_DISABLE_PREPARES_name, &const_ATTR_DISABLE_PREPARES_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_DISABLE_PREPARES_name, true); +#if defined(HAVE_PG_RESULT_MEMORY_SIZE) + + zval const_ATTR_RESULT_MEMORY_SIZE_value; + ZVAL_LONG(&const_ATTR_RESULT_MEMORY_SIZE_value, PDO_PGSQL_ATTR_RESULT_MEMORY_SIZE); + zend_string *const_ATTR_RESULT_MEMORY_SIZE_name = zend_string_init_interned("ATTR_RESULT_MEMORY_SIZE", sizeof("ATTR_RESULT_MEMORY_SIZE") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_RESULT_MEMORY_SIZE_name, &const_ATTR_RESULT_MEMORY_SIZE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_RESULT_MEMORY_SIZE_name, true); +#endif + + zval const_TRANSACTION_IDLE_value; + ZVAL_LONG(&const_TRANSACTION_IDLE_value, PGSQL_TRANSACTION_IDLE); + zend_string *const_TRANSACTION_IDLE_name = zend_string_init_interned("TRANSACTION_IDLE", sizeof("TRANSACTION_IDLE") - 1, true); + zend_class_constant *const_TRANSACTION_IDLE = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_IDLE_name, &const_TRANSACTION_IDLE_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_IDLE_name, true); + + zval const_TRANSACTION_ACTIVE_value; + ZVAL_LONG(&const_TRANSACTION_ACTIVE_value, PGSQL_TRANSACTION_ACTIVE); + zend_string *const_TRANSACTION_ACTIVE_name = zend_string_init_interned("TRANSACTION_ACTIVE", sizeof("TRANSACTION_ACTIVE") - 1, true); + zend_class_constant *const_TRANSACTION_ACTIVE = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_ACTIVE_name, &const_TRANSACTION_ACTIVE_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_ACTIVE_name, true); + + zval const_TRANSACTION_INTRANS_value; + ZVAL_LONG(&const_TRANSACTION_INTRANS_value, PGSQL_TRANSACTION_INTRANS); + zend_string *const_TRANSACTION_INTRANS_name = zend_string_init_interned("TRANSACTION_INTRANS", sizeof("TRANSACTION_INTRANS") - 1, true); + zend_class_constant *const_TRANSACTION_INTRANS = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_INTRANS_name, &const_TRANSACTION_INTRANS_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_INTRANS_name, true); + + zval const_TRANSACTION_INERROR_value; + ZVAL_LONG(&const_TRANSACTION_INERROR_value, PGSQL_TRANSACTION_INERROR); + zend_string *const_TRANSACTION_INERROR_name = zend_string_init_interned("TRANSACTION_INERROR", sizeof("TRANSACTION_INERROR") - 1, true); + zend_class_constant *const_TRANSACTION_INERROR = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_INERROR_name, &const_TRANSACTION_INERROR_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_INERROR_name, true); + + zval const_TRANSACTION_UNKNOWN_value; + ZVAL_LONG(&const_TRANSACTION_UNKNOWN_value, PGSQL_TRANSACTION_UNKNOWN); + zend_string *const_TRANSACTION_UNKNOWN_name = zend_string_init_interned("TRANSACTION_UNKNOWN", sizeof("TRANSACTION_UNKNOWN") - 1, true); + zend_class_constant *const_TRANSACTION_UNKNOWN = zend_declare_typed_class_constant(class_entry, const_TRANSACTION_UNKNOWN_name, &const_TRANSACTION_UNKNOWN_value, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_TRANSACTION_UNKNOWN_name, true); + + + zend_attribute *attribute_Deprecated_const_TRANSACTION_IDLE_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_IDLE, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_IDLE_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_IDLE_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + zend_string *attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str = zend_string_init("as it has no effect", strlen("as it has no effect"), 1); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_IDLE_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_IDLE_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_ACTIVE_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_ACTIVE, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_ACTIVE_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_INTRANS_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_INTRANS, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_INTRANS_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_INERROR_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_INERROR, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_INERROR_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_INERROR_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_INERROR_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_INERROR_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_attribute *attribute_Deprecated_const_TRANSACTION_UNKNOWN_0 = zend_add_class_constant_attribute(class_entry, const_TRANSACTION_UNKNOWN, ZSTR_KNOWN(ZEND_STR_DEPRECATED_CAPITALIZED), 2); + ZVAL_STR(&attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[0].value, ZSTR_KNOWN(ZEND_STR_8_DOT_5)); + attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[0].name = ZSTR_KNOWN(ZEND_STR_SINCE); + ZVAL_STR_COPY(&attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[1].value, attribute_Deprecated_const_TRANSACTION_IDLE_0_arg1_str); + attribute_Deprecated_const_TRANSACTION_UNKNOWN_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + return class_entry; +} diff --git a/thirdparty/php85/pdo_pgsql/pgsql_driver.c b/thirdparty/php85/pdo_pgsql/pgsql_driver.c new file mode 100644 index 00000000000..2919afffd96 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_driver.c @@ -0,0 +1,1485 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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.h" +#include "php_pdo_pgsql_int.h" +#include "zend_exceptions.h" +#include "zend_interfaces.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); + char *tmp; + if (UNEXPECTED(i == 0)) { + tmp = pemalloc(1, persistent); + tmp[0] = '\0'; + return tmp; + } + --i; + + 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(zend_object *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 *)(php_pdo_dbh_fetch_inner(dbh))->driver_data; + + ZVAL_OBJ(&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) { + GC_ADDREF(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; + zval *val; + zend_long lval; + + 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->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"; + } + + S->is_unbuffered = + driver_options + && (val = zend_hash_index_find(Z_ARRVAL_P(driver_options), PDO_ATTR_PREFETCH)) + && pdo_get_long_param(&lval, val) + ? !lval + : H->default_fetching_laziness + ; + + 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; + int err; + + 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); + if (escaped == NULL) { + return NULL; + } + 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), &err); + if (err) { + efree(quoted); + return 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; +} + +static bool _pdo_pgsql_send_copy_data(pdo_pgsql_db_handle *H, zval *line) { + size_t query_len; + zend_string *query; + + if (!try_convert_to_string(line)) { + return false; + } + + query_len = Z_STRLEN_P(line); + query = zend_string_alloc(query_len + 2, false); /* room for \n\0 */ + memcpy(ZSTR_VAL(query), Z_STRVAL_P(line), query_len + 1); + ZSTR_LEN(query) = query_len; + + if (query_len > 0 && ZSTR_VAL(query)[query_len - 1] != '\n') { + ZSTR_VAL(query)[query_len] = '\n'; + ZSTR_VAL(query)[query_len + 1] = '\0'; + ZSTR_LEN(query) ++; + } + + if (PQputCopyData(H->server, ZSTR_VAL(query), ZSTR_LEN(query)) != 1) { + zend_string_release_ex(query, false); + return false; + } + + zend_string_release_ex(query, false); + return true; +} + +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; + + ZEND_PARSE_PARAMETERS_START(2, 5) + Z_PARAM_STRING(table_name, table_name_len) + Z_PARAM_ITERABLE(pg_rows) + Z_PARAM_OPTIONAL + Z_PARAM_STRING(pg_delim, pg_delim_len) + Z_PARAM_STRING(pg_null_as, pg_null_as_len) + Z_PARAM_STRING_OR_NULL(pg_fields, pg_fields_len) + ZEND_PARSE_PARAMETERS_END(); + + 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; + zval *tmp; + zend_object_iterator *iter; + + PQclear(pgsql_result); + + if (Z_TYPE_P(pg_rows) == IS_ARRAY) { + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pg_rows), tmp) { + if (!_pdo_pgsql_send_copy_data(H, tmp)) { + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } ZEND_HASH_FOREACH_END(); + } else { + iter = Z_OBJCE_P(pg_rows)->get_iterator(Z_OBJCE_P(pg_rows), pg_rows, 0); + if (iter == NULL || EG(exception)) { + RETURN_THROWS(); + } + + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (EG(exception)) { + RETURN_THROWS(); + } + } + + for (; iter->funcs->valid(iter) == SUCCESS && EG(exception) == NULL; iter->funcs->move_forward(iter)) { + tmp = iter->funcs->get_current_data(iter); + if (!_pdo_pgsql_send_copy_data(H, tmp)) { + zend_iterator_dtor(iter); + pdo_pgsql_error(dbh, PGRES_FATAL_ERROR, NULL); + PDO_HANDLE_DBH_ERR(); + RETURN_FALSE; + } + } + zend_iterator_dtor(iter); + } + + 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(Z_OBJ_P(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; + case PDO_ATTR_PREFETCH: + if (!pdo_get_bool_param(&bval, val)) { + return false; + } + H->default_fetching_laziness = !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 + dbh->data_source_len; + 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_appendl(&conn_str, dbh->data_source, dbh->data_source_len); + 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 \ No newline at end of file diff --git a/thirdparty/php85/pdo_pgsql/pgsql_driver_arginfo.h b/thirdparty/php85/pdo_pgsql/pgsql_driver_arginfo.h new file mode 100644 index 00000000000..cd01e2e8e71 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_driver_arginfo.h @@ -0,0 +1,70 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: 30c01b4d2e7f836b81a31dc0c1a115883eb41568 */ + +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_OBJ_TYPE_MASK(0, rows, Traversable, MAY_BE_ARRAY, NULL) + 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/php85/pdo_pgsql/pgsql_sql_parser.c b/thirdparty/php85/pdo_pgsql/pgsql_sql_parser.c new file mode 100644 index 00000000000..ba40f6ed227 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_sql_parser.c @@ -0,0 +1,322 @@ +/* Generated by re2c 4.3 on Fri Jul 18 21:15:02 2025 */ +/* + +----------------------------------------------------------------------+ + | 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.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_pgsql_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + + + +{ + YYCTYPE yych; + unsigned int yyaccept = 0; + if ((YYLIMIT - YYCURSOR) < 2) YYFILL(2); + yych = *YYCURSOR; + if (yych <= '.') { + if (yych <= '$') { + if (yych <= '!') { + if (yych >= 0x01) goto yy2; + } else { + if (yych <= '"') goto yy4; + if (yych <= '#') goto yy2; + goto yy6; + } + } else { + if (yych <= '\'') { + if (yych <= '&') goto yy2; + goto yy7; + } else { + if (yych == '-') goto yy8; + goto yy2; + } + } + } else { + if (yych <= '?') { + if (yych <= '9') { + if (yych <= '/') goto yy9; + goto yy2; + } else { + if (yych <= ':') goto yy10; + if (yych <= '>') goto yy2; + goto yy11; + } + } else { + if (yych <= 'E') { + if (yych <= 'D') goto yy2; + goto yy12; + } else { + if (yych == 'e') goto yy12; + goto yy2; + } + } + } +yy1: + YYCURSOR = YYMARKER; + if (yyaccept <= 1) { + if (yyaccept == 0) goto yy5; + else goto yy15; + } else { + if (yyaccept == 2) goto yy20; + else goto yy31; + } +yy2: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '.') { + if (yych <= '$') { + if (yych <= '!') { + if (yych >= 0x01) goto yy2; + } else { + if (yych == '#') goto yy2; + } + } else { + if (yych <= '\'') { + if (yych <= '&') goto yy2; + } else { + if (yych != '-') goto yy2; + } + } + } else { + if (yych <= '?') { + if (yych <= '9') { + if (yych >= '0') goto yy2; + } else { + if (yych <= ':') goto yy3; + if (yych <= '>') goto yy2; + } + } else { + if (yych <= 'E') { + if (yych <= 'D') goto yy2; + } else { + if (yych != 'e') goto yy2; + } + } + } +yy3: + { RET(PDO_PARSER_TEXT); } +yy4: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych >= 0x01) goto yy14; +yy5: + { SKIP_ONE(PDO_PARSER_TEXT); } +yy6: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= '^') { + if (yych <= '$') { + if (yych <= '#') goto yy5; + goto yy16; + } else { + if (yych <= '@') goto yy5; + if (yych <= 'Z') goto yy17; + goto yy5; + } + } else { + if (yych <= '`') { + if (yych <= '_') goto yy17; + goto yy5; + } else { + if (yych <= 'z') goto yy17; + if (yych <= 0x7F) goto yy5; + goto yy17; + } + } +yy7: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych <= 0x00) goto yy5; + goto yy19; +yy8: + yych = *++YYCURSOR; + if (yych == '-') goto yy21; + goto yy5; +yy9: + yych = *++YYCURSOR; + if (yych == '*') goto yy23; + goto yy5; +yy10: + yych = *++YYCURSOR; + if (yych <= 'Z') { + if (yych <= '9') { + if (yych <= '/') goto yy5; + goto yy24; + } else { + if (yych <= ':') goto yy26; + if (yych <= '@') goto yy5; + goto yy24; + } + } else { + if (yych <= '_') { + if (yych <= '^') goto yy5; + goto yy24; + } else { + if (yych <= '`') goto yy5; + if (yych <= 'z') goto yy24; + goto yy5; + } + } +yy11: + yych = *++YYCURSOR; + if (yych == '?') goto yy27; + { RET(PDO_PARSER_BIND_POS); } +yy12: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych == '\'') goto yy28; + goto yy5; +yy13: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy14: + if (yych <= 0x00) goto yy1; + if (yych != '"') goto yy13; + yyaccept = 1; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '"') goto yy13; +yy15: + { RET(PDO_PARSER_TEXT); } +yy16: + ++YYCURSOR; + { RET(PDO_PARSER_CUSTOM_QUOTE); } +yy17: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 'Z') { + if (yych <= '/') { + if (yych == '$') goto yy16; + goto yy1; + } else { + if (yych <= '9') goto yy17; + if (yych <= '@') goto yy1; + goto yy17; + } + } else { + if (yych <= '`') { + if (yych == '_') goto yy17; + goto yy1; + } else { + if (yych <= 'z') goto yy17; + if (yych <= 0x7F) goto yy1; + goto yy17; + } + } +yy18: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; +yy19: + if (yych <= 0x00) goto yy1; + if (yych != '\'') goto yy18; + yyaccept = 2; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '\'') goto yy18; +yy20: + { RET(PDO_PARSER_TEXT); } +yy21: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych != '\n') goto yy21; +yy22: + { RET(PDO_PARSER_TEXT); } +yy23: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '*') goto yy29; + goto yy23; +yy24: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 'Z') { + if (yych <= '/') goto yy25; + if (yych <= '9') goto yy24; + if (yych >= 'A') goto yy24; + } else { + if (yych <= '_') { + if (yych >= '_') goto yy24; + } else { + if (yych <= '`') goto yy25; + if (yych <= 'z') goto yy24; + } + } +yy25: + { RET(PDO_PARSER_BIND); } +yy26: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == ':') goto yy26; + { RET(PDO_PARSER_TEXT); } +yy27: + ++YYCURSOR; + { RET(PDO_PARSER_ESCAPED_QUESTION); } +yy28: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= '\'') { + if (yych <= 0x00) goto yy1; + if (yych <= '&') goto yy28; + goto yy30; + } else { + if (yych == '\\') goto yy32; + goto yy28; + } +yy29: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '*') goto yy29; + if (yych == '/') goto yy33; + goto yy23; +yy30: + yyaccept = 3; + YYMARKER = ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych == '\'') goto yy28; +yy31: + { RET(PDO_PARSER_TEXT); } +yy32: + ++YYCURSOR; + if (YYLIMIT <= YYCURSOR) YYFILL(1); + yych = *YYCURSOR; + if (yych <= 0x00) goto yy1; + goto yy28; +yy33: + ++YYCURSOR; + goto yy22; +} + +} diff --git a/thirdparty/php85/pdo_pgsql/pgsql_statement.c b/thirdparty/php85/pdo_pgsql/pgsql_statement.c new file mode 100644 index 00000000000..ab442a2aedb --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/pgsql_statement.c @@ -0,0 +1,874 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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.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 + +#define FIN_DISCARD 0x1 +#define FIN_CLOSE 0x2 +#define FIN_ABORT 0x4 + + + +static void pgsql_stmt_finish(pdo_pgsql_stmt *S, int fin_mode) +{ + pdo_pgsql_db_handle *H = S->H; + + if (S->is_running_unbuffered && S->result && (fin_mode & FIN_ABORT)) { + PGcancel *cancel = PQgetCancel(H->server); + char errbuf[256]; + PQcancel(cancel, errbuf, 256); + PQfreeCancel(cancel); + S->is_running_unbuffered = false; + } + + if (S->result) { + /* free the resource */ + PQclear(S->result); + S->result = NULL; + } + + if (S->is_running_unbuffered) { + /* https://postgresql.org/docs/current/libpq-async.html: + * "PQsendQuery cannot be called again until PQgetResult has returned NULL" + * And as all single-row functions are connection-wise instead of statement-wise, + * any new single-row query has to make sure no preceding one is still running. + */ + // @todo Implement !(fin_mode & FIN_DISCARD) + // instead of discarding results we could store them to their statement + // so that their fetch() will get them (albeit not in lazy mode anymore). + while ((S->result = PQgetResult(H->server))) { + PQclear(S->result); + S->result = NULL; + } + S->is_running_unbuffered = false; + } + + if (S->stmt_name && S->is_prepared && (fin_mode & FIN_CLOSE)) { + PGresult *res; +#ifndef HAVE_PQCLOSEPREPARED + // TODO (??) libpq does not support close statement protocol < postgres 17 + // check if we can circumvent this. + char *q = NULL; + spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, q); + efree(q); +#else + res = PQclosePrepared(H->server, S->stmt_name); +#endif + if (res) { + PQclear(res); + } + + S->is_prepared = false; + if (H->running_stmt == S) { + H->running_stmt = NULL; + } + } +} + +static int pgsql_stmt_dtor(pdo_stmt_t *stmt) +{ + pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data; + bool server_obj_usable = php_pdo_stmt_valid_db_obj_handle(stmt); + + pgsql_stmt_finish(S, FIN_DISCARD|(server_obj_usable ? FIN_CLOSE|FIN_ABORT : 0)); + + if (S->stmt_name) { + 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; + int dispatch_result = 1; + + bool in_trans = stmt->dbh->methods->in_transaction(stmt->dbh); + + /* in unbuffered mode, finish any running statement: libpq explicitely prohibits this + * and returns a PGRES_FATAL_ERROR when PQgetResult gets called for stmt 2 if DEALLOCATE + * was called for stmt 1 inbetween + * (maybe it will change with pipeline mode in libpq 14?) */ + if (S->is_unbuffered && H->running_stmt) { + pgsql_stmt_finish(H->running_stmt, FIN_CLOSE); + H->running_stmt = NULL; + } + /* ensure that we free any previous unfetched results */ + pgsql_stmt_finish(S, 0); + + 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 = true; + + /* 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 = true; + PQclear(S->result); + S->result = NULL; + 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")) { + PGresult *res; +#ifndef HAVE_PQCLOSEPREPARED + char buf[100]; /* stmt_name == "pdo_crsr_%08x" */ + snprintf(buf, sizeof(buf), "DEALLOCATE %s", S->stmt_name); + res = PQexec(H->server, buf); +#else + res = PQclosePrepared(H->server, S->stmt_name); +#endif + if (res) { + PQclear(res); + } + goto stmt_retry; + } else { + pdo_pgsql_error_stmt(stmt, status, sqlstate); + return 0; + } + } + } + } + if (S->is_unbuffered) { + dispatch_result = PQsendQueryPrepared(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 { + 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 */ + if (S->is_unbuffered) { + dispatch_result = PQsendQueryParams(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 { + 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) */ + if (S->is_unbuffered) { + dispatch_result = PQsendQuery(H->server, ZSTR_VAL(stmt->active_query_string)); + } else { + S->result = PQexec(H->server, ZSTR_VAL(stmt->active_query_string)); + } + } + + H->running_stmt = S; + + if (S->is_unbuffered) { + if (!dispatch_result) { + pdo_pgsql_error_stmt(stmt, 0, NULL); + H->running_stmt = NULL; + return 0; + } + S->is_running_unbuffered = true; + (void)PQsetSingleRowMode(H->server); + /* no matter if it returns 0: PQ then transparently fallbacks to full result fetching */ + + /* try a first fetch to at least have column names and so on */ + S->result = PQgetResult(S->H->server); + } + + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_SINGLE_TUPLE) { + 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: ori_str = "NEXT"; break; + case PDO_FETCH_ORI_PRIOR: ori_str = "BACKWARD"; break; + case PDO_FETCH_ORI_FIRST: ori_str = "FIRST"; break; + case PDO_FETCH_ORI_LAST: ori_str = "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); + if (ori == PDO_FETCH_ORI_ABS || ori == PDO_FETCH_ORI_REL) { + 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->is_running_unbuffered && S->current_row >= stmt->row_count) { + ExecStatusType status; + + /* @todo in unbuffered mode, PQ allows multiple queries to be passed: + * column_count should be recomputed on each iteration */ + + if(S->result) { + PQclear(S->result); + S->result = NULL; + } + + S->result = PQgetResult(S->H->server); + status = PQresultStatus(S->result); + + if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_SINGLE_TUPLE) { + pdo_pgsql_error_stmt(stmt, status, pdo_pgsql_sqlstate(S->result)); + return 0; + } + + stmt->row_count = (zend_long)PQntuples(S->result); + S->current_row = 0; + + if (!stmt->row_count) { + S->is_running_unbuffered = false; + /* libpq requires looping until getResult returns null */ + pgsql_stmt_finish(S, 0); + } + } + 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 \ No newline at end of file diff --git a/thirdparty/php85/pdo_pgsql/php_pdo_pgsql.h b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql.h new file mode 100644 index 00000000000..fa28214f71a --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | 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: Edin Kadribasic | + +----------------------------------------------------------------------+ +*/ + +#ifndef PHP_PDO_PGSQL_H +#define PHP_PDO_PGSQL_H + +#include + +extern zend_module_entry pdo_pgsql_module_entry; +#define phpext_pdo_pgsql_ptr &pdo_pgsql_module_entry + +#include "php_version.h" +#define PHP_PDO_PGSQL_VERSION PHP_VERSION + +#ifdef ZTS +#include "TSRM.h" +#endif + +PHP_MINIT_FUNCTION(pdo_pgsql); +PHP_MSHUTDOWN_FUNCTION(pdo_pgsql); +PHP_MINFO_FUNCTION(pdo_pgsql); + +#endif /* PHP_PDO_PGSQL_H */ diff --git a/thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h new file mode 100644 index 00000000000..dadc2c240f8 --- /dev/null +++ b/thirdparty/php85/pdo_pgsql/php_pdo_pgsql_int.h @@ -0,0 +1,133 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +/* internal header; not supposed to be installed */ + +#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; + +typedef struct pdo_pgsql_stmt pdo_pgsql_stmt; + +/* 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; + bool emulate_prepares; + bool disable_prepares; + HashTable *lob_streams; + zend_fcall_info_cache *notice_callback; + bool default_fetching_laziness; + pdo_pgsql_stmt *running_stmt; +} pdo_pgsql_db_handle; + +typedef struct { + Oid pgsql_type; +} pdo_pgsql_column; + +struct pdo_pgsql_stmt { + 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; + bool is_unbuffered; + bool is_running_unbuffered; +}; + +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(zend_object *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/php85/pdo_sqlite/php_pdo_sqlite.h b/thirdparty/php85/pdo_sqlite/php_pdo_sqlite.h new file mode 100644 index 00000000000..c29a81ebb24 --- /dev/null +++ b/thirdparty/php85/pdo_sqlite/php_pdo_sqlite.h @@ -0,0 +1,42 @@ +/* + +----------------------------------------------------------------------+ + | 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_H +#define PHP_PDO_SQLITE_H + +extern zend_module_entry pdo_sqlite_module_entry; +#define phpext_pdo_sqlite_ptr &pdo_sqlite_module_entry + +#include "php_version.h" +#define PHP_PDO_SQLITE_VERSION PHP_VERSION + +#ifdef ZTS +#include "TSRM.h" +#endif + +enum pdo_sqlite_transaction_mode { + PDO_SQLITE_TRANSACTION_MODE_DEFERRED = 0, + PDO_SQLITE_TRANSACTION_MODE_IMMEDIATE = 1, + PDO_SQLITE_TRANSACTION_MODE_EXCLUSIVE = 2 +}; + +PHP_MINIT_FUNCTION(pdo_sqlite); +PHP_MSHUTDOWN_FUNCTION(pdo_sqlite); +PHP_RINIT_FUNCTION(pdo_sqlite); +PHP_RSHUTDOWN_FUNCTION(pdo_sqlite); +PHP_MINFO_FUNCTION(pdo_sqlite); + +#endif /* PHP_PDO_SQLITE_H */ diff --git a/thirdparty/php85/pdo_sqlite/php_pdo_sqlite_int.h b/thirdparty/php85/pdo_sqlite/php_pdo_sqlite_int.h new file mode 100644 index 00000000000..073e3bb6630 --- /dev/null +++ b/thirdparty/php85/pdo_sqlite/php_pdo_sqlite_int.h @@ -0,0 +1,90 @@ +/* + +----------------------------------------------------------------------+ + | 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 "php_pdo_sqlite.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; + zend_string *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; + + zend_string *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; + zend_fcall_info_cache authorizer_fcc; + enum pdo_sqlite_transaction_mode transaction_mode; +} 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, + PDO_SQLITE_ATTR_BUSY_STATEMENT, + PDO_SQLITE_ATTR_EXPLAIN_STATEMENT, + PDO_SQLITE_ATTR_TRANSACTION_MODE +}; + +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/php85/pdo_sqlite/sqlite_driver.c b/thirdparty/php85/pdo_sqlite/sqlite_driver.c new file mode 100644 index 00000000000..781437b1fdd --- /dev/null +++ b/thirdparty/php85/pdo_sqlite/sqlite_driver.c @@ -0,0 +1,962 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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_sqlite.h" +#include "php_pdo_sqlite_int.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; + + if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) { + zend_fcc_dtor(&H->authorizer_fcc); + } + + while (H->funcs) { + func = H->funcs; + H->funcs = func->next; + + if (H->db) { + /* delete the function from the handle */ + sqlite3_create_function(H->db, + ZSTR_VAL(func->funcname), + func->argc, + SQLITE_UTF8, + func, + NULL, NULL, NULL); + } + + zend_string_release(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, + ZSTR_VAL(collation->name), + SQLITE_UTF8, + collation, + NULL); + } + + zend_string_release(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; + } + + if (UNEXPECTED(zend_str_has_nul_byte(unquoted))) { + if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) { + zend_throw_exception_ex( + php_pdo_get_exception(), 0, + "SQLite PDO::quote does not support null bytes"); + } else if (dbh->error_mode == PDO_ERRMODE_WARNING) { + php_error_docref(NULL, E_WARNING, + "SQLite PDO::quote does not support null bytes"); + } + 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; + + const char *begin_statement = "BEGIN"; + switch (H->transaction_mode) { + case PDO_SQLITE_TRANSACTION_MODE_DEFERRED: + begin_statement = "BEGIN DEFERRED TRANSACTION"; + break; + case PDO_SQLITE_TRANSACTION_MODE_IMMEDIATE: + begin_statement = "BEGIN IMMEDIATE TRANSACTION"; + break; + case PDO_SQLITE_TRANSACTION_MODE_EXCLUSIVE: + begin_statement = "BEGIN EXCLUSIVE TRANSACTION"; + break; + } + + if (sqlite3_exec(H->db, begin_statement, 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) +{ + pdo_sqlite_db_handle *H = (pdo_sqlite_db_handle *)dbh->driver_data; + + switch (attr) { + case PDO_ATTR_CLIENT_VERSION: + case PDO_ATTR_SERVER_VERSION: + ZVAL_STRING(return_value, (char *)sqlite3_libversion()); + break; + case PDO_SQLITE_ATTR_TRANSACTION_MODE: + ZVAL_LONG(return_value, H->transaction_mode); + 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; + case PDO_SQLITE_ATTR_TRANSACTION_MODE: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + switch (lval) { + case PDO_SQLITE_TRANSACTION_MODE_DEFERRED: + case PDO_SQLITE_TRANSACTION_MODE_IMMEDIATE: + case PDO_SQLITE_TRANSACTION_MODE_EXCLUSIVE: + H->transaction_mode = lval; + return true; + default: + return false; + } + } + 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);); + + zval_ptr_dtor(&zargs[0]); + zval_ptr_dtor(&zargs[1]); + + if (!Z_ISUNDEF(retval)) { + if (Z_TYPE(retval) != IS_LONG) { + zend_string *func_name = get_active_function_or_method_name(); + zend_type_error("%s(): Return value of the collation callback must be of type int, %s returned", + ZSTR_VAL(func_name), zend_zval_value_name(&retval)); + zend_string_release(func_name); + zval_ptr_dtor(&retval); + return FAILURE; + } + ret = ZEND_NORMALIZE_BOOL(Z_LVAL(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->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; + zend_string *func_name; + 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_STR(func_name) + 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_WITH_CLEANUP(error); + + H = (pdo_sqlite_db_handle *)dbh->driver_data; + + func = (struct pdo_sqlite_func*)ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function(H->db, ZSTR_VAL(func_name), argc, flags | SQLITE_UTF8, func, php_sqlite3_func_callback, NULL, NULL); + if (ret == SQLITE_OK) { + func->funcname = zend_string_copy(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; + zend_string *func_name; + zend_long argc = -1; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_STR(func_name) + 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_WITH_CLEANUP(error); + + H = (pdo_sqlite_db_handle *)dbh->driver_data; + + func = (struct pdo_sqlite_func*)ecalloc(1, sizeof(*func)); + + ret = sqlite3_create_function(H->db, ZSTR_VAL(func_name), argc, SQLITE_UTF8, func, NULL, + php_sqlite3_func_step_callback, php_sqlite3_func_final_callback); + if (ret == SQLITE_OK) { + func->funcname = zend_string_copy(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; + zend_string *collation_name; + pdo_dbh_t *dbh; + pdo_sqlite_db_handle *H; + int ret; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(collation_name) + Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc) + ZEND_PARSE_PARAMETERS_END(); + + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(cleanup_fcc); + + H = (pdo_sqlite_db_handle *)dbh->driver_data; + + collation = (struct pdo_sqlite_collation*)ecalloc(1, sizeof(*collation)); + + ret = sqlite3_create_collation(H->db, ZSTR_VAL(collation_name), SQLITE_UTF8, collation, callback); + if (ret == SQLITE_OK) { + collation->name = zend_string_copy(collation_name); + + zend_fcc_dup(&collation->callback, &fcc); + + collation->next = H->collations; + H->collations = collation; + + RETURN_TRUE; + } + + zend_release_fcall_info_cache(&fcc); + + efree(collation); + RETURN_FALSE; + +cleanup_fcc: + zend_release_fcall_info_cache(&fcc); + RETURN_THROWS(); +} + +/* {{{ 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; + + if (ZEND_FCC_INITIALIZED(H->authorizer_fcc)) { + zend_get_gc_buffer_add_fcc(gc_buffer, &H->authorizer_fcc); + } + + 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); +} + +#define ZVAL_NULLABLE_STRING(zv, str) do { \ + zval *zv_ = zv; \ + const char *str_ = str; \ + if (str_) { \ + ZVAL_STRING(zv_, str_); \ + } else { \ + ZVAL_NULL(zv_); \ + } \ +} while (0) + +static int authorizer(void *autharg, int access_type, const char *arg1, const char *arg2, + const char *arg3, const char *arg4) +{ + if (PG(open_basedir) && *PG(open_basedir)) { + if (access_type == SQLITE_ATTACH) { + char *filename = make_filename_safe(arg1); + if (!filename) { + return SQLITE_DENY; + } + efree(filename); + } + } + + pdo_sqlite_db_handle *db_obj = autharg; + + /* fallback to access allowed if authorizer callback is not defined */ + if (!ZEND_FCC_INITIALIZED(db_obj->authorizer_fcc)) { + return SQLITE_OK; + } + + /* call userland authorizer callback, if set */ + zval retval; + zval argv[5]; + + ZVAL_LONG(&argv[0], access_type); + ZVAL_NULLABLE_STRING(&argv[1], arg1); + ZVAL_NULLABLE_STRING(&argv[2], arg2); + ZVAL_NULLABLE_STRING(&argv[3], arg3); + ZVAL_NULLABLE_STRING(&argv[4], arg4); + + int authreturn = SQLITE_DENY; + + HOOK_PHP_CALL_STACK(zend_call_known_fcc(&db_obj->authorizer_fcc, &retval, /* argc */ 5, argv, /* named_params */ NULL);); + if (Z_ISUNDEF(retval)) { + ZEND_ASSERT(EG(exception)); + } else { + if (Z_TYPE(retval) != IS_LONG) { + zend_string *func_name = get_active_function_or_method_name(); + zend_type_error("%s(): Return value of the authorizer callback must be of type int, %s returned", + ZSTR_VAL(func_name), zend_zval_value_name(&retval)); + zend_string_release(func_name); + } else { + authreturn = Z_LVAL(retval); + + if (authreturn != SQLITE_OK && authreturn != SQLITE_IGNORE && authreturn != SQLITE_DENY) { + zend_string *func_name = get_active_function_or_method_name(); + zend_value_error("%s(): Return value of the authorizer callback must be one of Pdo\\Sqlite::OK, Pdo\\Sqlite::DENY, or Pdo\\Sqlite::IGNORE", + ZSTR_VAL(func_name)); + zend_string_release(func_name); + authreturn = SQLITE_DENY; + } + } + } + + zval_ptr_dtor(&retval); + zval_ptr_dtor(&argv[1]); + zval_ptr_dtor(&argv[2]); + zval_ptr_dtor(&argv[3]); + zval_ptr_dtor(&argv[4]); + + return authreturn; +} + +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; + } + + sqlite3_set_authorizer(H->db, authorizer, H); + + 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 \ No newline at end of file diff --git a/thirdparty/php85/pdo_sqlite/sqlite_driver_arginfo.h b/thirdparty/php85/pdo_sqlite/sqlite_driver_arginfo.h new file mode 100644 index 00000000000..8785c187a97 --- /dev/null +++ b/thirdparty/php85/pdo_sqlite/sqlite_driver_arginfo.h @@ -0,0 +1,32 @@ +/* This is a generated file, edit the .stub.php file instead. + * Stub hash: dc901bd60d17c1a2cdb40a118e2c6cd6eb0396e3 */ + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_SQLite_Ext_sqliteCreateFunction, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, numArgs, IS_LONG, 0, "-1") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_SQLite_Ext_sqliteCreateAggregate, 0, 3, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, step, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO(0, finalize, IS_CALLABLE, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, numArgs, IS_LONG, 0, "-1") +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_SQLite_Ext_sqliteCreateCollation, 0, 2, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0) +ZEND_END_ARG_INFO() + +ZEND_METHOD(PDO_SQLite_Ext, sqliteCreateFunction); +ZEND_METHOD(PDO_SQLite_Ext, sqliteCreateAggregate); +ZEND_METHOD(PDO_SQLite_Ext, sqliteCreateCollation); + +static const zend_function_entry class_PDO_SQLite_Ext_methods[] = { + ZEND_ME(PDO_SQLite_Ext, sqliteCreateFunction, arginfo_class_PDO_SQLite_Ext_sqliteCreateFunction, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_SQLite_Ext, sqliteCreateAggregate, arginfo_class_PDO_SQLite_Ext_sqliteCreateAggregate, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_SQLite_Ext, sqliteCreateCollation, arginfo_class_PDO_SQLite_Ext_sqliteCreateCollation, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; diff --git a/thirdparty/php85/pdo_sqlite/sqlite_sql_parser.c b/thirdparty/php85/pdo_sqlite/sqlite_sql_parser.c new file mode 100644 index 00000000000..47406f3b6d4 --- /dev/null +++ b/thirdparty/php85/pdo_sqlite/sqlite_sql_parser.c @@ -0,0 +1,419 @@ +/* Generated by re2c 4.3 */ +#line 1 "ext/pdo_sqlite/sqlite_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.h" +#include "ext/pdo/php_pdo_driver.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 34 "ext/pdo_sqlite/sqlite_sql_parser.re" + + + +#line 34 "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 46 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 80 "ext/pdo_sqlite/sqlite_sql_parser.c" +yy4: + yyaccept = 0; + yych = *(YYMARKER = ++YYCURSOR); + if (yych >= 0x01) goto yy15; +yy5: +#line 44 "ext/pdo_sqlite/sqlite_sql_parser.re" + { SKIP_ONE(PDO_PARSER_TEXT); } +#line 88 "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 43 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_BIND_POS); } +#line 184 "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 37 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 218 "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 38 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 242 "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 45 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 254 "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 42 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_BIND); } +#line 336 "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 41 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 348 "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 40 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 380 "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 39 "ext/pdo_sqlite/sqlite_sql_parser.re" + { RET(PDO_PARSER_TEXT); } +#line 404 "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 47 "ext/pdo_sqlite/sqlite_sql_parser.re" + +} diff --git a/thirdparty/php85/pdo_sqlite/sqlite_statement.c b/thirdparty/php85/pdo_sqlite/sqlite_statement.c new file mode 100644 index 00000000000..eec9dd3c921 --- /dev/null +++ b/thirdparty/php85/pdo_sqlite/sqlite_statement.c @@ -0,0 +1,471 @@ +/* + +----------------------------------------------------------------------+ + | 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 >= 80500 +#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_sqlite.h" +#include "php_pdo_sqlite_int.h" + +#if defined(__APPLE__) +// If more than one usage, a Zend macro could be created +// around this runtime check +#include +#endif + +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 || !stmt->executed) { + 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"); + 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 (sqlite3_stmt_readonly(S->stmt)) { + ZVAL_TRUE(val); + } + break; + + case PDO_SQLITE_ATTR_BUSY_STATEMENT: + ZVAL_FALSE(val); + + if (sqlite3_stmt_busy(S->stmt)) { + ZVAL_TRUE(val); + } + break; + case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: +#if SQLITE_VERSION_NUMBER >= 3043000 +#if defined(__APPLE__) + if (__builtin_available(macOS 14.2, *)) { +#endif + ZVAL_LONG(val, (zend_long)sqlite3_stmt_isexplain(S->stmt)); + return 1; +#if defined(__APPLE__) + } else { + zend_value_error("explain statement unsupported"); + return 0; + } +#endif +#else + zend_value_error("explain statement unsupported"); + return 0; +#endif + default: + return 0; + } + + return 1; +} + +static int pdo_sqlite_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval *zval) +{ + switch (attr) { + case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: +#if SQLITE_VERSION_NUMBER >= 3043000 +#if defined(__APPLE__) + if (__builtin_available(macOS 14.2, *)) { +#endif + if (Z_TYPE_P(zval) != IS_LONG) { + zend_type_error("explain mode must be of type int, %s given", zend_zval_value_name(zval)); + return 0; + } + if (Z_LVAL_P(zval) < 0 || Z_LVAL_P(zval) > 2) { + zend_value_error("explain mode must be one of the Pdo\\Sqlite::EXPLAIN_MODE_* constants"); + return 0; + } + + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + if (sqlite3_stmt_explain(S->stmt, (int)Z_LVAL_P(zval)) != SQLITE_OK) { + return 0; + } + + return 1; +#if defined(__APPLE__) + } else { + zend_value_error("explain statement unsupported"); + return 0; + } +#endif +#else + zend_value_error("explain statement unsupported"); + return 0; +#endif + 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, + pdo_sqlite_stmt_set_attribute, /* set_attr */ + pdo_sqlite_stmt_get_attribute, /* get_attr */ + pdo_sqlite_stmt_col_meta, + NULL, /* next_rowset */ + pdo_sqlite_stmt_cursor_closer +}; +#endif \ No newline at end of file