Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support PostgreSQL: interface/test-cases/ci-testing #4

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions .github/workflows/ci-run-test-extensions.yml
Original file line number Diff line number Diff line change
@@ -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
31 changes: 28 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 3 additions & 1 deletion Makefile.in
Original file line number Diff line number Diff line change
@@ -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}"; \
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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
103 changes: 103 additions & 0 deletions configure
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
@@ -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])
32 changes: 26 additions & 6 deletions include/qlibc/extensions/qdatabase.h
Original file line number Diff line number Diff line change
@@ -51,6 +51,14 @@ extern "C" {
#define Q_ENABLE_MYSQL (1)
#endif /* _mysql_h */

#ifdef LIBPQ_FE_H
#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;
@@ -65,26 +73,33 @@ 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);
int (*get_row) (qdbresult_t *result);

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 *rs;
const void *row;
#endif
};

@@ -131,6 +146,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
660 changes: 580 additions & 80 deletions src/extensions/qdatabase.c

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
17 changes: 16 additions & 1 deletion tests/Makefile.in
Original file line number Diff line number Diff line change
@@ -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 $@ $<
368 changes: 368 additions & 0 deletions tests/test_qdatabase_pgsql.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,368 @@
/******************************************************************************
* 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.
*****************************************************************************/

#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_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 */
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: 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_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 */
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);
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)");

/* 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();