├── .clang-format ├── .github ├── install-swoole.sh └── workflows │ └── ext-swoole_postgresql.yml ├── .gitignore ├── README.md ├── composer.json ├── config.m4 ├── dump_db.sh ├── examples ├── connect.php ├── escape.php ├── pdo.php ├── prepare.php └── timeout.php ├── make.sh ├── php_swoole_postgresql.h ├── phpunit.xml.dist ├── swoole_postgresql.cc └── tests ├── bootstrap.php └── unit └── PostgreSQLTest.php /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Google 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignConsecutiveAssignments: false 7 | AlignConsecutiveDeclarations: false 8 | AlignEscapedNewlines: Right 9 | AlignOperands: true 10 | AlignTrailingComments: true 11 | AllowAllParametersOfDeclarationOnNextLine: true 12 | AllowShortBlocksOnASingleLine: false 13 | AllowShortCaseLabelsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: Empty 15 | AllowShortIfStatementsOnASingleLine: true 16 | AllowShortLoopsOnASingleLine: true 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: true 21 | BinPackArguments: false 22 | BinPackParameters: false 23 | BraceWrapping: 24 | AfterClass: false 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: false 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Attach 41 | BreakBeforeInheritanceComma: false 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializersBeforeComma: false 44 | BreakConstructorInitializers: BeforeColon 45 | BreakAfterJavaFieldAnnotations: false 46 | BreakStringLiterals: true 47 | ColumnLimit: 120 48 | CommentPragmas: '^ IWYU pragma:' 49 | CompactNamespaces: false 50 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 51 | ConstructorInitializerIndentWidth: 4 52 | ContinuationIndentWidth: 4 53 | Cpp11BracedListStyle: true 54 | DerivePointerAlignment: false 55 | DisableFormat: false 56 | ExperimentalAutoDetectBinPacking: false 57 | FixNamespaceComments: true 58 | ForEachMacros: 59 | - foreach 60 | - Q_FOREACH 61 | - BOOST_FOREACH 62 | IncludeBlocks: Preserve 63 | IncludeCategories: 64 | - Regex: '^' 65 | Priority: 2 66 | - Regex: '^<.*\.h>' 67 | Priority: 1 68 | - Regex: '^<.*' 69 | Priority: 2 70 | - Regex: '.*' 71 | Priority: 3 72 | IncludeIsMainRegex: '([-_](test|unittest))?$' 73 | IndentCaseLabels: false 74 | IndentPPDirectives: None 75 | IndentWidth: 4 76 | IndentWrappedFunctionNames: false 77 | JavaScriptQuotes: Leave 78 | JavaScriptWrapImports: true 79 | KeepEmptyLinesAtTheStartOfBlocks: false 80 | MacroBlockBegin: '' 81 | MacroBlockEnd: '' 82 | MaxEmptyLinesToKeep: 1 83 | NamespaceIndentation: None 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: false 86 | ObjCSpaceBeforeProtocolList: false 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 1 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyExcessCharacter: 1000000 93 | PenaltyReturnTypeOnItsOwnLine: 200 94 | PointerAlignment: Right 95 | ReflowComments: true 96 | SortIncludes: false 97 | SortUsingDeclarations: true 98 | SpaceAfterCStyleCast: true 99 | SpaceAfterTemplateKeyword: true 100 | SpaceBeforeAssignmentOperators: true 101 | SpaceBeforeParens: ControlStatements 102 | SpaceInEmptyParentheses: false 103 | SpacesBeforeTrailingComments: 2 104 | SpacesInAngles: false 105 | SpacesInContainerLiterals: true 106 | SpacesInCStyleCastParentheses: false 107 | SpacesInParentheses: false 108 | SpacesInSquareBrackets: false 109 | Standard: Auto 110 | TabWidth: 8 111 | UseTab: Never -------------------------------------------------------------------------------- /.github/install-swoole.sh: -------------------------------------------------------------------------------- 1 | wget https://github.com/swoole/swoole-src/archive/master.zip && 2 | unzip master.zip && 3 | cd swoole-src-master && 4 | phpize && ./configure && 5 | make -j$(sysctl -n hw.ncpu) && 6 | sudo make install && 7 | php --ini && 8 | php -v && 9 | sudo sh -c "echo 'extension=swoole.so' >> /etc/php/7.4/cli/conf.d/30-swoole.ini" && 10 | php -m && 11 | cd - && 12 | rm -rf swoole-src-master && 13 | pwd -------------------------------------------------------------------------------- /.github/workflows/ext-swoole_postgresql.yml: -------------------------------------------------------------------------------- 1 | name: ext-swoole_postgresql 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | container-job: 7 | runs-on: ubuntu-latest 8 | container: phpswoole/swoole:latest-dev 9 | services: 10 | postgres: 11 | image: postgres 12 | env: 13 | POSTGRES_PASSWORD: postgres 14 | ports: 15 | - 5432:5432 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: build 19 | run: | 20 | apt-get update 21 | apt-get install -y libpq-dev 22 | phpize 23 | ./configure 24 | make -j$(sysctl -n hw.ncpu) 25 | make install 26 | docker-php-ext-enable swoole_postgresql 27 | php --ri swoole_postgresql 28 | - name: composer 29 | run: composer update 30 | - name: tests 31 | run: php vendor/bin/phpunit 32 | env: 33 | POSTGRES_HOST: postgres 34 | POSTGRES_PORT: 5432 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.cproject 2 | /.idea 3 | /.project 4 | /examples/yield/ 5 | *.o 6 | *.lo 7 | *~ 8 | *.so 9 | *.loT 10 | *.pid 11 | /Debug/* 12 | modules/* 13 | /.deps 14 | /.libs/ 15 | /core 16 | /examples/core 17 | /Debug 18 | /CMakeFiles 19 | /cmake_install.cmake 20 | /CMakeCache.txt 21 | /Makefile 22 | /server 23 | /lib 24 | /bin 25 | /install_manifest.txt 26 | /wiki 27 | /config.guess 28 | /config.h 29 | /config.h.in 30 | /config.log 31 | /config.nice 32 | /config.status 33 | /config.sub 34 | /configure 35 | /Makefile.fragments 36 | /Makefile.global 37 | /Makefile.objects 38 | /install-sh 39 | /libtool 40 | /ltmain.sh 41 | /missing 42 | /mkinstalldirs 43 | /swoole.la 44 | /acinclude.m4 45 | /aclocal.m4 46 | /run-tests.php 47 | /autom4te.cache 48 | /build 49 | /examples/async/data.txt 50 | /examples/.idea 51 | /examples/recv_file.jpg 52 | /modules 53 | /examples/async/test.copy 54 | /examples/ssl_client 55 | /examples/ssl/ca.crt 56 | /examples/ssl/client.key 57 | /examples/ssl/client.pfx 58 | /examples/ssl/client 59 | /examples/ssl/corpssl.crt 60 | /examples/ssl/corpssl.key 61 | /examples/ext 62 | /examples/cpp_module/.cproject 63 | /examples/cpp_module/.project 64 | /benchmark/.idea/ 65 | examples/c_module/.cproject 66 | examples/c_module/.project 67 | /tools/.idea/ 68 | /tmp-php.ini 69 | .settings/ 70 | tests/.idea 71 | /core-tests/server/*.log 72 | cmake-build-debug/ 73 | *.cbp 74 | /.vscode 75 | /.vs 76 | /configure.in 77 | /configure.ac 78 | /core-tests/CMakeCache.txt 79 | /core-tests/CMakeFiles/ 80 | /core-tests/Makefile 81 | /core-tests/bin/ 82 | /core-tests/cmake_install.cmake 83 | /tools/vendor 84 | /tools/composer.lock 85 | /*.la 86 | /examples/wrapper/CMakeFiles 87 | /examples/wrapper/CMakeCache.txt 88 | /examples/wrapper/Makefile 89 | /examples/wrapper/server 90 | /examples/wrapper/cmake_install.cmake 91 | /vendor 92 | /.phpunit.result.cache 93 | composer.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swoole Coroutine Postgres Client 2 | 3 | `ext-postgresql` is the Swoole Postgres Client library can be used with in the coroutine context without blocking. 4 | 5 | ### Pre-requirement 6 | 7 | * `libpq` is required 8 | * `swoole` version >= 4.4.0 9 | 10 | ### Build & Installation 11 | 12 | ```bash 13 | git clone git@github.com:swoole/ext-postgresql.git 14 | phpize 15 | ./configure 16 | make && make install 17 | ``` 18 | 19 | Enable `swoole_postgresql` in php.ini by adding the following line: 20 | ``` 21 | extension=swoole_postgresql.so 22 | ``` 23 | 24 | ### How to use the Postgres Client 25 | 26 | ```php 27 | connect("host=127.0.0.1 port=5432 dbname=test user=root password=password"); 31 | $db->prepare('fortunes', 'SELECT id, message FROM Fortune'); 32 | $res = $db->execute('fortunes', []); 33 | $arr = $db->fetchAll($res); 34 | var_dump($arr); 35 | 36 | $db->prepare('select_query', 'SELECT id, randomnumber FROM World WHERE id = $1'); 37 | $res = $db->execute('select_query', [123]); 38 | $ret = $db->fetchAll($res); 39 | var_dump($ret); 40 | }); 41 | ``` 42 | 43 | You can find more examples in the `/examples` folder. 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "phpunit/phpunit": "^9.3", 4 | "ext-swoole": ">=4.5", 5 | "ext-swoole_postgresql": ">=4.5" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl $Id$ 2 | dnl config.m4 for extension swoole_postgresql 3 | 4 | dnl +----------------------------------------------------------------------+ 5 | dnl | Swoole | 6 | dnl +----------------------------------------------------------------------+ 7 | dnl | This source file is subject to version 2.0 of the Apache license, | 8 | dnl | that is bundled with this package in the file LICENSE, and is | 9 | dnl | available through the world-wide-web at the following url: | 10 | dnl | http://www.apache.org/licenses/LICENSE-2.0.html | 11 | dnl | If you did not receive a copy of the Apache2.0 license and are unable| 12 | dnl | to obtain it through the world-wide-web, please send a note to | 13 | dnl | license@swoole.com so we can mail you a copy immediately. | 14 | dnl +----------------------------------------------------------------------+ 15 | dnl | Author: Tianfeng Han | 16 | dnl +----------------------------------------------------------------------+ 17 | 18 | PHP_ARG_ENABLE(swoole_postgresql, swoole_postgresql support, 19 | [ --enable-swoole_postgresql Enable swoole_postgresql support], [enable_swoole_postgresql="yes"]) 20 | 21 | PHP_ARG_ENABLE(asan, whether to enable asan, 22 | [ --enable-asan Enable asan], no, no) 23 | 24 | PHP_ARG_WITH(libpq_dir, dir of libpq, 25 | [ --with-libpq-dir[=DIR] Include libpq support (requires libpq >= 9.5)], no, no) 26 | 27 | PHP_ARG_WITH(openssl_dir, dir of openssl, 28 | [ --with-openssl-dir[=DIR] Include OpenSSL support (requires OpenSSL >= 0.9.6)], no, no) 29 | 30 | AC_MSG_CHECKING([if compiling with clang]) 31 | AC_COMPILE_IFELSE([ 32 | AC_LANG_PROGRAM([], [[ 33 | #ifndef __clang__ 34 | not clang 35 | #endif 36 | ]])], 37 | [CLANG=yes], [CLANG=no] 38 | ) 39 | AC_MSG_RESULT([$CLANG]) 40 | 41 | if test "$CLANG" = "yes"; then 42 | CFLAGS="$CFLAGS -std=gnu89" 43 | fi 44 | 45 | if test "$PHP_SWOOLE_POSTGRESQL" != "no"; then 46 | 47 | PHP_ADD_LIBRARY(pthread) 48 | PHP_SUBST(SWOOLE_POSTGRESQL_SHARED_LIBADD) 49 | 50 | AC_CHECK_LIB(pq, PQconnectdb, AC_DEFINE(HAVE_POSTGRESQL, 1, [have postgresql])) 51 | 52 | if test "$PHP_ASAN" != "no"; then 53 | PHP_DEBUG=1 54 | CFLAGS="$CFLAGS -fsanitize=address -fno-omit-frame-pointer" 55 | fi 56 | 57 | if test "$PHP_TRACE_LOG" != "no"; then 58 | AC_DEFINE(SW_LOG_TRACE_OPEN, 1, [enable trace log]) 59 | fi 60 | 61 | if test "$PHP_LIBPQ" != "no" || test "$PHP_LIBPQ_DIR" != "no"; then 62 | if test "$PHP_LIBPQ_DIR" != "no"; then 63 | AC_DEFINE(HAVE_LIBPQ, 1, [have libpq]) 64 | AC_MSG_RESULT(libpq include success) 65 | PHP_ADD_INCLUDE("${PHP_LIBPQ_DIR}/include") 66 | PHP_ADD_LIBRARY_WITH_PATH(pq, "${PHP_LIBPQ_DIR}/${PHP_LIBDIR}") 67 | PGSQL_INCLUDE=$PHP_LIBPQ_DIR/include 68 | PHP_ADD_LIBRARY(pq, 1, SWOOLE_POSTGRESQL_SHARED_LIBADD) 69 | else 70 | dnl TODO macros below can be reused to find curl things 71 | dnl prepare pkg-config 72 | if test -z "$PKG_CONFIG"; then 73 | AC_PATH_PROG(PKG_CONFIG, pkg-config, no) 74 | fi 75 | AC_MSG_CHECKING(for libpq) 76 | if test "x${LIBPQ_LIBS+set}" = "xset" || test "x${LIBPQ_CFLAGS+set}" = "xset"; then 77 | AC_MSG_RESULT([using LIBPQ_CFLAGS and LIBPQ_LIBS]) 78 | elif test -x "$PKG_CONFIG" ; then 79 | dnl find pkg using pkg-config cli tool 80 | libpq_pkg_config_path="$PHP_SWOOLE_PGSQL/lib/pkgconfig" 81 | if test "xyes" = "x$PHP_SWOOLE_PGSQL" ; then 82 | libpq_pkg_config_path=/lib/pkgconfig 83 | fi 84 | if test "x" != "x$PKG_CONFIG_PATH"; then 85 | libpq_pkg_config_path="$libpq_pkg_config_path:$PKG_CONFIG_PATH" 86 | fi 87 | 88 | libpq_version_full=`env PKG_CONFIG_PATH=${libpq_pkg_config_path} $PKG_CONFIG --modversion libpq` 89 | AC_MSG_RESULT(${libpq_version_full}) 90 | LIBPQ_CFLAGS="`env PKG_CONFIG_PATH=${libpq_pkg_config_path} $PKG_CONFIG --cflags libpq`" 91 | LIBPQ_LIBS="`env PKG_CONFIG_PATH=${libpq_pkg_config_path} $PKG_CONFIG --libs libpq`" 92 | fi 93 | 94 | _libpq_saved_cflags="$CFLAGS" 95 | CFLAGS="$CFLAGS $LIBPQ_CFLAGS" 96 | AC_CHECK_HEADER(libpq-fe.h, [], [ 97 | dnl this is too long, wht so chaos? 98 | cat >&2 <&2 < tests/testdb.sql 2 | -------------------------------------------------------------------------------- /examples/connect.php: -------------------------------------------------------------------------------- 1 | connect("host=127.0.0.1;port=5432;dbname=test;user=postgres;password=postgres"); 8 | if (!$conn) { 9 | var_dump($pg->error); 10 | return; 11 | } 12 | $result = $pg->query('SELECT * FROM weather;'); 13 | if (!$result) { 14 | var_dump($pg->error); 15 | return; 16 | } 17 | $arr = $pg->fetchAll($result); 18 | var_dump($arr); 19 | 20 | $result = $pg->query('SELECT * FROM weather;'); 21 | if (!$result) { 22 | var_dump($pg->error); 23 | return; 24 | } 25 | $arr = $pg->fetchAll($result); 26 | var_dump($arr); 27 | }); 28 | 29 | Event::wait(); 30 | -------------------------------------------------------------------------------- /examples/escape.php: -------------------------------------------------------------------------------- 1 | connect("host=127.0.0.1;port=5432;dbname=test;user=postgres;password=postgres"); 6 | if (!$conn) { 7 | var_dump($pg->error); 8 | return; 9 | } 10 | $result = $pg->escape("' or 1=1 & 2"); 11 | if (!$result) { 12 | var_dump($pg->error); 13 | return; 14 | } 15 | var_dump($result); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/pdo.php: -------------------------------------------------------------------------------- 1 | query('SELECT * FROM weather'); 8 | var_dump($res->fetchAll()); -------------------------------------------------------------------------------- /examples/prepare.php: -------------------------------------------------------------------------------- 1 | connect("host=127.0.0.1;port=5432;dbname=test;user=postgres;password=postgres"); 8 | if (!$conn) { 9 | var_dump($pg->error); 10 | return; 11 | } 12 | 13 | $prepare_result = $pg->prepare('key', "INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) 14 | VALUES ($1, $2, $3, $4, $5) RETURNING id"); 15 | var_dump($prepare_result); 16 | $execute_result = $pg->execute('key', ['Beijing', rand(1000, 99999), 10, 0.75, '1993-11-23']); 17 | 18 | var_dump($execute_result); 19 | }); 20 | 21 | Event::wait(); 22 | -------------------------------------------------------------------------------- /examples/timeout.php: -------------------------------------------------------------------------------- 1 | connect("host=127.0.0.1;port=5432;dbname=test;user=postgres;password=postgres"); 8 | if (!$conn) { 9 | var_dump($pg->error); 10 | return; 11 | } 12 | $result = $pg->query('select pg_sleep(5)'); 13 | if (!$result) { 14 | var_dump($pg->error); 15 | return; 16 | } 17 | $arr = $pg->fetchAll($result); 18 | var_dump($arr); 19 | }); 20 | 21 | Event::wait(); 22 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | phpize --clean 2 | phpize 3 | ./configure 4 | make -j install 5 | -------------------------------------------------------------------------------- /php_swoole_postgresql.h: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | Swoole | 4 | +----------------------------------------------------------------------+ 5 | | This source file is subject to version 2.0 of the Apache license, | 6 | | that is bundled with this package in the file LICENSE, and is | 7 | | available through the world-wide-web at the following url: | 8 | | http://www.apache.org/licenses/LICENSE-2.0.html | 9 | | If you did not receive a copy of the Apache2.0 license and are unable| 10 | | to obtain it through the world-wide-web, please send a note to | 11 | | license@swoole.com so we can mail you a copy immediately. | 12 | +----------------------------------------------------------------------+ 13 | | Author: Zhenyu Wu <936321732@qq.com> | 14 | | Tianfeng Han | 15 | +----------------------------------------------------------------------+ 16 | */ 17 | #ifndef SWOOLE_POSTGRESQL_H_ 18 | #define SWOOLE_POSTGRESQL_H_ 19 | 20 | #include "ext/swoole/config.h" 21 | #include "ext/swoole/ext-src/php_swoole_cxx.h" 22 | #include "config.h" 23 | 24 | #define PHP_SWOOLE_EXT_PLUS_VERSION "4.8.0" 25 | #define PHP_SWOOLE_EXT_PLUS_VERSION_ID 40800 26 | 27 | #if SWOOLE_VERSION_ID < 40800 28 | #error "Ext version does not match the Swoole version" 29 | #endif 30 | 31 | #include 32 | 33 | #include 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests/unit 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /swoole_postgresql.cc: -------------------------------------------------------------------------------- 1 | /* 2 | +----------------------------------------------------------------------+ 3 | | Swoole | 4 | +----------------------------------------------------------------------+ 5 | | This source file is subject to version 2.0 of the Apache license, | 6 | | that is bundled with this package in the file LICENSE, and is | 7 | | available through the world-wide-web at the following url: | 8 | | http://www.apache.org/licenses/LICENSE-2.0.html | 9 | | If you did not receive a copy of the Apache2.0 license and are unable| 10 | | to obtain it through the world-wide-web, please send a note to | 11 | | license@swoole.com so we can mail you a copy immediately. | 12 | +----------------------------------------------------------------------+ 13 | | Author: Zhenyu Wu <936321732@qq.com> | 14 | | Tianfeng Han | 15 | +----------------------------------------------------------------------+ 16 | */ 17 | 18 | #include "php_swoole_postgresql.h" 19 | #include "swoole_api.h" 20 | 21 | namespace swoole { 22 | namespace postgresql { 23 | 24 | enum QueryType { NORMAL_QUERY, META_DATA, PREPARE }; 25 | 26 | struct Object { 27 | PGconn *conn; 28 | network::Socket *socket; 29 | Coroutine *co; 30 | PGresult *result; 31 | zval *return_value; 32 | zval *object; 33 | zval _object; 34 | ConnStatusType status; 35 | enum QueryType request_type; 36 | int row; 37 | bool connected; 38 | bool ignore_notices; 39 | bool log_notices; 40 | 41 | bool yield(zval *_return_value, EventType event, double timeout); 42 | bool wait_write_ready(); 43 | }; 44 | } // namespace postgresql 45 | } // namespace swoole 46 | 47 | #define PGSQL_ASSOC 1 << 0 48 | #define PGSQL_NUM 1 << 1 49 | #define PGSQL_BOTH (PGSQL_ASSOC | PGSQL_NUM) 50 | 51 | /* from postgresql/src/include/catalog/pg_type.h */ 52 | #define BOOLOID 16 53 | #define BYTEAOID 17 54 | #define INT2OID 21 55 | #define INT4OID 23 56 | #define INT8OID 20 57 | #define TEXTOID 25 58 | #define OIDOID 26 59 | #define FLOAT4OID 700 60 | #define FLOAT8OID 701 61 | 62 | using swoole::Coroutine; 63 | using swoole::Event; 64 | using swoole::Reactor; 65 | using swoole::coroutine::System; 66 | using swoole::network::Socket; 67 | using PGObject = swoole::postgresql::Object; 68 | using PGQueryType = swoole::postgresql::QueryType; 69 | 70 | PHP_MINIT_FUNCTION(swoole_postgresql); 71 | PHP_MINFO_FUNCTION(swoole_postgresql); 72 | 73 | void swoole_postgresql_init(int module_number); 74 | 75 | // clang-format off 76 | /* {{{ swoole_postgresql_deps 77 | */ 78 | static const zend_module_dep swoole_postgresql_deps[] = { 79 | ZEND_MOD_REQUIRED("swoole") 80 | ZEND_MOD_END 81 | }; 82 | /* }}} */ 83 | 84 | zend_module_entry swoole_postgresql_module_entry = { 85 | STANDARD_MODULE_HEADER_EX, NULL, 86 | swoole_postgresql_deps, 87 | "swoole_postgresql", 88 | NULL, 89 | PHP_MINIT(swoole_postgresql), 90 | NULL, 91 | NULL, 92 | NULL, 93 | PHP_MINFO(swoole_postgresql), 94 | PHP_SWOOLE_EXT_PLUS_VERSION, 95 | STANDARD_MODULE_PROPERTIES 96 | }; 97 | 98 | // clang-format on 99 | 100 | #ifdef COMPILE_DL_SWOOLE_POSTGRESQL 101 | ZEND_GET_MODULE(swoole_postgresql) 102 | #endif 103 | 104 | static zend_class_entry *swoole_postgresql_coro_ce; 105 | static zend_object_handlers swoole_postgresql_coro_handlers; 106 | static int le_result; 107 | 108 | struct PostgreSQLObject { 109 | PGObject object; 110 | zend_object std; 111 | }; 112 | 113 | static sw_inline PostgreSQLObject *php_swoole_postgresql_coro_fetch_object(zend_object *obj) { 114 | return (PostgreSQLObject *) ((char *) obj - swoole_postgresql_coro_handlers.offset); 115 | } 116 | 117 | static sw_inline PGObject *php_swoole_postgresql_coro_get_object(zval *zobject) { 118 | return &php_swoole_postgresql_coro_fetch_object(Z_OBJ_P(zobject))->object; 119 | } 120 | 121 | static int swoole_postgresql_coro_close(zval *zobject); 122 | 123 | static void php_swoole_postgresql_coro_free_object(zend_object *object) { 124 | PostgreSQLObject *postgresql_coro = php_swoole_postgresql_coro_fetch_object(object); 125 | if (postgresql_coro->object.conn) { 126 | zval zobject; 127 | ZVAL_OBJ(&zobject, object); 128 | swoole_postgresql_coro_close(&zobject); 129 | } 130 | zend_object_std_dtor(&postgresql_coro->std); 131 | } 132 | 133 | static zend_object *php_swoole_postgresql_coro_create_object(zend_class_entry *ce) { 134 | PostgreSQLObject *postgresql_coro = (PostgreSQLObject *) zend_object_alloc(sizeof(*postgresql_coro), ce); 135 | zend_object_std_init(&postgresql_coro->std, ce); 136 | object_properties_init(&postgresql_coro->std, ce); 137 | postgresql_coro->std.handlers = &swoole_postgresql_coro_handlers; 138 | 139 | Coroutine::get_current_safe(); 140 | 141 | do { 142 | PGObject *object = &postgresql_coro->object; 143 | object->object = &object->_object; 144 | ZVAL_OBJ(object->object, &postgresql_coro->std); 145 | } while (0); 146 | 147 | return &postgresql_coro->std; 148 | } 149 | 150 | static PHP_METHOD(swoole_postgresql_coro, __construct); 151 | static PHP_METHOD(swoole_postgresql_coro, __destruct); 152 | static PHP_METHOD(swoole_postgresql_coro, connect); 153 | static PHP_METHOD(swoole_postgresql_coro, escape); 154 | static PHP_METHOD(swoole_postgresql_coro, escapeLiteral); 155 | static PHP_METHOD(swoole_postgresql_coro, escapeIdentifier); 156 | static PHP_METHOD(swoole_postgresql_coro, query); 157 | static PHP_METHOD(swoole_postgresql_coro, prepare); 158 | static PHP_METHOD(swoole_postgresql_coro, execute); 159 | static PHP_METHOD(swoole_postgresql_coro, fetchAll); 160 | static PHP_METHOD(swoole_postgresql_coro, affectedRows); 161 | static PHP_METHOD(swoole_postgresql_coro, numRows); 162 | static PHP_METHOD(swoole_postgresql_coro, fieldCount); 163 | static PHP_METHOD(swoole_postgresql_coro, metaData); 164 | static PHP_METHOD(swoole_postgresql_coro, fetchObject); 165 | static PHP_METHOD(swoole_postgresql_coro, fetchAssoc); 166 | static PHP_METHOD(swoole_postgresql_coro, fetchArray); 167 | static PHP_METHOD(swoole_postgresql_coro, fetchRow); 168 | 169 | static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_type, int into_object); 170 | 171 | static void _free_result(zend_resource *rsrc); 172 | static int swoole_pgsql_coro_onReadable(Reactor *reactor, Event *event); 173 | static int swoole_pgsql_coro_onWritable(Reactor *reactor, Event *event); 174 | static int swoole_pgsql_coro_onError(Reactor *reactor, Event *event); 175 | static int swoole_postgresql_coro_close(zval *zobject); 176 | static int query_result_parse(PGObject *object); 177 | static int prepare_result_parse(PGObject *object); 178 | static int meta_data_result_parse(PGObject *object); 179 | static void _php_pgsql_free_params(char **params, int num_params); 180 | 181 | // clang-format off 182 | ZEND_BEGIN_ARG_INFO_EX(arginfo_swoole_void, 0, 0, 0) 183 | ZEND_END_ARG_INFO() 184 | 185 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_connect, 0, 0, -1) 186 | ZEND_ARG_INFO(0, conninfo) 187 | ZEND_END_ARG_INFO() 188 | 189 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_query, 0, 0, 0) 190 | ZEND_ARG_INFO(0, query) 191 | ZEND_END_ARG_INFO() 192 | 193 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_send_prepare, 0, 0, 2) 194 | ZEND_ARG_INFO(0, stmtname) 195 | ZEND_ARG_INFO(0, query) 196 | ZEND_END_ARG_INFO() 197 | 198 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_send_execute, 0, 0, 2) 199 | ZEND_ARG_INFO(0, stmtname) 200 | ZEND_ARG_INFO(0, pv_param_arr) 201 | ZEND_END_ARG_INFO() 202 | 203 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_all, 0, 0, 0) 204 | ZEND_ARG_INFO(0, result) 205 | ZEND_ARG_INFO(0, result_type) 206 | ZEND_END_ARG_INFO() 207 | 208 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_affected_rows, 0, 0, 0) 209 | ZEND_ARG_INFO(0, result) 210 | ZEND_END_ARG_INFO() 211 | 212 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_num_rows, 0, 0, 0) 213 | ZEND_ARG_INFO(0, result) 214 | ZEND_END_ARG_INFO() 215 | 216 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_field_count, 0, 0, 0) 217 | ZEND_ARG_INFO(0, result) 218 | ZEND_END_ARG_INFO() 219 | 220 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_meta_data, 0, 0, 1) 221 | ZEND_ARG_INFO(0, table_name) 222 | ZEND_END_ARG_INFO() 223 | 224 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_row, 0, 0, 1) 225 | ZEND_ARG_INFO(0, result) 226 | ZEND_ARG_INFO(0, row) 227 | ZEND_ARG_INFO(0, result_type) 228 | ZEND_END_ARG_INFO() 229 | 230 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_escape, 0, 0, 1) 231 | ZEND_ARG_INFO(0, string) 232 | ZEND_END_ARG_INFO() 233 | 234 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_assoc, 0, 0, 1) 235 | ZEND_ARG_INFO(0, result) 236 | ZEND_ARG_INFO(0, row) 237 | ZEND_END_ARG_INFO() 238 | 239 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_array, 0, 0, 1) 240 | ZEND_ARG_INFO(0, result) 241 | ZEND_ARG_INFO(0, row) 242 | ZEND_ARG_INFO(0, result_type) 243 | ZEND_END_ARG_INFO() 244 | 245 | ZEND_BEGIN_ARG_INFO_EX(arginfo_pg_fetch_object, 0, 0, 1) 246 | ZEND_ARG_INFO(0, result) 247 | ZEND_ARG_INFO(0, row) 248 | ZEND_ARG_INFO(0, class_name) 249 | ZEND_ARG_INFO(0, l) 250 | ZEND_ARG_INFO(0, ctor_params) 251 | ZEND_END_ARG_INFO() 252 | 253 | static const zend_function_entry swoole_postgresql_coro_methods[] = 254 | { 255 | PHP_ME(swoole_postgresql_coro, __construct, arginfo_swoole_void, ZEND_ACC_PUBLIC) 256 | PHP_ME(swoole_postgresql_coro, connect, arginfo_pg_connect, ZEND_ACC_PUBLIC) 257 | PHP_ME(swoole_postgresql_coro, query, arginfo_pg_query, ZEND_ACC_PUBLIC ) 258 | PHP_ME(swoole_postgresql_coro, prepare, arginfo_pg_send_prepare, ZEND_ACC_PUBLIC ) 259 | PHP_ME(swoole_postgresql_coro, execute, arginfo_pg_send_execute, ZEND_ACC_PUBLIC ) 260 | PHP_ME(swoole_postgresql_coro, fetchAll, arginfo_pg_fetch_all, ZEND_ACC_PUBLIC) 261 | PHP_ME(swoole_postgresql_coro, affectedRows, arginfo_pg_affected_rows, ZEND_ACC_PUBLIC) 262 | PHP_ME(swoole_postgresql_coro, numRows, arginfo_pg_num_rows, ZEND_ACC_PUBLIC) 263 | PHP_ME(swoole_postgresql_coro, fieldCount, arginfo_pg_field_count, ZEND_ACC_PUBLIC) 264 | PHP_ME(swoole_postgresql_coro, metaData, arginfo_pg_meta_data, ZEND_ACC_PUBLIC) 265 | PHP_ME(swoole_postgresql_coro, escape, arginfo_pg_escape, ZEND_ACC_PUBLIC) 266 | PHP_ME(swoole_postgresql_coro, escapeLiteral, arginfo_pg_escape, ZEND_ACC_PUBLIC) 267 | PHP_ME(swoole_postgresql_coro, escapeIdentifier, arginfo_pg_escape, ZEND_ACC_PUBLIC) 268 | PHP_ME(swoole_postgresql_coro, fetchObject, arginfo_pg_fetch_object, ZEND_ACC_PUBLIC) 269 | PHP_ME(swoole_postgresql_coro, fetchAssoc, arginfo_pg_fetch_assoc, ZEND_ACC_PUBLIC) 270 | PHP_ME(swoole_postgresql_coro, fetchArray, arginfo_pg_fetch_array, ZEND_ACC_PUBLIC) 271 | PHP_ME(swoole_postgresql_coro, fetchRow, arginfo_pg_fetch_row, ZEND_ACC_PUBLIC) 272 | PHP_ME(swoole_postgresql_coro, __destruct, arginfo_swoole_void, ZEND_ACC_PUBLIC) 273 | PHP_FE_END 274 | }; 275 | // clang-format on 276 | 277 | void swoole_postgresql_init(int module_number) { 278 | SW_INIT_CLASS_ENTRY(swoole_postgresql_coro, 279 | "Swoole\\Coroutine\\PostgreSQL", 280 | "Co\\PostgreSQL", 281 | swoole_postgresql_coro_methods); 282 | #ifdef SW_SET_CLASS_NOT_SERIALIZABLE 283 | SW_SET_CLASS_NOT_SERIALIZABLE(swoole_postgresql_coro); 284 | #else 285 | SW_SET_CLASS_SERIALIZABLE(swoole_postgresql_coro, zend_class_serialize_deny, zend_class_unserialize_deny); 286 | #endif 287 | SW_SET_CLASS_CLONEABLE(swoole_postgresql_coro, sw_zend_class_clone_deny); 288 | SW_SET_CLASS_UNSET_PROPERTY_HANDLER(swoole_postgresql_coro, sw_zend_class_unset_property_deny); 289 | SW_SET_CLASS_CUSTOM_OBJECT(swoole_postgresql_coro, 290 | php_swoole_postgresql_coro_create_object, 291 | php_swoole_postgresql_coro_free_object, 292 | PostgreSQLObject, 293 | std); 294 | 295 | le_result = zend_register_list_destructors_ex(_free_result, NULL, "pgsql result", module_number); 296 | zend_declare_property_null(swoole_postgresql_coro_ce, ZEND_STRL("error"), ZEND_ACC_PUBLIC); 297 | zend_declare_property_long(swoole_postgresql_coro_ce, ZEND_STRL("errCode"), 0, ZEND_ACC_PUBLIC); 298 | zend_declare_property_long(swoole_postgresql_coro_ce, ZEND_STRL("resultStatus"), 0, ZEND_ACC_PUBLIC); 299 | zend_declare_property_null(swoole_postgresql_coro_ce, ZEND_STRL("resultDiag"), ZEND_ACC_PUBLIC); 300 | zend_declare_property_null(swoole_postgresql_coro_ce, ZEND_STRL("notices"), ZEND_ACC_PUBLIC); 301 | 302 | SW_REGISTER_LONG_CONSTANT("SW_PGSQL_ASSOC", PGSQL_ASSOC); 303 | SW_REGISTER_LONG_CONSTANT("SW_PGSQL_NUM", PGSQL_NUM); 304 | SW_REGISTER_LONG_CONSTANT("SW_PGSQL_BOTH", PGSQL_BOTH); 305 | } 306 | 307 | static char *_php_pgsql_trim_message(const char *message, size_t *len) { 308 | size_t i = strlen(message); 309 | if (i > 2 && (message[i - 2] == '\r' || message[i - 2] == '\n') && message[i - 1] == '.') { 310 | --i; 311 | } 312 | while (i > 1 && (message[i - 1] == '\r' || message[i - 1] == '\n')) { 313 | --i; 314 | } 315 | if (len) { 316 | *len = i; 317 | } 318 | return estrndup(message, i); 319 | } 320 | 321 | static void _php_pgsql_notice_handler(void *resource_id, const char *message) { 322 | zval *notices; 323 | char *trimed_message; 324 | size_t trimed_message_len; 325 | PGObject *object = (PGObject *) resource_id; 326 | 327 | if (!object->ignore_notices) { 328 | notices = sw_zend_read_and_convert_property_array( 329 | swoole_postgresql_coro_ce, &object->_object, ZEND_STRL("notices"), 0); 330 | 331 | trimed_message = _php_pgsql_trim_message(message, &trimed_message_len); 332 | if (object->log_notices) { 333 | php_error_docref(NULL, E_NOTICE, "%s", trimed_message); 334 | } 335 | add_next_index_stringl(notices, trimed_message, trimed_message_len); 336 | efree(trimed_message); 337 | } 338 | } 339 | 340 | static PHP_METHOD(swoole_postgresql_coro, __construct) {} 341 | 342 | static PHP_METHOD(swoole_postgresql_coro, connect) { 343 | zval *conninfo; 344 | double timeout = Socket::default_connect_timeout; 345 | 346 | ZEND_PARSE_PARAMETERS_START(1, 2) 347 | Z_PARAM_ZVAL(conninfo) 348 | Z_PARAM_OPTIONAL 349 | Z_PARAM_DOUBLE(timeout) 350 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 351 | 352 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 353 | if (object->conn) { 354 | RETURN_FALSE; 355 | } 356 | 357 | zend::String dsn(conninfo); 358 | char *p = dsn.val(); 359 | for (size_t i = 0; i < dsn.len(); i++) { 360 | if (*p == ';') { 361 | *p = ' '; 362 | } 363 | p++; 364 | } 365 | 366 | PGconn *pgsql = PQconnectStart(dsn.val()); 367 | if (!pgsql) { 368 | RETURN_FALSE; 369 | } 370 | 371 | int fd = PQsocket(pgsql); 372 | if (sw_unlikely(fd < 0)) { 373 | RETURN_FALSE; 374 | } 375 | 376 | php_swoole_check_reactor(); 377 | 378 | if (!swoole_event_isset_handler(PHP_SWOOLE_FD_POSTGRESQL)) { 379 | swoole_event_set_handler(PHP_SWOOLE_FD_POSTGRESQL | SW_EVENT_READ, swoole_pgsql_coro_onReadable); 380 | swoole_event_set_handler(PHP_SWOOLE_FD_POSTGRESQL | SW_EVENT_WRITE, swoole_pgsql_coro_onWritable); 381 | swoole_event_set_handler(PHP_SWOOLE_FD_POSTGRESQL | SW_EVENT_ERROR, swoole_pgsql_coro_onError); 382 | } 383 | 384 | object->socket = swoole::make_socket(fd, (enum swFdType) PHP_SWOOLE_FD_POSTGRESQL); 385 | object->socket->object = object; 386 | object->conn = pgsql; 387 | object->status = CONNECTION_STARTED; 388 | object->connected = false; 389 | 390 | ON_SCOPE_EXIT { 391 | if (!object->connected) { 392 | object->conn = NULL; 393 | } 394 | }; 395 | 396 | PQsetnonblocking(pgsql, 1); 397 | PQsetNoticeProcessor(pgsql, _php_pgsql_notice_handler, object); 398 | 399 | if (pgsql == NULL || PQstatus(pgsql) == CONNECTION_BAD) { 400 | swoole_warning("Unable to connect to PostgreSQL server: [%s]", PQhost(pgsql)); 401 | if (pgsql) { 402 | PQfinish(pgsql); 403 | } 404 | RETURN_FALSE; 405 | } 406 | 407 | if (!object->yield(return_value, SW_EVENT_WRITE, timeout)) { 408 | const char *feedback; 409 | 410 | switch (PQstatus(pgsql)) { 411 | case CONNECTION_STARTED: 412 | feedback = "connection time out...please make sure your host,dbname,user and password is correct "; 413 | break; 414 | case CONNECTION_MADE: 415 | feedback = "Connected to server.."; 416 | break; 417 | default: 418 | feedback = " time out.."; 419 | break; 420 | } 421 | 422 | char *err_msg = PQerrorMessage(object->conn); 423 | if (pgsql == NULL || PQstatus(pgsql) == CONNECTION_STARTED) { 424 | swoole_warning(" [%s, %s] ", feedback, err_msg); 425 | } else if (PQstatus(pgsql) == CONNECTION_MADE) { 426 | PQfinish(pgsql); 427 | } 428 | zend_update_property_string(swoole_postgresql_coro_ce, 429 | SW_Z8_OBJ_P(ZEND_THIS), 430 | ZEND_STRL("error"), 431 | swoole_strerror(swoole_get_last_error())); 432 | RETURN_FALSE; 433 | } 434 | 435 | ZVAL_BOOL(return_value, object->connected); 436 | } 437 | 438 | static void connect_callback(PGObject *object, Reactor *reactor, Event *event) { 439 | PGconn *conn = object->conn; 440 | ConnStatusType status = PQstatus(conn); 441 | int events = 0; 442 | char *err_msg; 443 | 444 | swoole_event_del(object->socket); 445 | 446 | if (status != CONNECTION_OK) { 447 | PostgresPollingStatusType flag = PQconnectPoll(conn); 448 | switch (flag) { 449 | case PGRES_POLLING_READING: 450 | events = SW_EVENT_READ; 451 | break; 452 | case PGRES_POLLING_WRITING: 453 | events = SW_EVENT_WRITE; 454 | break; 455 | case PGRES_POLLING_OK: 456 | object->connected = true; 457 | events = 0; 458 | break; 459 | case PGRES_POLLING_FAILED: 460 | events = 0; 461 | err_msg = PQerrorMessage(conn); 462 | zend_update_property_string( 463 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error"), err_msg); 464 | break; 465 | default: 466 | swoole_warning("PQconnectPoll unexpected status"); 467 | break; 468 | } 469 | 470 | if (events) { 471 | event->socket->fd = PQsocket(conn); 472 | swoole_event_add(event->socket, events); 473 | return; 474 | } 475 | } 476 | 477 | if (object->connected == 1) { 478 | zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); 479 | } 480 | object->co->resume(); 481 | } 482 | 483 | static int swoole_pgsql_coro_onWritable(Reactor *reactor, Event *event) { 484 | PGObject *object = (PGObject *) event->socket->object; 485 | 486 | if (!object->connected) { 487 | connect_callback(object, reactor, event); 488 | return SW_OK; 489 | } 490 | 491 | if (object->co) { 492 | object->co->resume(); 493 | return SW_OK; 494 | } else { 495 | return reactor->default_write_handler(reactor, event); 496 | } 497 | } 498 | 499 | static int swoole_pgsql_coro_onReadable(Reactor *reactor, Event *event) { 500 | PGObject *object = (PGObject *) (event->socket->object); 501 | 502 | if (!object->connected) { 503 | connect_callback(object, reactor, event); 504 | return SW_OK; 505 | } 506 | 507 | switch (object->request_type) { 508 | case PGQueryType::NORMAL_QUERY: 509 | query_result_parse(object); 510 | break; 511 | case PGQueryType::META_DATA: 512 | meta_data_result_parse(object); 513 | break; 514 | case PGQueryType::PREPARE: 515 | prepare_result_parse(object); 516 | break; 517 | } 518 | 519 | return SW_OK; 520 | } 521 | 522 | static int meta_data_result_parse(PGObject *object) { 523 | int i, num_rows; 524 | zval elem; 525 | PGresult *pg_result; 526 | zend_bool extended = 0; 527 | pg_result = PQgetResult(object->conn); 528 | 529 | if (PQresultStatus(pg_result) != PGRES_TUPLES_OK || (num_rows = PQntuples(pg_result)) == 0) { 530 | php_swoole_fatal_error(E_WARNING, "Table doesn't exists"); 531 | return 0; 532 | } 533 | 534 | array_init(object->return_value); 535 | 536 | for (i = 0; i < num_rows; i++) { 537 | object->result = pg_result; 538 | char *name; 539 | array_init(&elem); 540 | /* pg_attribute.attnum */ 541 | add_assoc_long_ex(&elem, "num", sizeof("num") - 1, atoi(PQgetvalue(pg_result, i, 1))); 542 | /* pg_type.typname */ 543 | add_assoc_string_ex(&elem, "type", sizeof("type") - 1, PQgetvalue(pg_result, i, 2)); 544 | /* pg_attribute.attlen */ 545 | add_assoc_long_ex(&elem, "len", sizeof("len") - 1, atoi(PQgetvalue(pg_result, i, 3))); 546 | /* pg_attribute.attnonull */ 547 | add_assoc_bool_ex(&elem, "not null", sizeof("not null") - 1, !strcmp(PQgetvalue(pg_result, i, 4), "t")); 548 | /* pg_attribute.atthasdef */ 549 | add_assoc_bool_ex(&elem, "has default", sizeof("has default") - 1, !strcmp(PQgetvalue(pg_result, i, 5), "t")); 550 | /* pg_attribute.attndims */ 551 | add_assoc_long_ex(&elem, "array dims", sizeof("array dims") - 1, atoi(PQgetvalue(pg_result, i, 6))); 552 | /* pg_type.typtype */ 553 | add_assoc_bool_ex(&elem, "is enum", sizeof("is enum") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "e")); 554 | if (extended) { 555 | /* pg_type.typtype */ 556 | add_assoc_bool_ex(&elem, "is base", sizeof("is base") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "b")); 557 | add_assoc_bool_ex( 558 | &elem, "is composite", sizeof("is composite") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "c")); 559 | add_assoc_bool_ex(&elem, "is pesudo", sizeof("is pesudo") - 1, !strcmp(PQgetvalue(pg_result, i, 7), "p")); 560 | /* pg_description.description */ 561 | add_assoc_string_ex(&elem, "description", sizeof("description") - 1, PQgetvalue(pg_result, i, 8)); 562 | } 563 | /* pg_attribute.attname */ 564 | name = PQgetvalue(pg_result, i, 0); 565 | add_assoc_zval(object->return_value, name, &elem); 566 | } 567 | zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); 568 | zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag")); 569 | object->co->resume(); 570 | return SW_OK; 571 | } 572 | 573 | static void set_error_diag(const PGObject *object, const PGresult *pgsql_result) { 574 | const unsigned int error_codes[] = {PG_DIAG_SEVERITY, 575 | PG_DIAG_SQLSTATE, 576 | PG_DIAG_MESSAGE_PRIMARY, 577 | PG_DIAG_MESSAGE_DETAIL, 578 | PG_DIAG_MESSAGE_HINT, 579 | PG_DIAG_STATEMENT_POSITION, 580 | PG_DIAG_INTERNAL_POSITION, 581 | PG_DIAG_INTERNAL_QUERY, 582 | PG_DIAG_CONTEXT, 583 | PG_DIAG_SCHEMA_NAME, 584 | PG_DIAG_TABLE_NAME, 585 | PG_DIAG_COLUMN_NAME, 586 | PG_DIAG_DATATYPE_NAME, 587 | PG_DIAG_CONSTRAINT_NAME, 588 | PG_DIAG_SOURCE_FILE, 589 | PG_DIAG_SOURCE_LINE, 590 | PG_DIAG_SOURCE_FUNCTION}; 591 | 592 | const char *error_names[] = {"severity", 593 | "sqlstate", 594 | "message_primary", 595 | "message_detail", 596 | "message_hint", 597 | "statement_position", 598 | "internal_position", 599 | "internal_query", 600 | "content", 601 | "schema_name", 602 | "table_name", 603 | "column_name", 604 | "datatype_name", 605 | "constraint_name", 606 | "source_file", 607 | "source_line", 608 | "source_function"}; 609 | 610 | long unsigned int i; 611 | char *error_result; 612 | 613 | zval result_diag; 614 | array_init_size(&result_diag, sizeof(error_codes) / sizeof(int)); 615 | 616 | for (i = 0; i < sizeof(error_codes) / sizeof(int); i++) { 617 | error_result = PQresultErrorField(pgsql_result, error_codes[i]); 618 | 619 | if (error_result != nullptr) { 620 | add_assoc_string(&result_diag, error_names[i], error_result); 621 | } else { 622 | add_assoc_null(&result_diag, error_names[i]); 623 | } 624 | } 625 | 626 | zend_update_property(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag"), &result_diag); 627 | zval_dtor(&result_diag); 628 | } 629 | 630 | static int query_result_parse(PGObject *object) { 631 | PGresult *pgsql_result; 632 | ExecStatusType status; 633 | 634 | int error = 0; 635 | char *err_msg; 636 | int res; 637 | 638 | pgsql_result = PQgetResult(object->conn); 639 | status = PQresultStatus(pgsql_result); 640 | 641 | zend_update_property_long( 642 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultStatus"), status); 643 | 644 | switch (status) { 645 | case PGRES_EMPTY_QUERY: 646 | case PGRES_BAD_RESPONSE: 647 | case PGRES_NONFATAL_ERROR: 648 | case PGRES_FATAL_ERROR: 649 | err_msg = PQerrorMessage(object->conn); 650 | set_error_diag(object, pgsql_result); 651 | PQclear(pgsql_result); 652 | ZVAL_FALSE(object->return_value); 653 | zend_update_property_string( 654 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error"), err_msg); 655 | object->co->resume(); 656 | break; 657 | case PGRES_COMMAND_OK: /* successful command that did not return rows */ 658 | default: 659 | object->result = pgsql_result; 660 | object->row = 0; 661 | /* Wait to finish sending buffer */ 662 | res = PQflush(object->conn); 663 | ZVAL_RES(object->return_value, zend_register_resource(pgsql_result, le_result)); 664 | zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); 665 | zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag")); 666 | object->co->resume(); 667 | if (error != 0) { 668 | php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); 669 | } 670 | break; 671 | } 672 | (void) res; 673 | 674 | return SW_OK; 675 | } 676 | 677 | static int prepare_result_parse(PGObject *object) { 678 | int error = 0; 679 | char *err_msg; 680 | int res; 681 | 682 | PGresult *pgsql_result = PQgetResult(object->conn); 683 | ExecStatusType status = PQresultStatus(pgsql_result); 684 | 685 | zend_update_property_long( 686 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultStatus"), status); 687 | 688 | switch (status) { 689 | case PGRES_EMPTY_QUERY: 690 | case PGRES_BAD_RESPONSE: 691 | case PGRES_NONFATAL_ERROR: 692 | case PGRES_FATAL_ERROR: 693 | err_msg = PQerrorMessage(object->conn); 694 | set_error_diag(object, pgsql_result); 695 | PQclear(pgsql_result); 696 | ZVAL_FALSE(object->return_value); 697 | zend_update_property_string( 698 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error"), err_msg); 699 | object->co->resume(); 700 | if (error != 0) { 701 | php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); 702 | } 703 | break; 704 | case PGRES_COMMAND_OK: /* successful command that did not return rows */ 705 | /* Wait to finish sending buffer */ 706 | // res = PQflush(object->conn); 707 | PQclear(pgsql_result); 708 | ZVAL_TRUE(object->return_value); 709 | zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("error")); 710 | zend_update_property_null(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object->object), ZEND_STRL("resultDiag")); 711 | object->co->resume(); 712 | if (error != 0) { 713 | php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); 714 | } 715 | break; 716 | default: 717 | PQclear(pgsql_result); 718 | ZVAL_FALSE(object->return_value); 719 | zend_update_property_string(swoole_postgresql_coro_ce, 720 | SW_Z8_OBJ_P(object->object), 721 | ZEND_STRL("error"), 722 | "Bad result returned to prepare"); 723 | object->co->resume(); 724 | if (error != 0) { 725 | php_swoole_fatal_error(E_WARNING, "socket error. Error: %s [%d]", strerror(error), error); 726 | } 727 | break; 728 | } 729 | (void) res; 730 | 731 | return SW_OK; 732 | } 733 | 734 | bool PGObject::wait_write_ready() { 735 | int retval = 0; 736 | while ((retval = PQflush(conn)) == 1) { 737 | zval return_value; 738 | if (!yield(&return_value, SW_EVENT_WRITE, Socket::default_write_timeout)) { 739 | return false; 740 | } 741 | } 742 | 743 | if (retval == -1) { 744 | char *err_msg = PQerrorMessage(conn); 745 | zend_update_property_string(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(object), ZEND_STRL("error"), err_msg); 746 | return false; 747 | } 748 | 749 | return true; 750 | } 751 | 752 | bool PGObject::yield(zval *_return_value, EventType event, double timeout) { 753 | co = swoole::Coroutine::get_current_safe(); 754 | if (swoole_event_add(socket, event) < 0) { 755 | php_swoole_fatal_error(E_WARNING, "swoole_event_add failed"); 756 | RETVAL_FALSE; 757 | return false; 758 | } 759 | 760 | ON_SCOPE_EXIT { 761 | co = nullptr; 762 | if (!socket->removed && swoole_event_del(socket) < 0) { 763 | php_swoole_fatal_error(E_WARNING, "swoole_event_del failed"); 764 | } 765 | }; 766 | 767 | return_value = _return_value; 768 | 769 | if (!co->yield_ex(timeout)) { 770 | ZVAL_FALSE(_return_value); 771 | 772 | if (co->is_canceled()) { 773 | zend_update_property_string(swoole_postgresql_coro_ce, 774 | SW_Z8_OBJ_P(object), 775 | ZEND_STRL("error"), 776 | swoole_strerror(SW_ERROR_CO_CANCELED)); 777 | } else if (co->is_timedout()) { 778 | zend_update_property_string(swoole_postgresql_coro_ce, 779 | SW_Z8_OBJ_P(object), 780 | ZEND_STRL("error"), 781 | swoole_strerror(SW_ERROR_CO_TIMEDOUT)); 782 | } 783 | 784 | return false; 785 | } 786 | 787 | return true; 788 | } 789 | 790 | static PHP_METHOD(swoole_postgresql_coro, query) { 791 | zval *query; 792 | PGconn *pgsql; 793 | PGresult *pgsql_result; 794 | 795 | ZEND_PARSE_PARAMETERS_START(1, 1) 796 | Z_PARAM_ZVAL(query) 797 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 798 | 799 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 800 | if (!object || !object->conn) { 801 | RETURN_FALSE; 802 | } 803 | object->request_type = PGQueryType::NORMAL_QUERY; 804 | pgsql = object->conn; 805 | object->object = ZEND_THIS; 806 | 807 | while ((pgsql_result = PQgetResult(pgsql))) { 808 | PQclear(pgsql_result); 809 | } 810 | 811 | if (PQsendQuery(pgsql, Z_STRVAL_P(query)) == 0) { 812 | char *err_msg = PQerrorMessage(pgsql); 813 | zend_update_property_string(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), err_msg); 814 | RETURN_FALSE; 815 | } 816 | 817 | if (!object->wait_write_ready()) { 818 | RETURN_FALSE; 819 | } 820 | 821 | object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout); 822 | } 823 | 824 | static PHP_METHOD(swoole_postgresql_coro, prepare) { 825 | zval *query, *stmtname; 826 | PGconn *pgsql; 827 | int is_non_blocking; 828 | PGresult *pgsql_result; 829 | 830 | ZEND_PARSE_PARAMETERS_START(2, 2) 831 | Z_PARAM_ZVAL(stmtname) 832 | Z_PARAM_ZVAL(query) 833 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 834 | 835 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 836 | if (!object || !object->conn) { 837 | RETURN_FALSE; 838 | } 839 | object->request_type = PGQueryType::PREPARE; 840 | pgsql = object->conn; 841 | object->object = ZEND_THIS; 842 | 843 | is_non_blocking = PQisnonblocking(pgsql); 844 | 845 | if (is_non_blocking == 0 && PQsetnonblocking(pgsql, 1) == -1) { 846 | php_swoole_fatal_error(E_NOTICE, "Cannot set connection to nonblocking mode"); 847 | RETURN_FALSE; 848 | } 849 | 850 | while ((pgsql_result = PQgetResult(pgsql))) { 851 | PQclear(pgsql_result); 852 | } 853 | 854 | if (!PQsendPrepare(pgsql, Z_STRVAL_P(stmtname), Z_STRVAL_P(query), 0, NULL)) { 855 | if (is_non_blocking) { 856 | RETURN_FALSE; 857 | } else { 858 | /*if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { 859 | PQreset(pgsql); 860 | }*/ 861 | if (!PQsendPrepare(pgsql, Z_STRVAL_P(stmtname), Z_STRVAL_P(query), 0, NULL)) { 862 | RETURN_FALSE; 863 | } 864 | } 865 | } 866 | 867 | if (!object->wait_write_ready()) { 868 | RETURN_FALSE; 869 | } 870 | object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout); 871 | } 872 | 873 | static PHP_METHOD(swoole_postgresql_coro, execute) { 874 | zval *pv_param_arr, *tmp; 875 | int num_params = 0; 876 | char **params = NULL; 877 | zval *stmtname; 878 | PGconn *pgsql; 879 | int is_non_blocking; 880 | PGresult *pgsql_result; 881 | 882 | ZEND_PARSE_PARAMETERS_START(2, 2) 883 | Z_PARAM_ZVAL(stmtname) 884 | Z_PARAM_ZVAL(pv_param_arr) 885 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 886 | 887 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 888 | if (!object || !object->conn) { 889 | RETURN_FALSE; 890 | } 891 | object->request_type = PGQueryType::NORMAL_QUERY; 892 | pgsql = object->conn; 893 | object->object = ZEND_THIS; 894 | 895 | is_non_blocking = PQisnonblocking(pgsql); 896 | 897 | if (is_non_blocking == 0 && PQsetnonblocking(pgsql, 1) == -1) { 898 | php_swoole_fatal_error(E_NOTICE, "Cannot set connection to nonblocking mode"); 899 | RETURN_FALSE; 900 | } 901 | 902 | while ((pgsql_result = PQgetResult(pgsql))) { 903 | PQclear(pgsql_result); 904 | } 905 | 906 | num_params = zend_hash_num_elements(Z_ARRVAL_P(pv_param_arr)); 907 | if (num_params > 0) { 908 | int i = 0; 909 | params = (char **) safe_emalloc(sizeof(char *), num_params, 0); 910 | 911 | ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pv_param_arr), tmp) { 912 | if (Z_TYPE_P(tmp) == IS_NULL) { 913 | params[i] = NULL; 914 | } else { 915 | zval tmp_val; 916 | ZVAL_COPY(&tmp_val, tmp); 917 | convert_to_string(&tmp_val); 918 | if (Z_TYPE(tmp_val) != IS_STRING) { 919 | php_swoole_fatal_error(E_WARNING, "Error converting parameter"); 920 | zval_ptr_dtor(&tmp_val); 921 | _php_pgsql_free_params(params, num_params); 922 | RETURN_FALSE; 923 | } 924 | params[i] = estrndup(Z_STRVAL(tmp_val), Z_STRLEN(tmp_val)); 925 | zval_ptr_dtor(&tmp_val); 926 | } 927 | i++; 928 | } 929 | ZEND_HASH_FOREACH_END(); 930 | } 931 | 932 | if (PQsendQueryPrepared(pgsql, Z_STRVAL_P(stmtname), num_params, (const char *const *) params, NULL, NULL, 0)) { 933 | _php_pgsql_free_params(params, num_params); 934 | } else if (is_non_blocking) { 935 | _php_pgsql_free_params(params, num_params); 936 | RETURN_FALSE; 937 | } else { 938 | /* 939 | if ((PGG(auto_reset_persistent) & 2) && PQstatus(pgsql) != CONNECTION_OK) { 940 | PQreset(pgsql); 941 | } 942 | */ 943 | if (!PQsendQueryPrepared( 944 | pgsql, Z_STRVAL_P(stmtname), num_params, (const char *const *) params, NULL, NULL, 0)) { 945 | _php_pgsql_free_params(params, num_params); 946 | RETURN_FALSE; 947 | } 948 | } 949 | if (!object->wait_write_ready()) { 950 | RETURN_FALSE; 951 | } 952 | object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout); 953 | } 954 | 955 | static void _php_pgsql_free_params(char **params, int num_params) { 956 | if (num_params > 0) { 957 | for (int i = 0; i < num_params; i++) { 958 | if (params[i]) { 959 | efree(params[i]); 960 | } 961 | } 962 | efree(params); 963 | } 964 | } 965 | 966 | /* {{{ void php_pgsql_get_field_value */ 967 | static inline void php_pgsql_get_field_value( 968 | zval *value, PGresult *pgsql_result, zend_long result_type, int row, int column) { 969 | if (PQgetisnull(pgsql_result, row, column)) { 970 | ZVAL_NULL(value); 971 | } else { 972 | char *element = PQgetvalue(pgsql_result, row, column); 973 | if (element) { 974 | const size_t element_len = PQgetlength(pgsql_result, row, column); 975 | Oid pgsql_type = PQftype(pgsql_result, column); 976 | 977 | switch (pgsql_type) { 978 | case BOOLOID: 979 | ZVAL_BOOL(value, *element == 't'); 980 | break; 981 | case FLOAT4OID: 982 | case FLOAT8OID: 983 | if (element_len == sizeof("Infinity") - 1 && strcmp(element, "Infinity") == 0) { 984 | ZVAL_DOUBLE(value, ZEND_INFINITY); 985 | } else if (element_len == sizeof("-Infinity") - 1 && strcmp(element, "-Infinity") == 0) { 986 | ZVAL_DOUBLE(value, -ZEND_INFINITY); 987 | } else if (element_len == sizeof("NaN") - 1 && strcmp(element, "NaN") == 0) { 988 | ZVAL_DOUBLE(value, ZEND_NAN); 989 | } else { 990 | ZVAL_DOUBLE(value, zend_strtod(element, NULL)); 991 | } 992 | break; 993 | case OIDOID: 994 | case INT2OID: 995 | case INT4OID: 996 | #if SIZEOF_ZEND_LONG >= 8 997 | case INT8OID: 998 | #endif 999 | { 1000 | zend_long long_value; 1001 | #if PHP_VERSION_ID < 80100 1002 | ZEND_ATOL(long_value, element); 1003 | #else 1004 | long_value = ZEND_ATOL(element); 1005 | #endif 1006 | ZVAL_LONG(value, long_value); 1007 | break; 1008 | } 1009 | case BYTEAOID: { 1010 | size_t tmp_len; 1011 | char *tmp_ptr = (char *) PQunescapeBytea((unsigned char *) element, &tmp_len); 1012 | if (!tmp_ptr) { 1013 | /* PQunescapeBytea returned an error */ 1014 | ZVAL_NULL(value); 1015 | } else { 1016 | ZVAL_STRINGL(value, tmp_ptr, tmp_len); 1017 | PQfreemem(tmp_ptr); 1018 | } 1019 | break; 1020 | } 1021 | default: 1022 | ZVAL_STRINGL(value, element, element_len); 1023 | } 1024 | } else { 1025 | ZVAL_NULL(value); 1026 | } 1027 | } 1028 | } 1029 | /* }}} */ 1030 | 1031 | /* {{{ php_pgsql_result2array 1032 | */ 1033 | int swoole_pgsql_result2array(PGresult *pg_result, zval *ret_array, long result_type) { 1034 | zval row; 1035 | const char *field_name; 1036 | size_t num_fields, unknown_columns; 1037 | int pg_numrows, pg_row; 1038 | uint32_t i; 1039 | assert(Z_TYPE_P(ret_array) == IS_ARRAY); 1040 | 1041 | if ((pg_numrows = PQntuples(pg_result)) <= 0) { 1042 | return FAILURE; 1043 | } 1044 | for (pg_row = 0; pg_row < pg_numrows; pg_row++) { 1045 | array_init(&row); 1046 | unknown_columns = 0; 1047 | for (i = 0, num_fields = PQnfields(pg_result); i < num_fields; i++) { 1048 | if (result_type & PGSQL_ASSOC) { 1049 | zval value; 1050 | php_pgsql_get_field_value(&value, pg_result, result_type, pg_row, i); 1051 | field_name = PQfname(pg_result, i); 1052 | if (0 == strcmp("?column?", field_name)) { 1053 | if (unknown_columns > 0) { 1054 | field_name = (std::string(field_name) + std::to_string(unknown_columns)).c_str(); 1055 | } 1056 | ++unknown_columns; 1057 | } 1058 | add_assoc_zval(&row, field_name, &value); 1059 | } 1060 | if (result_type & PGSQL_NUM) { 1061 | zval value; 1062 | php_pgsql_get_field_value(&value, pg_result, result_type, pg_row, i); 1063 | add_next_index_zval(&row, &value); 1064 | } 1065 | } 1066 | add_index_zval(ret_array, pg_row, &row); 1067 | } 1068 | return SUCCESS; 1069 | } 1070 | 1071 | static PHP_METHOD(swoole_postgresql_coro, fetchAll) { 1072 | zval *result; 1073 | PGresult *pgsql_result; 1074 | zend_long result_type = PGSQL_ASSOC; 1075 | 1076 | ZEND_PARSE_PARAMETERS_START(1, 2) 1077 | Z_PARAM_RESOURCE(result) 1078 | Z_PARAM_OPTIONAL 1079 | Z_PARAM_LONG(result_type) 1080 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1081 | 1082 | if ((pgsql_result = (PGresult *) zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { 1083 | RETURN_FALSE; 1084 | } 1085 | 1086 | array_init(return_value); 1087 | if (swoole_pgsql_result2array(pgsql_result, return_value, result_type) == FAILURE) { 1088 | zval_dtor(return_value); 1089 | RETURN_FALSE; 1090 | } 1091 | } 1092 | 1093 | static PHP_METHOD(swoole_postgresql_coro, affectedRows) { 1094 | zval *result; 1095 | PGresult *pgsql_result; 1096 | 1097 | ZEND_PARSE_PARAMETERS_START(1, 1) 1098 | Z_PARAM_RESOURCE(result) 1099 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1100 | 1101 | if ((pgsql_result = (PGresult *) zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { 1102 | RETURN_FALSE; 1103 | } 1104 | 1105 | RETVAL_LONG(atoi(PQcmdTuples(pgsql_result))); 1106 | } 1107 | 1108 | // query's num 1109 | static PHP_METHOD(swoole_postgresql_coro, numRows) { 1110 | zval *result; 1111 | PGresult *pgsql_result; 1112 | 1113 | ZEND_PARSE_PARAMETERS_START(1, 1) 1114 | Z_PARAM_RESOURCE(result) 1115 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1116 | 1117 | if ((pgsql_result = (PGresult *) zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { 1118 | RETURN_FALSE; 1119 | } 1120 | 1121 | RETVAL_LONG(PQntuples(pgsql_result)); 1122 | } 1123 | 1124 | // query's field count 1125 | static PHP_METHOD(swoole_postgresql_coro, fieldCount) { 1126 | zval *result; 1127 | PGresult *pgsql_result; 1128 | 1129 | ZEND_PARSE_PARAMETERS_START(1, 1) 1130 | Z_PARAM_RESOURCE(result) 1131 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1132 | 1133 | if ((pgsql_result = (PGresult *) zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { 1134 | RETURN_FALSE; 1135 | } 1136 | 1137 | RETVAL_LONG(PQnfields(pgsql_result)); 1138 | } 1139 | 1140 | static PHP_METHOD(swoole_postgresql_coro, metaData) { 1141 | char *table_name; 1142 | size_t table_name_len; 1143 | zend_bool extended = 0; 1144 | PGconn *pgsql; 1145 | 1146 | PGresult *pg_result; 1147 | char *src, *tmp_name, *tmp_name2 = NULL; 1148 | char *escaped; 1149 | smart_str querystr = {0}; 1150 | size_t new_len; 1151 | 1152 | ZEND_PARSE_PARAMETERS_START(1, 1) 1153 | Z_PARAM_STRING(table_name, table_name_len) 1154 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1155 | 1156 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 1157 | if (!object || !object->conn) { 1158 | RETURN_FALSE; 1159 | } 1160 | object->request_type = PGQueryType::META_DATA; 1161 | pgsql = object->conn; 1162 | object->object = ZEND_THIS; 1163 | 1164 | while ((pg_result = PQgetResult(pgsql))) { 1165 | PQclear(pg_result); 1166 | } 1167 | 1168 | if (table_name_len == 0) { 1169 | php_swoole_fatal_error(E_WARNING, "The table name must be specified"); 1170 | RETURN_FALSE; 1171 | } 1172 | 1173 | src = estrdup(table_name); 1174 | tmp_name = php_strtok_r(src, ".", &tmp_name2); 1175 | if (!tmp_name) { 1176 | efree(src); 1177 | php_swoole_fatal_error(E_WARNING, "The table name must be specified"); 1178 | RETURN_FALSE; 1179 | } 1180 | if (!tmp_name2 || !*tmp_name2) { 1181 | /* Default schema */ 1182 | tmp_name2 = tmp_name; 1183 | tmp_name = (char *) "public"; 1184 | } 1185 | 1186 | if (extended) { 1187 | smart_str_appends( 1188 | &querystr, 1189 | "SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotNULL, a.atthasdef, a.attndims, t.typtype, " 1190 | "d.description " 1191 | "FROM pg_class as c " 1192 | " JOIN pg_attribute a ON (a.attrelid = c.oid) " 1193 | " JOIN pg_type t ON (a.atttypid = t.oid) " 1194 | " JOIN pg_namespace n ON (c.relnamespace = n.oid) " 1195 | " LEFT JOIN pg_description d ON (d.objoid=a.attrelid AND d.objsubid=a.attnum AND c.oid=d.objoid) " 1196 | "WHERE a.attnum > 0 AND c.relname = '"); 1197 | } else { 1198 | smart_str_appends( 1199 | &querystr, 1200 | "SELECT a.attname, a.attnum, t.typname, a.attlen, a.attnotnull, a.atthasdef, a.attndims, t.typtype " 1201 | "FROM pg_class as c " 1202 | " JOIN pg_attribute a ON (a.attrelid = c.oid) " 1203 | " JOIN pg_type t ON (a.atttypid = t.oid) " 1204 | " JOIN pg_namespace n ON (c.relnamespace = n.oid) " 1205 | "WHERE a.attnum > 0 AND c.relname = '"); 1206 | } 1207 | escaped = (char *) safe_emalloc(strlen(tmp_name2), 2, 1); 1208 | new_len = PQescapeStringConn(pgsql, escaped, tmp_name2, strlen(tmp_name2), NULL); 1209 | if (new_len) { 1210 | smart_str_appendl(&querystr, escaped, new_len); 1211 | } 1212 | efree(escaped); 1213 | 1214 | smart_str_appends(&querystr, "' AND n.nspname = '"); 1215 | escaped = (char *) safe_emalloc(strlen(tmp_name), 2, 1); 1216 | new_len = PQescapeStringConn(pgsql, escaped, tmp_name, strlen(tmp_name), NULL); 1217 | if (new_len) { 1218 | smart_str_appendl(&querystr, escaped, new_len); 1219 | } 1220 | efree(escaped); 1221 | 1222 | smart_str_appends(&querystr, "' ORDER BY a.attnum;"); 1223 | smart_str_0(&querystr); 1224 | efree(src); 1225 | 1226 | // pg_result = PQexec(pgsql, ZSTR_VAL(querystr.s)); 1227 | 1228 | int ret = PQsendQuery(pgsql, ZSTR_VAL(querystr.s)); 1229 | if (ret == 0) { 1230 | char *err_msg = PQerrorMessage(pgsql); 1231 | swoole_warning("error:[%s]", err_msg); 1232 | } 1233 | smart_str_free(&querystr); 1234 | object->yield(return_value, SW_EVENT_READ, Socket::default_read_timeout); 1235 | } 1236 | 1237 | /* {{{ void php_pgsql_fetch_hash */ 1238 | static void php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, zend_long result_type, int into_object) { 1239 | zval *result, *zrow = NULL; 1240 | PGresult *pgsql_result; 1241 | PGObject *pg_result; 1242 | int i, num_fields, pgsql_row, use_row; 1243 | zend_long row = -1; 1244 | char *field_name; 1245 | zval *ctor_params = NULL; 1246 | zend_class_entry *ce = NULL; 1247 | 1248 | if (into_object) { 1249 | zend_string *class_name = NULL; 1250 | 1251 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|z!Sz", &result, &zrow, &class_name, &ctor_params) == FAILURE) { 1252 | RETURN_FALSE; 1253 | } 1254 | if (!class_name) { 1255 | ce = zend_standard_class_def; 1256 | } else { 1257 | ce = zend_fetch_class(class_name, ZEND_FETCH_CLASS_AUTO); 1258 | } 1259 | if (!ce) { 1260 | php_swoole_fatal_error(E_WARNING, "Could not find class '%s'", ZSTR_VAL(class_name)); 1261 | return; 1262 | } 1263 | result_type = PGSQL_ASSOC; 1264 | } else { 1265 | if (zend_parse_parameters(ZEND_NUM_ARGS(), "r|z!l", &result, &zrow, &result_type) == FAILURE) { 1266 | RETURN_FALSE; 1267 | } 1268 | } 1269 | if (zrow == NULL) { 1270 | row = -1; 1271 | } else { 1272 | row = zval_get_long(zrow); 1273 | if (row < 0) { 1274 | php_swoole_fatal_error(E_WARNING, "The row parameter must be greater or equal to zero"); 1275 | RETURN_FALSE; 1276 | } 1277 | } 1278 | use_row = ZEND_NUM_ARGS() > 1 && row != -1; 1279 | 1280 | if (!(result_type & PGSQL_BOTH)) { 1281 | php_swoole_fatal_error(E_WARNING, "Invalid result type"); 1282 | RETURN_FALSE; 1283 | } 1284 | 1285 | if ((pgsql_result = (PGresult *) zend_fetch_resource(Z_RES_P(result), "PostgreSQL result", le_result)) == NULL) { 1286 | RETURN_FALSE; 1287 | } 1288 | 1289 | pg_result = php_swoole_postgresql_coro_get_object(ZEND_THIS); 1290 | if (!pg_result || !pg_result->conn) { 1291 | RETURN_FALSE; 1292 | } 1293 | 1294 | if (use_row) { 1295 | if (row < 0 || row >= PQntuples(pgsql_result)) { 1296 | php_swoole_fatal_error(E_WARNING, 1297 | "Unable to jump to row " ZEND_LONG_FMT " on PostgreSQL result index " ZEND_LONG_FMT, 1298 | row, 1299 | Z_LVAL_P(result)); 1300 | RETURN_FALSE; 1301 | } 1302 | pgsql_row = (int) row; 1303 | pg_result->row = pgsql_row; 1304 | } else { 1305 | /* If 2nd param is NULL, use internal row counter to access next row */ 1306 | pgsql_row = pg_result->row; 1307 | if (pgsql_row < 0 || pgsql_row >= PQntuples(pgsql_result)) { 1308 | RETURN_FALSE; 1309 | } 1310 | pg_result->row++; 1311 | } 1312 | 1313 | array_init(return_value); 1314 | for (i = 0, num_fields = PQnfields(pgsql_result); i < num_fields; i++) { 1315 | if (result_type & PGSQL_NUM) { 1316 | zval value; 1317 | php_pgsql_get_field_value(&value, pgsql_result, result_type, pgsql_row, i); 1318 | add_index_zval(return_value, i, &value); 1319 | } 1320 | 1321 | if (result_type & PGSQL_ASSOC) { 1322 | zval value; 1323 | php_pgsql_get_field_value(&value, pgsql_result, result_type, pgsql_row, i); 1324 | field_name = PQfname(pgsql_result, i); 1325 | add_assoc_zval(return_value, field_name, &value); 1326 | } 1327 | } 1328 | 1329 | if (into_object) { 1330 | zval dataset; 1331 | zend_fcall_info fci; 1332 | zend_fcall_info_cache fcc; 1333 | zval retval; 1334 | 1335 | ZVAL_COPY_VALUE(&dataset, return_value); 1336 | object_and_properties_init(return_value, ce, NULL); 1337 | if (!ce->default_properties_count && !ce->__set) { 1338 | Z_OBJ_P(return_value)->properties = Z_ARR(dataset); 1339 | } else { 1340 | zend_merge_properties(return_value, Z_ARRVAL(dataset)); 1341 | zval_ptr_dtor(&dataset); 1342 | } 1343 | 1344 | if (ce->constructor) { 1345 | fci.size = sizeof(fci); 1346 | ZVAL_UNDEF(&fci.function_name); 1347 | fci.object = Z_OBJ_P(return_value); 1348 | fci.retval = &retval; 1349 | fci.params = NULL; 1350 | fci.param_count = 0; 1351 | 1352 | if (ctor_params && Z_TYPE_P(ctor_params) != IS_NULL) { 1353 | if (zend_fcall_info_args(&fci, ctor_params) == FAILURE) { 1354 | /* Two problems why we throw exceptions here: PHP is typeless 1355 | * and hence passing one argument that's not an array could be 1356 | * by mistake and the other way round is possible, too. The 1357 | * single value is an array. Also we'd have to make that one 1358 | * argument passed by reference. 1359 | */ 1360 | zend_throw_exception(zend_ce_exception, "Parameter ctor_params must be an array", 0); 1361 | return; 1362 | } 1363 | } 1364 | 1365 | #if PHP_VERSION_ID < 70300 1366 | fcc.initialized = 1; 1367 | #endif 1368 | fcc.function_handler = ce->constructor; 1369 | #if PHP_VERSION_ID >= 70100 1370 | fcc.calling_scope = zend_get_executed_scope(); 1371 | #else 1372 | fcc.calling_scope = EG(scope); 1373 | #endif 1374 | fcc.called_scope = Z_OBJCE_P(return_value); 1375 | fcc.object = Z_OBJ_P(return_value); 1376 | 1377 | if (zend_call_function(&fci, &fcc) == FAILURE) { 1378 | zend_throw_exception_ex(zend_ce_exception, 1379 | 0, 1380 | "Could not execute %s::%s()", 1381 | ZSTR_VAL(ce->name), 1382 | ZSTR_VAL(ce->constructor->common.function_name)); 1383 | } else { 1384 | zval_ptr_dtor(&retval); 1385 | } 1386 | if (fci.params) { 1387 | efree(fci.params); 1388 | } 1389 | } else if (ctor_params) { 1390 | zend_throw_exception_ex(zend_ce_exception, 1391 | 0, 1392 | "Class %s does not have a constructor hence you cannot use ctor_params", 1393 | ZSTR_VAL(ce->name)); 1394 | } 1395 | } 1396 | } 1397 | /* }}} */ 1398 | 1399 | /* {{{ proto array fetchRow(resource result [, int row [, int result_type]]) 1400 | Get a row as an enumerated array */ 1401 | static PHP_METHOD(swoole_postgresql_coro, fetchRow) { 1402 | php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_NUM, 0); 1403 | } 1404 | /* }}} */ 1405 | 1406 | /* {{{ proto array fetchAssoc(resource result [, int row]) 1407 | Fetch a row as an assoc array */ 1408 | static PHP_METHOD(swoole_postgresql_coro, fetchAssoc) { 1409 | /* pg_fetch_assoc() is added from PHP 4.3.0. It should raise error, when 1410 | there is 3rd parameter */ 1411 | if (ZEND_NUM_ARGS() > 2) WRONG_PARAM_COUNT; 1412 | php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_ASSOC, 0); 1413 | } 1414 | /* }}} */ 1415 | 1416 | /* {{{ proto array fetchArray(resource result [, int row [, int result_type]]) 1417 | Fetch a row as an array */ 1418 | static PHP_METHOD(swoole_postgresql_coro, fetchArray) { 1419 | php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_BOTH, 0); 1420 | } 1421 | /* }}} */ 1422 | 1423 | /* {{{ proto object fetchObject(resource result [, int row [, string class_name [, NULL|array ctor_params]]]) 1424 | Fetch a row as an object */ 1425 | static PHP_METHOD(swoole_postgresql_coro, fetchObject) { 1426 | /* fetchObject() allowed result_type used to be. 3rd parameter 1427 | must be allowed for compatibility */ 1428 | php_pgsql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, PGSQL_ASSOC, 1); 1429 | } 1430 | 1431 | static void _free_result(zend_resource *rsrc) { 1432 | PGresult *pg_result = (PGresult *) rsrc->ptr; 1433 | PQclear(pg_result); 1434 | } 1435 | 1436 | static int swoole_pgsql_coro_onError(Reactor *reactor, Event *event) { 1437 | PGObject *object = (PGObject *) (event->socket->object); 1438 | zval *zobject = object->object; 1439 | 1440 | zend_update_property_string(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(zobject), ZEND_STRL("error"), "onerror"); 1441 | object->connected = false; 1442 | ZVAL_FALSE(object->return_value); 1443 | object->co->resume(); 1444 | 1445 | return SW_OK; 1446 | } 1447 | 1448 | static PHP_METHOD(swoole_postgresql_coro, __destruct) {} 1449 | 1450 | static int swoole_postgresql_coro_close(zval *zobject) { 1451 | PGObject *object = php_swoole_postgresql_coro_get_object(zobject); 1452 | if (!object || !object->conn) { 1453 | php_swoole_fatal_error(E_WARNING, "object is not instanceof swoole_postgresql_coro"); 1454 | return FAILURE; 1455 | } 1456 | 1457 | if (sw_reactor()) { 1458 | swSocket *_socket = object->socket; 1459 | if (!_socket->removed) { 1460 | sw_reactor()->del(_socket); 1461 | } 1462 | _socket->object = nullptr; 1463 | _socket->free(); 1464 | } 1465 | 1466 | PGresult *res; 1467 | if (object->connected) { 1468 | while ((res = PQgetResult(object->conn))) { 1469 | PQclear(res); 1470 | } 1471 | /** 1472 | * PQfinish will close fd 1473 | */ 1474 | PQfinish(object->conn); 1475 | /** 1476 | * fd marked -1, prevent double close 1477 | */ 1478 | object->socket->fd = -1; 1479 | object->conn = nullptr; 1480 | object->connected = false; 1481 | } 1482 | object->co = nullptr; 1483 | return SUCCESS; 1484 | } 1485 | 1486 | static PHP_METHOD(swoole_postgresql_coro, escape) { 1487 | char *str; 1488 | size_t l_str; 1489 | PGconn *pgsql; 1490 | 1491 | ZEND_PARSE_PARAMETERS_START(1, 1) 1492 | Z_PARAM_STRING(str, l_str) 1493 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1494 | 1495 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 1496 | if (!object || !object->conn) { 1497 | RETURN_FALSE; 1498 | } 1499 | pgsql = object->conn; 1500 | 1501 | zend_string *result = zend_string_alloc(l_str * 2, 0); 1502 | int error = 0; 1503 | size_t new_len = PQescapeStringConn(object->conn, result->val, str, l_str, &error); 1504 | 1505 | if (new_len == 0 || error) { 1506 | zend_update_property_string( 1507 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), PQerrorMessage(pgsql)); 1508 | zend_update_property_long(swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("errCode"), error); 1509 | zend_string_free(result); 1510 | RETURN_FALSE; 1511 | } else { 1512 | result->val[new_len] = 0; 1513 | result->len = new_len; 1514 | RETURN_STR(result); 1515 | } 1516 | } 1517 | 1518 | static PHP_METHOD(swoole_postgresql_coro, escapeLiteral) { 1519 | char *str, *tmp; 1520 | size_t l_str; 1521 | PGconn *pgsql; 1522 | 1523 | ZEND_PARSE_PARAMETERS_START(1, 1) 1524 | Z_PARAM_STRING(str, l_str) 1525 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1526 | 1527 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 1528 | if (!object || !object->conn) { 1529 | RETURN_FALSE; 1530 | } 1531 | pgsql = object->conn; 1532 | 1533 | tmp = PQescapeLiteral(pgsql, str, l_str); 1534 | if (tmp == nullptr) { 1535 | zend_update_property_string( 1536 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), PQerrorMessage(pgsql)); 1537 | 1538 | RETURN_FALSE; 1539 | } 1540 | 1541 | RETVAL_STRING(tmp); 1542 | PQfreemem(tmp); 1543 | } 1544 | 1545 | static PHP_METHOD(swoole_postgresql_coro, escapeIdentifier) { 1546 | char *str, *tmp; 1547 | size_t l_str; 1548 | PGconn *pgsql; 1549 | 1550 | ZEND_PARSE_PARAMETERS_START(1, 1) 1551 | Z_PARAM_STRING(str, l_str) 1552 | ZEND_PARSE_PARAMETERS_END_EX(RETURN_FALSE); 1553 | 1554 | PGObject *object = php_swoole_postgresql_coro_get_object(ZEND_THIS); 1555 | if (!object || !object->conn) { 1556 | RETURN_FALSE; 1557 | } 1558 | pgsql = object->conn; 1559 | 1560 | tmp = PQescapeIdentifier(pgsql, str, l_str); 1561 | if (tmp == nullptr) { 1562 | zend_update_property_string( 1563 | swoole_postgresql_coro_ce, SW_Z8_OBJ_P(ZEND_THIS), ZEND_STRL("error"), PQerrorMessage(pgsql)); 1564 | 1565 | RETURN_FALSE; 1566 | } 1567 | 1568 | RETVAL_STRING(tmp); 1569 | PQfreemem(tmp); 1570 | } 1571 | 1572 | /* {{{ PHP_MINIT_FUNCTION 1573 | */ 1574 | PHP_MINIT_FUNCTION(swoole_postgresql) { 1575 | #if 0 1576 | if (PHP_SWOOLE_EXT_POSTGRESQL_VERSION_ID != swoole_version_id()) { 1577 | php_swoole_fatal_error(E_CORE_ERROR, 1578 | "Ext version (%d) does not match the Swoole version (%d)", 1579 | PHP_SWOOLE_EXT_POSTGRESQL_VERSION_ID, 1580 | swoole_version_id()); 1581 | return FAILURE; 1582 | } 1583 | #endif 1584 | swoole_postgresql_init(module_number); 1585 | 1586 | return SUCCESS; 1587 | } 1588 | /* }}} */ 1589 | 1590 | /* {{{ PHP_MINFO_FUNCTION 1591 | */ 1592 | PHP_MINFO_FUNCTION(swoole_postgresql) { 1593 | char buf[64]; 1594 | php_info_print_table_start(); 1595 | php_info_print_table_header(2, "Swoole PostgreSQL", "enabled"); 1596 | php_info_print_table_row(2, "Author", "Swoole Team "); 1597 | php_info_print_table_row(2, "Version", SWOOLE_VERSION); 1598 | snprintf(buf, sizeof(buf), "%s %s", __DATE__, __TIME__); 1599 | php_info_print_table_row(2, "Built", buf); 1600 | 1601 | #ifdef SW_DEBUG 1602 | php_info_print_table_row(2, "debug", "enabled"); 1603 | #endif 1604 | #ifdef SW_LOG_TRACE_OPEN 1605 | php_info_print_table_row(2, "trace_log", "enabled"); 1606 | #endif 1607 | 1608 | php_info_print_table_end(); 1609 | 1610 | DISPLAY_INI_ENTRIES(); 1611 | } 1612 | /* }}} */ 1613 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | SWOOLE_LOG_INFO, 4 | 'trace_flags' => 0, 5 | ]); 6 | 7 | if (getenv('POSTGRES_HOST')) { 8 | $host = getenv('POSTGRES_HOST'); 9 | $port = getenv('POSTGRES_PORT'); 10 | define('TEST_DB_URI', "host={$host};port={$port};dbname=postgres;user=postgres;password=postgres"); 11 | } else { 12 | define('TEST_DB_URI', 'host=127.0.0.1;port=5432;dbname=test;user=postgres;password=postgres'); 13 | } 14 | 15 | Swoole\Coroutine\run(function () { 16 | $pg = new Swoole\Coroutine\PostgreSQL(); 17 | $pg->connect(TEST_DB_URI); 18 | $retval = $pg->query('DROP TABLE IF EXISTS weather'); 19 | if (!$retval) { 20 | var_dump($retval, $pg->error, $pg->notices); 21 | } 22 | 23 | $retval = $pg->query('CREATE TABLE weather ( 24 | id SERIAL primary key NOT NULL, 25 | city character varying(80), 26 | temp_lo integer, 27 | temp_hi integer, 28 | prcp real, 29 | date date)'); 30 | 31 | if (!$retval) { 32 | var_dump($retval, $pg->error, $pg->notices); 33 | } 34 | 35 | $retval = $pg->query("INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27') RETURNING id;"); 36 | if (!$retval) { 37 | var_dump($retval, $pg->error, $pg->notices); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /tests/unit/PostgreSQLTest.php: -------------------------------------------------------------------------------- 1 | connect(TEST_DB_URI, 5); 13 | $this->assertNotFalse($conn, (string) $pg->error); 14 | 15 | return $pg; 16 | } 17 | 18 | public function testEscape() 19 | { 20 | run(function () { 21 | $pg = $this->getConn(); 22 | $result = $pg->escape("' or 1=1 & 2"); 23 | $this->assertNotFalse($result, (string) $pg->error); 24 | $this->assertEquals("'' or 1=1 & 2", $result); 25 | }); 26 | } 27 | 28 | public function testInsert() 29 | { 30 | run(function () { 31 | $pg = $this->getConn(); 32 | $result = $pg->query("INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) 33 | VALUES ('Shanghai', 88, 10, 0.75,'1993-11-27') RETURNING id"); 34 | $this->assertNotFalse($result, (string) $pg->error); 35 | $this->assertEquals(1, $pg->numRows($result)); 36 | $this->assertGreaterThan(1, $pg->fetchAssoc($result)['id']); 37 | }); 38 | } 39 | 40 | public function testPrepare() 41 | { 42 | run(function () { 43 | $pg = $this->getConn(); 44 | $prepare_result = $pg->prepare('key', "INSERT INTO weather(city, temp_lo, temp_hi, prcp, date) 45 | VALUES ($1, $2, $3, $4, $5) RETURNING id"); 46 | $this->assertNotFalse($prepare_result, (string) $pg->error); 47 | $execute_result = $pg->execute('key', ['Beijing', rand(1000, 99999), 10, 0.75, '1993-11-23']); 48 | $this->assertNotFalse($execute_result, (string) $pg->error); 49 | }); 50 | } 51 | 52 | public function testQuery() 53 | { 54 | run(function () { 55 | $pg = $this->getConn(); 56 | $result = $pg->query('SELECT * FROM weather;'); 57 | $this->assertNotFalse($result, (string) $pg->error); 58 | 59 | $arr = $pg->fetchAll($result); 60 | $this->assertIsArray($arr); 61 | $this->assertEquals($arr[0]['city'], 'San Francisco'); 62 | }); 63 | } 64 | 65 | public function testNoFieldName() 66 | { 67 | run(function () { 68 | $pg = $this->getConn(); 69 | $result = $pg->query('SELECT 11, 22'); 70 | $this->assertNotFalse($result, (string) $pg->error); 71 | 72 | $arr = $pg->fetchAll($result); 73 | $this->assertIsArray($arr); 74 | $this->assertEquals($arr[0]['?column?'], 11); 75 | $this->assertEquals($arr[0]['?column?1'], 22); 76 | }); 77 | } 78 | 79 | public function testNotConnected() 80 | { 81 | run(function () { 82 | $pg = new Swoole\Coroutine\PostgreSQL(); 83 | 84 | $this->assertFalse($pg->escape('')); 85 | $this->assertFalse($pg->escapeLiteral('')); 86 | $this->assertFalse($pg->escapeIdentifier('')); 87 | $this->assertFalse($pg->query('')); 88 | $this->assertFalse($pg->prepare('', '')); 89 | $this->assertFalse($pg->execute('', [])); 90 | $this->assertFalse($pg->metaData('')); 91 | }); 92 | } 93 | 94 | public function testConnectFailed() 95 | { 96 | run(function () { 97 | $pg = new Swoole\Coroutine\PostgreSQL(); 98 | $this->assertFalse($pg->connect('')); 99 | $conn = $pg->connect(TEST_DB_URI); 100 | $this->assertNotFalse($conn, (string) $pg->error); 101 | }); 102 | } 103 | } 104 | --------------------------------------------------------------------------------