/* * Original work: Copyright (c) 2014, Oculus VR, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * RakNet License.txt file in the licenses directory of this source tree. An additional grant * of patent rights can be found in the RakNet Patents.txt file in the same directory. * * * Modified work: Copyright (c) 2016-2021, SLikeSoft UG (haftungsbeschränkt) * * This source code was modified by SLikeSoft. Modifications are licensed under the MIT-style * license found in the license.txt file in the root directory of this source tree. */ #include "PostgreSQLInterface.h" #include "slikenet/VariadicSQLParser.h" // libpq-fe.h is part of PostgreSQL which must be installed on this computer to use the PostgreRepository #include "libpq-fe.h" #ifdef _CONSOLE_1 #include "Console1Includes.h" #elif defined(_WIN32) #include // htonl #elif defined(_CONSOLE_2) #include "Console2Includes.h" #else #include #endif // alloca #ifdef _COMPATIBILITY_1 #elif defined(_WIN32) #include #else //#include #endif #include "slikenet/string.h" #include "slikenet/assert.h" #include "slikenet/BitStream.h" #include "slikenet/FormatString.h" #include "slikenet/LinuxStrings.h" #include "slikenet/linux_adapter.h" #include "slikenet/osx_adapter.h" #define PQEXECPARAM_FORMAT_TEXT 0 #define PQEXECPARAM_FORMAT_BINARY 1 PostgreSQLInterface::PostgreSQLInterface() { pgConn=0; isConnected=false; lastError[0]=0; pgConnAllocatedHere=false; } PostgreSQLInterface::~PostgreSQLInterface() { if (isConnected && pgConnAllocatedHere) PQfinish(pgConn); } bool PostgreSQLInterface::Connect(const char *conninfo) { if (!isConnected) { _conninfo=conninfo; pgConn=PQconnectdb(conninfo); ConnStatusType status = PQstatus(pgConn); isConnected=status==CONNECTION_OK; if (!isConnected) { PQfinish(pgConn); return false; } pgConnAllocatedHere=true; } return isConnected; } void PostgreSQLInterface::AssignConnection(PGconn *_pgConn) { pgConnAllocatedHere=false; pgConn=_pgConn; ConnStatusType status = PQstatus(pgConn); isConnected=status==CONNECTION_OK; } PGconn *PostgreSQLInterface::GetPGConn(void) const { return pgConn; } void PostgreSQLInterface::Disconnect(void) { if (isConnected) { PQfinish(pgConn); isConnected=false; } } void PostgreSQLInterface::Rollback(void) { PGresult *result = PQexec(pgConn, "ROLLBACK;"); PQclear(result); } bool PostgreSQLInterface::IsResultSuccessful(PGresult *result, bool rollbackOnFailure) { if (result==0) return false; bool success=false; ExecStatusType execStatus = PQresultStatus(result); strcpy_s(lastError,PQresultErrorMessage(result)); switch (execStatus) { case PGRES_COMMAND_OK: success=true; break; case PGRES_EMPTY_QUERY: break; case PGRES_TUPLES_OK: success=true; break; case PGRES_COPY_OUT: break; case PGRES_COPY_IN: break; case PGRES_BAD_RESPONSE: break; case PGRES_NONFATAL_ERROR: break; case PGRES_FATAL_ERROR: if (rollbackOnFailure) Rollback(); break; } return success; } bool PostgreSQLInterface::ExecuteBlockingCommand(const char *command, PGresult **result, bool rollbackOnFailure) { *result = PQexec(pgConn, command); return IsResultSuccessful(*result, rollbackOnFailure); } bool PostgreSQLInterface::PQGetValueFromBinary(int *output, PGresult *result, unsigned int rowIndex, const char *columnName) { return PQGetValueFromBinary(output,result,(int) rowIndex,columnName); } bool PostgreSQLInterface::PQGetValueFromBinary(int *output, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex==-1) return false; char *binaryData = PQgetvalue(result, rowIndex, columnIndex); if (binaryData==0) return false; if (binaryData) { RakAssert(PQgetlength(result, rowIndex, columnIndex)==sizeof(int)); memcpy(output, binaryData, sizeof(int)); EndianSwapInPlace((char*)output, sizeof(int)); return true; } return false; } bool PostgreSQLInterface::PQGetValueFromBinary(unsigned int *output, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex==-1) return false; char *binaryData = PQgetvalue(result, rowIndex, columnIndex); if (binaryData==0 || PQgetlength(result, rowIndex, columnIndex)==0) return false; if (binaryData) { RakAssert(PQgetlength(result, rowIndex, columnIndex)==sizeof(unsigned int)); memcpy(output, binaryData, sizeof(unsigned int)); EndianSwapInPlace((char*)output, sizeof(unsigned int)); return true; } return false; } bool PostgreSQLInterface::PQGetValueFromBinary(long long *output, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex==-1) return false; char *binaryData = PQgetvalue(result, rowIndex, columnIndex); if (binaryData==0) return false; if (binaryData) { RakAssert(PQgetlength(result, rowIndex, columnIndex)==sizeof(long long)); memcpy(output, binaryData, sizeof(long long)); EndianSwapInPlace((char*)output, sizeof(long long)); return true; } return false; } bool PostgreSQLInterface::PQGetValueFromBinary(float *output, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex==-1) return false; char *binaryData = PQgetvalue(result, rowIndex, columnIndex); if (binaryData==0) return false; RakAssert(PQgetlength(result, rowIndex, columnIndex) == sizeof(float)); RakAssert(binaryData); if (binaryData) { memcpy(output, binaryData, sizeof(float)); EndianSwapInPlace((char*)output, sizeof(float)); return true; } return false; } bool PostgreSQLInterface::PQGetValueFromBinary(double *output, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex==-1) return false; char *binaryData = PQgetvalue(result, rowIndex, columnIndex); if (binaryData==0) return false; RakAssert(PQgetlength(result, rowIndex, columnIndex) == sizeof(double)); RakAssert(binaryData); if (binaryData) { memcpy(output, binaryData, sizeof(double)); EndianSwapInPlace((char*)output, sizeof(double)); return true; } return false; } bool PostgreSQLInterface::PQGetValueFromBinary(bool *output, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex==-1) return false; char *binaryData = PQgetvalue(result, rowIndex, columnIndex); if (binaryData==0) return false; *output = binaryData[0]!=0; // Seems to return 1 byte only /* int tempResult=0; // int len = PQgetlength(result, rowIndex, columnIndex); // RakAssert(len==sizeof(int)); // bug here, returns 0, but is an int RakAssert(binaryData); if (binaryData) { memcpy(&tempResult, binaryData, sizeof(int)); EndianSwapInPlace((char*)&tempResult, sizeof(int)); *output = tempResult!=0; return true; } */ return true; } bool PostgreSQLInterface::PQGetValueFromBinary(SLNet::RakString *output, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex==-1) return false; char *gv; gv = PQgetvalue(result, rowIndex, columnIndex); if (gv && gv[0]) *output=gv; else output->Clear(); return !output->IsEmpty(); } bool PostgreSQLInterface::PQGetValueFromBinary(char **output, unsigned int *outputLength, PGresult *result, int rowIndex, const char *columnName) { return PQGetValueFromBinary(output, (int*) outputLength,result, rowIndex,columnName); } bool PostgreSQLInterface::PQGetValueFromBinary(char **output, int *outputLength, PGresult *result, int rowIndex, const char *columnName) { int columnIndex = PQfnumber(result, columnName); if (columnIndex!=-1) { *outputLength=PQgetlength(result, rowIndex, columnIndex); if (*outputLength==0) { *output=0; return false; } else { *output = (char*) rakMalloc_Ex(*outputLength,_FILE_AND_LINE_); memcpy(*output, PQgetvalue(result, rowIndex, columnIndex), *outputLength); return true; } } else return false; } void PostgreSQLInterface::EndianSwapInPlace(char* data, int dataLength) { static bool alreadyNetworkOrder=(htonl(12345) == 12345); if (alreadyNetworkOrder) return; int i; char tmp; for (i=0; i < dataLength/2; i++) { tmp=data[i]; data[i]=data[dataLength-1-i]; data[dataLength-1-i]=tmp; } } char *PostgreSQLInterface::GetLocalTimestamp(void) { static char strRes[512]; PGresult *result; if (ExecuteBlockingCommand("SELECT LOCALTIMESTAMP", &result, false)) { char *ts=PQgetvalue(result, 0, 0); if (ts) { sprintf_s(strRes,"Local timestamp is: %s\n", ts); } else { sprintf_s(strRes,"Can't read current time\n"); } PQclear(result); } else sprintf_s(strRes,"Failed to read LOCALTIMESTAMP\n"); return (char*)strRes; } long long PostgreSQLInterface::GetEpoch(void) { PGresult *result; long long out; result = QueryVariadic("EXTRACT(EPOCH FROM current_timestamp) as out);"); PostgreSQLInterface::PQGetValueFromBinary(&out, result, 0, "out"); PQclear(result); return out; } const char* PostgreSQLInterface::GetLastError(void) const { return (char*)lastError; } void PostgreSQLInterface::EncodeQueryInput(const char *colName, unsigned int value, SLNet::RakString ¶mTypeStr, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { (void)numParams; (void)paramData; (void)paramLength; (void)paramFormat; if (!paramTypeStr.IsEmpty()) { paramTypeStr += ", "; valueStr += ", "; } paramTypeStr += colName; valueStr += FormatString("%i", value); } void PostgreSQLInterface::EncodeQueryUpdate(const char *colName, unsigned int value, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { (void)numParams; (void)paramData; (void)paramLength; (void)paramFormat; if (!valueStr.IsEmpty()) { valueStr += ", "; } valueStr += colName; valueStr += " = "; valueStr += FormatString("%i", value); } void PostgreSQLInterface::EncodeQueryInput(const char *colName, int value, SLNet::RakString ¶mTypeStr, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { (void)numParams; (void)paramData; (void)paramLength; (void)paramFormat; if (!paramTypeStr.IsEmpty()) { paramTypeStr += ", "; valueStr += ", "; } paramTypeStr += colName; valueStr += FormatString("%i", value); } void PostgreSQLInterface::EncodeQueryInput(const char *colName, bool value, SLNet::RakString ¶mTypeStr, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { (void)numParams; (void)paramData; (void)paramLength; (void)paramFormat; if (!paramTypeStr.IsEmpty()) { paramTypeStr += ", "; valueStr += ", "; } paramTypeStr += colName; if (value) valueStr += "true"; else valueStr += "false"; } void PostgreSQLInterface::EncodeQueryUpdate(const char *colName, int value, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { (void)numParams; (void)paramData; (void)paramLength; (void)paramFormat; if (!valueStr.IsEmpty()) { valueStr += ", "; } valueStr += colName; valueStr += " = "; valueStr += FormatString("%i", value); } void PostgreSQLInterface::EncodeQueryInput(const char *colName, float value, SLNet::RakString ¶mTypeStr, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { (void)numParams; (void)paramData; (void)paramLength; (void)paramFormat; if (!paramTypeStr.IsEmpty()) { paramTypeStr += ", "; valueStr += ", "; } paramTypeStr += colName; valueStr += FormatString("%f", value); } void PostgreSQLInterface::EncodeQueryUpdate(const char *colName, float value, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { (void)numParams; (void)paramData; (void)paramLength; (void)paramFormat; if (!valueStr.IsEmpty()) { valueStr += ", "; } valueStr += colName; valueStr += " = "; valueStr += FormatString("%f", value); } void PostgreSQLInterface::EncodeQueryInput(const char *colName, char *binaryData, int binaryDataLength, SLNet::RakString ¶mTypeStr, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat, bool writeEmpty) { if (!writeEmpty && (binaryData==0 || binaryDataLength==0)) return; if (binaryData==0) binaryDataLength=0; if (!paramTypeStr.IsEmpty()) { paramTypeStr += ", "; valueStr += ", "; } paramTypeStr += colName; valueStr+=FormatString("$%i::bytea", numParams+1); paramData[numParams]=binaryData; paramLength[numParams]=binaryDataLength; paramFormat[numParams]=PQEXECPARAM_FORMAT_BINARY; numParams++; } void PostgreSQLInterface::EncodeQueryUpdate(const char *colName, char *binaryData, int binaryDataLength, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat) { if (binaryData==0 || binaryDataLength==0) return; if (binaryData==0) binaryDataLength=0; if (!valueStr.IsEmpty()) { valueStr += ", "; } valueStr += colName; valueStr += " = "; valueStr+=FormatString("$%i::bytea", numParams+1); paramData[numParams]=binaryData; paramLength[numParams]=binaryDataLength; paramFormat[numParams]=PQEXECPARAM_FORMAT_BINARY; numParams++; } void PostgreSQLInterface::EncodeQueryInput(const char *colName, const char *str, SLNet::RakString ¶mTypeStr, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat, bool writeEmpty, const char *type) { if (!writeEmpty && (str==0 || str[0]==0)) return; static char *emptyStr=(char*)""; static char *emptyDate=(char*)"01/01/01"; if (!paramTypeStr.IsEmpty()) { paramTypeStr += ", "; valueStr += ", "; } paramTypeStr += colName; valueStr+=FormatString("$%i::%s", numParams+1, type); if (writeEmpty && (str==0 || str[0]==0)) { if (strcmp(type,"date")==0) { paramData[numParams]=emptyDate; paramLength[numParams]=(int)strlen(emptyDate); } else { paramData[numParams]=emptyStr; paramLength[numParams]=0; } } else { paramData[numParams]=(char*) str; paramLength[numParams]=(int) strlen(str); } paramFormat[numParams]=PQEXECPARAM_FORMAT_TEXT; numParams++; } void PostgreSQLInterface::EncodeQueryUpdate(const char *colName, const char *str, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat, const char *type) { if (str==0 || str[0]==0) return; if (!valueStr.IsEmpty()) { valueStr += ", "; } valueStr += colName; valueStr+=" = "; valueStr+=FormatString("$%i::%s", numParams+1, type); paramData[numParams]=(char*) str; paramLength[numParams]=(int) strlen(str); paramFormat[numParams]=PQEXECPARAM_FORMAT_TEXT; numParams++; } void PostgreSQLInterface::EncodeQueryInput(const char *colName, const SLNet::RakString &str, SLNet::RakString ¶mTypeStr, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat, bool writeEmpty, const char *type) { EncodeQueryInput(colName, str.C_String(), paramTypeStr, valueStr, numParams, paramData, paramLength, paramFormat, writeEmpty, type); } void PostgreSQLInterface::EncodeQueryUpdate(const char *colName, const SLNet::RakString &str, SLNet::RakString &valueStr, int &numParams, char **paramData, int *paramLength, int *paramFormat, const char *type) { EncodeQueryUpdate(colName, str.C_String(), valueStr, numParams, paramData, paramLength, paramFormat, type); } SLNet::RakString PostgreSQLInterface::GetEscapedString(const char *input) const { unsigned long len = (unsigned long) strlen(input); char *fn = (char*) rakMalloc_Ex(len*2+1,_FILE_AND_LINE_); int error; PQescapeStringConn(pgConn, fn, input, len, &error); SLNet::RakString output; // Use assignment so it doesn't parse printf escape strings output = fn; rakFree_Ex(fn,_FILE_AND_LINE_); return output; } PGresult * PostgreSQLInterface::QueryVariadic( const char * input, ... ) { SLNet::RakString query; PGresult *result; DataStructures::List indices; if ( input==0 || input[0]==0 ) return 0; // Lookup this query in the stored query table. If it doesn't exist, prepare it. SLNet::RakString inputStr; inputStr=input; unsigned int preparedQueryIndex; for (preparedQueryIndex=0; preparedQueryIndex < preparedQueries.Size(); preparedQueryIndex++) { if (preparedQueries[preparedQueryIndex].StrICmp(inputStr)==0) break; } // Find out how many params there are // Find out the type of each param (%f, %s) indices.Clear(false, _FILE_AND_LINE_); GetTypeMappingIndices( input, indices ); if (preparedQueryIndex==preparedQueries.Size()) { // if (indices.Size()>0) // query += " ("; SLNet::RakString formatCopy; SLNet::RakString insertion; formatCopy=input; unsigned int i; unsigned int indexOffset=0; for (i=0; i < indices.Size(); i++) { // if (i!=0) // query += ","; // query+=typeMappings[indices[i].typeMappingIndex].type; formatCopy.SetChar(indices[i].strIndex+indexOffset, '$'); // if (i < 9) // formatCopy.SetChar(indices[i].strIndex+1, i+1+'0'); // else insertion= SLNet::RakString("%i::%s", i+1, VariadicSQLParser::GetTypeMappingAtIndex(indices[i].typeMappingIndex)); formatCopy.SetChar(indices[i].strIndex+1+indexOffset, insertion); indexOffset+=(unsigned int) insertion.GetLength()-1; } // if (indices.Size()>0) // query += ")"; // query += " AS "; query += formatCopy; // query += ";\n"; formatCopy+= ";\n"; result = PQprepare(pgConn, SLNet::RakString("PGSQL_ExecuteVariadic_%i", preparedQueries.Size()), formatCopy.C_String(), indices.Size(), nullptr); if (IsResultSuccessful(result, false)) { PQclear(result); preparedQueries.Insert(inputStr, _FILE_AND_LINE_); } else { printf(formatCopy.C_String()); printf("\n"); printf(lastError); RakAssert(0); PQclear(result); return 0; } } // char *paramData[512]; // int paramLength[512]; // int paramFormat[512]; va_list argptr; va_start(argptr, input); char **paramData; int *paramLength; int *paramFormat; ExtractArguments(argptr, indices, ¶mData, ¶mLength); paramFormat= SLNet::OP_NEW_ARRAY(indices.Size(),_FILE_AND_LINE_); for (unsigned int i=0; i < indices.Size(); i++) paramFormat[i]=PQEXECPARAM_FORMAT_BINARY; result = PQexecPrepared(pgConn, SLNet::RakString("PGSQL_ExecuteVariadic_%i", preparedQueryIndex), indices.Size(), paramData, paramLength, paramFormat, PQEXECPARAM_FORMAT_BINARY ); VariadicSQLParser::FreeArguments(indices, paramData, paramLength); SLNet::OP_DELETE_ARRAY(paramFormat,_FILE_AND_LINE_); va_end(argptr); if (!IsResultSuccessful(result, false)) { printf(lastError); PQclear(result); return 0; } return result; } /* PGresult * PostgreSQLInterface::QueryVariadic( const char * input, ... ) { SLNet::RakString query; PGresult *result; unsigned int i; DataStructures::List indices; if ( input==0 || input[0]==0 ) return 0; // Find out how many params there are // Find out the type of each param (%f, %s) GetTypeMappingIndices( input, indices ); char *paramData[512]; int paramLength[512]; int paramFormat[512]; va_list argptr; int variadicArgIndex; va_start(argptr, input); for (variadicArgIndex=0, i=0; i < indices.Size(); i++, variadicArgIndex++) { if (typeMappings[indices[i].typeMappingIndex].inputType=='i' || typeMappings[indices[i].typeMappingIndex].inputType=='d') { int val = va_arg( argptr, int ); paramData[i]=(char*) &val; paramLength[i]=sizeof(val); if (!SLNet::BitStream::IsNetworkOrder()) SLNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); } else if (typeMappings[indices[i].typeMappingIndex].inputType=='s') { char* val = va_arg( argptr, char* ); paramData[i]=val; paramLength[i]=(int) strlen(val); } else if (typeMappings[indices[i].typeMappingIndex].inputType=='b') { bool val = va_arg( argptr, bool ); paramData[i]=(char*) &val; paramLength[i]=sizeof(bool); if (!SLNet::BitStream::IsNetworkOrder()) SLNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); } else if (typeMappings[indices[i].typeMappingIndex].inputType=='f') { float val = va_arg( argptr, float ); paramData[i]=(char*) &val; paramLength[i]=sizeof(float); if (!SLNet::BitStream::IsNetworkOrder()) SLNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); } else if (typeMappings[indices[i].typeMappingIndex].inputType=='g') { double val = va_arg( argptr, double ); paramData[i]=(char*) &val; paramLength[i]=sizeof(double); if (!SLNet::BitStream::IsNetworkOrder()) SLNet::BitStream::ReverseBytesInPlace((unsigned char*) paramData[i], paramLength[i]); } else if (typeMappings[indices[i].typeMappingIndex].inputType=='a') { char* val = va_arg( argptr, char* ); paramData[i]=val; variadicArgIndex++; paramLength[i]=va_arg( argptr, unsigned int ); } paramFormat[i]=PQEXECPARAM_FORMAT_BINARY; } va_end(argptr); // Replace each %whatever with $index::type SLNet::RakString inputCopy; inputCopy=input; unsigned int lastIndex=0; for (i=0; i < indices.Size(); i++) { query+=inputCopy.SubStr(lastIndex, indices[i].strIndex-lastIndex); query+=SLNet::RakString("$%i::", i+1); query+=typeMappings[indices[i].typeMappingIndex].type; lastIndex=indices[i].strIndex+2; // +2 is to skip the %whateverCharacter } query+=inputCopy.SubStr(lastIndex, (unsigned int)-1); result = PQexecParams(pgConn, query.C_String(),indices.Size(),0,paramData,paramLength,paramFormat,PQEXECPARAM_FORMAT_BINARY); if (!IsResultSuccessful(result, false)) { PQclear(result); return 0; } return result; } */ void PostgreSQLInterface::ClearResult(PGresult *result) { PQclear(result); }