You've already forked pgbackrest
							
							
				mirror of
				https://github.com/pgbackrest/pgbackrest.git
				synced 2025-10-30 23:37:45 +02:00 
			
		
		
		
	Add PostgreSQL query client.
This direct interface to libpq allows simple queries to be run against PostgreSQL and supports timeouts. Testing is performed using a shim that can use scripted responses to test all aspects of the client code. The shim will be very useful for testing backup scenarios on complex topologies. Reviewed by Cynthia Shang.
This commit is contained in:
		| @@ -52,6 +52,14 @@ | ||||
|  | ||||
|                         <p>Add Perl interface to C storage layer.</p> | ||||
|                     </release-item> | ||||
|  | ||||
|                     <release-item> | ||||
|                         <release-item-contributor-list> | ||||
|                             <release-item-reviewer id="cynthia.shang"/> | ||||
|                         </release-item-contributor-list> | ||||
|  | ||||
|                         <p>Add PostgreSQL query client.</p> | ||||
|                     </release-item> | ||||
|                 </release-development-list> | ||||
|             </release-core-list> | ||||
|         </release> | ||||
|   | ||||
| @@ -758,14 +758,16 @@ | ||||
|             </execute> | ||||
|  | ||||
|             <execute if="{[os-type-is-debian]}" user="root" pre="y"> | ||||
|                 <exe-cmd>apt-get install build-essential libssl-dev libxml2-dev libperl-dev zlib1g-dev</exe-cmd> | ||||
|                 <exe-cmd> | ||||
|                     apt-get install build-essential libssl-dev libxml2-dev libperl-dev zlib1g-dev | ||||
|                             libpq-dev</exe-cmd> | ||||
|                 <exe-cmd-extra>-y 2>&1</exe-cmd-extra> | ||||
|             </execute> | ||||
|  | ||||
|             <execute if="{[os-type-is-centos6]}" user="root" pre="y"> | ||||
|                 <exe-cmd> | ||||
|                     yum install build-essential gcc openssl-devel libxml2-devel | ||||
|                         perl-ExtUtils-Embed | ||||
|                         postgresql-devel perl-ExtUtils-Embed | ||||
|                 </exe-cmd> | ||||
|                 <exe-cmd-extra>-y 2>&1</exe-cmd-extra> | ||||
|             </execute> | ||||
| @@ -773,7 +775,7 @@ | ||||
|             <execute if="{[os-type-is-centos7]}" user="root" pre="y"> | ||||
|                 <exe-cmd> | ||||
|                     yum install build-essential gcc make openssl-devel libxml2-devel | ||||
|                         perl-ExtUtils-Embed | ||||
|                         postgresql-devel perl-ExtUtils-Embed | ||||
|                 </exe-cmd> | ||||
|                 <exe-cmd-extra>-y 2>&1</exe-cmd-extra> | ||||
|             </execute> | ||||
|   | ||||
| @@ -127,6 +127,7 @@ SRCS = \ | ||||
| 	info/infoPg.c \ | ||||
| 	perl/config.c \ | ||||
| 	perl/exec.c \ | ||||
| 	postgres/client.c \ | ||||
| 	postgres/interface.c \ | ||||
| 	postgres/interface/v083.c \ | ||||
| 	postgres/interface/v084.c \ | ||||
| @@ -449,6 +450,9 @@ perl/config.o: perl/config.c build.auto.h common/assert.h common/debug.h common/ | ||||
| perl/exec.o: perl/exec.c ../libc/LibC.h build.auto.h common/assert.h common/compress/gzip/compress.h common/compress/gzip/decompress.h common/crypto/cipherBlock.h common/crypto/common.h common/crypto/hash.h common/debug.h common/encode.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/filter/size.h common/io/http/client.h common/io/http/header.h common/io/http/query.h common/io/io.h common/io/read.h common/io/read.intern.h common/io/write.h common/io/write.intern.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h config/load.h config/parse.h perl/config.h perl/embed.auto.c perl/exec.h perl/libc.auto.c postgres/interface.h postgres/pageChecksum.h storage/helper.h storage/info.h storage/posix/storage.h storage/read.h storage/read.intern.h storage/s3/storage.h storage/s3/storage.intern.h storage/storage.h storage/storage.intern.h storage/write.h storage/write.intern.h version.h ../libc/xs/common/encode.xsh ../libc/xs/crypto/hash.xsh ../libc/xs/storage/storage.xsh ../libc/xs/storage/storageRead.xsh ../libc/xs/storage/storageWrite.xsh | ||||
| 	$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c perl/exec.c -o perl/exec.o | ||||
|  | ||||
| postgres/client.o: postgres/client.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/log.h common/logLevel.h common/macro.h common/memContext.h common/object.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h common/wait.h postgres/client.h | ||||
| 	$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c postgres/client.c -o postgres/client.o | ||||
|  | ||||
| postgres/interface.o: postgres/interface.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/memContext.h common/regExp.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h postgres/interface.h postgres/interface/version.h postgres/version.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h | ||||
| 	$(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c postgres/interface.c -o postgres/interface.o | ||||
|  | ||||
|   | ||||
							
								
								
									
										51
									
								
								src/configure
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										51
									
								
								src/configure
									
									
									
									
										vendored
									
									
								
							| @@ -2861,6 +2861,57 @@ LIBS="$LIBS_BEFORE_PERL `perl -MExtUtils::Embed -e ldopts`" | ||||
| CLIBRARY="`perl -MExtUtils::Embed -e ccopts`" | ||||
|  | ||||
|  | ||||
| # Check required pq library | ||||
| { $as_echo "$as_me:${as_lineno-$LINENO}: checking for PQconnectdb in -lpq" >&5 | ||||
| $as_echo_n "checking for PQconnectdb in -lpq... " >&6; } | ||||
| if ${ac_cv_lib_pq_PQconnectdb+:} false; then : | ||||
|   $as_echo_n "(cached) " >&6 | ||||
| else | ||||
|   ac_check_lib_save_LIBS=$LIBS | ||||
| LIBS="-lpq  $LIBS" | ||||
| cat confdefs.h - <<_ACEOF >conftest.$ac_ext | ||||
| /* end confdefs.h.  */ | ||||
|  | ||||
| /* Override any GCC internal prototype to avoid an error. | ||||
|    Use char because int might match the return type of a GCC | ||||
|    builtin and then its argument prototype would still apply.  */ | ||||
| #ifdef __cplusplus | ||||
| extern "C" | ||||
| #endif | ||||
| char PQconnectdb (); | ||||
| int | ||||
| main () | ||||
| { | ||||
| return PQconnectdb (); | ||||
|   ; | ||||
|   return 0; | ||||
| } | ||||
| _ACEOF | ||||
| if ac_fn_c_try_link "$LINENO"; then : | ||||
|   ac_cv_lib_pq_PQconnectdb=yes | ||||
| else | ||||
|   ac_cv_lib_pq_PQconnectdb=no | ||||
| fi | ||||
| rm -f core conftest.err conftest.$ac_objext \ | ||||
|     conftest$ac_exeext conftest.$ac_ext | ||||
| LIBS=$ac_check_lib_save_LIBS | ||||
| fi | ||||
| { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pq_PQconnectdb" >&5 | ||||
| $as_echo "$ac_cv_lib_pq_PQconnectdb" >&6; } | ||||
| if test "x$ac_cv_lib_pq_PQconnectdb" = xyes; then : | ||||
|   cat >>confdefs.h <<_ACEOF | ||||
| #define HAVE_LIBPQ 1 | ||||
| _ACEOF | ||||
|  | ||||
|   LIBS="-lpq $LIBS" | ||||
|  | ||||
| else | ||||
|   as_fn_error $? "library 'pq' is required" "$LINENO" 5 | ||||
| fi | ||||
|  | ||||
| CINCLUDE="$CINCLUDE -I`pg_config --includedir`" | ||||
|  | ||||
|  | ||||
| # Check required openssl libraries | ||||
| { $as_echo "$as_me:${as_lineno-$LINENO}: checking for EVP_get_digestbyname in -lcrypto" >&5 | ||||
| $as_echo_n "checking for EVP_get_digestbyname in -lcrypto... " >&6; } | ||||
|   | ||||
| @@ -39,6 +39,10 @@ AC_CHECK_LIB([perl], [perl_parse], [], [AC_MSG_ERROR([library 'perl' is required | ||||
| LIBS="$LIBS_BEFORE_PERL `perl -MExtUtils::Embed -e ldopts`" | ||||
| AC_SUBST(CLIBRARY, "`perl -MExtUtils::Embed -e ccopts`") | ||||
|  | ||||
| # Check required pq library | ||||
| AC_CHECK_LIB([pq], [PQconnectdb], [], [AC_MSG_ERROR([library 'pq' is required])]) | ||||
| AC_SUBST(CINCLUDE, "$CINCLUDE -I`pg_config --includedir`") | ||||
|  | ||||
| # Check required openssl libraries | ||||
| AC_CHECK_LIB([crypto], [EVP_get_digestbyname], [], [AC_MSG_ERROR([library 'crypto' is required])]) | ||||
| AC_CHECK_LIB([ssl], [SSL_new], [], [AC_MSG_ERROR([library 'ssl' is required])]) | ||||
|   | ||||
							
								
								
									
										371
									
								
								src/postgres/client.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								src/postgres/client.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,371 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Postgres Client | ||||
| ***********************************************************************************************************************************/ | ||||
| #include "build.auto.h" | ||||
|  | ||||
| #include <libpq-fe.h> | ||||
|  | ||||
| #include "common/debug.h" | ||||
| #include "common/log.h" | ||||
| #include "common/memContext.h" | ||||
| #include "common/object.h" | ||||
| #include "common/time.h" | ||||
| #include "common/type/list.h" | ||||
| #include "common/type/string.h" | ||||
| #include "common/type/variantList.h" | ||||
| #include "common/wait.h" | ||||
| #include "postgres/client.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Object type | ||||
| ***********************************************************************************************************************************/ | ||||
| struct PgClient | ||||
| { | ||||
|     MemContext *memContext; | ||||
|     const String *host; | ||||
|     unsigned int port; | ||||
|     const String *database; | ||||
|     const String *user; | ||||
|     TimeMSec queryTimeout; | ||||
|  | ||||
|     PGconn *connection; | ||||
| }; | ||||
|  | ||||
| OBJECT_DEFINE_FREE(PG_CLIENT); | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Close protocol connection | ||||
| ***********************************************************************************************************************************/ | ||||
| OBJECT_DEFINE_FREE_RESOURCE_BEGIN(PG_CLIENT, LOG, logLevelTrace) | ||||
| { | ||||
|     PQfinish(this->connection); | ||||
| } | ||||
| OBJECT_DEFINE_FREE_RESOURCE_END(LOG); | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Create object | ||||
| ***********************************************************************************************************************************/ | ||||
| PgClient * | ||||
| pgClientNew(const String *host, const unsigned int port, const String *database, const String *user, const TimeMSec queryTimeout) | ||||
| { | ||||
|     FUNCTION_LOG_BEGIN(logLevelDebug); | ||||
|         FUNCTION_LOG_PARAM(STRING, host); | ||||
|         FUNCTION_LOG_PARAM(UINT, port); | ||||
|         FUNCTION_LOG_PARAM(STRING, database); | ||||
|         FUNCTION_LOG_PARAM(STRING, user); | ||||
|         FUNCTION_LOG_PARAM(TIME_MSEC, queryTimeout); | ||||
|     FUNCTION_LOG_END(); | ||||
|  | ||||
|     ASSERT(port >= 1 && port <= 65535); | ||||
|     ASSERT(database != NULL); | ||||
|  | ||||
|     PgClient *this = NULL; | ||||
|  | ||||
|     MEM_CONTEXT_NEW_BEGIN("PgClient") | ||||
|     { | ||||
|         this = memNew(sizeof(PgClient)); | ||||
|         this->memContext = memContextCurrent(); | ||||
|  | ||||
|         this->host = strDup(host); | ||||
|         this->port = port; | ||||
|         this->database = strDup(database); | ||||
|         this->user = strDup(user); | ||||
|         this->queryTimeout = queryTimeout; | ||||
|     } | ||||
|     MEM_CONTEXT_NEW_END(); | ||||
|  | ||||
|     FUNCTION_LOG_RETURN(PG_CLIENT, this); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Just ignore notices and warnings | ||||
| ***********************************************************************************************************************************/ | ||||
| static void | ||||
| pgClientNoticeProcessor(void *arg, const char *message) | ||||
| { | ||||
|     (void)arg; | ||||
|     (void)message; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Encode string to escape ' and \ | ||||
| ***********************************************************************************************************************************/ | ||||
| static String * | ||||
| pgClientEscape(const String *string) | ||||
| { | ||||
|     FUNCTION_TEST_BEGIN(); | ||||
|         FUNCTION_TEST_PARAM(STRING, string); | ||||
|     FUNCTION_TEST_END(); | ||||
|  | ||||
|     ASSERT(string != NULL); | ||||
|  | ||||
|     String *result = strNew("'"); | ||||
|  | ||||
|     // Iterate all characters in the string | ||||
|     for (unsigned stringIdx = 0; stringIdx < strSize(string); stringIdx++) | ||||
|     { | ||||
|         char stringChar = strPtr(string)[stringIdx]; | ||||
|  | ||||
|         // These characters are escaped | ||||
|         if (stringChar == '\'' || stringChar == '\\') | ||||
|             strCatChr(result, '\\'); | ||||
|  | ||||
|         strCatChr(result, stringChar); | ||||
|     } | ||||
|  | ||||
|     strCatChr(result, '\''); | ||||
|  | ||||
|     FUNCTION_TEST_RETURN(result); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Open connection to PostgreSQL | ||||
| ***********************************************************************************************************************************/ | ||||
| PgClient * | ||||
| pgClientOpen(PgClient *this) | ||||
| { | ||||
|     FUNCTION_LOG_BEGIN(logLevelDebug); | ||||
|         FUNCTION_LOG_PARAM(PG_CLIENT, this); | ||||
|     FUNCTION_LOG_END(); | ||||
|  | ||||
|     ASSERT(this != NULL); | ||||
|     CHECK(this->connection == NULL); | ||||
|  | ||||
|     MEM_CONTEXT_TEMP_BEGIN() | ||||
|     { | ||||
|         // Base connection string | ||||
|         String *connInfo = strNewFmt("dbname=%s port=%u", strPtr(pgClientEscape(this->database)), this->port); | ||||
|  | ||||
|         // Add user if specified | ||||
|         if (this->user != NULL) | ||||
|             strCatFmt(connInfo, " user=%s", strPtr(pgClientEscape(this->user))); | ||||
|  | ||||
|         // Add host if specified | ||||
|         if (this->host != NULL) | ||||
|             strCatFmt(connInfo, " host=%s", strPtr(pgClientEscape(this->host))); | ||||
|  | ||||
|         // Make the connection | ||||
|         this->connection = PQconnectdb(strPtr(connInfo)); | ||||
|  | ||||
|         // Set a callback to shutdown the connection | ||||
|         memContextCallbackSet(this->memContext, pgClientFreeResource, this); | ||||
|  | ||||
|         // Handle errors | ||||
|         if (PQstatus(this->connection) != CONNECTION_OK) | ||||
|         { | ||||
|             THROW_FMT( | ||||
|                 DbConnectError, "unable to connect to '%s': %s", strPtr(connInfo), | ||||
|                 strPtr(strTrim(strNew(PQerrorMessage(this->connection))))); | ||||
|         } | ||||
|  | ||||
|         // Set notice and warning processor | ||||
|         PQsetNoticeProcessor(this->connection, pgClientNoticeProcessor, NULL); | ||||
|     } | ||||
|     MEM_CONTEXT_TEMP_END(); | ||||
|  | ||||
|     FUNCTION_LOG_RETURN(PG_CLIENT, this); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Execute a query and return results | ||||
| ***********************************************************************************************************************************/ | ||||
| VariantList * | ||||
| pgClientQuery(PgClient *this, const String *query) | ||||
| { | ||||
|     FUNCTION_LOG_BEGIN(logLevelDebug); | ||||
|         FUNCTION_LOG_PARAM(PG_CLIENT, this); | ||||
|         FUNCTION_LOG_PARAM(STRING, query); | ||||
|     FUNCTION_LOG_END(); | ||||
|  | ||||
|     ASSERT(this != NULL); | ||||
|     CHECK(this->connection != NULL); | ||||
|     ASSERT(query != NULL); | ||||
|  | ||||
|     VariantList *result = NULL; | ||||
|  | ||||
|     MEM_CONTEXT_TEMP_BEGIN() | ||||
|     { | ||||
|         // Send the query without waiting for results so we can timeout if needed | ||||
|         if (!PQsendQuery(this->connection, strPtr(query))) | ||||
|         { | ||||
|             THROW_FMT( | ||||
|                 DbQueryError, "unable to send query '%s': %s", strPtr(query), | ||||
|                 strPtr(strTrim(strNew(PQerrorMessage(this->connection))))); | ||||
|         } | ||||
|  | ||||
|         // Wait for a result | ||||
|         Wait *wait = waitNew(this->queryTimeout); | ||||
|         bool busy = false; | ||||
|  | ||||
|         do | ||||
|         { | ||||
|             PQconsumeInput(this->connection); | ||||
|             busy = PQisBusy(this->connection); | ||||
|         } | ||||
|         while (busy && waitMore(wait)); | ||||
|  | ||||
|         // If the query is still busy after the timeout attempt to cancel | ||||
|         if (busy) | ||||
|         { | ||||
|             PGcancel *cancel = PQgetCancel(this->connection); | ||||
|             CHECK(cancel != NULL); | ||||
|  | ||||
|             TRY_BEGIN() | ||||
|             { | ||||
|                 char error[256]; | ||||
|  | ||||
|                 if (!PQcancel(cancel, error, sizeof(error))) | ||||
|                     THROW_FMT(DbQueryError, "unable to cancel query '%s': %s", strPtr(query), strPtr(strTrim(strNew(error)))); | ||||
|             } | ||||
|             FINALLY() | ||||
|             { | ||||
|                 PQfreeCancel(cancel); | ||||
|             } | ||||
|             TRY_END(); | ||||
|         } | ||||
|  | ||||
|         // Get the result (even if query was cancelled -- to prevent the connection being left in a bad state) | ||||
|         PGresult *pgResult = PQgetResult(this->connection); | ||||
|  | ||||
|         TRY_BEGIN() | ||||
|         { | ||||
|             // Throw timeout error if cancelled | ||||
|             if (busy) | ||||
|                 THROW_FMT(DbQueryError, "query '%s' timed out after %" PRIu64 "ms", strPtr(query), this->queryTimeout); | ||||
|  | ||||
|             // If this was a command that returned no results then we are done | ||||
|             int resultStatus = PQresultStatus(pgResult); | ||||
|  | ||||
|             if (resultStatus != PGRES_COMMAND_OK) | ||||
|             { | ||||
|                 // Expect some rows to be returned | ||||
|                 if (resultStatus != PGRES_TUPLES_OK) | ||||
|                 { | ||||
|                     THROW_FMT( | ||||
|                         DbQueryError, "unable to execute query '%s': %s", strPtr(query), | ||||
|                         strPtr(strTrim(strNew(PQresultErrorMessage(pgResult))))); | ||||
|                 } | ||||
|  | ||||
|                 // Fetch row and column values | ||||
|                 result = varLstNew(); | ||||
|  | ||||
|                 MEM_CONTEXT_BEGIN(lstMemContext((List *)result)) | ||||
|                 { | ||||
|                     int rowTotal = PQntuples(pgResult); | ||||
|                     int columnTotal = PQnfields(pgResult); | ||||
|  | ||||
|                     // Get column types | ||||
|                     Oid *columnType = memNew(sizeof(int) * (size_t)columnTotal); | ||||
|  | ||||
|                     for (int columnIdx = 0; columnIdx < columnTotal; columnIdx++) | ||||
|                         columnType[columnIdx] = PQftype(pgResult, columnIdx); | ||||
|  | ||||
|                     // Get values | ||||
|                     for (int rowIdx = 0; rowIdx < rowTotal; rowIdx++) | ||||
|                     { | ||||
|                         VariantList *resultRow = varLstNew(); | ||||
|  | ||||
|                         for (int columnIdx = 0; columnIdx < columnTotal; columnIdx++) | ||||
|                         { | ||||
|                             char *value = PQgetvalue(pgResult, rowIdx, columnIdx); | ||||
|  | ||||
|                             // If value is zero-length then check if it is null | ||||
|                             if (value[0] == '\0' && PQgetisnull(pgResult, rowIdx, columnIdx)) | ||||
|                             { | ||||
|                                 varLstAdd(resultRow, NULL); | ||||
|                             } | ||||
|                             // Else convert the value to a variant | ||||
|                             else | ||||
|                             { | ||||
|                                 // Convert column type.  Not all PostgreSQL types are supported but these should suffice. | ||||
|                                 switch (columnType[columnIdx]) | ||||
|                                 { | ||||
|                                     // Boolean type | ||||
|                                     case 16:                            // bool | ||||
|                                     { | ||||
|                                         varLstAdd(resultRow, varNewBool(varBoolForce(varNewStrZ(value)))); | ||||
|                                         break; | ||||
|                                     } | ||||
|  | ||||
|                                     // Text/char types | ||||
|                                     case 18:                            // char | ||||
|                                     case 19:                            // name | ||||
|                                     case 25:                            // text | ||||
|                                     { | ||||
|                                         varLstAdd(resultRow, varNewStrZ(value)); | ||||
|                                         break; | ||||
|                                     } | ||||
|  | ||||
|                                     // Integer types | ||||
|                                     case 20:                            // int8 | ||||
|                                     case 21:                            // int2 | ||||
|                                     case 23:                            // int4 | ||||
|                                     case 26:                            // oid | ||||
|                                     { | ||||
|                                         varLstAdd(resultRow, varNewInt64(cvtZToInt64(value))); | ||||
|                                         break; | ||||
|                                     } | ||||
|  | ||||
|                                     default: | ||||
|                                     { | ||||
|                                         THROW_FMT( | ||||
|                                             FormatError, "unable to parse type %u in column %d for query '%s'", | ||||
|                                             columnType[columnIdx], columnIdx, strPtr(query)); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                         varLstAdd(result, varNewVarLst(resultRow)); | ||||
|                     } | ||||
|                 } | ||||
|                 MEM_CONTEXT_END(); | ||||
|             } | ||||
|         } | ||||
|         FINALLY() | ||||
|         { | ||||
|             // Free the result | ||||
|             PQclear(pgResult); | ||||
|  | ||||
|             // Need to get a NULL result to complete the request | ||||
|             CHECK(PQgetResult(this->connection) == NULL); | ||||
|         } | ||||
|         TRY_END(); | ||||
|  | ||||
|         varLstMove(result, MEM_CONTEXT_OLD()); | ||||
|     } | ||||
|     MEM_CONTEXT_TEMP_END(); | ||||
|  | ||||
|     FUNCTION_LOG_RETURN(VARIANT_LIST, result); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Close connection to PostgreSQL | ||||
| ***********************************************************************************************************************************/ | ||||
| void | ||||
| pgClientClose(PgClient *this) | ||||
| { | ||||
|     FUNCTION_LOG_BEGIN(logLevelDebug); | ||||
|         FUNCTION_LOG_PARAM(PG_CLIENT, this); | ||||
|     FUNCTION_LOG_END(); | ||||
|  | ||||
|     ASSERT(this != NULL); | ||||
|     CHECK(this->connection != NULL); | ||||
|  | ||||
|     memContextCallbackClear(this->memContext); | ||||
|     PQfinish(this->connection); | ||||
|     this->connection = NULL; | ||||
|  | ||||
|     FUNCTION_LOG_RETURN_VOID(); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Render as string for logging | ||||
| ***********************************************************************************************************************************/ | ||||
| String * | ||||
| pgClientToLog(const PgClient *this) | ||||
| { | ||||
|     return strNewFmt( | ||||
|         "{host: %s, port: %u, database: %s, user: %s, queryTimeout %" PRIu64 "}", strPtr(strToLog(this->host)), this->port, | ||||
|         strPtr(strToLog(this->database)), strPtr(strToLog(this->user)), this->queryTimeout); | ||||
| } | ||||
							
								
								
									
										47
									
								
								src/postgres/client.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/postgres/client.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| PostgreSQL Client | ||||
|  | ||||
| Connect to a PostgreSQL database and run queries.  This is not intended to be a general purpose client but is suitable for | ||||
| pgBackRest's limited needs.  In particular, data type support is limited to text, int, and bool types so it may be necessary to add | ||||
| casts to queries to output one of these types. | ||||
| ***********************************************************************************************************************************/ | ||||
| #ifndef POSTGRES_QUERY_H | ||||
| #define POSTGRES_QUERY_H | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Object type | ||||
| ***********************************************************************************************************************************/ | ||||
| #define PG_CLIENT_TYPE                                               PgClient | ||||
| #define PG_CLIENT_PREFIX                                             pgClient | ||||
|  | ||||
| typedef struct PgClient PgClient; | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Constructor | ||||
| ***********************************************************************************************************************************/ | ||||
| PgClient *pgClientNew( | ||||
|     const String *host, const unsigned int port, const String *database, const String *user, const TimeMSec queryTimeout); | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Functions | ||||
| ***********************************************************************************************************************************/ | ||||
| PgClient *pgClientOpen(PgClient *this); | ||||
| VariantList *pgClientQuery(PgClient *this, const String *query); | ||||
| void pgClientClose(PgClient *this); | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Destructor | ||||
| ***********************************************************************************************************************************/ | ||||
| void pgClientFree(PgClient *this); | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Macros for function logging | ||||
| ***********************************************************************************************************************************/ | ||||
| String *pgClientToLog(const PgClient *this); | ||||
|  | ||||
| #define FUNCTION_LOG_PG_CLIENT_TYPE                                                                                                \ | ||||
|     PgClient * | ||||
| #define FUNCTION_LOG_PG_CLIENT_FORMAT(value, buffer, bufferSize)                                                                   \ | ||||
|     FUNCTION_LOG_STRING_OBJECT_FORMAT(value, pgClientToLog, buffer, bufferSize) | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										2
									
								
								test/Vagrantfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								test/Vagrantfile
									
									
									
									
										vendored
									
									
								
							| @@ -60,7 +60,7 @@ Vagrant.configure(2) do |config| | ||||
|         #--------------------------------------------------------------------------------------------------------------------------- | ||||
|         echo 'Install Build Tools' && date | ||||
|         apt-get install -y devscripts build-essential lintian git lcov cloc txt2man debhelper libssl-dev zlib1g-dev libperl-dev \ | ||||
|              libxml2-dev liblz4-dev | ||||
|              libxml2-dev liblz4-dev libpq-dev | ||||
|  | ||||
|         #--------------------------------------------------------------------------------------------------------------------------- | ||||
|         echo 'Install AWS CLI' && date | ||||
|   | ||||
| @@ -315,6 +315,13 @@ unit: | ||||
|   - name: postgres | ||||
|  | ||||
|     test: | ||||
|       # ---------------------------------------------------------------------------------------------------------------------------- | ||||
|       - name: client | ||||
|         total: 1 | ||||
|  | ||||
|         coverage: | ||||
|           postgres/client: full | ||||
|  | ||||
|       # ---------------------------------------------------------------------------------------------------------------------------- | ||||
|       - name: interface | ||||
|         total: 5 | ||||
|   | ||||
| @@ -483,7 +483,7 @@ sub containerBuild | ||||
|                         "        http://yum.postgresql.org/9.1/redhat/rhel-6-x86_64/pgdg-centos91-9.1-6.noarch.rpm \\\n" . | ||||
|                         "        http://yum.postgresql.org/9.2/redhat/rhel-6-x86_64/pgdg-centos92-9.2-8.noarch.rpm \\\n" . | ||||
|                         "        https://download.postgresql.org/pub/repos/yum/11/redhat/rhel-6-x86_64/" . | ||||
|                             "pgdg-redhat-repo-latest.noarch.rpm"; | ||||
|                             "pgdg-redhat-repo-latest.noarch.rpm && \\\n"; | ||||
|                 } | ||||
|                 elsif ($strOS eq VM_CO7) | ||||
|                 { | ||||
| @@ -491,8 +491,10 @@ sub containerBuild | ||||
|                         "    rpm -ivh \\\n" . | ||||
|                         "        http://yum.postgresql.org/9.2/redhat/rhel-7-x86_64/pgdg-centos92-9.2-3.noarch.rpm \\\n" . | ||||
|                         "        https://download.postgresql.org/pub/repos/yum/11/redhat/rhel-7-x86_64/" . | ||||
|                             "pgdg-redhat-repo-latest.noarch.rpm"; | ||||
|                             "pgdg-redhat-repo-latest.noarch.rpm && \\\n"; | ||||
|                 } | ||||
|  | ||||
|                 $strScript .= "    yum -y install postgresql-devel"; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
| @@ -502,7 +504,7 @@ sub containerBuild | ||||
|                         "' >> /etc/apt/sources.list.d/pgdg.list && \\\n" . | ||||
|                     "    wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \\\n" . | ||||
|                     "    apt-get update && \\\n" . | ||||
|                     "    apt-get install -y postgresql-common && \\\n" . | ||||
|                     "    apt-get install -y postgresql-common libpq-dev && \\\n" . | ||||
|                     "    sed -i 's/^\\#create\\_main\\_cluster.*\$/create\\_main\\_cluster \\= false/' " . | ||||
|                         "/etc/postgresql-common/createcluster.conf"; | ||||
|             } | ||||
|   | ||||
| @@ -411,6 +411,7 @@ sub run | ||||
|                     '-I. -Itest -std=c99 -fPIC -g -Wno-clobbered -D_POSIX_C_SOURCE=200112L' . | ||||
|                         ' `perl -MExtUtils::Embed -e ccopts`' . | ||||
|                         ' `xml2-config --cflags`' . ($self->{bProfile} ? " -pg" : '') . | ||||
|                         ' -I`pg_config --includedir`' . | ||||
|                     ($self->{oTest}->{&TEST_DEBUG_UNIT_SUPPRESS} ? '' : " -DDEBUG_UNIT") . | ||||
|                     (vmWithBackTrace($self->{oTest}->{&TEST_VM}) && $self->{bBackTrace} ? ' -DWITH_BACKTRACE' : '') . | ||||
|                     ($self->{oTest}->{&TEST_CDEF} ? " $self->{oTest}->{&TEST_CDEF}" : '') . | ||||
|   | ||||
| @@ -1,11 +1,12 @@ | ||||
| --- control | ||||
| +++ control | ||||
| @@ -4,11 +4,9 @@ | ||||
| @@ -4,11 +4,10 @@ | ||||
|  Maintainer: Debian PostgreSQL Maintainers <team+postgresql@tracker.debian.org> | ||||
|  Uploaders: Adrian Vondendriesch <adrian.vondendriesch@credativ.de> | ||||
|  Build-Depends: debhelper (>= 9), | ||||
| -               libio-socket-ssl-perl, | ||||
|                 libperl-dev, | ||||
| +               libpq-dev, | ||||
|                 libssl-dev, | ||||
|                 libxml-checker-perl, | ||||
| -               libxml-libxml-perl, | ||||
|   | ||||
							
								
								
									
										299
									
								
								test/src/common/harnessPq.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										299
									
								
								test/src/common/harnessPq.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,299 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Pq Test Harness | ||||
| ***********************************************************************************************************************************/ | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|  | ||||
| #include <string.h> | ||||
|  | ||||
| #include <libpq-fe.h> | ||||
|  | ||||
| #include "common/type/json.h" | ||||
| #include "common/type/string.h" | ||||
| #include "common/type/variantList.h" | ||||
|  | ||||
| #include "common/harnessPq.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Script that defines how shim functions operate | ||||
| ***********************************************************************************************************************************/ | ||||
| HarnessPq *harnessPqScript; | ||||
| unsigned int harnessPqScriptIdx; | ||||
|  | ||||
| // If there is a script failure change the behavior of cleanup functions to return immediately so the real error will be reported | ||||
| // rather than a bogus scripting error during cleanup | ||||
| bool harnessPqScriptFail; | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Set pq script | ||||
| ***********************************************************************************************************************************/ | ||||
| void | ||||
| harnessPqScriptSet(HarnessPq *harnessPqScriptParam) | ||||
| { | ||||
|     if (harnessPqScript != NULL) | ||||
|         THROW(AssertError, "previous pq script has not yet completed"); | ||||
|  | ||||
|     if (harnessPqScriptParam[0].function == NULL) | ||||
|         THROW(AssertError, "pq script must have entries"); | ||||
|  | ||||
|     harnessPqScript = harnessPqScriptParam; | ||||
|     harnessPqScriptIdx = 0; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Run pq script | ||||
| ***********************************************************************************************************************************/ | ||||
| static HarnessPq * | ||||
| harnessPqScriptRun(const char *function, const VariantList *param, HarnessPq *parent) | ||||
| { | ||||
|     // Convert params to json for comparison and reporting | ||||
|     String *paramStr = param ? jsonFromVar(varNewVarLst(param), 0) : strNew(""); | ||||
|  | ||||
|     // Ensure script has not ended | ||||
|     if (harnessPqScript == NULL) | ||||
|     { | ||||
|         harnessPqScriptFail = true; | ||||
|         THROW_FMT(AssertError, "pq script ended before %s (%s)", function, strPtr(paramStr)); | ||||
|     } | ||||
|  | ||||
|     // Get current script item | ||||
|     HarnessPq *result = &harnessPqScript[harnessPqScriptIdx]; | ||||
|  | ||||
|     // Check that expected function was called | ||||
|     if (strcmp(result->function, function) != 0) | ||||
|     { | ||||
|         harnessPqScriptFail = true; | ||||
|  | ||||
|         THROW_FMT( | ||||
|             AssertError, "pq script [%u] expected function '%s' but got '%s'", harnessPqScriptIdx, result->function, function); | ||||
|     } | ||||
|  | ||||
|     // Check that parameters match | ||||
|     if ((param != NULL && result->param == NULL) || (param == NULL && result->param != NULL) || | ||||
|         (param != NULL && result->param != NULL && !strEqZ(paramStr, result->param))) | ||||
|     { | ||||
|         harnessPqScriptFail = true; | ||||
|  | ||||
|         THROW_FMT( | ||||
|             AssertError, "pq script [%u] function '%s', expects param '%s' but got '%s'", harnessPqScriptIdx, result->function, | ||||
|             result->param ? result->param : "NULL", param ? strPtr(paramStr) : "NULL"); | ||||
|     } | ||||
|  | ||||
|     // Make sure the session matches with the parent as a sanity check | ||||
|     if (parent != NULL && result->session != parent->session) | ||||
|     { | ||||
|         THROW_FMT( | ||||
|             AssertError, "pq script [%u] function '%s', expects session '%u' but got '%u'", harnessPqScriptIdx, result->function, | ||||
|             result->session, parent->session); | ||||
|     } | ||||
|  | ||||
|     // Sleep if requested | ||||
|     if (result->sleep > 0) | ||||
|         sleepMSec(result->sleep); | ||||
|  | ||||
|     harnessPqScriptIdx++; | ||||
|  | ||||
|     if (harnessPqScript[harnessPqScriptIdx].function == NULL) | ||||
|         harnessPqScript = NULL; | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQconnectdb() | ||||
| ***********************************************************************************************************************************/ | ||||
| PGconn *PQconnectdb(const char *conninfo) | ||||
| { | ||||
|     return (PGconn *)harnessPqScriptRun(HRNPQ_CONNECTDB, varLstAdd(varLstNew(), varNewStrZ(conninfo)), NULL); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQstatus() | ||||
| ***********************************************************************************************************************************/ | ||||
| ConnStatusType PQstatus(const PGconn *conn) | ||||
| { | ||||
|     return (ConnStatusType)harnessPqScriptRun(HRNPQ_STATUS, NULL, (HarnessPq *)conn)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQerrorMessage() | ||||
| ***********************************************************************************************************************************/ | ||||
| char *PQerrorMessage(const PGconn *conn) | ||||
| { | ||||
|     return (char *)harnessPqScriptRun(HRNPQ_ERRORMESSAGE, NULL, (HarnessPq *)conn)->resultZ; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQsetNoticeProcessor() | ||||
| ***********************************************************************************************************************************/ | ||||
| PQnoticeProcessor | ||||
| PQsetNoticeProcessor(PGconn *conn, PQnoticeProcessor proc, void *arg) | ||||
| { | ||||
|     (void)conn; | ||||
|  | ||||
|     // Call the processor that was passed so we have coverage | ||||
|     proc(arg, "test notice"); | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQsendQuery() | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| PQsendQuery(PGconn *conn, const char *query) | ||||
| { | ||||
|     return harnessPqScriptRun(HRNPQ_SENDQUERY, varLstAdd(varLstNew(), varNewStrZ(query)), (HarnessPq *)conn)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQconsumeInput() | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| PQconsumeInput(PGconn *conn) | ||||
| { | ||||
|     return harnessPqScriptRun(HRNPQ_CONSUMEINPUT, NULL, (HarnessPq *)conn)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQisBusy() | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| PQisBusy(PGconn *conn) | ||||
| { | ||||
|     return harnessPqScriptRun(HRNPQ_ISBUSY, NULL, (HarnessPq *)conn)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQgetCancel() | ||||
| ***********************************************************************************************************************************/ | ||||
| PGcancel * | ||||
| PQgetCancel(PGconn *conn) | ||||
| { | ||||
|     return (PGcancel *)harnessPqScriptRun(HRNPQ_GETCANCEL, NULL, (HarnessPq *)conn); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQcancel() | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| PQcancel(PGcancel *cancel, char *errbuf, int errbufsize) | ||||
| { | ||||
|     HarnessPq *harnessPq = harnessPqScriptRun(HRNPQ_CANCEL, NULL, (HarnessPq *)cancel); | ||||
|  | ||||
|     if (!harnessPq->resultInt) | ||||
|     { | ||||
|         strncpy(errbuf, harnessPq->resultZ, (size_t)errbufsize); | ||||
|         errbuf[errbufsize - 1] = '\0'; | ||||
|     } | ||||
|  | ||||
|     return harnessPq->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQfreeCancel() | ||||
| ***********************************************************************************************************************************/ | ||||
| void | ||||
| PQfreeCancel(PGcancel *cancel) | ||||
| { | ||||
|     harnessPqScriptRun(HRNPQ_FREECANCEL, NULL, (HarnessPq *)cancel); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQgetResult() | ||||
| ***********************************************************************************************************************************/ | ||||
| PGresult * | ||||
| PQgetResult(PGconn *conn) | ||||
| { | ||||
|     if (!harnessPqScriptFail) | ||||
|     { | ||||
|         HarnessPq *harnessPq = harnessPqScriptRun(HRNPQ_GETRESULT, NULL, (HarnessPq *)conn); | ||||
|         return harnessPq->resultNull ? NULL : (PGresult *)harnessPq; | ||||
|     } | ||||
|  | ||||
|     return NULL; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQresultStatus() | ||||
| ***********************************************************************************************************************************/ | ||||
| ExecStatusType | ||||
| PQresultStatus(const PGresult *res) | ||||
| { | ||||
|     return (ExecStatusType)harnessPqScriptRun(HRNPQ_RESULTSTATUS, NULL, (HarnessPq *)res)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQresultErrorMessage() | ||||
| ***********************************************************************************************************************************/ | ||||
| char * | ||||
| PQresultErrorMessage(const PGresult *res) | ||||
| { | ||||
|     return (char *)harnessPqScriptRun(HRNPQ_RESULTERRORMESSAGE, NULL, (HarnessPq *)res)->resultZ; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQntuples() | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| PQntuples(const PGresult *res) | ||||
| { | ||||
|     return harnessPqScriptRun(HRNPQ_NTUPLES, NULL, (HarnessPq *)res)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQnfields() | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| PQnfields(const PGresult *res) | ||||
| { | ||||
|     return harnessPqScriptRun(HRNPQ_NFIELDS, NULL, (HarnessPq *)res)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQgetisnull() | ||||
| ***********************************************************************************************************************************/ | ||||
| int | ||||
| PQgetisnull(const PGresult *res, int tup_num, int field_num) | ||||
| { | ||||
|     return harnessPqScriptRun( | ||||
|         HRNPQ_GETISNULL, varLstAdd(varLstAdd(varLstNew(), varNewInt(tup_num)), varNewInt(field_num)), (HarnessPq *)res)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQftype() | ||||
| ***********************************************************************************************************************************/ | ||||
| Oid | ||||
| PQftype(const PGresult *res, int field_num) | ||||
| { | ||||
|     return (Oid)harnessPqScriptRun(HRNPQ_FTYPE, varLstAdd(varLstNew(), varNewInt(field_num)), (HarnessPq *)res)->resultInt; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQgetvalue() | ||||
| ***********************************************************************************************************************************/ | ||||
| char * | ||||
| PQgetvalue(const PGresult *res, int tup_num, int field_num) | ||||
| { | ||||
|     return (char *)harnessPqScriptRun( | ||||
|         HRNPQ_GETVALUE, varLstAdd(varLstAdd(varLstNew(), varNewInt(tup_num)), varNewInt(field_num)), (HarnessPq *)res)->resultZ; | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQclear() | ||||
| ***********************************************************************************************************************************/ | ||||
| void | ||||
| PQclear(PGresult *res) | ||||
| { | ||||
|     if (!harnessPqScriptFail) | ||||
|         harnessPqScriptRun(HRNPQ_CLEAR, NULL, (HarnessPq *)res); | ||||
| } | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Shim for PQfinish() | ||||
| ***********************************************************************************************************************************/ | ||||
| void PQfinish(PGconn *conn) | ||||
| { | ||||
|     if (!harnessPqScriptFail) | ||||
|         harnessPqScriptRun(HRNPQ_FINISH, NULL, (HarnessPq *)conn); | ||||
| } | ||||
|  | ||||
| #endif // HARNESS_PQ_REAL | ||||
							
								
								
									
										65
									
								
								test/src/common/harnessPq.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								test/src/common/harnessPq.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Pq Test Harness | ||||
|  | ||||
| Scripted testing for PostgreSQL pqlib so exact results can be returned for unit testing.  See PostgreSQL client unit tests for | ||||
| usage examples. | ||||
| ***********************************************************************************************************************************/ | ||||
| #ifndef TEST_COMMON_HARNESS_PQ_H | ||||
| #define TEST_COMMON_HARNESS_PQ_H | ||||
|  | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|  | ||||
| #include "common/time.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Function constants | ||||
| ***********************************************************************************************************************************/ | ||||
| #define HRNPQ_CANCEL                                                "PQcancel" | ||||
| #define HRNPQ_CLEAR                                                 "PQclear" | ||||
| #define HRNPQ_CONNECTDB                                             "PQconnectdb" | ||||
| #define HRNPQ_CONSUMEINPUT                                          "PQconsumeInput" | ||||
| #define HRNPQ_ERRORMESSAGE                                          "PQerrorMessage" | ||||
| #define HRNPQ_FINISH                                                "PQfinish" | ||||
| #define HRNPQ_FREECANCEL                                            "PQfreeCancel" | ||||
| #define HRNPQ_FTYPE                                                 "PQftype" | ||||
| #define HRNPQ_GETCANCEL                                             "PQgetCancel" | ||||
| #define HRNPQ_GETISNULL                                             "PQgetisnull" | ||||
| #define HRNPQ_GETRESULT                                             "PQgetResult" | ||||
| #define HRNPQ_GETVALUE                                              "PQgetvalue" | ||||
| #define HRNPQ_ISBUSY                                                "PQisbusy" | ||||
| #define HRNPQ_NFIELDS                                               "PQnfields" | ||||
| #define HRNPQ_NTUPLES                                               "PQntuples" | ||||
| #define HRNPQ_RESULTERRORMESSAGE                                    "PQresultErrorMessage" | ||||
| #define HRNPQ_RESULTSTATUS                                          "PQresultStatus" | ||||
| #define HRNPQ_SENDQUERY                                             "PQsendQuery" | ||||
| #define HRNPQ_STATUS                                                "PQstatus" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Data type constants | ||||
| ***********************************************************************************************************************************/ | ||||
| #define HRNPQ_TYPE_BOOL                                             16 | ||||
| #define HRNPQ_TYPE_INT                                              20 | ||||
| #define HRNPQ_TYPE_TEXT                                             25 | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Structure for scripting pq responses | ||||
| ***********************************************************************************************************************************/ | ||||
| typedef struct HarnessPq | ||||
| { | ||||
|     unsigned int session;                                           // Session number when mutliple sessions are run concurrently | ||||
|     const char *function;                                           // Function call expected | ||||
|     const char *param;                                              // Params expected by the function for verification | ||||
|     int resultInt;                                                  // Int result value | ||||
|     const char *resultZ;                                            // Zero-terminated result value | ||||
|     bool resultNull;                                                // Return null from function that normally returns a struct ptr | ||||
|     TimeMSec sleep;                                                 // Sleep specified milliseconds before returning from function | ||||
| } HarnessPq; | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Functions | ||||
| ***********************************************************************************************************************************/ | ||||
| void harnessPqScriptSet(HarnessPq *harnessPqScriptParam); | ||||
|  | ||||
| #endif // HARNESS_PQ_REAL | ||||
|  | ||||
| #endif | ||||
							
								
								
									
										290
									
								
								test/src/module/postgres/clientTest.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								test/src/module/postgres/clientTest.c
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,290 @@ | ||||
| /*********************************************************************************************************************************** | ||||
| Test PostgreSQL Client | ||||
|  | ||||
| This test can be run two ways: | ||||
|  | ||||
| 1) The default uses a pqlib shim to simulate a PostgreSQL connection.  This will work with all VM types. | ||||
|  | ||||
| 2) Optionally use a real cluster for testing (only works with debian/pg11).  The test Makefile must be manually updated with the | ||||
| -DHARNESS_PQ_REAL flag and -lpq must be added to the libs list.  This method does not have 100% coverage but is very close. | ||||
| ***********************************************************************************************************************************/ | ||||
| #include "common/type/json.h" | ||||
|  | ||||
| #include "common/harnessPq.h" | ||||
|  | ||||
| /*********************************************************************************************************************************** | ||||
| Test Run | ||||
| ***********************************************************************************************************************************/ | ||||
| void | ||||
| testRun(void) | ||||
| { | ||||
|     FUNCTION_HARNESS_VOID(); | ||||
|  | ||||
|     // ***************************************************************************************************************************** | ||||
|     if (testBegin("pgClient")) | ||||
|     { | ||||
|         // Create and start the test database | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifdef HARNESS_PQ_REAL | ||||
|         if (system("sudo pg_createcluster 11 test") != 0) | ||||
|             THROW(AssertError, "unable to create cluster"); | ||||
|  | ||||
|         if (system("sudo pg_ctlcluster 11 test start") != 0) | ||||
|             THROW(AssertError, "unable to start cluster"); | ||||
|  | ||||
|         if (system(strPtr(strNewFmt("sudo -u postgres psql -c 'create user %s superuser'", testUser()))) != 0) | ||||
|             THROW(AssertError, "unable to create superuser"); | ||||
| #endif | ||||
|  | ||||
|         // Test connection error | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_CONNECTDB, .param = "[\"dbname='postg \\\\'\\\\\\\\res' port=5433\"]"}, | ||||
|             {.function = HRNPQ_STATUS, .resultInt = CONNECTION_BAD}, | ||||
|             {.function = HRNPQ_ERRORMESSAGE, .resultZ = | ||||
|                 "could not connect to server: No such file or directory\n" | ||||
|                     "\tIs the server running locally and accepting\n" | ||||
|                     "\tconnections on Unix domain socket \"/var/run/postgresql/.s.PGSQL.5433\"?\n"}, | ||||
|             {.function = HRNPQ_FINISH}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         PgClient *client = NULL; | ||||
|         TEST_ASSIGN(client, pgClientNew(NULL, 5433, strNew("postg '\\res"), NULL, 3000), "new client"); | ||||
|         TEST_ERROR( | ||||
|             pgClientOpen(client), DbConnectError, | ||||
|             "unable to connect to 'dbname='postg \\'\\\\res' port=5433': could not connect to server: No such file or directory\n" | ||||
|                 "\tIs the server running locally and accepting\n" | ||||
|                 "\tconnections on Unix domain socket \"/var/run/postgresql/.s.PGSQL.5433\"?"); | ||||
|         TEST_RESULT_VOID(pgClientFree(client), "free client"); | ||||
|  | ||||
|         // Test send error | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_CONNECTDB, .param = "[\"dbname='postgres' port=5432\"]"}, | ||||
|             {.function = HRNPQ_STATUS, .resultInt = CONNECTION_OK}, | ||||
|             {.function = HRNPQ_SENDQUERY, .param = "[\"select bogus from pg_class\"]", .resultInt = 0}, | ||||
|             {.function = HRNPQ_ERRORMESSAGE, .resultZ = "another command is already in progress\n"}, | ||||
|             {.function = HRNPQ_FINISH}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         TEST_ASSIGN(client, pgClientOpen(pgClientNew(NULL, 5432, strNew("postgres"), NULL, 3000)), "new client"); | ||||
|  | ||||
| #ifdef HARNESS_PQ_REAL | ||||
|         PQsendQuery(client->connection, "select bogus from pg_class"); | ||||
| #endif | ||||
|  | ||||
|         String *query = strNew("select bogus from pg_class"); | ||||
|  | ||||
|         TEST_ERROR( | ||||
|             pgClientQuery(client, query), DbQueryError, | ||||
|             "unable to send query 'select bogus from pg_class': another command is already in progress"); | ||||
|  | ||||
|         TEST_RESULT_VOID(pgClientFree(client), "free client"); | ||||
|  | ||||
|         // Connect | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_CONNECTDB, .param = strPtr( | ||||
|                 strNewFmt("[\"dbname='postgres' port=5432 user='%s' host='/var/run/postgresql'\"]", testUser()))}, | ||||
|             {.function = HRNPQ_STATUS, .resultInt = CONNECTION_OK}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         TEST_ASSIGN( | ||||
|             client, pgClientOpen(pgClientNew(strNew("/var/run/postgresql"), 5432, strNew("postgres"), strNew(testUser()), 500)), | ||||
|             "new client"); | ||||
|  | ||||
|         // Invalid query | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_SENDQUERY, .param = "[\"select bogus from pg_class\"]", .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT}, | ||||
|             {.function = HRNPQ_ISBUSY}, | ||||
|             {.function = HRNPQ_GETRESULT}, | ||||
|             {.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_FATAL_ERROR}, | ||||
|             {.function = HRNPQ_RESULTERRORMESSAGE, .resultZ = | ||||
|                 "ERROR:  column \"bogus\" does not exist\n" | ||||
|                     "LINE 1: select bogus from pg_class\n" | ||||
|                     "               ^                 \n"}, | ||||
|             {.function = HRNPQ_CLEAR}, | ||||
|             {.function = HRNPQ_GETRESULT, .resultNull = true}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         query = strNew("select bogus from pg_class"); | ||||
|  | ||||
|         TEST_ERROR( | ||||
|             pgClientQuery(client, query), DbQueryError, | ||||
|             "unable to execute query 'select bogus from pg_class': ERROR:  column \"bogus\" does not exist\n" | ||||
|                 "LINE 1: select bogus from pg_class\n" | ||||
|                 "               ^"); | ||||
|  | ||||
|         // Timeout query | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_SENDQUERY, .param = "[\"select pg_sleep(3000)\"]", .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT, .sleep = 600}, | ||||
|             {.function = HRNPQ_ISBUSY, .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT}, | ||||
|             {.function = HRNPQ_ISBUSY, .resultInt = 1}, | ||||
|             {.function = HRNPQ_GETCANCEL}, | ||||
|             {.function = HRNPQ_CANCEL, .resultInt = 1}, | ||||
|             {.function = HRNPQ_FREECANCEL}, | ||||
|             {.function = HRNPQ_GETRESULT}, | ||||
|             {.function = HRNPQ_CLEAR}, | ||||
|             {.function = HRNPQ_GETRESULT, .resultNull = true}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         query = strNew("select pg_sleep(3000)"); | ||||
|  | ||||
|         TEST_ERROR(pgClientQuery(client, query), DbQueryError, "query 'select pg_sleep(3000)' timed out after 500ms"); | ||||
|  | ||||
|         // Cancel error (can only be run with the scripted tests | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_SENDQUERY, .param = "[\"select pg_sleep(3000)\"]", .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT, .sleep = 600}, | ||||
|             {.function = HRNPQ_ISBUSY, .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT}, | ||||
|             {.function = HRNPQ_ISBUSY, .resultInt = 1}, | ||||
|             {.function = HRNPQ_GETCANCEL}, | ||||
|             {.function = HRNPQ_CANCEL, .resultInt = 0, .resultZ = "test error"}, | ||||
|             {.function = HRNPQ_FREECANCEL}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
|  | ||||
|         query = strNew("select pg_sleep(3000)"); | ||||
|  | ||||
|         TEST_ERROR(pgClientQuery(client, query), DbQueryError, "unable to cancel query 'select pg_sleep(3000)': test error"); | ||||
| #endif | ||||
|  | ||||
|         // Execute do block and raise notice | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_SENDQUERY, .param = "[\"do $$ begin raise notice 'mememe'; end $$\"]", .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT}, | ||||
|             {.function = HRNPQ_ISBUSY}, | ||||
|             {.function = HRNPQ_GETRESULT}, | ||||
|             {.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_COMMAND_OK}, | ||||
|             {.function = HRNPQ_CLEAR}, | ||||
|             {.function = HRNPQ_GETRESULT, .resultNull = true}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         query = strNew("do $$ begin raise notice 'mememe'; end $$"); | ||||
|  | ||||
|         TEST_RESULT_PTR(pgClientQuery(client, query), NULL, "execute do block"); | ||||
|  | ||||
|         // Unsupported type | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_SENDQUERY, .param = "[\"select clock_timestamp()\"]", .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT}, | ||||
|             {.function = HRNPQ_ISBUSY}, | ||||
|             {.function = HRNPQ_GETRESULT}, | ||||
|             {.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, | ||||
|             {.function = HRNPQ_NTUPLES, .resultInt = 1}, | ||||
|             {.function = HRNPQ_NFIELDS, .resultInt = 1}, | ||||
|             {.function = HRNPQ_FTYPE, .param = "[0]", .resultInt = 1184}, | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = "2019-07-25 12:06:09.000282+00"}, | ||||
|             {.function = HRNPQ_CLEAR}, | ||||
|             {.function = HRNPQ_GETRESULT, .resultNull = true}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         query = strNew("select clock_timestamp()"); | ||||
|  | ||||
|         TEST_ERROR( | ||||
|             pgClientQuery(client, query), FormatError, | ||||
|             "unable to parse type 1184 in column 0 for query 'select clock_timestamp()'"); | ||||
|  | ||||
|         // Successful query | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_SENDQUERY, .param = | ||||
|                 "[\"select oid, case when relname = 'pg_class' then null::text else '' end, relname, relname = 'pg_class'" | ||||
|                     "  from pg_class where relname in ('pg_class', 'pg_proc')" | ||||
|                     " order by relname\"]", | ||||
|                 .resultInt = 1}, | ||||
|             {.function = HRNPQ_CONSUMEINPUT}, | ||||
|             {.function = HRNPQ_ISBUSY}, | ||||
|             {.function = HRNPQ_GETRESULT}, | ||||
|             {.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK}, | ||||
|  | ||||
|             {.function = HRNPQ_NTUPLES, .resultInt = 2}, | ||||
|             {.function = HRNPQ_NFIELDS, .resultInt = 4}, | ||||
|             {.function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_INT}, | ||||
|             {.function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_TEXT}, | ||||
|             {.function = HRNPQ_FTYPE, .param = "[2]", .resultInt = HRNPQ_TYPE_TEXT}, | ||||
|             {.function = HRNPQ_FTYPE, .param = "[3]", .resultInt = HRNPQ_TYPE_BOOL}, | ||||
|  | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = "1259"}, | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = ""}, | ||||
|             {.function = HRNPQ_GETISNULL, .param = "[0,1]", .resultInt = 1}, | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[0,2]", .resultZ = "pg_class"}, | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[0,3]", .resultZ = "t"}, | ||||
|  | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[1,0]", .resultZ = "1255"}, | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[1,1]", .resultZ = ""}, | ||||
|             {.function = HRNPQ_GETISNULL, .param = "[1,1]", .resultInt = 0}, | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[1,2]", .resultZ = "pg_proc"}, | ||||
|             {.function = HRNPQ_GETVALUE, .param = "[1,3]", .resultZ = "f"}, | ||||
|  | ||||
|             {.function = HRNPQ_CLEAR}, | ||||
|             {.function = HRNPQ_GETRESULT, .resultNull = true}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|  | ||||
|         query = strNew( | ||||
|             "select oid, case when relname = 'pg_class' then null::text else '' end, relname, relname = 'pg_class'" | ||||
|             "  from pg_class where relname in ('pg_class', 'pg_proc')" | ||||
|             " order by relname"); | ||||
|  | ||||
|         TEST_RESULT_STR( | ||||
|             strPtr(jsonFromVar(varNewVarLst(pgClientQuery(client, query)), 0)), | ||||
|             "[[1259,null,\"pg_class\",true],[1255,\"\",\"pg_proc\",false]]", "simple query"); | ||||
|  | ||||
|         // Close connection | ||||
|         // ------------------------------------------------------------------------------------------------------------------------- | ||||
| #ifndef HARNESS_PQ_REAL | ||||
|         harnessPqScriptSet((HarnessPq []) | ||||
|         { | ||||
|             {.function = HRNPQ_FINISH}, | ||||
|             {.function = HRNPQ_GETRESULT, .resultNull = true}, | ||||
|             {.function = NULL} | ||||
|         }); | ||||
| #endif | ||||
|         TEST_RESULT_VOID(pgClientClose(client), "close client"); | ||||
|     } | ||||
|  | ||||
|     FUNCTION_HARNESS_RESULT_VOID(); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user