From b8a24257ebcb24d7815a276aa75922892f97f274 Mon Sep 17 00:00:00 2001 From: SunBeau Date: Tue, 7 May 2024 22:32:28 +0800 Subject: [PATCH 1/7] Support PostgreSQL: interface/test-cases/ci-testing --- .github/workflows/ci-run-test-extensions.yml | 89 ++++++ CMakeLists.txt | 31 +- Makefile.in | 4 +- README.md | 2 +- configure | 103 ++++++ configure.ac | 22 ++ include/qlibc/extensions/qdatabase.h | 20 +- src/extensions/qdatabase.c | 315 +++++++++++++++++-- src/internal/qlibpq.h | 183 +++++++++++ tests/CMakeLists.txt | 4 + tests/Makefile.in | 17 +- tests/test_qdatabase_pgsql.c | 303 ++++++++++++++++++ 12 files changed, 1059 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/ci-run-test-extensions.yml create mode 100644 src/internal/qlibpq.h create mode 100644 tests/test_qdatabase_pgsql.c diff --git a/.github/workflows/ci-run-test-extensions.yml b/.github/workflows/ci-run-test-extensions.yml new file mode 100644 index 00000000..c01bd667 --- /dev/null +++ b/.github/workflows/ci-run-test-extensions.yml @@ -0,0 +1,89 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: ci-run-test-extensions + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + test-on-ubuntu-2204: + name: test-on-ubuntu-22.04 + runs-on: ubuntu-latest + container: + image: docker.io/ubuntu:22.04 + env: + TZ: Asia/Shanghai + DEBIAN_FRONTEND: noninteractive + volumes: + - ${{ github.workspace }}:${{ github.workspace }} + + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Intall Deps + run: | + export DEBIAN_FRONTEND=noninteractive + echo "Asia/Shanghai" > /etc/timezone + apt-get update + apt-get install -y gcc make cmake autoconf sudo lsb-release curl ca-certificates tzdata + dpkg-reconfigure --frontend noninteractive tzdata + + - name: Install database + run: | + install -d /usr/share/postgresql-common/pgdg + curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc + sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + apt-get update + apt-get install -y postgresql-15 libpq-dev + + - name: Start database + run: | + sudo -u postgres echo "123456" > /tmp/pg.pwfile + sudo -u postgres /usr/lib/postgresql/15/bin/initdb -D /tmp/data -U postgres -A md5 --pwfile=/tmp/pg.pwfile + sudo -u postgres /usr/lib/postgresql/15/bin/pg_ctl -D /tmp/data -l /tmp/data/logfile start + PGPASSWORD=123456 psql -h 127.0.0.1 -U postgres -c "SELECT 1" + + - name: Configure And make + working-directory: ${{ github.workspace }} + run: | + # autoconf + ./configure --with-pgsql \ + --with-pgsql-incdir=/usr/include/postgresql \ + --with-pgsql-libdir=/usr/lib/x86_64-linux-gnu + make + + - name: Test + working-directory: ${{ github.workspace }} + run: | + cd tests/ + make test + make test-ext + make clean + cd ../ + make clean + + - name: Cmake And make + working-directory: ${{ github.workspace }} + run: | + mkdir build/ && cd build/ + cmake .. \ + -DWITH_PGSQL=ON \ + -DWITH_PGSQL_INCLUDE_DIR=/usr/include/postgresql \ + -DWITH_PGSQL_LIBRARY_DIR=/usr/lib/x86_64-linux-gnu + make && make install + + - name: Cmake Test + working-directory: ${{ github.workspace }} + run: | + cd build/ + make test diff --git a/CMakeLists.txt b/CMakeLists.txt index 63ce5540..103840bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,25 @@ IF (NOT CMAKE_USE_PTHREADS_INIT) MESSAGE(FATAL_ERROR "Couldn't find pthreads.") ENDIF() +IF(NOT "${WITH_PGSQL}" STREQUAL "") + message(STATUS "with_pgsql: ${WITH_PGSQL}") + # check "libpq-fe.h" + if(EXISTS "${WITH_PGSQL_INCLUDE_DIR}/libpq-fe.h") + message(STATUS "found 'libpq-fe.h' header.") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DENABLE_PGSQL") + else() + message(FATAL_ERROR "Cannot find 'libpq-fe.h' header. Use '-DWITH_PGSQL_INCLUDE_DIR=DIR' to specify the directory where 'libpq-fe.h' is located.") + endif() + + # check "libpq.so" + if(EXISTS "${WITH_PGSQL_LIBRARY_DIR}/libpq.so") + message(STATUS "found 'libpq.so' library.") + SET(QLIBC_LINK_LIBS "pq") + else() + message(FATAL_ERROR "Cannot find 'libpq.so' library. Use '-DWITH_PGSQL_LIBRARY_DIR=DIR' to specify the directory where 'libpq.so' is located.") + endif() +ENDIF() + SET(SRC_SUBPATHS containers/*.c utilities/*.c @@ -80,20 +99,26 @@ FILE(GLOB_RECURSE SRC_LIB_EXT SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${qlibc_SOURCE_DIR}/lib) SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${qlibc_SOURCE_DIR}/lib) +LIST(APPEND QLIBC_INCLUDE_DIRECTORIES ${qlibc_SOURCE_DIR}/src/internal) +LIST(APPEND QLIBC_INCLUDE_DIRECTORIES ${WITH_PGSQL_INCLUDE_DIR}) +LIST(APPEND QLIBC_LINK_DIRECTORIES ${WITH_PGSQL_LIBRARY_DIR}) + +INCLUDE_DIRECTORIES(${QLIBC_INCLUDE_DIRECTORIES}) +LINK_DIRECTORIES(${QLIBC_LINK_DIRECTORIES}) + ADD_LIBRARY(qlibc-static STATIC ${SRC_LIB}) ADD_LIBRARY(qlibc SHARED ${SRC_LIB}) ADD_LIBRARY(qlibcext-static STATIC ${SRC_LIB_EXT}) ADD_LIBRARY(qlibcext SHARED ${SRC_LIB_EXT}) -INCLUDE_DIRECTORIES(${qlibc_SOURCE_DIR}/src/internal) TARGET_INCLUDE_DIRECTORIES(qlibc-static PUBLIC ${qlibc_SOURCE_DIR}/include/qlibc) TARGET_INCLUDE_DIRECTORIES(qlibc PUBLIC ${qlibc_SOURCE_DIR}/include/qlibc) TARGET_INCLUDE_DIRECTORIES(qlibcext-static PUBLIC ${qlibc_SOURCE_DIR}/include/qlibc) TARGET_LINK_LIBRARIES(qlibc-static PRIVATE ${CMAKE_THREAD_LIBS_INIT}) TARGET_LINK_LIBRARIES(qlibc PRIVATE ${CMAKE_THREAD_LIBS_INIT}) -TARGET_LINK_LIBRARIES(qlibcext-static PRIVATE ${CMAKE_THREAD_LIBS_INIT}) -TARGET_LINK_LIBRARIES(qlibcext PUBLIC qlibc) +TARGET_LINK_LIBRARIES(qlibcext-static PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${QLIBC_LINK_LIBS}) +TARGET_LINK_LIBRARIES(qlibcext PUBLIC qlibc ${QLIBC_LINK_LIBS}) SET(QLIBC_HEADER "${qlibc_SOURCE_DIR}/include/qlibc") INSTALL(DIRECTORY ${QLIBC_HEADER} DESTINATION include) diff --git a/Makefile.in b/Makefile.in index 6372eeb1..ae431b4e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -34,6 +34,9 @@ all: test: all make -C tests test +test-ext: all + make -C tests test-ext + install: make -C src install @@ -44,7 +47,6 @@ uninstall: clean: make -C src clean - distclean: clean @for DIR in src tests examples; do \ echo "===> $${DIR}"; \ diff --git a/README.md b/README.md index 86df44d3..dfcf1421 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Please refer the LICENSE document included in the package for more details. * INI-style Configuration File Parser. * HTTP client. * Rotating File Logger. - * Database(MySQL) interface. + * Database(MySQL/PostgreSQL) interface. * [Token-Bucket](https://en.wikipedia.org/wiki/Token_bucket) ## qLibc Tables at a Glance diff --git a/configure b/configure index 64ab9529..49a1e077 100755 --- a/configure +++ b/configure @@ -732,6 +732,9 @@ enable_ext_qhttpclient enable_ext_qdatabase with_openssl with_mysql +with_pgsql +with_pgsql_incdir +with_pgsql_libdir ' ac_precious_vars='build_alias host_alias @@ -1372,6 +1375,11 @@ Optional Packages: extension API. When it's enabled, user applications need to link mysql client library. (ex: -lmysqlclient) + --with-pgsql This will enable PostgreSQL database support in + qdatabase extension API. When it's enabled, user + applications need to link libpq library. (ex: -lpq) + --with-pgsql-incdir=DIR site header files for PostgreSQL in DIR. + --with-pgsql-libdir=DIR site library files for PostgreSQL in DIR Some influential environment variables: CC C compiler command @@ -4843,6 +4851,101 @@ See \`config.log' for more details" "$LINENO" 5; } fi fi + +# Check whether --with-pgsql was given. +if test "${with_pgsql+set}" = set; then : + withval=$with_pgsql; +else + withval=no +fi + +if test "$withval" = yes; then + # check libpq-fe.h + as_ac_File=`$as_echo "ac_cv_file_$with_pgsql_incdir/libpq-fe.h" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $with_pgsql_incdir/libpq-fe.h" >&5 +$as_echo_n "checking for $with_pgsql_incdir/libpq-fe.h... " >&6; } +if eval \${$as_ac_File+:} false; then : + $as_echo_n "(cached) " >&6 +else + test "$cross_compiling" = yes && + as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5 +if test -r "$with_pgsql_incdir/libpq-fe.h"; then + eval "$as_ac_File=yes" +else + eval "$as_ac_File=no" +fi +fi +eval ac_res=\$$as_ac_File + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_File"\" = x"yes"; then : + withval=yes +else + withval=no +fi + + if test "$withval" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: find '$with_pgsql_incdir/libpq-fe.h'" >&5 +$as_echo "$as_me: find '$with_pgsql_incdir/libpq-fe.h'" >&6;} + CPPFLAGS="$CPPFLAGS -DENABLE_PGSQL -I$with_pgsql_incdir" + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "Cannot find 'libpq-fe.h' header. Use --with-pgsql-incdir=DIR to specify the directory where 'libpq-fe.h' is located. +See \`config.log' for more details" "$LINENO" 5; } + fi + # check libpq.so + as_ac_File=`$as_echo "ac_cv_file_$with_pgsql_libdir/libpq.so" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $with_pgsql_libdir/libpq.so" >&5 +$as_echo_n "checking for $with_pgsql_libdir/libpq.so... " >&6; } +if eval \${$as_ac_File+:} false; then : + $as_echo_n "(cached) " >&6 +else + test "$cross_compiling" = yes && + as_fn_error $? "cannot check for file existence when cross compiling" "$LINENO" 5 +if test -r "$with_pgsql_libdir/libpq.so"; then + eval "$as_ac_File=yes" +else + eval "$as_ac_File=no" +fi +fi +eval ac_res=\$$as_ac_File + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_File"\" = x"yes"; then : + withval=yes +else + withval=no +fi + + if test "$withval" = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: find '$with_pgsql_libdir/libpq.so'" >&5 +$as_echo "$as_me: find '$with_pgsql_libdir/libpq.so'" >&6;} + CPPFLAGS="$CPPFLAGS -Wl,-rpath,$with_pgsql_libdir -L$with_pgsql_libdir -lpq" + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "Cannot find 'libpq.so' library. Use --with-pgsql-libdir=DIR to specify the directory where 'libpq.so' is located. +See \`config.log' for more details" "$LINENO" 5; } + fi +fi + +# Check whether --with-pgsql_incdir was given. +if test "${with_pgsql_incdir+set}" = set; then : + withval=$with_pgsql_incdir; +else + withval=no +fi + + +# Check whether --with-pgsql_libdir was given. +if test "${with_pgsql_libdir+set}" = set; then : + withval=$with_pgsql_libdir; +else + withval=no +fi + + { $as_echo "$as_me:${as_lineno-$LINENO}: CFLAGS $CFLAGS" >&5 $as_echo "$as_me: CFLAGS $CFLAGS" >&6;} { $as_echo "$as_me:${as_lineno-$LINENO}: CPPFLAGS $CPPFLAGS" >&5 diff --git a/configure.ac b/configure.ac index 0630fa55..bceef450 100644 --- a/configure.ac +++ b/configure.ac @@ -198,6 +198,28 @@ if test "$withval" = yes; then fi fi +AC_ARG_WITH([pgsql],[AS_HELP_STRING([--with-pgsql], [This will enable PostgreSQL database support in qdatabase extension API. When it's enabled, user applications need to link libpq library. (ex: -lpq)])],[],[withval=no]) +if test "$withval" = yes; then + # check libpq-fe.h + AC_CHECK_FILE([$with_pgsql_incdir/libpq-fe.h],[withval=yes],[withval=no]) + if test "$withval" = yes; then + AC_MSG_NOTICE([find '$with_pgsql_incdir/libpq-fe.h']) + CPPFLAGS="$CPPFLAGS -DENABLE_PGSQL -I$with_pgsql_incdir" + else + AC_MSG_FAILURE([Cannot find 'libpq-fe.h' header. Use --with-pgsql-incdir=DIR to specify the directory where 'libpq-fe.h' is located.]) + fi + # check libpq.so + AC_CHECK_FILE([$with_pgsql_libdir/libpq.so],[withval=yes],[withval=no]) + if test "$withval" = yes; then + AC_MSG_NOTICE([find '$with_pgsql_libdir/libpq.so']) + CPPFLAGS="$CPPFLAGS -Wl,-rpath,$with_pgsql_libdir -L$with_pgsql_libdir -lpq" + else + AC_MSG_FAILURE([Cannot find 'libpq.so' library. Use --with-pgsql-libdir=DIR to specify the directory where 'libpq.so' is located.]) + fi +fi +AC_ARG_WITH([pgsql_incdir],[AS_HELP_STRING([--with-pgsql-incdir=DIR], [site header files for PostgreSQL in DIR.])],[],[withval=no]) +AC_ARG_WITH([pgsql_libdir],[AS_HELP_STRING([--with-pgsql-libdir=DIR], [site library files for PostgreSQL in DIR])],[],[withval=no]) + AC_MSG_NOTICE([CFLAGS $CFLAGS]) AC_MSG_NOTICE([CPPFLAGS $CPPFLAGS]) #AC_MSG_NOTICE([LIBS $LIBS]) diff --git a/include/qlibc/extensions/qdatabase.h b/include/qlibc/extensions/qdatabase.h index 1101731d..3da36d06 100644 --- a/include/qlibc/extensions/qdatabase.h +++ b/include/qlibc/extensions/qdatabase.h @@ -51,6 +51,10 @@ extern "C" { #define Q_ENABLE_MYSQL (1) #endif /* _mysql_h */ +#ifdef LIBPQ_FE_H +#define Q_ENABLE_PGSQL (1) +#endif /* LIBPQ_FE_H */ + /* types */ typedef struct qdbresult_s qdbresult_t; typedef struct qdb_s qdb_t; @@ -65,11 +69,11 @@ extern qdb_t *qdb(const char *dbtype, */ struct qdbresult_s { /* encapsulated member functions */ - const char *(*getstr) (qdbresult_t *result, const char *field); + const char *(*get_str) (qdbresult_t *result, const char *field); const char *(*get_str_at) (qdbresult_t *result, int idx); - int (*getint) (qdbresult_t *result, const char *field); + int (*get_int) (qdbresult_t *result, const char *field); int (*get_int_at) (qdbresult_t *result, int idx); - bool (*getnext) (qdbresult_t *result); + bool (*get_next) (qdbresult_t *result); int (*get_cols) (qdbresult_t *result); int (*get_rows) (qdbresult_t *result); @@ -86,6 +90,11 @@ struct qdbresult_s { int cols; int cursor; #endif + +#ifdef Q_ENABLE_PGSQL + /* private variables for pgsql database - do not access directly */ + void *pgsql; +#endif }; /* qdb object structure */ @@ -131,6 +140,11 @@ struct qdb_s { /* private variables for mysql database - do not access directly */ MYSQL *mysql; #endif + +#ifdef Q_ENABLE_PGSQL + /* private variables for pgsql database - do not access directly */ + void *pgsql; +#endif }; #ifdef __cplusplus diff --git a/src/extensions/qdatabase.c b/src/extensions/qdatabase.c index ad238307..29895baf 100644 --- a/src/extensions/qdatabase.c +++ b/src/extensions/qdatabase.c @@ -77,7 +77,7 @@ #ifndef DISABLE_QDATABASE -#if defined(ENABLE_MYSQL) || defined( _DOXYGEN_SKIP) +#if defined(ENABLE_MYSQL) || defined(ENABLE_PGSQL) ||defined( _DOXYGEN_SKIP) #ifdef ENABLE_MYSQL #include "mysql.h" @@ -86,7 +86,11 @@ #define Q_MYSQL_OPT_CONNECT_TIMEOUT (10) #define Q_MYSQL_OPT_READ_TIMEOUT (30) #define Q_MYSQL_OPT_WRITE_TIMEOUT (30) -#endif +#endif /* ENABLE_MYSQL */ + +#ifdef ENABLE_PGSQL +#include "qlibpq.h" +#endif /* ENABLE_PGSQL */ #include #include @@ -120,11 +124,11 @@ static const char *get_error(qdb_t *db, unsigned int *errorno); static void free_(qdb_t *db); // qdbresult_t object -static const char *_resultGetStr(qdbresult_t *result, const char *field); -static const char *_resultGetStrAt(qdbresult_t *result, int idx); -static int _resultGetInt(qdbresult_t *result, const char *field); -static int _resultGetIntAt(qdbresult_t *result, int idx); -static bool _resultGetNext(qdbresult_t *result); +static const char *result_get_str(qdbresult_t *result, const char *field); +static const char *result_get_str_at(qdbresult_t *result, int idx); +static int result_get_int(qdbresult_t *result, const char *field); +static int result_get_int_at(qdbresult_t *result, int idx); +static bool result_get_next(qdbresult_t *result); static int result_get_cols(qdbresult_t *result); static int result_get_rows(qdbresult_t *result); @@ -132,39 +136,52 @@ static int result_get_row(qdbresult_t *result); static void result_free(qdbresult_t *result); -#endif +#endif /* _DOXYGEN_SKIP */ /** * Initialize internal connector structure * - * @param dbtype database server type. currently "MYSQL" is only supported + * @param dbtype database server type. currently "MYSQL" and "PGSQL" are supported * @param addr ip or fqdn address. * @param port port number + * @param database database name. * @param username database username * @param password database password - * @param database database server type. currently "MYSQL" is only supported * @param autocommit sets autocommit mode on if autocommit is true, off if * autocommit is false + * (can only be set to true, when dbtype is "PGSQL") * * @return a pointer of qdb_t object in case of successful, * otherwise returns NULL. * * @code - * qdb_t *db = qdb("MYSQL", - * "dbhost.qdecoder.org", 3306, "test", "secret", - * "sampledb", true); + * qdb_t *db = qdb("MYSQL", "dbhost.qdecoder.org", 3306, + * "sampledb", "test", "secret", true); + * if (db == NULL) { + * printf("ERROR: Not supported database type.\n"); + * return -1; + * } + * @endcode + * + * @code + * qdb_t *db = qdb("PGSQL", "127.0.0.1", 5432, + * "sampledb", "test", "secret", true); * if (db == NULL) { * printf("ERROR: Not supported database type.\n"); * return -1; * } * @endcode + */ -qdb_t *qdb(const char *dbtype, const char *addr, int port, const char *username, - const char *password, const char *database, bool autocommit) +qdb_t *qdb(const char *dbtype, + const char *addr, int port, const char *database, + const char *username, const char *password, bool autocommit) { // check db type #ifdef Q_ENABLE_MYSQL if (strcmp(dbtype, "MYSQL")) return NULL; +#elif defined(Q_ENABLE_PGSQL) + if (strcmp(dbtype, "PGSQL")) return NULL; #else return NULL; #endif @@ -176,6 +193,12 @@ qdb_t *qdb(const char *dbtype, const char *addr, int port, const char *username, return NULL; } +#ifdef Q_ENABLE_PGSQL + if (autocommit == false) { + return NULL; + } +#endif + // initialize qdb_t *db; if ((db = (qdb_t *)malloc(sizeof(qdb_t))) == NULL) return NULL; @@ -195,6 +218,8 @@ qdb_t *qdb(const char *dbtype, const char *addr, int port, const char *username, // set db handler #ifdef Q_ENABLE_MYSQL db->mysql = NULL; +#elif defined(Q_ENABLE_PGSQL) + db->pgsql = NULL; #endif // assign methods @@ -304,6 +329,61 @@ static bool open_(qdb_t *db) db->connected = true; Q_MUTEX_LEAVE(db->qmutex); return true; +#elif defined(Q_ENABLE_PGSQL) + Q_MUTEX_ENTER(db->qmutex); + + bool ret = true; + PGconn* pgconn = NULL; + + // initialize handler + if (db->pgsql != NULL) close_(db); + + if ((db->pgsql = pgsql_init()) == NULL) { + ret = false; + goto lable_quit; + } + + // set options + /* do nothing */ + + /* int to char* */ + char pgport[10]; + snprintf(pgport, sizeof(pgport), "%d", db->info.port); + + // try to connect + pgconn = PQsetdbLogin(db->info.addr, pgport, + NULL, NULL, + db->info.database, db->info.username, db->info.password); + if (pgconn == NULL) { + close_(db); // free pgsql handler + ret = false; + goto lable_quit; + } + + if (PQstatus(pgconn) != CONNECTION_OK) { + const char* emsg = PQerrorMessage(pgconn); + pgsql_set_emsg(db->pgsql, emsg); + // fprintf(stderr, "%s", emsg); + ret = false; + goto lable_quit; + } + + qtype_cast(pgsql_t*, db->pgsql)->pgconn = pgconn; + + // set auto-commit + /* do nothing */ + + // set flag + db->connected = true; + +lable_quit: + + if (ret == false) { + PQfinish(pgconn); + } + + Q_MUTEX_LEAVE(db->qmutex); + return ret; #else return false; #endif @@ -334,6 +414,17 @@ static bool close_(qdb_t *db) } db->connected = false; + Q_MUTEX_LEAVE(db->qmutex); + return true; +#elif defined(Q_ENABLE_PGSQL) + Q_MUTEX_ENTER(db->qmutex); + + if (db->pgsql != NULL) { + pgsql_close(db->pgsql); + db->pgsql = NULL; + } + db->connected = false; + Q_MUTEX_LEAVE(db->qmutex); return true; #else @@ -365,6 +456,20 @@ static int execute_update(qdb_t *db, const char *query) if ((affected = mysql_affected_rows(db->mysql)) < 0) affected = -1; } + Q_MUTEX_LEAVE(db->qmutex); + return affected; +#elif defined(Q_ENABLE_PGSQL) + Q_MUTEX_ENTER(db->qmutex); + + int affected = -1; + + // query + DEBUG("%s", query); + if (pgsql_query(db->pgsql, query, PQ_WRITE) == 0) { + /* get affected rows */ + if ((affected = pgsql_affected_rows(db->pgsql)) < 0) affected = -1; + } + Q_MUTEX_LEAVE(db->qmutex); return affected; #else @@ -431,11 +536,41 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) result->cursor = 0; /* assign methods */ - result->get_str = _resultGetStr; - result->get_str_at = _resultGetStrAt; - result->get_int = _resultGetInt; - result->get_int_at = _resultGetIntAt; - result->get_next = _resultGetNext; + result->get_str = result_get_str; + result->get_str_at = result_get_str_at; + result->get_int = result_get_int; + result->get_int_at = result_get_int_at; + result->get_next = result_get_next; + + result->get_cols = result_get_cols; + result->get_rows = result_get_rows; + result->get_row = result_get_row; + + result->free = result_free; + + return result; +#elif defined(Q_ENABLE_PGSQL) + // query + DEBUG("%s", query); + if (pgsql_query(db->pgsql, query, PQ_READ)) return NULL; + + // store + qdbresult_t *result = (qdbresult_t *)malloc(sizeof(qdbresult_t)); + if (result == NULL) return NULL; + result->pgsql = db->pgsql; + + /* get meta data */ + // result->row = NULL; + qtype_cast(pgsql_t*, result->pgsql)->rows = pgsql_num_rows(result->pgsql); + qtype_cast(pgsql_t*, result->pgsql)->cols = pgsql_num_fields(result->pgsql); + qtype_cast(pgsql_t*, result->pgsql)->cursor = 0; + + /* assign methods */ + result->get_str = result_get_str; + result->get_str_at = result_get_str_at; + result->get_int = result_get_int; + result->get_int_at = result_get_int_at; + result->get_next = result_get_next; result->get_cols = result_get_cols; result->get_rows = result_get_rows; @@ -500,6 +635,18 @@ static bool begin_tran(qdb_t *db) } result->free(result); return true; +#elif defined(Q_ENABLE_PGSQL) + Q_MUTEX_ENTER(db->qmutex); + if (qtype_cast(qmutex_t*, db->qmutex)->count != 1) { + Q_MUTEX_LEAVE(db->qmutex); + return false; + } + + if (pgsql_query(db->pgsql, "START TRANSACTION", PQ_WRITE) < 0) { + Q_MUTEX_LEAVE(db->qmutex); + return false; + } + return true; #else return false; #endif @@ -526,6 +673,16 @@ static bool commit(qdb_t *db) Q_MUTEX_LEAVE(db->qmutex); } return ret; +#elif defined(Q_ENABLE_PGSQL) + bool ret = false; + if (pgsql_query(db->pgsql, "COMMIT", PQ_WRITE) == 0) { + ret = true; + } + + if (qtype_cast(qmutex_t*, db->qmutex)->count > 0) { + Q_MUTEX_LEAVE(db->qmutex); + } + return ret; #else return false; #endif @@ -552,6 +709,16 @@ static bool rollback(qdb_t *db) Q_MUTEX_LEAVE(db->qmutex); } return ret; +#elif defined(Q_ENABLE_PGSQL) + bool ret = false; + if (pgsql_query(db->pgsql, "ROLLBACK", PQ_WRITE) == 0) { + ret = true; + } + + if (qtype_cast(qmutex_t*, db->qmutex)->count > 0) { + Q_MUTEX_LEAVE(db->qmutex); + } + return ret; #else return 0; #endif @@ -577,8 +744,17 @@ static bool rollback(qdb_t *db) static bool set_fetchtype(qdb_t *db, bool fromdb) { if (db == NULL) return false; + +#ifdef Q_ENABLE_MYSQL db->info.fetchtype = fromdb; return true; +#elif defined(Q_ENABLE_PGSQL) + if (db->pgsql == NULL) return false; + pgsql_set_emsg(db->pgsql, "unsupported operation"); + return false; +#else + return false; +#endif } /** @@ -623,6 +799,17 @@ static bool ping(qdb_t *db) } } + return false; +#elif defined(Q_ENABLE_PGSQL) + if (db->connected == true && pgsql_query(db->pgsql, NULL, PQ_PING) == 0) { + return true; + } else { // ping test failed + if (open_(db) == true) { // try re-connect + DEBUG("Connection recovered."); + return true; + } + } + return false; #else return false; @@ -650,6 +837,14 @@ static const char *get_error(qdb_t *db, unsigned int *errorno) eno = mysql_errno(db->mysql); if (eno == 0) emsg = "(no error)"; else emsg = mysql_error(db->mysql); +#elif defined(Q_ENABLE_PGSQL) + emsg = qtype_cast(pgsql_t*, db->pgsql)->emsg; + if (emsg == NULL) { + eno = 0; + emsg = "(no error)"; + } else { + eno = 1; + } #else emsg = "(not implemented)"; #endif @@ -695,7 +890,7 @@ static void free_(qdb_t *db) * @note * Do not free returned string. */ -static const char *_resultGetStr(qdbresult_t *result, const char *field) +static const char *result_get_str(qdbresult_t *result, const char *field) { #ifdef Q_ENABLE_MYSQL if (result == NULL || result->rs == NULL || result->cols <= 0) return NULL; @@ -710,6 +905,34 @@ static const char *_resultGetStr(qdbresult_t *result, const char *field) } return NULL; +#elif defined(Q_ENABLE_PGSQL) + if (result == NULL + || result->pgsql == NULL + || qtype_cast(pgsql_t*, result->pgsql)->cols <= 0) { + return NULL; + } + + const char *val = NULL; + int rows = qtype_cast(pgsql_t*, result->pgsql)->rows; + int cols = qtype_cast(pgsql_t*, result->pgsql)->cols; + int cur = qtype_cast(pgsql_t*, result->pgsql)->cursor; + + /* get row num */ + int row = -1; + for (int i = 0; i < rows; i++) { + val = PQfname(qtype_cast(pgsql_t*, result->pgsql)->pgresult, i); + if (!strcasecmp(val, field)) { + row = i; + break; + } + } + + if (row == -1) { + return NULL; + } + + val = PQgetvalue(qtype_cast(pgsql_t*, result->pgsql)->pgresult, cur, row); + return val; #else return NULL; #endif @@ -723,7 +946,7 @@ static const char *_resultGetStr(qdbresult_t *result, const char *field) * * @return a string pointer if successful, otherwise returns NULL. */ -static const char *_resultGetStrAt(qdbresult_t *result, int idx) +static const char *result_get_str_at(qdbresult_t *result, int idx) { #ifdef Q_ENABLE_MYSQL if (result == NULL @@ -734,6 +957,19 @@ static const char *_resultGetStrAt(qdbresult_t *result, int idx) return NULL; } return result->row[idx-1]; +#elif defined(Q_ENABLE_PGSQL) + if (result == NULL + || result->pgsql == NULL + || qtype_cast(pgsql_t*, result->pgsql)->rows == 0 + || qtype_cast(pgsql_t*, result->pgsql)->cols == 0 + || idx <= 0 + || idx > qtype_cast(pgsql_t*, result->pgsql)->cols) { + return NULL; + } + + int cur = qtype_cast(pgsql_t*, result->pgsql)->cursor; + const char *val = PQgetvalue(qtype_cast(pgsql_t*, result->pgsql)->pgresult, cur, idx - 1); + return val; #else return NULL; #endif @@ -747,7 +983,7 @@ static const char *_resultGetStrAt(qdbresult_t *result, int idx) * * @return a integer converted value */ -static int _resultGetInt(qdbresult_t *result, const char *field) +static int result_get_int(qdbresult_t *result, const char *field) { const char *val = result->get_str(result, field); if (val == NULL) return 0; @@ -762,7 +998,7 @@ static int _resultGetInt(qdbresult_t *result, const char *field) * * @return a integer converted value */ -static int _resultGetIntAt(qdbresult_t *result, int idx) +static int result_get_int_at(qdbresult_t *result, int idx) { const char *val = result->get_str_at(result, idx); if (val == NULL) return 0; @@ -776,7 +1012,7 @@ static int _resultGetIntAt(qdbresult_t *result, int idx) * * @return true if successful, false if no more rows are left */ -static bool _resultGetNext(qdbresult_t *result) +static bool result_get_next(qdbresult_t *result) { #ifdef Q_ENABLE_MYSQL if (result == NULL || result->rs == NULL) return false; @@ -784,6 +1020,20 @@ static bool _resultGetNext(qdbresult_t *result) if ((result->row = mysql_fetch_row(result->rs)) == NULL) return false; result->cursor++; + return true; +#elif defined(Q_ENABLE_PGSQL) + if (result == NULL + || result->pgsql == NULL + || qtype_cast(pgsql_t*, result->pgsql)->pgresult == NULL) { + return false; + } + + int cursor = qtype_cast(pgsql_t*, result->pgsql)->cursor; + if (++cursor == qtype_cast(pgsql_t*, result->pgsql)->rows) { + return false; + } + + qtype_cast(pgsql_t*, result->pgsql)->cursor = cursor; return true; #else return false; @@ -802,6 +1052,9 @@ static int result_get_cols(qdbresult_t *result) #ifdef Q_ENABLE_MYSQL if (result == NULL || result->rs == NULL) return 0; return result->cols; +#elif defined(Q_ENABLE_PGSQL) + if (result == NULL || result->pgsql == NULL) return 0; + return qtype_cast(pgsql_t*, result->pgsql)->cols; #else return 0; #endif @@ -819,6 +1072,9 @@ static int result_get_rows(qdbresult_t *result) #ifdef Q_ENABLE_MYSQL if (result == NULL || result->rs == NULL) return 0; return mysql_num_rows(result->rs); +#elif defined(Q_ENABLE_PGSQL) + if (result == NULL || result->pgsql == NULL) return 0; + return qtype_cast(pgsql_t*, result->pgsql)->rows; #else return 0; #endif @@ -839,6 +1095,9 @@ static int result_get_row(qdbresult_t *result) #ifdef Q_ENABLE_MYSQL if (result == NULL || result->rs == NULL) return 0; return result->cursor; +#elif defined(Q_ENABLE_PGSQL) + if (result == NULL || result->pgsql == NULL) return 0; + return qtype_cast(pgsql_t*, result->pgsql)->cursor; #else return 0; #endif @@ -862,6 +1121,12 @@ static void result_free(qdbresult_t *result) } free(result); return; +#elif defined(Q_ENABLE_PGSQL) + if (result) { + result->pgsql = NULL; + free(result); + } + return; #else return; #endif diff --git a/src/internal/qlibpq.h b/src/internal/qlibpq.h new file mode 100644 index 00000000..60353cf6 --- /dev/null +++ b/src/internal/qlibpq.h @@ -0,0 +1,183 @@ +/****************************************************************************** + * qLibc + * + * Copyright (c) 2024 SunBeau. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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. + *****************************************************************************/ + +/* Wrap the low-level functions of the libpq library */ + +#ifndef QLIBPQ_H +#define QLIBPQ_H + +#include "libpq-fe.h" +#include +#include + +#define qtype_cast(_type, _src) ((_type)_src) + +typedef enum { + PQ_UNKNOWN, + PQ_WRITE, + PQ_READ, + PQ_PING, +} pgquery_t; + +typedef struct { + char *emsg; + PGconn *pgconn; + + PGresult *pgresult; + int rows; + int cols; + int cursor; +} pgsql_t; + +static inline pgsql_t* pgsql_init(void) +{ + pgsql_t* pgsql = (pgsql_t *)calloc(1, sizeof(pgsql_t)); + return pgsql; +} + +static inline const char* pgsql_set_emsg(pgsql_t* pgsql, const char* emsg) +{ + if (pgsql == NULL || emsg == NULL) { + return NULL; + } + + if (pgsql->emsg) { + free(pgsql->emsg); + } + + pgsql->emsg = strdup(emsg); + return pgsql->emsg; +} + +static inline void pgsql_close(pgsql_t* pgsql) +{ + if (pgsql == NULL) { + return; + } + + if (pgsql->emsg) { + free(pgsql->emsg); + pgsql->emsg = NULL; + } + + if (pgsql->pgresult) { + PQclear(pgsql->pgresult); + pgsql->pgresult = NULL; + } + + if (pgsql->pgconn) { + PQfinish(pgsql->pgconn); + pgsql->pgconn = NULL; + } + + free(pgsql); +} + +static inline void pgsql_query_result(pgsql_t* pgsql) +{ + if (pgsql->pgresult) { + PQclear(pgsql->pgresult); + pgsql->pgresult = NULL; + } +} + +static inline int pgsql_query(pgsql_t* pgsql, const char *query, pgquery_t type) +{ + if (pgsql == NULL || pgsql->pgconn == NULL) { + return -1; /* error */ + } + + /* free previous query results */ + pgsql_query_result(pgsql); + + if (type == PQ_PING) { + query = ""; + } + + PGresult *result = PQexec(pgsql->pgconn, query); + ExecStatusType status = PQresultStatus(result); + + switch (type) { + case PQ_PING: { + if (status != PGRES_EMPTY_QUERY) { + return -1; /* error */ + } + break; + } + + case PQ_READ: { + if (status != PGRES_TUPLES_OK) { + return -1; /* error */ + } + break; + } + + default: { + if (status != PGRES_COMMAND_OK) { + return -1; /* error */ + } + break; + } + } + + pgsql->pgresult = result; + + return 0; /* ok */ +} + +static inline int pgsql_affected_rows(pgsql_t* pgsql) +{ + if (pgsql && pgsql->pgresult) { + return atoi(PQcmdTuples(pgsql->pgresult)); + } + + /* error */ + return -1; +} + +static inline int pgsql_num_rows(pgsql_t* pgsql) +{ + if (pgsql && pgsql->pgresult) { + return PQntuples(pgsql->pgresult); + } + + /* error */ + return -1; +} + +static inline int pgsql_num_fields(pgsql_t* pgsql) +{ + if (pgsql && pgsql->pgresult) { + return PQnfields(pgsql->pgresult); + } + + /* error */ + return -1; +} + +#endif /* QLIBPQ_H */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d7721b58..459c24df 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,6 +12,10 @@ SET(test_list test_qhash ) +IF(NOT "${WITH_PGSQL}" STREQUAL "") + LIST(APPEND test_list test_qdatabase_pgsql) +ENDIF() + SET(test_file_list test_qhash_data_1.bin test_qhash_data_2.bin diff --git a/tests/Makefile.in b/tests/Makefile.in index f5fd7ece..3cd2730b 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -52,13 +52,18 @@ TARGETS = \ test_qstack \ test_qhash +TARGETS_EXT = \ + test_qdatabase_pgsql + LIBQLIBC = ${QLIBC_LIBDIR}/libqlibc.a ${DEPLIBS} LIBQLIBCEXT = ${QLIBC_LIBDIR}/libqlibcext.a ## Main all: ${TARGETS} +ext: ${TARGETS_EXT} + +run: test test-ext -run: test test: all @./launcher.sh ${TARGETS} @@ -92,10 +97,20 @@ test_qvector: test_qvector.o test_qhash: test_qhash.o ${CC} ${CFLAGS} ${CPPFLAGS} -g -o $@ test_qhash.o ${LIBQLIBC} +## Test extensions +test-ext: ext + @./launcher.sh ${TARGETS_EXT} + +test_qdatabase_pgsql: test_qdatabase_pgsql.o + ${CC} -o $@ test_qdatabase_pgsql.o ${LIBQLIBCEXT} ${LIBQLIBC} ${CFLAGS} ${CPPFLAGS} + ## Clear Module clean: ${RM} -f *.o ${TARGETS} +clean-ext: + ${RM} -f *.o ${TARGETS_EXT} + ## Compile Module .c.o: ${CC} ${CFLAGS} ${CPPFLAGS} -c -o $@ $< diff --git a/tests/test_qdatabase_pgsql.c b/tests/test_qdatabase_pgsql.c new file mode 100644 index 00000000..96605e9f --- /dev/null +++ b/tests/test_qdatabase_pgsql.c @@ -0,0 +1,303 @@ +/****************************************************************************** + * qLibc + * + * Copyright (c) 2024 SunBeau. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are 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. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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 code is written and updated by following people and released under + * the same license as above qLibc license. + * Copyright (c) 2015 Zhenjiang Xie - https://github.com/Charles0429 + *****************************************************************************/ + +#include "libpq-fe.h" + +#include "qunit.h" +#include "qlibc.h" +#include "qlibcext.h" + +const char *default_dbtype = "PGSQL"; +const char *default_addr = "127.0.0.1"; +const int default_port = 5432; +const char *default_database = "postgres"; +const char *default_username = "postgres"; +const char *default_password = "123456"; +const bool default_autocommit = true; + +QUNIT_START("Test qdatabase_pgsql.c"); + +qdb_t* test_connect_database(const char *database) { + if (database == NULL) { + database = default_database; + } + + qdb_t *db = qdb( + default_dbtype, + default_addr, + default_port, + database, + default_username, + default_password, + default_autocommit + ); + ASSERT_NOT_NULL(db); + ASSERT_FALSE(db->connected); + ASSERT_FALSE(db->get_conn_status(db)); + + ASSERT_TRUE(db->open(db)); + ASSERT_TRUE(db->connected); + ASSERT_TRUE(db->get_conn_status(db)); + + return db; +} + +qdb_t* test_connect_testdb(qdb_t* database) { + qdb_t *testdb = NULL; + char query[128]; + const char *test_database = "qdb_testdb"; + + /* test execute_update() */ + snprintf(query, sizeof(query), "DROP DATABASE IF EXISTS %s;", test_database); + ASSERT_EQUAL_INT(database->execute_update(database, query), 0); + + /* create new database */ + snprintf(query, sizeof(query), "CREATE DATABASE %s;", test_database); + ASSERT_EQUAL_INT(database->execute_update(database, query), 0); + + /* connect "qdb_testdb" database */ + testdb = qdb( + default_dbtype, + default_addr, + default_port, + test_database, + default_username, + default_password, + default_autocommit + ); + ASSERT_NOT_NULL(testdb); + ASSERT_FALSE(testdb->connected); + + ASSERT_TRUE(testdb->open(testdb)); + ASSERT_TRUE(testdb->connected); + + return testdb; +} + +/* + CREATE TABLE animals ( + animal_id INT PRIMARY KEY, + animal_name VARCHAR(255), + species VARCHAR(255), + gender VARCHAR(255), + age INT, + weight INT, + description VARCHAR(255) + ); + + INSERT INTO animals (animal_id, animal_name, species, gender, age, weight, description) + VALUES + (1, 'Dog', 'Canis lupus familiaris', 'Male', 2, 15, 'A friendly dog named Max'), + (2, 'Cat', 'Felis catus', 'Female', 5, 8, 'A lazy cat named Lucy'), + (3, 'Rabbit', 'Oryctolagus cuniculus', 'Male', 1, 2, 'A cute little rabbit named Bugs'), + (4, 'Hamster', 'Mesocricetus auratus', 'Female', 2, 50, 'A hyperactive hamster named Sparky'), + (5, 'Fish', 'Poecilia reticulata', 'Female', 1, 0.1, 'A peaceful betta fish named Blue'); +*/ +void test_create_table_animals(qdb_t* database) { + int rows; + + rows = database->execute_update(database, + "CREATE TABLE animals (" + "animal_id INT PRIMARY KEY," + "animal_name VARCHAR(255)," + "species VARCHAR(255)," + "gender VARCHAR(255)," + "age INT," + "weight INT," + "description VARCHAR(255)" + ");" + ); + ASSERT_EQUAL_INT(rows, 0); + + rows = database->execute_updatef(database, + "INSERT INTO animals VALUES" + "(%s, %s, %s, %s, %s, %s, %s)," + "(%s, %s, %s, %s, %s, %s, %s)," + "(%s, %s, %s, %s, %s, %s, %s)," + "(%s, %s, %s, %s, %s, %s, %s)," + "(%s, %s, %s, %s, %s, %s, %s);", + "1", "'Dog'", "'Canis lupus familiaris'", "'Male'", "2", "15", "'A friendly dog named Max'", + "2", "'Cat'", "'Felis catus'", "'Female'", "5", "8", "'A lazy cat named Lucy'", + "3", "'Rabbit'", "'Oryctolagus cuniculus'", "'Male'", "1", "2", "'A cute little rabbit named Bugs'", + "4", "'Hamster'", "'Mesocricetus auratus'", "'Female'", "2", "50", "'A hyperactive hamster named Sparky'", + "5", "'Fish'", "'Poecilia reticulata'", "'Female'", "1", "0.1", "'A peaceful betta fish named Blue'"); + ASSERT_EQUAL_INT(rows, 5); +} + +TEST("Test1: qdb()/open()/close()/free()") { + qdb_t *db = NULL; + + /* test qdb() */ + db = qdb( + default_dbtype, + default_addr, + default_port, + default_database, + default_username, + default_password, + default_autocommit + ); + + ASSERT_NOT_NULL(db); + ASSERT_FALSE(db->connected); + ASSERT_TRUE(db->open(db)); + ASSERT_TRUE(db->connected); + ASSERT_TRUE(db->close(db)); + ASSERT_FALSE(db->connected); + db->free(db); +} + +TEST("Test2: execute_updatef()") { + qdb_t *default_db = test_connect_database(default_database); + ASSERT_TRUE(default_db->connected); + qdb_t *test_db = test_connect_testdb(default_db); + + /* test: execute_updatef */ + test_create_table_animals(test_db); + + /* disconnect */ + ASSERT_TRUE(test_db->close(test_db)); + ASSERT_TRUE(default_db->close(default_db)); + + /* free */ + test_db->free(test_db); + default_db->free(default_db); +} + +TEST("Test3: execute_queryf()") { + qdb_t *default_db = test_connect_database(default_database); + ASSERT_TRUE(default_db->connected); + qdb_t *test_db = test_connect_testdb(default_db); + test_create_table_animals(test_db); + + /* test: execute_queryf */ + qdbresult_t *qrst = test_db->execute_queryf(test_db, "SELECT * FROM %s;", "animals"); + ASSERT_EQUAL_INT(qrst->get_rows(qrst), 5); + ASSERT_EQUAL_INT(qrst->get_cols(qrst), 7); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 0); + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Dog"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Canis lupus familiaris"); + ASSERT_TRUE(qrst->get_next(qrst)); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 1); + ASSERT_TRUE(qrst->get_next(qrst)); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 2); + + ASSERT_EQUAL_INT(qrst->get_int(qrst, "animal_id"), 3); + ASSERT_EQUAL_INT(qrst->get_int_at(qrst, 5), 1); + qrst->free(qrst); + + /* disconnect */ + ASSERT_TRUE(test_db->close(test_db)); + ASSERT_TRUE(default_db->close(default_db)); + + /* free */ + test_db->free(test_db); + default_db->free(default_db); +} + +TEST("Test4: transaction") { + qdb_t *default_db = test_connect_database(default_database); + ASSERT_TRUE(default_db->connected); + qdb_t *test_db = test_connect_testdb(default_db); + test_create_table_animals(test_db); + int rows; + qdbresult_t *qrst; + + /* test: rollback */ + ASSERT_TRUE(test_db->begin_tran(test_db)); + rows = test_db->execute_update(test_db, "DELETE FROM animals WHERE animal_id > 3;"); + ASSERT_EQUAL_INT(rows, 2); + qrst = test_db->execute_queryf(test_db, "SELECT * FROM %s;", "animals"); + ASSERT_EQUAL_INT(qrst->get_rows(qrst), 3); + qrst->free(qrst); + ASSERT_TRUE(test_db->rollback(test_db)); /* rollback */ + + /* test: commit */ + ASSERT_TRUE(test_db->begin_tran(test_db)); + qrst = test_db->execute_queryf(test_db, "SELECT * FROM %s;", "animals"); + ASSERT_EQUAL_INT(qrst->get_rows(qrst), 5); + rows = test_db->execute_update(test_db, "DELETE FROM animals WHERE animal_id > 3;"); + ASSERT_EQUAL_INT(rows, 2); + ASSERT_TRUE(test_db->commit(test_db)); /* commit */ + + qrst = test_db->execute_queryf(test_db, "SELECT * FROM %s;", "animals"); + ASSERT_EQUAL_INT(qrst->get_rows(qrst), 3); + qrst->free(qrst); + + /* disconnect */ + ASSERT_TRUE(test_db->close(test_db)); + ASSERT_TRUE(default_db->close(default_db)); + + /* free */ + test_db->free(test_db); + default_db->free(default_db); +} + +TEST("Test5: autocommit") { + qdb_t *db = qdb( + default_dbtype, + default_addr, + default_port, + default_database, + default_username, + default_password, + false + ); + + /* Autocommit set to false is not supported in pgsql */ + ASSERT_NULL(db); +} + +TEST("Test6: other") { + qdb_t *default_db = test_connect_database(default_database); + ASSERT_TRUE(default_db->connected); + qdb_t *test_db = test_connect_testdb(default_db); + test_create_table_animals(test_db); + + /* test:get_error */ + ASSERT_EQUAL_STR(test_db->get_error(test_db, NULL), "(no error)"); + ASSERT_FALSE(test_db->set_fetchtype(test_db, true)); + ASSERT_EQUAL_STR(test_db->get_error(test_db, NULL), "unsupported operation"); + + /* test: ping */ + ASSERT_TRUE(test_db->ping(test_db)); + + /* disconnect */ + ASSERT_TRUE(test_db->close(test_db)); + ASSERT_TRUE(default_db->close(default_db)); + + /* free */ + test_db->free(test_db); + default_db->free(default_db); +} + +QUNIT_END(); \ No newline at end of file From b97c5813c439dca46a825f3ef3f15e17fcbef4b5 Mon Sep 17 00:00:00 2001 From: SunBeau Date: Fri, 17 May 2024 09:33:34 +0800 Subject: [PATCH 2/7] merge qlibpq.h into qdatabase.c --- src/extensions/qdatabase.c | 152 +++++++++++++++++++++++++++++- src/internal/qlibpq.h | 183 ------------------------------------- 2 files changed, 151 insertions(+), 184 deletions(-) delete mode 100644 src/internal/qlibpq.h diff --git a/src/extensions/qdatabase.c b/src/extensions/qdatabase.c index 29895baf..a2a562ee 100644 --- a/src/extensions/qdatabase.c +++ b/src/extensions/qdatabase.c @@ -89,7 +89,157 @@ #endif /* ENABLE_MYSQL */ #ifdef ENABLE_PGSQL -#include "qlibpq.h" +#include "libpq-fe.h" + +/* +Wrap the low-level functions of the libpq library. +Provide interfaces with naming convention similar to mysql_xyz(). +*/ + +#define qtype_cast(_type, _src) ((_type)_src) + +typedef enum { + PQ_UNKNOWN, + PQ_WRITE, + PQ_READ, + PQ_PING, +} pgquery_t; + +typedef struct { + char *emsg; + PGconn *pgconn; + + PGresult *pgresult; + int rows; + int cols; + int cursor; +} pgsql_t; + +static inline pgsql_t* pgsql_init(void) +{ + pgsql_t* pgsql = (pgsql_t *)calloc(1, sizeof(pgsql_t)); + return pgsql; +} + +static inline const char* pgsql_set_emsg(pgsql_t* pgsql, const char* emsg) +{ + if (pgsql == NULL || emsg == NULL) { + return NULL; + } + + if (pgsql->emsg) { + free(pgsql->emsg); + } + + pgsql->emsg = strdup(emsg); + return pgsql->emsg; +} + +static inline void pgsql_close(pgsql_t* pgsql) +{ + if (pgsql == NULL) { + return; + } + + if (pgsql->emsg) { + free(pgsql->emsg); + pgsql->emsg = NULL; + } + + if (pgsql->pgresult) { + PQclear(pgsql->pgresult); + pgsql->pgresult = NULL; + } + + if (pgsql->pgconn) { + PQfinish(pgsql->pgconn); + pgsql->pgconn = NULL; + } + + free(pgsql); +} + +static inline void pgsql_query_result(pgsql_t* pgsql) +{ + if (pgsql->pgresult) { + PQclear(pgsql->pgresult); + pgsql->pgresult = NULL; + } +} + +static inline int pgsql_query(pgsql_t* pgsql, const char *query, pgquery_t type) +{ + if (pgsql == NULL || pgsql->pgconn == NULL) { + return -1; /* error */ + } + + /* free previous query results */ + pgsql_query_result(pgsql); + + if (type == PQ_PING) { + query = ""; + } + + PGresult *result = PQexec(pgsql->pgconn, query); + ExecStatusType status = PQresultStatus(result); + + switch (type) { + case PQ_PING: { + if (status != PGRES_EMPTY_QUERY) { + return -1; /* error */ + } + break; + } + + case PQ_READ: { + if (status != PGRES_TUPLES_OK) { + return -1; /* error */ + } + break; + } + + default: { + if (status != PGRES_COMMAND_OK) { + return -1; /* error */ + } + break; + } + } + + pgsql->pgresult = result; + + return 0; /* ok */ +} + +static inline int pgsql_affected_rows(pgsql_t* pgsql) +{ + if (pgsql && pgsql->pgresult) { + return atoi(PQcmdTuples(pgsql->pgresult)); + } + + /* error */ + return -1; +} + +static inline int pgsql_num_rows(pgsql_t* pgsql) +{ + if (pgsql && pgsql->pgresult) { + return PQntuples(pgsql->pgresult); + } + + /* error */ + return -1; +} + +static inline int pgsql_num_fields(pgsql_t* pgsql) +{ + if (pgsql && pgsql->pgresult) { + return PQnfields(pgsql->pgresult); + } + + /* error */ + return -1; +} #endif /* ENABLE_PGSQL */ #include diff --git a/src/internal/qlibpq.h b/src/internal/qlibpq.h deleted file mode 100644 index 60353cf6..00000000 --- a/src/internal/qlibpq.h +++ /dev/null @@ -1,183 +0,0 @@ -/****************************************************************************** - * qLibc - * - * Copyright (c) 2024 SunBeau. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are 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. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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. - *****************************************************************************/ - -/* Wrap the low-level functions of the libpq library */ - -#ifndef QLIBPQ_H -#define QLIBPQ_H - -#include "libpq-fe.h" -#include -#include - -#define qtype_cast(_type, _src) ((_type)_src) - -typedef enum { - PQ_UNKNOWN, - PQ_WRITE, - PQ_READ, - PQ_PING, -} pgquery_t; - -typedef struct { - char *emsg; - PGconn *pgconn; - - PGresult *pgresult; - int rows; - int cols; - int cursor; -} pgsql_t; - -static inline pgsql_t* pgsql_init(void) -{ - pgsql_t* pgsql = (pgsql_t *)calloc(1, sizeof(pgsql_t)); - return pgsql; -} - -static inline const char* pgsql_set_emsg(pgsql_t* pgsql, const char* emsg) -{ - if (pgsql == NULL || emsg == NULL) { - return NULL; - } - - if (pgsql->emsg) { - free(pgsql->emsg); - } - - pgsql->emsg = strdup(emsg); - return pgsql->emsg; -} - -static inline void pgsql_close(pgsql_t* pgsql) -{ - if (pgsql == NULL) { - return; - } - - if (pgsql->emsg) { - free(pgsql->emsg); - pgsql->emsg = NULL; - } - - if (pgsql->pgresult) { - PQclear(pgsql->pgresult); - pgsql->pgresult = NULL; - } - - if (pgsql->pgconn) { - PQfinish(pgsql->pgconn); - pgsql->pgconn = NULL; - } - - free(pgsql); -} - -static inline void pgsql_query_result(pgsql_t* pgsql) -{ - if (pgsql->pgresult) { - PQclear(pgsql->pgresult); - pgsql->pgresult = NULL; - } -} - -static inline int pgsql_query(pgsql_t* pgsql, const char *query, pgquery_t type) -{ - if (pgsql == NULL || pgsql->pgconn == NULL) { - return -1; /* error */ - } - - /* free previous query results */ - pgsql_query_result(pgsql); - - if (type == PQ_PING) { - query = ""; - } - - PGresult *result = PQexec(pgsql->pgconn, query); - ExecStatusType status = PQresultStatus(result); - - switch (type) { - case PQ_PING: { - if (status != PGRES_EMPTY_QUERY) { - return -1; /* error */ - } - break; - } - - case PQ_READ: { - if (status != PGRES_TUPLES_OK) { - return -1; /* error */ - } - break; - } - - default: { - if (status != PGRES_COMMAND_OK) { - return -1; /* error */ - } - break; - } - } - - pgsql->pgresult = result; - - return 0; /* ok */ -} - -static inline int pgsql_affected_rows(pgsql_t* pgsql) -{ - if (pgsql && pgsql->pgresult) { - return atoi(PQcmdTuples(pgsql->pgresult)); - } - - /* error */ - return -1; -} - -static inline int pgsql_num_rows(pgsql_t* pgsql) -{ - if (pgsql && pgsql->pgresult) { - return PQntuples(pgsql->pgresult); - } - - /* error */ - return -1; -} - -static inline int pgsql_num_fields(pgsql_t* pgsql) -{ - if (pgsql && pgsql->pgresult) { - return PQnfields(pgsql->pgresult); - } - - /* error */ - return -1; -} - -#endif /* QLIBPQ_H */ From 4ffead47d63d10594451b76b6c06a8c80b9b2fa1 Mon Sep 17 00:00:00 2001 From: SunBeau Date: Fri, 17 May 2024 12:48:31 +0800 Subject: [PATCH 3/7] reduce code duplication --- include/qlibc/extensions/qdatabase.h | 4 + src/extensions/qdatabase.c | 293 +++++++++++++++------------ 2 files changed, 165 insertions(+), 132 deletions(-) diff --git a/include/qlibc/extensions/qdatabase.h b/include/qlibc/extensions/qdatabase.h index 3da36d06..c6abccd8 100644 --- a/include/qlibc/extensions/qdatabase.h +++ b/include/qlibc/extensions/qdatabase.h @@ -55,6 +55,10 @@ extern "C" { #define Q_ENABLE_PGSQL (1) #endif /* LIBPQ_FE_H */ +#if defined(Q_ENABLE_MYSQL) && defined(Q_ENABLE_PGSQL) +#error "only can enable one in the same time" +#endif + /* types */ typedef struct qdbresult_s qdbresult_t; typedef struct qdb_s qdb_t; diff --git a/src/extensions/qdatabase.c b/src/extensions/qdatabase.c index a2a562ee..2d76431e 100644 --- a/src/extensions/qdatabase.c +++ b/src/extensions/qdatabase.c @@ -79,6 +79,14 @@ #if defined(ENABLE_MYSQL) || defined(ENABLE_PGSQL) ||defined( _DOXYGEN_SKIP) +#include +#include +#include +#include +#include + +#define qtype_cast(_type, _src) ((_type)_src) + #ifdef ENABLE_MYSQL #include "mysql.h" /* mysql specific connector options */ @@ -96,8 +104,6 @@ Wrap the low-level functions of the libpq library. Provide interfaces with naming convention similar to mysql_xyz(). */ -#define qtype_cast(_type, _src) ((_type)_src) - typedef enum { PQ_UNKNOWN, PQ_WRITE, @@ -242,11 +248,6 @@ static inline int pgsql_num_fields(pgsql_t* pgsql) } #endif /* ENABLE_PGSQL */ -#include -#include -#include -#include -#include #include "qinternal.h" #include "extensions/qdatabase.h" @@ -554,30 +555,28 @@ static bool close_(qdb_t *db) { if (db == NULL) return false; -#ifdef Q_ENABLE_MYSQL +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) Q_MUTEX_ENTER(db->qmutex); +#if defined(Q_ENABLE_MYSQL) if (db->mysql != NULL) { mysql_close(db->mysql); db->mysql = NULL; mysql_library_end(); } - db->connected = false; - - Q_MUTEX_LEAVE(db->qmutex); - return true; -#elif defined(Q_ENABLE_PGSQL) - Q_MUTEX_ENTER(db->qmutex); +#endif /* Q_ENABLE_MYSQL */ +#if defined(Q_ENABLE_PGSQL) if (db->pgsql != NULL) { pgsql_close(db->pgsql); db->pgsql = NULL; } - db->connected = false; +#endif /* Q_ENABLE_PGSQL */ + db->connected = false; Q_MUTEX_LEAVE(db->qmutex); return true; -#else +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return false; #endif } @@ -594,35 +593,30 @@ static int execute_update(qdb_t *db, const char *query) { if (db == NULL || db->connected == false) return -1; -#ifdef Q_ENABLE_MYSQL +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) Q_MUTEX_ENTER(db->qmutex); int affected = -1; // query DEBUG("%s", query); +#if defined(Q_ENABLE_MYSQL) if (mysql_query(db->mysql, query) == 0) { /* get affected rows */ if ((affected = mysql_affected_rows(db->mysql)) < 0) affected = -1; } +#endif /* Q_ENABLE_MYSQL */ - Q_MUTEX_LEAVE(db->qmutex); - return affected; -#elif defined(Q_ENABLE_PGSQL) - Q_MUTEX_ENTER(db->qmutex); - - int affected = -1; - - // query - DEBUG("%s", query); +#if defined(Q_ENABLE_PGSQL) if (pgsql_query(db->pgsql, query, PQ_WRITE) == 0) { /* get affected rows */ if ((affected = pgsql_affected_rows(db->pgsql)) < 0) affected = -1; } +#endif /* Q_ENABLE_PGSQL */ Q_MUTEX_LEAVE(db->qmutex); return affected; -#else +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return -1; #endif } @@ -659,9 +653,12 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) { if (db == NULL || db->connected == false) return NULL; -#ifdef Q_ENABLE_MYSQL - // query +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) + DEBUG("%s", query); + +#if defined(Q_ENABLE_MYSQL) + // query if (mysql_query(db->mysql, query)) return NULL; // store @@ -684,24 +681,10 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) result->row = NULL; result->cols = mysql_num_fields(result->rs); result->cursor = 0; +#endif /* Q_ENABLE_MYSQL */ - /* assign methods */ - result->get_str = result_get_str; - result->get_str_at = result_get_str_at; - result->get_int = result_get_int; - result->get_int_at = result_get_int_at; - result->get_next = result_get_next; - - result->get_cols = result_get_cols; - result->get_rows = result_get_rows; - result->get_row = result_get_row; - - result->free = result_free; - - return result; -#elif defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_PGSQL) // query - DEBUG("%s", query); if (pgsql_query(db->pgsql, query, PQ_READ)) return NULL; // store @@ -714,6 +697,7 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) qtype_cast(pgsql_t*, result->pgsql)->rows = pgsql_num_rows(result->pgsql); qtype_cast(pgsql_t*, result->pgsql)->cols = pgsql_num_fields(result->pgsql); qtype_cast(pgsql_t*, result->pgsql)->cursor = 0; +#endif /* Q_ENABLE_PGSQL */ /* assign methods */ result->get_str = result_get_str; @@ -729,7 +713,7 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) result->free = result_free; return result; -#else +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return NULL; #endif } @@ -770,13 +754,14 @@ static bool begin_tran(qdb_t *db) { if (db == NULL) return false; -#ifdef Q_ENABLE_MYSQL +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) Q_MUTEX_ENTER(db->qmutex); - if (db->qmutex.count != 1) { + if (qtype_cast(qmutex_t*, db->qmutex)->count != 1) { Q_MUTEX_LEAVE(db->qmutex); return false; } +#if defined(Q_ENABLE_MYSQL) qdbresult_t *result; result = db->execute_query(db, "START TRANSACTION"); if (result == NULL) { @@ -785,19 +770,17 @@ static bool begin_tran(qdb_t *db) } result->free(result); return true; -#elif defined(Q_ENABLE_PGSQL) - Q_MUTEX_ENTER(db->qmutex); - if (qtype_cast(qmutex_t*, db->qmutex)->count != 1) { - Q_MUTEX_LEAVE(db->qmutex); - return false; - } +#endif /* Q_ENABLE_MYSQL */ +#if defined(Q_ENABLE_PGSQL) if (pgsql_query(db->pgsql, "START TRANSACTION", PQ_WRITE) < 0) { Q_MUTEX_LEAVE(db->qmutex); return false; } return true; -#else +#endif /* Q_ENABLE_PGSQL */ + +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return false; #endif } @@ -813,27 +796,26 @@ static bool commit(qdb_t *db) { if (db == NULL) return false; -#ifdef Q_ENABLE_MYSQL +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) bool ret = false; + +#if defined(Q_ENABLE_MYSQL) if (mysql_commit(db->mysql) == 0) { ret = true; } +#endif /* Q_ENABLE_MYSQL */ - if (db->qmutex.count > 0) { - Q_MUTEX_LEAVE(db->qmutex); - } - return ret; -#elif defined(Q_ENABLE_PGSQL) - bool ret = false; +#if defined(Q_ENABLE_PGSQL) if (pgsql_query(db->pgsql, "COMMIT", PQ_WRITE) == 0) { ret = true; } +#endif /* Q_ENABLE_PGSQL */ if (qtype_cast(qmutex_t*, db->qmutex)->count > 0) { Q_MUTEX_LEAVE(db->qmutex); } return ret; -#else +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return false; #endif } @@ -849,27 +831,26 @@ static bool rollback(qdb_t *db) { if (db == NULL) return false; -#ifdef Q_ENABLE_MYSQL +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) bool ret = false; + +#if defined(Q_ENABLE_MYSQL) if (mysql_rollback(db->mysql) == 0) { ret = true; } +#endif /* Q_ENABLE_MYSQL */ - if (db->qmutex.count > 0) { - Q_MUTEX_LEAVE(db->qmutex); - } - return ret; -#elif defined(Q_ENABLE_PGSQL) - bool ret = false; +#if defined(Q_ENABLE_PGSQL) if (pgsql_query(db->pgsql, "ROLLBACK", PQ_WRITE) == 0) { ret = true; } +#endif /* Q_ENABLE_PGSQL */ if (qtype_cast(qmutex_t*, db->qmutex)->count > 0) { Q_MUTEX_LEAVE(db->qmutex); } return ret; -#else +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return 0; #endif } @@ -895,14 +876,20 @@ static bool set_fetchtype(qdb_t *db, bool fromdb) { if (db == NULL) return false; -#ifdef Q_ENABLE_MYSQL +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) + +#if defined(Q_ENABLE_MYSQL) db->info.fetchtype = fromdb; return true; -#elif defined(Q_ENABLE_PGSQL) +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) if (db->pgsql == NULL) return false; pgsql_set_emsg(db->pgsql, "unsupported operation"); return false; -#else +#endif /* Q_ENABLE_PGSQL */ + +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return false; #endif } @@ -939,21 +926,24 @@ static bool ping(qdb_t *db) { if (db == NULL) return false; -#ifdef Q_ENABLE_MYSQL +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) + bool ping_failed = false; + +#if defined(Q_ENABLE_MYSQL) if (db->connected == true && mysql_ping(db->mysql) == 0) { return true; - } else { // ping test failed - if (open_(db) == true) { // try re-connect - DEBUG("Connection recovered."); - return true; - } } + ping_failed = true; +#endif /* Q_ENABLE_MYSQL */ - return false; -#elif defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_PGSQL) if (db->connected == true && pgsql_query(db->pgsql, NULL, PQ_PING) == 0) { return true; - } else { // ping test failed + } + ping_failed = true; +#endif /* Q_ENABLE_PGSQL */ + + if (ping_failed) { // ping test failed if (open_(db) == true) { // try re-connect DEBUG("Connection recovered."); return true; @@ -961,7 +951,7 @@ static bool ping(qdb_t *db) } return false; -#else +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return false; #endif } @@ -983,11 +973,15 @@ static const char *get_error(qdb_t *db, unsigned int *errorno) unsigned int eno = 0; const char *emsg; -#ifdef Q_ENABLE_MYSQL + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) eno = mysql_errno(db->mysql); if (eno == 0) emsg = "(no error)"; else emsg = mysql_error(db->mysql); -#elif defined(Q_ENABLE_PGSQL) +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) emsg = qtype_cast(pgsql_t*, db->pgsql)->emsg; if (emsg == NULL) { eno = 0; @@ -995,7 +989,8 @@ static const char *get_error(qdb_t *db, unsigned int *errorno) } else { eno = 1; } -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ emsg = "(not implemented)"; #endif @@ -1042,8 +1037,11 @@ static void free_(qdb_t *db) */ static const char *result_get_str(qdbresult_t *result, const char *field) { -#ifdef Q_ENABLE_MYSQL - if (result == NULL || result->rs == NULL || result->cols <= 0) return NULL; + if (result == NULL) return NULL; + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) + if (result->rs == NULL || result->cols <= 0) return NULL; if (result->fields == NULL) result->fields = mysql_fetch_fields(result->rs); @@ -1055,10 +1053,10 @@ static const char *result_get_str(qdbresult_t *result, const char *field) } return NULL; -#elif defined(Q_ENABLE_PGSQL) - if (result == NULL - || result->pgsql == NULL - || qtype_cast(pgsql_t*, result->pgsql)->cols <= 0) { +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) + if (result->pgsql == NULL || qtype_cast(pgsql_t*, result->pgsql)->cols <= 0) { return NULL; } @@ -1083,7 +1081,8 @@ static const char *result_get_str(qdbresult_t *result, const char *field) val = PQgetvalue(qtype_cast(pgsql_t*, result->pgsql)->pgresult, cur, row); return val; -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return NULL; #endif } @@ -1098,18 +1097,21 @@ static const char *result_get_str(qdbresult_t *result, const char *field) */ static const char *result_get_str_at(qdbresult_t *result, int idx) { -#ifdef Q_ENABLE_MYSQL - if (result == NULL - || result->rs == NULL - || result->cursor == 0 - || idx <= 0 - || idx > result->cols ) { + if (result == NULL) return NULL; + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) + if (result->rs == NULL + || result->cursor == 0 + || idx <= 0 + || idx > result->cols ) { return NULL; } return result->row[idx-1]; -#elif defined(Q_ENABLE_PGSQL) - if (result == NULL - || result->pgsql == NULL +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) + if (result->pgsql == NULL || qtype_cast(pgsql_t*, result->pgsql)->rows == 0 || qtype_cast(pgsql_t*, result->pgsql)->cols == 0 || idx <= 0 @@ -1120,7 +1122,8 @@ static const char *result_get_str_at(qdbresult_t *result, int idx) int cur = qtype_cast(pgsql_t*, result->pgsql)->cursor; const char *val = PQgetvalue(qtype_cast(pgsql_t*, result->pgsql)->pgresult, cur, idx - 1); return val; -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return NULL; #endif } @@ -1164,16 +1167,20 @@ static int result_get_int_at(qdbresult_t *result, int idx) */ static bool result_get_next(qdbresult_t *result) { -#ifdef Q_ENABLE_MYSQL - if (result == NULL || result->rs == NULL) return false; + if (result == NULL) return false; + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) + if (result->rs == NULL) return false; if ((result->row = mysql_fetch_row(result->rs)) == NULL) return false; result->cursor++; return true; -#elif defined(Q_ENABLE_PGSQL) - if (result == NULL - || result->pgsql == NULL +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) + if (result->pgsql == NULL || qtype_cast(pgsql_t*, result->pgsql)->pgresult == NULL) { return false; } @@ -1185,7 +1192,8 @@ static bool result_get_next(qdbresult_t *result) qtype_cast(pgsql_t*, result->pgsql)->cursor = cursor; return true; -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return false; #endif } @@ -1199,13 +1207,19 @@ static bool result_get_next(qdbresult_t *result) */ static int result_get_cols(qdbresult_t *result) { -#ifdef Q_ENABLE_MYSQL - if (result == NULL || result->rs == NULL) return 0; + if (result == NULL) return 0; + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) + if (result->rs == NULL) return 0; return result->cols; -#elif defined(Q_ENABLE_PGSQL) - if (result == NULL || result->pgsql == NULL) return 0; +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) + if (result->pgsql == NULL) return 0; return qtype_cast(pgsql_t*, result->pgsql)->cols; -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return 0; #endif } @@ -1219,13 +1233,19 @@ static int result_get_cols(qdbresult_t *result) */ static int result_get_rows(qdbresult_t *result) { -#ifdef Q_ENABLE_MYSQL - if (result == NULL || result->rs == NULL) return 0; + if (result == NULL) return 0; + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) + if (result->rs == NULL) return 0; return mysql_num_rows(result->rs); -#elif defined(Q_ENABLE_PGSQL) - if (result == NULL || result->pgsql == NULL) return 0; +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) + if (result->pgsql == NULL) return 0; return qtype_cast(pgsql_t*, result->pgsql)->rows; -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return 0; #endif } @@ -1242,13 +1262,19 @@ static int result_get_rows(qdbresult_t *result) */ static int result_get_row(qdbresult_t *result) { -#ifdef Q_ENABLE_MYSQL - if (result == NULL || result->rs == NULL) return 0; + if (result == NULL) return 0; + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) + if (result->rs == NULL) return 0; return result->cursor; -#elif defined(Q_ENABLE_PGSQL) - if (result == NULL || result->pgsql == NULL) return 0; +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) + if (result->pgsql == NULL) return 0; return qtype_cast(pgsql_t*, result->pgsql)->cursor; -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return 0; #endif } @@ -1260,8 +1286,10 @@ static int result_get_row(qdbresult_t *result) */ static void result_free(qdbresult_t *result) { -#ifdef Q_ENABLE_MYSQL if (result == NULL) return; + +#if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) +#if defined(Q_ENABLE_MYSQL) if (result->rs != NULL) { if (result->fetchtype == true) { while (mysql_fetch_row(result->rs) != NULL); @@ -1271,13 +1299,14 @@ static void result_free(qdbresult_t *result) } free(result); return; -#elif defined(Q_ENABLE_PGSQL) - if (result) { - result->pgsql = NULL; - free(result); - } +#endif /* Q_ENABLE_MYSQL */ + +#if defined(Q_ENABLE_PGSQL) + result->pgsql = NULL; + free(result); return; -#else +#endif /* Q_ENABLE_PGSQL */ +#else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return; #endif } From 48534f364354611538bd58b18a5fe3475017c9ff Mon Sep 17 00:00:00 2001 From: SunBeau Date: Sat, 18 May 2024 19:47:57 +0800 Subject: [PATCH 4/7] change a little code --- src/extensions/qdatabase.c | 14 ++++++++++---- tests/test_qdatabase_pgsql.c | 6 +----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/extensions/qdatabase.c b/src/extensions/qdatabase.c index 2d76431e..e8c7da5e 100644 --- a/src/extensions/qdatabase.c +++ b/src/extensions/qdatabase.c @@ -322,7 +322,16 @@ static void result_free(qdbresult_t *result); * return -1; * } * @endcode - + * + * @note + * The pgsql server and libpq library does not provide a setting for autocommit.
+ * The following instructions are from the official manual(https://www.postgresql.org/docs/15/sql-begin.html):
+ * BEGIN initiates a transaction block, that is, all statements after a BEGIN command + * will be executed in a single transaction until an explicit COMMIT or ROLLBACK is given. + * By default (without BEGIN), PostgreSQL executes transactions in “autocommit” mode, + * that is, each statement is executed in its own transaction + * and a commit is implicitly performed at the end of the statement + * (if execution was successful, otherwise a rollback is done). */ qdb_t *qdb(const char *dbtype, const char *addr, int port, const char *database, @@ -521,9 +530,6 @@ static bool open_(qdb_t *db) qtype_cast(pgsql_t*, db->pgsql)->pgconn = pgconn; - // set auto-commit - /* do nothing */ - // set flag db->connected = true; diff --git a/tests/test_qdatabase_pgsql.c b/tests/test_qdatabase_pgsql.c index 96605e9f..ffa18bf4 100644 --- a/tests/test_qdatabase_pgsql.c +++ b/tests/test_qdatabase_pgsql.c @@ -25,10 +25,6 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ -/* This code is written and updated by following people and released under - * the same license as above qLibc license. - * Copyright (c) 2015 Zhenjiang Xie - https://github.com/Charles0429 - *****************************************************************************/ #include "libpq-fe.h" @@ -300,4 +296,4 @@ TEST("Test6: other") { default_db->free(default_db); } -QUNIT_END(); \ No newline at end of file +QUNIT_END(); From 8ab8d84a1ee1fe60cda3c74156a7953af8a8adc9 Mon Sep 17 00:00:00 2001 From: SunBeau Date: Sat, 18 May 2024 23:59:03 +0800 Subject: [PATCH 5/7] support fetchtype --- src/extensions/qdatabase.c | 145 ++++++++++++++++++++++++----------- tests/test_qdatabase_pgsql.c | 39 +++++++++- 2 files changed, 136 insertions(+), 48 deletions(-) diff --git a/src/extensions/qdatabase.c b/src/extensions/qdatabase.c index e8c7da5e..7542687b 100644 --- a/src/extensions/qdatabase.c +++ b/src/extensions/qdatabase.c @@ -106,9 +106,10 @@ Provide interfaces with naming convention similar to mysql_xyz(). typedef enum { PQ_UNKNOWN, - PQ_WRITE, - PQ_READ, PQ_PING, + PQ_QUERY, + PQ_UPDATE, + PQ_TRANSACTION, } pgquery_t; typedef struct { @@ -173,7 +174,8 @@ static inline void pgsql_query_result(pgsql_t* pgsql) } } -static inline int pgsql_query(pgsql_t* pgsql, const char *query, pgquery_t type) +static inline int pgsql_query(pgsql_t* pgsql, bool fetchtype, + const char *query, pgquery_t type) { if (pgsql == NULL || pgsql->pgconn == NULL) { return -1; /* error */ @@ -186,8 +188,28 @@ static inline int pgsql_query(pgsql_t* pgsql, const char *query, pgquery_t type) query = ""; } - PGresult *result = PQexec(pgsql->pgconn, query); - ExecStatusType status = PQresultStatus(result); + PGresult *result = NULL; + ExecStatusType status = -1; + + if (fetchtype && type == PQ_QUERY) { + if (PQsendQuery(pgsql->pgconn, query)) { + if (PQsetSingleRowMode(pgsql->pgconn)) { + result = PQgetResult(pgsql->pgconn); + } else { + return -1; /* error */ + } + } else { + return -1; /* error */ + } + } else { + result = PQexec(pgsql->pgconn, query); + } + + if (result == NULL) { + return -1; /* error */ + } + + status = PQresultStatus(result); switch (type) { case PQ_PING: { @@ -197,8 +219,9 @@ static inline int pgsql_query(pgsql_t* pgsql, const char *query, pgquery_t type) break; } - case PQ_READ: { - if (status != PGRES_TUPLES_OK) { + case PQ_QUERY: { + if (status != PGRES_TUPLES_OK && + status != PGRES_SINGLE_TUPLE) { return -1; /* error */ } break; @@ -246,6 +269,27 @@ static inline int pgsql_num_fields(pgsql_t* pgsql) /* error */ return -1; } + +static inline int pgsql_fetch_row(pgsql_t* pgsql) +{ + if (pgsql && pgsql->pgconn) { + PGresult *result = PQgetResult(pgsql->pgconn); + if (result) { + pgsql_query_result(pgsql); + pgsql->pgresult = result; + pgsql->rows = pgsql_num_rows(pgsql); + pgsql->cols = pgsql_num_fields(pgsql); + return 0; + } else { + /* error */ + return -1; + } + } + + /* error */ + return -1; +} + #endif /* ENABLE_PGSQL */ #include "qinternal.h" @@ -614,7 +658,7 @@ static int execute_update(qdb_t *db, const char *query) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, query, PQ_WRITE) == 0) { + if (pgsql_query(db->pgsql, db->info.fetchtype, query, PQ_UPDATE) == 0) { /* get affected rows */ if ((affected = pgsql_affected_rows(db->pgsql)) < 0) affected = -1; } @@ -691,7 +735,7 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) #if defined(Q_ENABLE_PGSQL) // query - if (pgsql_query(db->pgsql, query, PQ_READ)) return NULL; + if (pgsql_query(db->pgsql, db->info.fetchtype, query, PQ_QUERY)) return NULL; // store qdbresult_t *result = (qdbresult_t *)malloc(sizeof(qdbresult_t)); @@ -779,7 +823,7 @@ static bool begin_tran(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, "START TRANSACTION", PQ_WRITE) < 0) { + if (pgsql_query(db->pgsql, db->info.fetchtype, "START TRANSACTION", PQ_TRANSACTION) < 0) { Q_MUTEX_LEAVE(db->qmutex); return false; } @@ -812,7 +856,7 @@ static bool commit(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, "COMMIT", PQ_WRITE) == 0) { + if (pgsql_query(db->pgsql, db->info.fetchtype, "COMMIT", PQ_TRANSACTION) == 0) { ret = true; } #endif /* Q_ENABLE_PGSQL */ @@ -847,7 +891,7 @@ static bool rollback(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, "ROLLBACK", PQ_WRITE) == 0) { + if (pgsql_query(db->pgsql, db->info.fetchtype, "ROLLBACK", PQ_TRANSACTION) == 0) { ret = true; } #endif /* Q_ENABLE_PGSQL */ @@ -890,9 +934,8 @@ static bool set_fetchtype(qdb_t *db, bool fromdb) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (db->pgsql == NULL) return false; - pgsql_set_emsg(db->pgsql, "unsupported operation"); - return false; + db->info.fetchtype = fromdb; + return true; #endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ @@ -943,7 +986,7 @@ static bool ping(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (db->connected == true && pgsql_query(db->pgsql, NULL, PQ_PING) == 0) { + if (db->connected == true && pgsql_query(db->pgsql, db->info.fetchtype, NULL, PQ_PING) == 0) { return true; } ping_failed = true; @@ -1062,31 +1105,29 @@ static const char *result_get_str(qdbresult_t *result, const char *field) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (result->pgsql == NULL || qtype_cast(pgsql_t*, result->pgsql)->cols <= 0) { + pgsql_t* pgsql = result->pgsql; + if (pgsql == NULL || pgsql->cols <= 0) { return NULL; } - const char *val = NULL; - int rows = qtype_cast(pgsql_t*, result->pgsql)->rows; - int cols = qtype_cast(pgsql_t*, result->pgsql)->cols; - int cur = qtype_cast(pgsql_t*, result->pgsql)->cursor; - - /* get row num */ - int row = -1; - for (int i = 0; i < rows; i++) { - val = PQfname(qtype_cast(pgsql_t*, result->pgsql)->pgresult, i); - if (!strcasecmp(val, field)) { - row = i; - break; - } + /* get field num */ + const char *value = NULL; + int field_num = PQfnumber(pgsql->pgresult, field); + if (field_num == -1) { + return NULL; } - if (row == -1) { - return NULL; + /* get row num */ + int row_num = -1; + if (pgsql->rows == 1) { + row_num = 0; + } else { + row_num = pgsql->cursor; } - val = PQgetvalue(qtype_cast(pgsql_t*, result->pgsql)->pgresult, cur, row); - return val; + /* get value */ + value = PQgetvalue(pgsql->pgresult, row_num, field_num); + return value; #endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return NULL; @@ -1117,17 +1158,26 @@ static const char *result_get_str_at(qdbresult_t *result, int idx) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) + pgsql_t* pgsql = result->pgsql; if (result->pgsql == NULL - || qtype_cast(pgsql_t*, result->pgsql)->rows == 0 - || qtype_cast(pgsql_t*, result->pgsql)->cols == 0 + || pgsql->rows == 0 + || pgsql->cols == 0 || idx <= 0 - || idx > qtype_cast(pgsql_t*, result->pgsql)->cols) { + || idx > pgsql->cols) { return NULL; } - int cur = qtype_cast(pgsql_t*, result->pgsql)->cursor; - const char *val = PQgetvalue(qtype_cast(pgsql_t*, result->pgsql)->pgresult, cur, idx - 1); - return val; + /* get row num */ + int row_num = -1; + if (pgsql->rows == 1) { + row_num = 0; + } else { + row_num = pgsql->cursor; + } + + /* get value */ + const char *value = PQgetvalue(pgsql->pgresult, row_num, idx - 1); + return value; #endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return NULL; @@ -1186,17 +1236,20 @@ static bool result_get_next(qdbresult_t *result) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (result->pgsql == NULL - || qtype_cast(pgsql_t*, result->pgsql)->pgresult == NULL) { + pgsql_t* pgsql = result->pgsql; + + if (pgsql == NULL || pgsql->pgresult == NULL) { return false; } - int cursor = qtype_cast(pgsql_t*, result->pgsql)->cursor; - if (++cursor == qtype_cast(pgsql_t*, result->pgsql)->rows) { - return false; + int cursor = pgsql->cursor; + if (++cursor >= pgsql->rows) { + if (pgsql_fetch_row(pgsql) != 0) { + return false; + } } - qtype_cast(pgsql_t*, result->pgsql)->cursor = cursor; + pgsql->cursor = cursor; return true; #endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ diff --git a/tests/test_qdatabase_pgsql.c b/tests/test_qdatabase_pgsql.c index ffa18bf4..78373457 100644 --- a/tests/test_qdatabase_pgsql.c +++ b/tests/test_qdatabase_pgsql.c @@ -273,6 +273,43 @@ TEST("Test5: autocommit") { ASSERT_NULL(db); } +TEST("Test6: fetchtype") { + qdb_t *default_db = test_connect_database(default_database); + ASSERT_TRUE(default_db->connected); + qdb_t *test_db = test_connect_testdb(default_db); + test_create_table_animals(test_db); + + /* test: set_fetchtype true */ + ASSERT_TRUE(test_db->set_fetchtype(test_db, true)); + + /* test: execute_queryf in fetchtype mode */ + qdbresult_t *qrst = test_db->execute_queryf(test_db, "SELECT * FROM %s;", "animals"); + ASSERT_EQUAL_INT(qrst->get_rows(qrst), 1); + ASSERT_EQUAL_INT(qrst->get_cols(qrst), 7); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 0); + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Dog"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Canis lupus familiaris"); + ASSERT_TRUE(qrst->get_next(qrst)); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 1); + ASSERT_TRUE(qrst->get_next(qrst)); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 2); + + ASSERT_EQUAL_INT(qrst->get_int(qrst, "animal_id"), 3); + ASSERT_EQUAL_INT(qrst->get_int_at(qrst, 5), 1); + qrst->free(qrst); + + /* test: set_fetchtype false */ + ASSERT_TRUE(test_db->set_fetchtype(test_db, false)); + + /* disconnect */ + ASSERT_TRUE(test_db->close(test_db)); + ASSERT_TRUE(default_db->close(default_db)); + + /* free */ + test_db->free(test_db); + default_db->free(default_db); +} + TEST("Test6: other") { qdb_t *default_db = test_connect_database(default_database); ASSERT_TRUE(default_db->connected); @@ -281,8 +318,6 @@ TEST("Test6: other") { /* test:get_error */ ASSERT_EQUAL_STR(test_db->get_error(test_db, NULL), "(no error)"); - ASSERT_FALSE(test_db->set_fetchtype(test_db, true)); - ASSERT_EQUAL_STR(test_db->get_error(test_db, NULL), "unsupported operation"); /* test: ping */ ASSERT_TRUE(test_db->ping(test_db)); From 01fca687d17f1727ea66d241124ef22561be998b Mon Sep 17 00:00:00 2001 From: SunBeau Date: Wed, 22 May 2024 20:06:50 +0800 Subject: [PATCH 6/7] set errno --- src/extensions/qdatabase.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/extensions/qdatabase.c b/src/extensions/qdatabase.c index 7542687b..1a51ba2b 100644 --- a/src/extensions/qdatabase.c +++ b/src/extensions/qdatabase.c @@ -84,6 +84,7 @@ #include #include #include +#include #define qtype_cast(_type, _src) ((_type)_src) @@ -383,9 +384,15 @@ qdb_t *qdb(const char *dbtype, { // check db type #ifdef Q_ENABLE_MYSQL - if (strcmp(dbtype, "MYSQL")) return NULL; + if (strcmp(dbtype, "MYSQL")) { + errno = ENOTSUP; + return NULL; + } #elif defined(Q_ENABLE_PGSQL) - if (strcmp(dbtype, "PGSQL")) return NULL; + if (strcmp(dbtype, "PGSQL")) { + errno = ENOTSUP; + return NULL; + } #else return NULL; #endif @@ -399,6 +406,7 @@ qdb_t *qdb(const char *dbtype, #ifdef Q_ENABLE_PGSQL if (autocommit == false) { + errno = EINVAL; return NULL; } #endif From dda555b4a4a3296df5888de888e9b48665a4a234 Mon Sep 17 00:00:00 2001 From: SunBeau Date: Sun, 2 Jun 2024 00:08:11 +0800 Subject: [PATCH 7/7] optimize and fix bug --- include/qlibc/extensions/qdatabase.h | 10 +- src/extensions/qdatabase.c | 347 +++++++++++++-------------- tests/test_qdatabase_pgsql.c | 70 ++++-- 3 files changed, 226 insertions(+), 201 deletions(-) diff --git a/include/qlibc/extensions/qdatabase.h b/include/qlibc/extensions/qdatabase.h index c6abccd8..92e2a5c4 100644 --- a/include/qlibc/extensions/qdatabase.h +++ b/include/qlibc/extensions/qdatabase.h @@ -85,19 +85,21 @@ struct qdbresult_s { void (*free) (qdbresult_t *result); + bool fetchtype; + int cursor; + int cols; + #ifdef Q_ENABLE_MYSQL /* private variables for mysql database - do not access directly */ - bool fetchtype; MYSQL_RES *rs; MYSQL_FIELD *fields; MYSQL_ROW row; - int cols; - int cursor; #endif #ifdef Q_ENABLE_PGSQL /* private variables for pgsql database - do not access directly */ - void *pgsql; + void *rs; + const void *row; #endif }; diff --git a/src/extensions/qdatabase.c b/src/extensions/qdatabase.c index 1a51ba2b..a8c33faf 100644 --- a/src/extensions/qdatabase.c +++ b/src/extensions/qdatabase.c @@ -82,6 +82,7 @@ #include #include #include +#include #include #include #include @@ -114,103 +115,124 @@ typedef enum { } pgquery_t; typedef struct { - char *emsg; - PGconn *pgconn; + PGresult *pgresult_ref; + int idx; +} qpgrow_t; +typedef struct { + PGconn *pgconn_ref; PGresult *pgresult; - int rows; - int cols; int cursor; -} pgsql_t; + qpgrow_t qpgrow; +} qpgresult_t; -static inline pgsql_t* pgsql_init(void) -{ - pgsql_t* pgsql = (pgsql_t *)calloc(1, sizeof(pgsql_t)); - return pgsql; -} +typedef struct { + bool fetchtype; + PGconn *pgconn; + qpgresult_t qpgresult; +} qpgsql_t; -static inline const char* pgsql_set_emsg(pgsql_t* pgsql, const char* emsg) +static inline char* pgsql_row_get_str_at(const qpgrow_t *qpgrow, int field_idx) { - if (pgsql == NULL || emsg == NULL) { + if (qpgrow == NULL || + qpgrow->idx < 0 || + qpgrow->pgresult_ref == NULL) { return NULL; } - if (pgsql->emsg) { - free(pgsql->emsg); - } - - pgsql->emsg = strdup(emsg); - return pgsql->emsg; + return PQgetvalue(qpgrow->pgresult_ref, qpgrow->idx, field_idx); } -static inline void pgsql_close(pgsql_t* pgsql) +static inline void pgsql_free_result(qpgresult_t* qpgresult) { - if (pgsql == NULL) { + if (qpgresult == NULL) { return; } - if (pgsql->emsg) { - free(pgsql->emsg); - pgsql->emsg = NULL; + if (qpgresult->pgresult) { + PQclear(qpgresult->pgresult); + qpgresult->pgresult = NULL; } - if (pgsql->pgresult) { - PQclear(pgsql->pgresult); - pgsql->pgresult = NULL; + qpgresult->pgconn_ref = NULL; + qpgresult->cursor = 0; + + qpgresult->qpgrow.pgresult_ref = NULL; + qpgresult->qpgrow.idx = 0; +} + +static inline qpgsql_t* pgsql_init(void) +{ + return calloc(1, sizeof(qpgsql_t)); +} + +static inline const char* pgsql_error(qpgsql_t* qpgsql) +{ + if (qpgsql == NULL || qpgsql->pgconn == NULL) { + return "(connection error)"; } - if (pgsql->pgconn) { - PQfinish(pgsql->pgconn); - pgsql->pgconn = NULL; + return PQerrorMessage(qpgsql->pgconn); +} + +static inline uint32_t pgsql_errno(qpgsql_t* qpgsql) +{ + const char* emsg = pgsql_error(qpgsql); + if (strlen(emsg) > 0) { + return 1; } - free(pgsql); + return 0; } -static inline void pgsql_query_result(pgsql_t* pgsql) +static inline void pgsql_close(qpgsql_t* qpgsql) { - if (pgsql->pgresult) { - PQclear(pgsql->pgresult); - pgsql->pgresult = NULL; + if (qpgsql == NULL) { + return; + } + + pgsql_free_result(&qpgsql->qpgresult); + + if (qpgsql->pgconn) { + PQfinish(qpgsql->pgconn); + qpgsql->pgconn = NULL; } + + free(qpgsql); } -static inline int pgsql_query(pgsql_t* pgsql, bool fetchtype, - const char *query, pgquery_t type) +static inline int pgsql_query(qpgsql_t* qpgsql, const char *query, pgquery_t type) { - if (pgsql == NULL || pgsql->pgconn == NULL) { + if (qpgsql == NULL || qpgsql->pgconn == NULL) { return -1; /* error */ } + PGresult *pgresult = NULL; + ExecStatusType status = -1; + PGconn *pgconn = qpgsql->pgconn; + /* free previous query results */ - pgsql_query_result(pgsql); + pgsql_free_result(&qpgsql->qpgresult); if (type == PQ_PING) { query = ""; } - PGresult *result = NULL; - ExecStatusType status = -1; - - if (fetchtype && type == PQ_QUERY) { - if (PQsendQuery(pgsql->pgconn, query)) { - if (PQsetSingleRowMode(pgsql->pgconn)) { - result = PQgetResult(pgsql->pgconn); - } else { - return -1; /* error */ - } + if (qpgsql->fetchtype && type == PQ_QUERY) { + if (PQsendQuery(pgconn, query) && PQsetSingleRowMode(pgconn)) { + pgresult = PQgetResult(pgconn); } else { return -1; /* error */ } } else { - result = PQexec(pgsql->pgconn, query); + pgresult = PQexec(pgconn, query); } - if (result == NULL) { + if (pgresult == NULL) { return -1; /* error */ } - status = PQresultStatus(result); + status = PQresultStatus(pgresult); switch (type) { case PQ_PING: { @@ -236,59 +258,79 @@ static inline int pgsql_query(pgsql_t* pgsql, bool fetchtype, } } - pgsql->pgresult = result; + qpgsql->qpgresult.pgresult = pgresult; + qpgsql->qpgresult.pgconn_ref = pgconn; return 0; /* ok */ } -static inline int pgsql_affected_rows(pgsql_t* pgsql) +static inline qpgresult_t* pgsql_get_result(qpgsql_t* qpgsql) +{ + if (qpgsql == NULL || qpgsql->qpgresult.pgresult == NULL) { + return NULL; + } + + return &qpgsql->qpgresult; +} + +static inline int pgsql_affected_rows(qpgsql_t* qpgsql) { - if (pgsql && pgsql->pgresult) { - return atoi(PQcmdTuples(pgsql->pgresult)); + if (qpgsql && qpgsql->qpgresult.pgresult) { + return atoi(PQcmdTuples(qpgsql->qpgresult.pgresult)); } /* error */ return -1; } -static inline int pgsql_num_rows(pgsql_t* pgsql) +static inline int pgsql_num_rows(qpgresult_t *qpgresult) { - if (pgsql && pgsql->pgresult) { - return PQntuples(pgsql->pgresult); + if (qpgresult && qpgresult->pgresult) { + return PQntuples(qpgresult->pgresult); } /* error */ return -1; } -static inline int pgsql_num_fields(pgsql_t* pgsql) +static inline int pgsql_num_fields(qpgresult_t *qpgresult) { - if (pgsql && pgsql->pgresult) { - return PQnfields(pgsql->pgresult); + if (qpgresult && qpgresult->pgresult) { + return PQnfields(qpgresult->pgresult); } /* error */ return -1; } -static inline int pgsql_fetch_row(pgsql_t* pgsql) +static inline qpgrow_t* pgsql_fetch_row(qpgresult_t *qpgresult) { - if (pgsql && pgsql->pgconn) { - PGresult *result = PQgetResult(pgsql->pgconn); - if (result) { - pgsql_query_result(pgsql); - pgsql->pgresult = result; - pgsql->rows = pgsql_num_rows(pgsql); - pgsql->cols = pgsql_num_fields(pgsql); - return 0; + if (qpgresult == NULL || qpgresult->pgresult == NULL) { + return NULL; + } + + PGresult *pgresult = qpgresult->pgresult; + qpgrow_t *qpgrow = &qpgresult->qpgrow; + + int cursor = qpgresult->cursor; + int rows = PQntuples(pgresult); + if (rows > cursor) { + qpgrow->idx = cursor; + qpgrow->pgresult_ref = pgresult; + qpgresult->cursor++; + return qpgrow; + } else { + PGconn *pgconn = qpgresult->pgconn_ref; + PGresult *pgresult = PQgetResult(pgconn); + if (pgresult) { + pgsql_free_result(qpgresult); + qpgresult->pgresult = pgresult; + qpgresult->pgconn_ref = pgconn; + return pgsql_fetch_row(qpgresult); } else { - /* error */ - return -1; + return NULL; } } - - /* error */ - return -1; } #endif /* ENABLE_PGSQL */ @@ -573,14 +615,14 @@ static bool open_(qdb_t *db) } if (PQstatus(pgconn) != CONNECTION_OK) { - const char* emsg = PQerrorMessage(pgconn); - pgsql_set_emsg(db->pgsql, emsg); + // const char* emsg = PQerrorMessage(pgconn); // fprintf(stderr, "%s", emsg); + errno = ECONNREFUSED; ret = false; goto lable_quit; } - qtype_cast(pgsql_t*, db->pgsql)->pgconn = pgconn; + qtype_cast(qpgsql_t*, db->pgsql)->pgconn = pgconn; // set flag db->connected = true; @@ -666,7 +708,7 @@ static int execute_update(qdb_t *db, const char *query) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, db->info.fetchtype, query, PQ_UPDATE) == 0) { + if (pgsql_query(db->pgsql, query, PQ_UPDATE) == 0) { /* get affected rows */ if ((affected = pgsql_affected_rows(db->pgsql)) < 0) affected = -1; } @@ -715,15 +757,22 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) DEBUG("%s", query); -#if defined(Q_ENABLE_MYSQL) - // query - if (mysql_query(db->mysql, query)) return NULL; - // store qdbresult_t *result = (qdbresult_t *)malloc(sizeof(qdbresult_t)); if (result == NULL) return NULL; + // init result->fetchtype = db->info.fetchtype; + result->cursor = 0; + result->row = NULL; + +#if defined(Q_ENABLE_MYSQL) + // query + if (mysql_query(db->mysql, query)) { + free(result); + return NULL; + } + if (result->fetchtype == false) { result->rs = mysql_store_result(db->mysql); } else { @@ -736,25 +785,23 @@ static qdbresult_t *execute_query(qdb_t *db, const char *query) /* get meta data */ result->fields = NULL; - result->row = NULL; result->cols = mysql_num_fields(result->rs); - result->cursor = 0; #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) + qtype_cast(qpgsql_t*, db->pgsql)->fetchtype = result->fetchtype; + // query - if (pgsql_query(db->pgsql, db->info.fetchtype, query, PQ_QUERY)) return NULL; + if (pgsql_query(db->pgsql, query, PQ_QUERY)) return NULL; - // store - qdbresult_t *result = (qdbresult_t *)malloc(sizeof(qdbresult_t)); - if (result == NULL) return NULL; - result->pgsql = db->pgsql; + result->rs = pgsql_get_result(db->pgsql); + if (result->rs == NULL) { + free(result); + return NULL; + } /* get meta data */ - // result->row = NULL; - qtype_cast(pgsql_t*, result->pgsql)->rows = pgsql_num_rows(result->pgsql); - qtype_cast(pgsql_t*, result->pgsql)->cols = pgsql_num_fields(result->pgsql); - qtype_cast(pgsql_t*, result->pgsql)->cursor = 0; + result->cols = pgsql_num_fields(result->rs); #endif /* Q_ENABLE_PGSQL */ /* assign methods */ @@ -831,7 +878,7 @@ static bool begin_tran(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, db->info.fetchtype, "START TRANSACTION", PQ_TRANSACTION) < 0) { + if (pgsql_query(db->pgsql, "START TRANSACTION", PQ_TRANSACTION) < 0) { Q_MUTEX_LEAVE(db->qmutex); return false; } @@ -864,7 +911,7 @@ static bool commit(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, db->info.fetchtype, "COMMIT", PQ_TRANSACTION) == 0) { + if (pgsql_query(db->pgsql, "COMMIT", PQ_TRANSACTION) == 0) { ret = true; } #endif /* Q_ENABLE_PGSQL */ @@ -899,7 +946,7 @@ static bool rollback(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (pgsql_query(db->pgsql, db->info.fetchtype, "ROLLBACK", PQ_TRANSACTION) == 0) { + if (pgsql_query(db->pgsql, "ROLLBACK", PQ_TRANSACTION) == 0) { ret = true; } #endif /* Q_ENABLE_PGSQL */ @@ -994,7 +1041,7 @@ static bool ping(qdb_t *db) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (db->connected == true && pgsql_query(db->pgsql, db->info.fetchtype, NULL, PQ_PING) == 0) { + if (db->connected == true && pgsql_query(db->pgsql, NULL, PQ_PING) == 0) { return true; } ping_failed = true; @@ -1039,13 +1086,9 @@ static const char *get_error(qdb_t *db, unsigned int *errorno) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - emsg = qtype_cast(pgsql_t*, db->pgsql)->emsg; - if (emsg == NULL) { - eno = 0; - emsg = "(no error)"; - } else { - eno = 1; - } + eno = pgsql_errno(db->pgsql); + if (eno == 0) emsg = "(no error)"; + else emsg = pgsql_error(db->pgsql); #endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ emsg = "(not implemented)"; @@ -1097,9 +1140,8 @@ static const char *result_get_str(qdbresult_t *result, const char *field) if (result == NULL) return NULL; #if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) -#if defined(Q_ENABLE_MYSQL) if (result->rs == NULL || result->cols <= 0) return NULL; - +#if defined(Q_ENABLE_MYSQL) if (result->fields == NULL) result->fields = mysql_fetch_fields(result->rs); int i; @@ -1113,29 +1155,14 @@ static const char *result_get_str(qdbresult_t *result, const char *field) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - pgsql_t* pgsql = result->pgsql; - if (pgsql == NULL || pgsql->cols <= 0) { - return NULL; - } - /* get field num */ - const char *value = NULL; - int field_num = PQfnumber(pgsql->pgresult, field); + qpgresult_t *qpgresult = result->rs; + int field_num = PQfnumber(qpgresult->pgresult, field); if (field_num == -1) { return NULL; } - /* get row num */ - int row_num = -1; - if (pgsql->rows == 1) { - row_num = 0; - } else { - row_num = pgsql->cursor; - } - - /* get value */ - value = PQgetvalue(pgsql->pgresult, row_num, field_num); - return value; + return result->get_str_at(result, field_num + 1); #endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return NULL; @@ -1155,38 +1182,21 @@ static const char *result_get_str_at(qdbresult_t *result, int idx) if (result == NULL) return NULL; #if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) -#if defined(Q_ENABLE_MYSQL) if (result->rs == NULL || result->cursor == 0 || idx <= 0 - || idx > result->cols ) { + || idx > result->cols) { return NULL; } + +#if defined(Q_ENABLE_MYSQL) return result->row[idx-1]; #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - pgsql_t* pgsql = result->pgsql; - if (result->pgsql == NULL - || pgsql->rows == 0 - || pgsql->cols == 0 - || idx <= 0 - || idx > pgsql->cols) { - return NULL; - } - - /* get row num */ - int row_num = -1; - if (pgsql->rows == 1) { - row_num = 0; - } else { - row_num = pgsql->cursor; - } - - /* get value */ - const char *value = PQgetvalue(pgsql->pgresult, row_num, idx - 1); - return value; + return pgsql_row_get_str_at(result->row, idx - 1); #endif /* Q_ENABLE_PGSQL */ + #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return NULL; #endif @@ -1234,9 +1244,9 @@ static bool result_get_next(qdbresult_t *result) if (result == NULL) return false; #if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) -#if defined(Q_ENABLE_MYSQL) if (result->rs == NULL) return false; +#if defined(Q_ENABLE_MYSQL) if ((result->row = mysql_fetch_row(result->rs)) == NULL) return false; result->cursor++; @@ -1244,20 +1254,9 @@ static bool result_get_next(qdbresult_t *result) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - pgsql_t* pgsql = result->pgsql; - - if (pgsql == NULL || pgsql->pgresult == NULL) { - return false; - } - - int cursor = pgsql->cursor; - if (++cursor >= pgsql->rows) { - if (pgsql_fetch_row(pgsql) != 0) { - return false; - } - } + if ((result->row = pgsql_fetch_row(result->rs)) == NULL) return false; + result->cursor++; - pgsql->cursor = cursor; return true; #endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ @@ -1277,15 +1276,8 @@ static int result_get_cols(qdbresult_t *result) if (result == NULL) return 0; #if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) -#if defined(Q_ENABLE_MYSQL) if (result->rs == NULL) return 0; return result->cols; -#endif /* Q_ENABLE_MYSQL */ - -#if defined(Q_ENABLE_PGSQL) - if (result->pgsql == NULL) return 0; - return qtype_cast(pgsql_t*, result->pgsql)->cols; -#endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return 0; #endif @@ -1303,15 +1295,16 @@ static int result_get_rows(qdbresult_t *result) if (result == NULL) return 0; #if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) -#if defined(Q_ENABLE_MYSQL) if (result->rs == NULL) return 0; + +#if defined(Q_ENABLE_MYSQL) return mysql_num_rows(result->rs); #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - if (result->pgsql == NULL) return 0; - return qtype_cast(pgsql_t*, result->pgsql)->rows; + return pgsql_num_rows(result->rs); #endif /* Q_ENABLE_PGSQL */ + #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return 0; #endif @@ -1332,15 +1325,8 @@ static int result_get_row(qdbresult_t *result) if (result == NULL) return 0; #if defined(Q_ENABLE_MYSQL) || defined(Q_ENABLE_PGSQL) -#if defined(Q_ENABLE_MYSQL) if (result->rs == NULL) return 0; return result->cursor; -#endif /* Q_ENABLE_MYSQL */ - -#if defined(Q_ENABLE_PGSQL) - if (result->pgsql == NULL) return 0; - return qtype_cast(pgsql_t*, result->pgsql)->cursor; -#endif /* Q_ENABLE_PGSQL */ #else /* Q_ENABLE_MYSQL || Q_ENABLE_PGSQL */ return 0; #endif @@ -1369,7 +1355,10 @@ static void result_free(qdbresult_t *result) #endif /* Q_ENABLE_MYSQL */ #if defined(Q_ENABLE_PGSQL) - result->pgsql = NULL; + if (result->rs != NULL) { + pgsql_free_result(result->rs); + result->rs = NULL; + } free(result); return; #endif /* Q_ENABLE_PGSQL */ diff --git a/tests/test_qdatabase_pgsql.c b/tests/test_qdatabase_pgsql.c index 78373457..81c6e353 100644 --- a/tests/test_qdatabase_pgsql.c +++ b/tests/test_qdatabase_pgsql.c @@ -199,16 +199,33 @@ TEST("Test3: execute_queryf()") { qdbresult_t *qrst = test_db->execute_queryf(test_db, "SELECT * FROM %s;", "animals"); ASSERT_EQUAL_INT(qrst->get_rows(qrst), 5); ASSERT_EQUAL_INT(qrst->get_cols(qrst), 7); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 0); - ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Dog"); - ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Canis lupus familiaris"); - ASSERT_TRUE(qrst->get_next(qrst)); - ASSERT_EQUAL_INT(qrst->get_row(qrst), 1); - ASSERT_TRUE(qrst->get_next(qrst)); - ASSERT_EQUAL_INT(qrst->get_row(qrst), 2); - - ASSERT_EQUAL_INT(qrst->get_int(qrst, "animal_id"), 3); - ASSERT_EQUAL_INT(qrst->get_int_at(qrst, 5), 1); + ASSERT_NULL(qrst->get_str(qrst, "animal_name")); + ASSERT_NULL(qrst->get_str_at(qrst, 3)); + + int i = 1; + while (qrst->get_next(qrst)) { + ASSERT_EQUAL_INT(qrst->get_row(qrst), i); + if (i == 1) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Dog"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Canis lupus familiaris"); + } else if (i == 2) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Cat"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Felis catus"); + } else if (i == 3) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Rabbit"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Oryctolagus cuniculus"); + } else if (i == 4) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Hamster"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Mesocricetus auratus"); + } else if (i == 5) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Fish"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Poecilia reticulata"); + } + i++; + } + ASSERT_EQUAL_INT(i, 6); qrst->free(qrst); /* disconnect */ @@ -286,16 +303,33 @@ TEST("Test6: fetchtype") { qdbresult_t *qrst = test_db->execute_queryf(test_db, "SELECT * FROM %s;", "animals"); ASSERT_EQUAL_INT(qrst->get_rows(qrst), 1); ASSERT_EQUAL_INT(qrst->get_cols(qrst), 7); + ASSERT_EQUAL_INT(qrst->get_row(qrst), 0); - ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Dog"); - ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Canis lupus familiaris"); - ASSERT_TRUE(qrst->get_next(qrst)); - ASSERT_EQUAL_INT(qrst->get_row(qrst), 1); - ASSERT_TRUE(qrst->get_next(qrst)); - ASSERT_EQUAL_INT(qrst->get_row(qrst), 2); - - ASSERT_EQUAL_INT(qrst->get_int(qrst, "animal_id"), 3); - ASSERT_EQUAL_INT(qrst->get_int_at(qrst, 5), 1); + ASSERT_NULL(qrst->get_str(qrst, "animal_name")); + ASSERT_NULL(qrst->get_str_at(qrst, 3)); + + int i = 1; + while (qrst->get_next(qrst)) { + ASSERT_EQUAL_INT(qrst->get_row(qrst), i); + if (i == 1) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Dog"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Canis lupus familiaris"); + } else if (i == 2) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Cat"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Felis catus"); + } else if (i == 3) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Rabbit"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Oryctolagus cuniculus"); + } else if (i == 4) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Hamster"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Mesocricetus auratus"); + } else if (i == 5) { + ASSERT_EQUAL_STR(qrst->get_str(qrst, "animal_name"), "Fish"); + ASSERT_EQUAL_STR(qrst->get_str_at(qrst, 3), "Poecilia reticulata"); + } + i++; + } + ASSERT_EQUAL_INT(i, 6); qrst->free(qrst); /* test: set_fetchtype false */