diff --git a/deps/Makefile b/deps/Makefile index dba5fc6c4..a45694368 100644 --- a/deps/Makefile +++ b/deps/Makefile @@ -303,6 +303,7 @@ pcre: pcre/pcre/.libs/libpcre.a postgresql/postgresql/src/interfaces/libpq/libpq.a : libssl/openssl/libssl.a cd postgresql && rm -rf postgresql-*/ || true cd postgresql && tar -zxf postgresql-*.tar.gz + cd postgresql/postgresql && patch -p0 < ../get_result_from_pgconn.patch cd postgresql/postgresql && ./configure --with-ssl=openssl --with-includes=$(shell pwd)/libssl/openssl/include/openssl --with-libraries=$(shell pwd)/libssl/openssl/lib --enable-static --without-readline cd postgresql/postgresql/src/interfaces/libpq && CC=${CC} CXX=${CXX} ${MAKE} MAKELEVEL=0 #cd postgresql/postgresql && CC=${CC} CXX=${CXX} ${MAKE} -f src/interfaces/libpq/Makefile all diff --git a/deps/postgresql/get_result_from_pgconn.patch b/deps/postgresql/get_result_from_pgconn.patch new file mode 100644 index 000000000..6bb37bca1 --- /dev/null +++ b/deps/postgresql/get_result_from_pgconn.patch @@ -0,0 +1,40 @@ +diff --git src/interfaces/libpq/fe-exec.c src/interfaces/libpq/fe-exec.c +index fa9d6aad..cd5cd23d 100644 +--- src/interfaces/libpq/fe-exec.c ++++ src/interfaces/libpq/fe-exec.c +@@ -4467,3 +4467,20 @@ PQunescapeBytea(const unsigned char *strtext, size_t *retbuflen) + *retbuflen = buflen; + return tmpbuf; + } ++ ++/* ++ * PQgetResultFromPGconn ++ * Get error result from PGconn ++ */ ++const PGresult * ++PQgetResultFromPGconn(PGconn *conn) ++{ ++ if (!conn) ++ return NULL; ++ ++ if (conn->asyncStatus != PGASYNC_IDLE) ++ return NULL; ++ ++ return conn->result; ++} ++ +diff --git src/interfaces/libpq/libpq-fe.h src/interfaces/libpq/libpq-fe.h +index 7476dbe0..472d0083 100644 +--- src/interfaces/libpq/libpq-fe.h ++++ src/interfaces/libpq/libpq-fe.h +@@ -668,6 +668,9 @@ extern PQsslKeyPassHook_OpenSSL_type PQgetSSLKeyPassHook_OpenSSL(void); + extern void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type hook); + extern int PQdefaultSSLKeyPassHook_OpenSSL(char *buf, int size, PGconn *conn); + ++/* Get PGresult directly from PGconn. WARNING: DO NOT RELEASE THIS RESULT */ ++extern const PGresult *PQgetResultFromPGconn(PGconn *conn); ++ + #ifdef __cplusplus + } + #endif + diff --git a/include/PgSQL_Backend.h b/include/PgSQL_Backend.h index ec643db1a..15d75697f 100644 --- a/include/PgSQL_Backend.h +++ b/include/PgSQL_Backend.h @@ -1,5 +1,5 @@ -#ifndef __CLASS_POSTGRESQL_BACKEND_H -#define __CLASS_POSTGRESQL_BACKEND_H +#ifndef __CLASS_PGSQL_BACKEND_H +#define __CLASS_PGSQL_BACKEND_H #include "proxysql.h" #include "cpp.h" @@ -24,4 +24,4 @@ class PgSQL_Backend void reset(); //< A method that resets and releases resources associated with this backend instance }; -#endif /* __CLASS_POSTGRESQL_BACKEND_H */ +#endif /* __CLASS_PGSQLL_BACKEND_H */ diff --git a/include/PgSQL_Connection.h b/include/PgSQL_Connection.h index e99a17ef7..c25982e6e 100644 --- a/include/PgSQL_Connection.h +++ b/include/PgSQL_Connection.h @@ -1,8 +1,9 @@ -#ifndef __CLASS_POSTGRESQL_CONNECTION_H -#define __CLASS_POSTGRESQL_CONNECTION_H +#ifndef __CLASS_PGSQL_CONNECTION_H +#define __CLASS_PGSQL_CONNECTION_H #include "proxysql.h" #include "cpp.h" +#include "PgSQL_Error_Helper.h" #ifndef PROXYJSON #define PROXYJSON @@ -449,22 +450,8 @@ class PgSQL_Connection_Placeholder { void process_rows_in_ASYNC_STMT_EXECUTE_STORE_RESULT_CONT(unsigned long long& processed_bytes); void async_free_result(); - /** - * @brief Returns if the connection is **for sure**, known to be in an active transaction. - * @details The function considers two things: - * 1. If 'server_status' is flagged with 'SERVER_STATUS_IN_TRANS'. - * 2. If the connection has 'autcommit=0' and 'autocommit_false_is_transaction' is set. - * @return True if the connection is known to be in a transaction, or equivalent state. - */ - bool IsKnownActiveTransaction(); - /** - * @brief Returns if the connection is in a **potential transaction**. - * @details This function is a more strict version of 'IsKnownActiveTransaction', which also considers - * connections which holds 'unknown_transaction_status' as potentially active transactions. - * @return True if the connection is in potentially in an active transaction. - */ - bool IsActiveTransaction(); - bool IsServerOffline(); + + bool IsAutoCommit(); bool AutocommitFalse_AndSavepoint(); bool MultiplexDisabled(bool check_delay_token = true); @@ -489,14 +476,23 @@ private: // these will be removed MySQL_ResultSet *MyRS; MySQL_ResultSet *MyRS_reuse; -}; - -enum PG_ERROR_TYPE { - PG_NO_ERROR, - PG_CONNECT_FAILED, - PG_QUERY_FAILED, - PG_RESULT_FAILED, + bool IsServerOffline(); + /** + * @brief Returns if the connection is **for sure**, known to be in an active transaction. + * @details The function considers two things: + * 1. If 'server_status' is flagged with 'SERVER_STATUS_IN_TRANS'. + * 2. If the connection has 'autcommit=0' and 'autocommit_false_is_transaction' is set. + * @return True if the connection is known to be in a transaction, or equivalent state. + */ + bool IsKnownActiveTransaction(); + /** + * @brief Returns if the connection is in a **potential transaction**. + * @details This function is a more strict version of 'IsKnownActiveTransaction', which also considers + * connections which holds 'unknown_transaction_status' as potentially active transactions. + * @return True if the connection is in potentially in an active transaction. + */ + bool IsActiveTransaction(); }; class PgSQL_Connection : public PgSQL_Connection_Placeholder { @@ -521,41 +517,69 @@ public: void compute_unknown_transaction_status(); void async_free_result(); void flush(); + bool IsActiveTransaction(); + bool IsKnownActiveTransaction(); + bool IsServerOffline(); - std::string get_error_code_from_result() const; + int get_server_version() { + return PQserverVersion(pgsql_conn); + } + + int get_protocol_version() { + return PQprotocolVersion(pgsql_conn); + } bool is_error_present() const { - return err_type != PG_NO_ERROR; + if (error_info.severity == PGSQL_ERROR_SEVERITY::ERRSEVERITY_FATAL || + error_info.severity == PGSQL_ERROR_SEVERITY::ERRSEVERITY_ERROR || + error_info.severity == PGSQL_ERROR_SEVERITY::ERRSEVERITY_PANIC) { + return true; + } + return false; } - PG_ERROR_TYPE get_error_type() const { - return err_type; + PGSQL_ERROR_CATEGORY get_error_category() const { + return error_info.category; } std::string get_error_message() const { - return err_msg; + return error_info.message; + } + + std::string get_error_code_with_message() const { + return ("[" + std::string(error_info.sqlstate) + "] " + error_info.message); + } + + const char* get_error_code_str() const { + return error_info.sqlstate; + } + + PGSQL_ERROR_CODES get_error_code() const { + return error_info.code; + } + + void set_error(const char* code, const char* message, bool is_fatal) { + PgSQL_Error_Helper::fill_error_info(error_info, code, message, is_fatal ? "FATAL" : "ERROR"); } - void set_error(PG_ERROR_TYPE _err_type, const std::string& _err_msg) { - err_type = _err_type; - err_msg = _err_msg; + void set_error_from_result(const PGresult* result, uint16_t ext_fields = 0) { + PgSQL_Error_Helper::fill_error_info(error_info, result, ext_fields); } void reset_error() { - err_type = PG_NO_ERROR; - err_msg.clear(); + reset_error_info(error_info, false); } PGresult* get_last_result() const { return last_result; } - void set_last_result(PGresult* res) { + void set_last_result(PGresult* result) { if (last_result) { PQclear(last_result); } - last_result = res; + last_result = result; } void reset_last_result() { @@ -565,66 +589,18 @@ public: } } void optimize() {} - //PgSQL_Conn_Param conn_params; + //PgSQL_Conn_Param conn_params; + PgSQL_ErrorInfo error_info; PGconn* pgsql_conn; PGresult* last_result; PgSQL_Query_Result* query_result; PgSQL_Query_Result* query_result_reuse; - - PG_ERROR_TYPE err_type; - std::string err_msg; - bool first_result; //PgSQL_SrvC* parent; //PgSQL_Connection_userinfo* userinfo; //PgSQL_Data_Stream* myds; //int fd; - - /*std::string get_error_code(PG_ERROR_TYPE* errtype = NULL) { - assert(pgsql_conn); - - std::string error_code = PGCONN_NO_ERROR; - - ConnStatusType status = PQstatus(pgsql_conn); - if (status == CONNECTION_BAD) { - if (errtype) *errtype = PG_CONNECTION_ERROR; - error_code = PQparameterStatus(pgsql_conn, "SQLSTATE"); - } - else if (pgsql_result != NULL) { - ExecStatusType status = PQresultStatus(pgsql_result); - if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { - if (errtype) *errtype = PG_SERVER_ERROR; - error_code = PQresultErrorField(pgsql_result, PG_DIAG_SQLSTATE); - } - } - - return error_code; - } - - std::string get_error_message(PG_ERROR_TYPE* errtype = NULL) { - assert(pgsql_conn); - - std::string error_message{}; - - ConnStatusType status = PQstatus(pgsql_conn); - if (status == CONNECTION_BAD) { - if (errtype) *errtype = PG_CONNECTION_ERROR; - error_message = PQerrorMessage(pgsql_conn); - } - else if (pgsql_result != NULL) { - ExecStatusType status = PQresultStatus(pgsql_result); - if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { - if (errtype) *errtype = PG_SERVER_ERROR; - error_message = PQresultErrorMessage(pgsql_result); - } - } - - return error_message; - }*/ }; - - - -#endif /* __CLASS_POSTGRESQL_CONNECTION_H */ +#endif /* __CLASS_PGSQL_CONNECTION_H */ diff --git a/include/PgSQL_Data_Stream.h b/include/PgSQL_Data_Stream.h index e2a6b0314..23a1cbe56 100644 --- a/include/PgSQL_Data_Stream.h +++ b/include/PgSQL_Data_Stream.h @@ -1,5 +1,5 @@ -#ifndef __CLASS_POSTGRESQL_DATA_STREAM_H -#define __CLASS_POSTGRESQL_DATA_STREAM_H +#ifndef __CLASS_PGSQL_DATA_STREAM_H +#define __CLASS_PGSQL_DATA_STREAM_H #include "proxysql.h" #include "cpp.h" @@ -58,7 +58,7 @@ public: } }; -enum pgsql_sslstatus { POSTGRESQL_SSLSTATUS_OK, POSTGRESQL_SSLSTATUS_WANT_IO, POSTGRESQL_SSLSTATUS_FAIL }; +enum pgsql_sslstatus { PGSQL_SSLSTATUS_OK, PGSQL_SSLSTATUS_WANT_IO, PGSQL_SSLSTATUS_FAIL }; class PgSQL_Data_Stream { @@ -269,4 +269,4 @@ public: void reset_connection(); }; -#endif /* __CLASS_MYSQL_DATA_STREAM_H */ +#endif /* __CLASS_PGSQL_DATA_STREAM_H */ diff --git a/include/PgSQL_Error_Helper.h b/include/PgSQL_Error_Helper.h new file mode 100644 index 000000000..34d1a4472 --- /dev/null +++ b/include/PgSQL_Error_Helper.h @@ -0,0 +1,653 @@ +#ifndef __CLASS_PGSQL_ERROR_HELPER_H +#define __CLASS_PGSQL_ERROR_HELPER_H + +#include + +#define PGSQL_ERROR_FIELD_TEXT 0x0001 +#define PGSQL_ERROR_FIELD_DETAIL 0x0002 +#define PGSQL_ERROR_FIELD_HINT 0x0004 +#define PGSQL_ERROR_FIELD_POSITION 0x0008 +#define PGSQL_ERROR_FIELD_INTERNAL_POSITION 0x0010 +#define PGSQL_ERROR_FIELD_INTERNAL_QUERY 0x0020 +#define PGSQL_ERROR_FIELD_CONTEXT 0x0040 +#define PGSQL_ERROR_FIELD_SCHEMA_NAME 0x0080 +#define PGSQL_ERROR_FIELD_TABLE_NAME 0x0100 +#define PGSQL_ERROR_FIELD_COLUMN_NAME 0x0200 +#define PGSQL_ERROR_FIELD_DATA_TYPE_NAME 0x0400 +#define PGSQL_ERROR_FIELD_CONSTRAINT_NAME 0x0800 +#define PGSQL_ERROR_FIELD_FILE 0x1000 +#define PGSQL_ERROR_FIELD_LINE 0x2000 +#define PGSQL_ERROR_FIELD_ROUTINE 0x4000 +#define PGSQL_ERROR_FIELD_ALL 0xFFFF + +// these are standard SQLSTATES +enum class PGSQL_ERROR_CODES : uint8_t { + ERRCODE_SUCCESSFUL_COMPLETION, + ERRCODE_WARNING, + ERRCODE_DYNAMIC_RESULT_SETS_RETURNED, + ERRCODE_IMPLICIT_ZERO_BIT_PADDING, + ERRCODE_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION, + ERRCODE_PRIVILEGE_NOT_GRANTED, + ERRCODE_PRIVILEGE_NOT_REVOKED, + ERRCODE_STRING_DATA_RIGHT_TRUNCATION, + ERRCODE_DEPRECATED_FEATURE, + ERRCODE_NO_DATA, + ERRCODE_NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED, + ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE, + ERRCODE_CONNECTION_EXCEPTION, + ERRCODE_CONNECTION_DOES_NOT_EXIST, + ERRCODE_CONNECTION_FAILURE, + ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, + ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION, + ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN, + ERRCODE_PROTOCOL_VIOLATION, + ERRCODE_TRIGGERED_ACTION_EXCEPTION, + ERRCODE_FEATURE_NOT_SUPPORTED, + ERRCODE_INVALID_TRANSACTION_INITIATION, + ERRCODE_LOCATOR_EXCEPTION, + ERRCODE_INVALID_LOCATOR_SPECIFICATION, + ERRCODE_INVALID_GRANTOR, + ERRCODE_INVALID_GRANT_OPERATION, + ERRCODE_INVALID_ROLE_SPECIFICATION, + ERRCODE_CARDINALITY_VIOLATION, + ERRCODE_DATA_EXCEPTION, + ERRCODE_ARRAY_ELEMENT_ERROR, + ERRCODE_ARRAY_SUBSCRIPT_ERROR, + ERRCODE_CHARACTER_NOT_IN_REPERTOIRE, + ERRCODE_DATETIME_FIELD_OVERFLOW, + ERRCODE_DIVISION_BY_ZERO, + ERRCODE_ERROR_IN_ASSIGNMENT, + ERRCODE_ESCAPE_CHARACTER_CONFLICT, + ERRCODE_INDICATOR_OVERFLOW, + ERRCODE_INTERVAL_FIELD_OVERFLOW, + ERRCODE_INVALID_ARGUMENT_FOR_LOG, + ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION, + ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION, + ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST, + ERRCODE_INVALID_DATETIME_FORMAT, + ERRCODE_INVALID_ESCAPE_CHARACTER, + ERRCODE_INVALID_ESCAPE_OCTET, + ERRCODE_INVALID_ESCAPE_SEQUENCE, + ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER, + ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE, + ERRCODE_INVALID_PARAMETER_VALUE, + ERRCODE_INVALID_REGULAR_EXPRESSION, + ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE, + ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE, + ERRCODE_INVALID_TABLESAMPLE_ARGUMENT, + ERRCODE_INVALID_TABLESAMPLE_REPEAT, + ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE, + ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER, + ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH, + ERRCODE_NULL_VALUE_NOT_ALLOWED, + ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER, + ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE, + ERRCODE_STRING_DATA_LENGTH_MISMATCH, + ERRCODE_SUBSTRING_ERROR, + ERRCODE_TRIM_ERROR, + ERRCODE_UNTERMINATED_C_STRING, + ERRCODE_ZERO_LENGTH_CHARACTER_STRING, + ERRCODE_FLOATING_POINT_EXCEPTION, + ERRCODE_INVALID_TEXT_REPRESENTATION, + ERRCODE_INVALID_BINARY_REPRESENTATION, + ERRCODE_BAD_COPY_FILE_FORMAT, + ERRCODE_UNTRANSLATABLE_CHARACTER, + ERRCODE_NOT_AN_XML_DOCUMENT, + ERRCODE_INVALID_XML_DOCUMENT, + ERRCODE_INVALID_XML_CONTENT, + ERRCODE_INVALID_XML_COMMENT, + ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION, + ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION, + ERRCODE_RESTRICT_VIOLATION, + ERRCODE_NOT_NULL_VIOLATION, + ERRCODE_FOREIGN_KEY_VIOLATION, + ERRCODE_UNIQUE_VIOLATION, + ERRCODE_CHECK_VIOLATION, + ERRCODE_EXCLUSION_VIOLATION, + ERRCODE_INVALID_CURSOR_STATE, + ERRCODE_INVALID_TRANSACTION_STATE, + ERRCODE_ACTIVE_SQL_TRANSACTION, + ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE, + ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL, + ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION, + ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION, + ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION, + ERRCODE_READ_ONLY_SQL_TRANSACTION, + ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED, + ERRCODE_NO_ACTIVE_SQL_TRANSACTION, + ERRCODE_IN_FAILED_SQL_TRANSACTION, + ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, + ERRCODE_INVALID_SQL_STATEMENT_NAME, + ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION, + ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, + ERRCODE_INVALID_PASSWORD, + ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST, + ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST, + ERRCODE_INVALID_TRANSACTION_TERMINATION, + ERRCODE_SQL_ROUTINE_EXCEPTION, + ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT, + ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED, + ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED, + ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED, + ERRCODE_INVALID_CURSOR_NAME, + ERRCODE_EXTERNAL_ROUTINE_EXCEPTION, + ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED, + ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED, + ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED, + ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED, + ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION, + ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED, + ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED, + ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED, + ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED, + ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED, + ERRCODE_SAVEPOINT_EXCEPTION, + ERRCODE_S_E_INVALID_SPECIFICATION, + ERRCODE_INVALID_CATALOG_NAME, + ERRCODE_INVALID_SCHEMA_NAME, + ERRCODE_TRANSACTION_ROLLBACK, + ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION, + ERRCODE_T_R_SERIALIZATION_FAILURE, + ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN, + ERRCODE_T_R_DEADLOCK_DETECTED, + ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, + ERRCODE_SYNTAX_ERROR, + ERRCODE_INSUFFICIENT_PRIVILEGE, + ERRCODE_CANNOT_COERCE, + ERRCODE_GROUPING_ERROR, + ERRCODE_WINDOWING_ERROR, + ERRCODE_INVALID_RECURSION, + ERRCODE_INVALID_FOREIGN_KEY, + ERRCODE_INVALID_NAME, + ERRCODE_NAME_TOO_LONG, + ERRCODE_RESERVED_NAME, + ERRCODE_DATATYPE_MISMATCH, + ERRCODE_INDETERMINATE_DATATYPE, + ERRCODE_COLLATION_MISMATCH, + ERRCODE_INDETERMINATE_COLLATION, + ERRCODE_WRONG_OBJECT_TYPE, + ERRCODE_GENERATED_ALWAYS, + ERRCODE_UNDEFINED_COLUMN, + ERRCODE_UNDEFINED_CURSOR, + ERRCODE_UNDEFINED_DATABASE, + ERRCODE_UNDEFINED_FUNCTION, + ERRCODE_UNDEFINED_PSTATEMENT, + ERRCODE_UNDEFINED_SCHEMA, + ERRCODE_UNDEFINED_TABLE, + ERRCODE_UNDEFINED_PARAMETER, + ERRCODE_UNDEFINED_OBJECT, + ERRCODE_DUPLICATE_COLUMN, + ERRCODE_DUPLICATE_CURSOR, + ERRCODE_DUPLICATE_DATABASE, + ERRCODE_DUPLICATE_FUNCTION, + ERRCODE_DUPLICATE_PSTATEMENT, + ERRCODE_DUPLICATE_SCHEMA, + ERRCODE_DUPLICATE_TABLE, + ERRCODE_DUPLICATE_ALIAS, + ERRCODE_DUPLICATE_OBJECT, + ERRCODE_AMBIGUOUS_COLUMN, + ERRCODE_AMBIGUOUS_FUNCTION, + ERRCODE_AMBIGUOUS_PARAMETER, + ERRCODE_AMBIGUOUS_ALIAS, + ERRCODE_INVALID_COLUMN_REFERENCE, + ERRCODE_INVALID_COLUMN_DEFINITION, + ERRCODE_INVALID_CURSOR_DEFINITION, + ERRCODE_INVALID_DATABASE_DEFINITION, + ERRCODE_INVALID_FUNCTION_DEFINITION, + ERRCODE_INVALID_PSTATEMENT_DEFINITION, + ERRCODE_INVALID_SCHEMA_DEFINITION, + ERRCODE_INVALID_TABLE_DEFINITION, + ERRCODE_INVALID_OBJECT_DEFINITION, + ERRCODE_WITH_CHECK_OPTION_VIOLATION, + ERRCODE_INSUFFICIENT_RESOURCES, + ERRCODE_DISK_FULL, + ERRCODE_OUT_OF_MEMORY, + ERRCODE_TOO_MANY_CONNECTIONS, + ERRCODE_CONFIGURATION_LIMIT_EXCEEDED, + ERRCODE_PROGRAM_LIMIT_EXCEEDED, + ERRCODE_STATEMENT_TOO_COMPLEX, + ERRCODE_TOO_MANY_COLUMNS, + ERRCODE_TOO_MANY_ARGUMENTS, + ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE, + ERRCODE_OBJECT_IN_USE, + ERRCODE_CANT_CHANGE_RUNTIME_PARAM, + ERRCODE_LOCK_NOT_AVAILABLE, + ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE, + ERRCODE_OPERATOR_INTERVENTION, + ERRCODE_QUERY_CANCELED, + ERRCODE_ADMIN_SHUTDOWN, + ERRCODE_CRASH_SHUTDOWN, + ERRCODE_CANNOT_CONNECT_NOW, + ERRCODE_DATABASE_DROPPED, + ERRCODE_IDLE_SESSION_TIMEOUT, + ERRCODE_SYSTEM_ERROR, + ERRCODE_IO_ERROR, + ERRCODE_UNDEFINED_FILE, + ERRCODE_DUPLICATE_FILE, + ERRCODE_CONFIG_FILE_ERROR, + ERRCODE_LOCK_FILE_EXISTS, + ERRCODE_FDW_ERROR, + ERRCODE_FDW_COLUMN_NAME_NOT_FOUND, + ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED, + ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR, + ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION, + ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE, + ERRCODE_FDW_INVALID_COLUMN_NAME, + ERRCODE_FDW_INVALID_COLUMN_NUMBER, + ERRCODE_FDW_INVALID_DATA_TYPE, + ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS, + ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER, + ERRCODE_FDW_INVALID_HANDLE, + ERRCODE_FDW_INVALID_OPTION_INDEX, + ERRCODE_FDW_INVALID_OPTION_NAME, + ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH, + ERRCODE_FDW_INVALID_STRING_FORMAT, + ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER, + ERRCODE_FDW_TOO_MANY_HANDLES, + ERRCODE_FDW_OUT_OF_MEMORY, + ERRCODE_FDW_NO_SCHEMAS, + ERRCODE_FDW_OPTION_NAME_NOT_FOUND, + ERRCODE_FDW_REPLY_HANDLE, + ERRCODE_FDW_SCHEMA_NOT_FOUND, + ERRCODE_FDW_TABLE_NOT_FOUND, + ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION, + ERRCODE_FDW_UNABLE_TO_CREATE_REPLY, + ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION, + ERRCODE_PLPGSQL_ERROR, + ERRCODE_RAISE_EXCEPTION, + ERRCODE_ASSERT_FAILURE, + ERRCODE_INTERNAL_ERROR, + ERRCODE_DATA_CORRUPTED, + ERRCODE_INDEX_CORRUPTED, + // Add more error codes here if needed. Make sure to update error_code_str also. + ERRCODE_UNKNOWN, + PGSQL_ERROR_CODES_COUNT // This should always be the last entry +}; + +// Enum to represent different error types +enum class PGSQL_ERROR_CLASS : uint8_t { + ERRCLASS_UNKNOWN_ERROR, + ERRCLASS_SUCCESS, + ERRCLASS_WARNING, + ERRCLASS_NO_DATA, + ERRCLASS_SQL_STATEMENT_NOT_YET_COMPLETE, + ERRCLASS_CONNECTION_EXCEPTION, + ERRCLASS_TRIGGERED_ACTION_EXCEPTION, + ERRCLASS_FEATURE_NOT_SUPPORTED, + ERRCLASS_INVALID_TRANSACTION_INITIATION, + ERRCLASS_LOCATOR_EXCEPTION, + ERRCLASS_INVALID_GRANTOR, + ERRCLASS_INVALID_ROLE_SPECIFICATION, + ERRCLASS_DIAGNOSTICS_EXCEPTION, + ERRCLASS_CASE_NOT_FOUND, + ERRCLASS_CARDINALITY_VIOLATION, + ERRCLASS_DATA_EXCEPTION, + ERRCLASS_INTEGRITY_CONSTRAINT_VIOLATION, + ERRCLASS_INVALID_CURSOR_STATE, + ERRCLASS_INVALID_TRANSACTION_STATE, + ERRCLASS_INVALID_SQL_STATEMENT_NAME, + ERRCLASS_TRIGGERED_DATA_CHANGE_VIOLATION, + ERRCLASS_INVALID_AUTHORIZATION_SPECIFICATION, + ERRCLASS_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST, + ERRCLASS_INVALID_TRANSACTION_TERMINATION, + ERRCLASS_SQL_ROUTINE_EXCEPTION, + ERRCLASS_INVALID_CURSOR_NAME, + ERRCLASS_EXTERNAL_ROUTINE_EXCEPTION, + ERRCLASS_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION, + ERRCLASS_SAVEPOINT_EXCEPTION, + ERRCLASS_INVALID_CATALOG_NAME, + ERRCLASS_INVALID_SCHEMA_NAME, + ERRCLASS_TRANSACTION_ROLLBACK, + ERRCLASS_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, + ERRCLASS_WITH_CHECK_OPTION_VIOLATION, + ERRCLASS_INSUFFICIENT_RESOURCES, + ERRCLASS_PROGRAM_LIMIT_EXCEEDED, + ERRCLASS_OBJECT_NOT_IN_PREREQUISITE_STATE, + ERRCLASS_OPERATOR_INTERVENTION, + ERRCLASS_SYSTEM_ERROR_UNSPECIFIED, + ERRCLASS_CONFIG_FILE_ERROR, + ERRCLASS_CRASH_SHUTDOWN, + ERRCLASS_PROTOCOL_VIOLATION, + ERRCLASS_FOREIGN_DATA_WRAPPER_ERROR, + ERRCLASS_PLPGSQL_ERROR, + ERRCLASS_INTERNAL_ERROR, + ERRCLASS_MYSQL_SPECIFIC_ERROR +}; + +// Enum to represent error categories +enum class PGSQL_ERROR_CATEGORY : uint8_t { + ERRCATEGORY_UNKNOWN_CATEGORY, + ERRCATEGORY_STATUS, // For success and warnings, which are not errors. + ERRCATEGORY_CLIENT_ERROR, + ERRCATEGORY_CONNECTION_ERROR, + ERRCATEGORY_AUTHORIZATION_ERROR, + ERRCATEGORY_RESOURCE_ERROR, + ERRCATEGORY_CONFIGURATION_ERROR, + ERRCATEGORY_SYNTAX_ERROR, + ERRCATEGORY_FEATURE_NOT_SUPPORTED, + ERRCATEGORY_TRANSACTION_ERROR, + ERRCATEGORY_DATA_ERROR, + ERRCATEGORY_ROUTINE_ERROR, + ERRCATEGORY_CURSOR_ERROR, + ERRCATEGORY_EXTERNAL_ROUTINE_ERROR, + ERRCATEGORY_RESOURCE_LIMIT_ERROR, + ERRCATEGORY_OBJECT_STATE_ERROR, + ERRCATEGORY_OPERATOR_INTERVENTION_ERROR, + ERRCATEGORY_FDW_ERROR, + ERRCATEGORY_PLPGSQL_ERROR, + ERRCATEGORY_INTERNAL_ERROR_CATEGORY +}; + +enum class PGSQL_ERROR_SEVERITY : uint8_t { + ERRSEVERITY_UNKNOWN_SEVERITY, + ERRSEVERITY_FATAL, + ERRSEVERITY_PANIC, + ERRSEVERITY_ERROR, + ERRSEVERITY_WARNING, + ERRSEVERITY_NOTICE, + ERRSEVERITY_DEBUG, + ERRSEVERITY_INFO, + ERRSEVERITY_LOG, + PGSQL_ERROR_SEVERITY_COUNT +}; + +struct PgSQL_ErrorInfo_Ext { + // bitmap fields present in the error message + PGSQL_ERROR_SEVERITY text; + std::string detail; + std::string hint; + std::string position; + std::string internal_position; + std::string internal_query; + std::string context; + std::string schema_name; + std::string table_name; + std::string column_name; + std::string datatype_name; + std::string constraint_name; + std::string source_file; + std::string source_line; + std::string source_function; + void reset(); +}; + +struct pg_result; +typedef struct pg_result PGresult; + +struct PgSQL_ErrorInfo { + PGSQL_ERROR_SEVERITY severity = PGSQL_ERROR_SEVERITY::ERRSEVERITY_UNKNOWN_SEVERITY; + PGSQL_ERROR_CODES code = PGSQL_ERROR_CODES::ERRCODE_SUCCESSFUL_COMPLETION; + PGSQL_ERROR_CLASS type = PGSQL_ERROR_CLASS::ERRCLASS_UNKNOWN_ERROR; + PGSQL_ERROR_CATEGORY category = PGSQL_ERROR_CATEGORY::ERRCATEGORY_UNKNOWN_CATEGORY; + char sqlstate[5 + 1] = {}; // 5 bytes for SQLSTATE + 1 for null terminator + PgSQL_ErrorInfo_Ext* ext_info = NULL; + std::string message; +}; + +void reset_error_info(PgSQL_ErrorInfo& err_info, bool release_extented); + +class PgSQL_Error_Helper { +public: + static constexpr const char* get_error_code(PGSQL_ERROR_CODES err_code) { + return error_code_str[static_cast(err_code)]; + } + + static void fill_error_info(PgSQL_ErrorInfo& err_info, const PGresult* result, uint16_t ext_fields); + static void fill_error_info(PgSQL_ErrorInfo& err_info, const char* code, const char* msg, const char* severity); + +private: + static PGSQL_ERROR_CODES identify_error_code(const char* code); + static PGSQL_ERROR_CLASS identify_error_class(const char* code); + static PGSQL_ERROR_CATEGORY categorize_error_class(PGSQL_ERROR_CLASS err_class); + static PGSQL_ERROR_SEVERITY identify_error_severity(const char* severity); + static void fill_extended_error_info(PgSQL_ErrorInfo& err_info, const PGresult* result, uint16_t ext_fields); + + /* All the error codes from https://www.postgresql.org/docs/current/errcodes-appendix.html */ + static constexpr const char* error_code_str[] = { + "00000", // ERRCODE_SUCCESSFUL_COMPLETION + "01000", // ERRCODE_WARNING + "0100C", // ERRCODE_DYNAMIC_RESULT_SETS_RETURNED + "01008", // ERRCODE_IMPLICIT_ZERO_BIT_PADDING + "01003", // ERRCODE_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION + "01007", // ERRCODE_PRIVILEGE_NOT_GRANTED + "01006", // ERRCODE_PRIVILEGE_NOT_REVOKED + "01004", // ERRCODE_STRING_DATA_RIGHT_TRUNCATION + "01P01", // ERRCODE_DEPRECATED_FEATURE + "02000", // ERRCODE_NO_DATA + "02001", // ERRCODE_NO_ADDITIONAL_DYNAMIC_RESULT_SETS_RETURNED + "03000", // ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE + "08000", // ERRCODE_CONNECTION_EXCEPTION + "08003", // ERRCODE_CONNECTION_DOES_NOT_EXIST + "08006", // ERRCODE_CONNECTION_FAILURE + "08001", // ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION + "08004", // ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION + "08007", // ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN + "08P01", // ERRCODE_PROTOCOL_VIOLATION + "09000", // ERRCODE_TRIGGERED_ACTION_EXCEPTION + "0A000", // ERRCODE_FEATURE_NOT_SUPPORTED + "0B000", // ERRCODE_INVALID_TRANSACTION_INITIATION + "0F000", // ERRCODE_LOCATOR_EXCEPTION + "0F001", // ERRCODE_INVALID_LOCATOR_SPECIFICATION + "0L000", // ERRCODE_INVALID_GRANTOR + "0LP01", // ERRCODE_INVALID_GRANT_OPERATION + "0P000", // ERRCODE_INVALID_ROLE_SPECIFICATION + "21000", // ERRCODE_CARDINALITY_VIOLATION + "22000", // ERRCODE_DATA_EXCEPTION + "2202E", // ERRCODE_ARRAY_ELEMENT_ERROR + "2202E", // ERRCODE_ARRAY_SUBSCRIPT_ERROR + "22021", // ERRCODE_CHARACTER_NOT_IN_REPERTOIRE + "22008", // ERRCODE_DATETIME_FIELD_OVERFLOW + "22012", // ERRCODE_DIVISION_BY_ZERO + "22005", // ERRCODE_ERROR_IN_ASSIGNMENT + "2200B", // ERRCODE_ESCAPE_CHARACTER_CONFLICT + "22022", // ERRCODE_INDICATOR_OVERFLOW + "22015", // ERRCODE_INTERVAL_FIELD_OVERFLOW + "2201E", // ERRCODE_INVALID_ARGUMENT_FOR_LOG + "2201F", // ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION + "2201G", // ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION + "22018", // ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST + "22007", // ERRCODE_INVALID_DATETIME_FORMAT + "22019", // ERRCODE_INVALID_ESCAPE_CHARACTER + "2200D", // ERRCODE_INVALID_ESCAPE_OCTET + "22025", // ERRCODE_INVALID_ESCAPE_SEQUENCE + "22P06", // ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER + "22010", // ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE + "22023", // ERRCODE_INVALID_PARAMETER_VALUE + "2201B", // ERRCODE_INVALID_REGULAR_EXPRESSION + "2201W", // ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE + "2201X", // ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE + "2202H", // ERRCODE_INVALID_TABLESAMPLE_ARGUMENT + "2202G", // ERRCODE_INVALID_TABLESAMPLE_REPEAT + "22009", // ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE + "2200C", // ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER + "2200G", // ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH + "22004", // ERRCODE_NULL_VALUE_NOT_ALLOWED + "22002", // ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER + "22003", // ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE + "22026", // ERRCODE_STRING_DATA_LENGTH_MISMATCH + "22011", // ERRCODE_SUBSTRING_ERROR + "22027", // ERRCODE_TRIM_ERROR + "22024", // ERRCODE_UNTERMINATED_C_STRING + "2200F", // ERRCODE_ZERO_LENGTH_CHARACTER_STRING + "22P01", // ERRCODE_FLOATING_POINT_EXCEPTION + "22P02", // ERRCODE_INVALID_TEXT_REPRESENTATION + "22P03", // ERRCODE_INVALID_BINARY_REPRESENTATION + "22P04", // ERRCODE_BAD_COPY_FILE_FORMAT + "22P05", // ERRCODE_UNTRANSLATABLE_CHARACTER + "2200L", // ERRCODE_NOT_AN_XML_DOCUMENT + "2200M", // ERRCODE_INVALID_XML_DOCUMENT + "2200N", // ERRCODE_INVALID_XML_CONTENT + "2200S", // ERRCODE_INVALID_XML_COMMENT + "2200T", // ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION + "23000", // ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION + "23001", // ERRCODE_RESTRICT_VIOLATION + "23502", // ERRCODE_NOT_NULL_VIOLATION + "23503", // ERRCODE_FOREIGN_KEY_VIOLATION + "23505", // ERRCODE_UNIQUE_VIOLATION + "23514", // ERRCODE_CHECK_VIOLATION + "23P01", // ERRCODE_EXCLUSION_VIOLATION + "24000", // ERRCODE_INVALID_CURSOR_STATE + "25000", // ERRCODE_INVALID_TRANSACTION_STATE + "25001", // ERRCODE_ACTIVE_SQL_TRANSACTION + "25002", // ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE + "25008", // ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL + "25003", // ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION + "25004", // ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION + "25005", // ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION + "25006", // ERRCODE_READ_ONLY_SQL_TRANSACTION + "25007", // ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED + "25P01", // ERRCODE_NO_ACTIVE_SQL_TRANSACTION + "25P02", // ERRCODE_IN_FAILED_SQL_TRANSACTION + "25P03", // ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT + "26000", // ERRCODE_INVALID_SQL_STATEMENT_NAME + "27000", // ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION + "28000", // ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION + "28P01", // ERRCODE_INVALID_PASSWORD + "2B000", // ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST + "2BP01", // ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST + "2D000", // ERRCODE_INVALID_TRANSACTION_TERMINATION + "2F000", // ERRCODE_SQL_ROUTINE_EXCEPTION + "2F005", // ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT + "2F002", // ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED + "2F003", // ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED + "2F004", // ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED + "34000", // ERRCODE_INVALID_CURSOR_NAME + "38000", // ERRCODE_EXTERNAL_ROUTINE_EXCEPTION + "38001", // ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED + "38002", // ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED + "38003", // ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED + "38004", // ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED + "39000", // ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION + "39001", // ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED + "39004", // ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED + "39P01", // ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED + "39P02", // ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED + "39P03", // ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED + "3B000", // ERRCODE_SAVEPOINT_EXCEPTION + "3B001", // ERRCODE_S_E_INVALID_SPECIFICATION + "3D000", // ERRCODE_INVALID_CATALOG_NAME + "3F000", // ERRCODE_INVALID_SCHEMA_NAME + "40000", // ERRCODE_TRANSACTION_ROLLBACK + "40002", // ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION + "40001", // ERRCODE_T_R_SERIALIZATION_FAILURE + "40003", // ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN + "40P01", // ERRCODE_T_R_DEADLOCK_DETECTED + "42000", // ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION + "42601", // ERRCODE_SYNTAX_ERROR + "42501", // ERRCODE_INSUFFICIENT_PRIVILEGE + "42846", // ERRCODE_CANNOT_COERCE + "42803", // ERRCODE_GROUPING_ERROR + "42P20", // ERRCODE_WINDOWING_ERROR + "42P19", // ERRCODE_INVALID_RECURSION + "42830", // ERRCODE_INVALID_FOREIGN_KEY + "42602", // ERRCODE_INVALID_NAME + "42622", // ERRCODE_NAME_TOO_LONG + "42939", // ERRCODE_RESERVED_NAME + "42804", // ERRCODE_DATATYPE_MISMATCH + "42P18", // ERRCODE_INDETERMINATE_DATATYPE + "42P21", // ERRCODE_COLLATION_MISMATCH + "42P22", // ERRCODE_INDETERMINATE_COLLATION + "42809", // ERRCODE_WRONG_OBJECT_TYPE + "428C9", // ERRCODE_GENERATED_ALWAYS + "42703", // ERRCODE_UNDEFINED_COLUMN + "34000", // ERRCODE_UNDEFINED_CURSOR + "3D000", // ERRCODE_UNDEFINED_DATABASE + "42883", // ERRCODE_UNDEFINED_FUNCTION + "26000", // ERRCODE_UNDEFINED_PSTATEMENT + "3F000", // ERRCODE_UNDEFINED_SCHEMA + "42P01", // ERRCODE_UNDEFINED_TABLE + "42P02", // ERRCODE_UNDEFINED_PARAMETER + "42704", // ERRCODE_UNDEFINED_OBJECT + "42701", // ERRCODE_DUPLICATE_COLUMN + "42P03", // ERRCODE_DUPLICATE_CURSOR + "42P04", // ERRCODE_DUPLICATE_DATABASE + "42723", // ERRCODE_DUPLICATE_FUNCTION + "42P05", // ERRCODE_DUPLICATE_PSTATEMENT + "42P06", // ERRCODE_DUPLICATE_SCHEMA + "42P07", // ERRCODE_DUPLICATE_TABLE + "42712", // ERRCODE_DUPLICATE_ALIAS + "42710", // ERRCODE_DUPLICATE_OBJECT + "42702", // ERRCODE_AMBIGUOUS_COLUMN + "42725", // ERRCODE_AMBIGUOUS_FUNCTION + "42P08", // ERRCODE_AMBIGUOUS_PARAMETER + "42P09", // ERRCODE_AMBIGUOUS_ALIAS + "42P10", // ERRCODE_INVALID_COLUMN_REFERENCE + "42611", // ERRCODE_INVALID_COLUMN_DEFINITION + "42P11", // ERRCODE_INVALID_CURSOR_DEFINITION + "42P12", // ERRCODE_INVALID_DATABASE_DEFINITION + "42P13", // ERRCODE_INVALID_FUNCTION_DEFINITION + "42P14", // ERRCODE_INVALID_PSTATEMENT_DEFINITION + "42P15", // ERRCODE_INVALID_SCHEMA_DEFINITION + "42P16", // ERRCODE_INVALID_TABLE_DEFINITION + "42P17", // ERRCODE_INVALID_OBJECT_DEFINITION + "44000", // ERRCODE_WITH_CHECK_OPTION_VIOLATION + "53000", // ERRCODE_INSUFFICIENT_RESOURCES + "53100", // ERRCODE_DISK_FULL + "53200", // ERRCODE_OUT_OF_MEMORY + "53300", // ERRCODE_TOO_MANY_CONNECTIONS + "53400", // ERRCODE_CONFIGURATION_LIMIT_EXCEEDED + "54000", // ERRCODE_PROGRAM_LIMIT_EXCEEDED + "54001", // ERRCODE_STATEMENT_TOO_COMPLEX + "54011", // ERRCODE_TOO_MANY_COLUMNS + "54023", // ERRCODE_TOO_MANY_ARGUMENTS + "55000", // ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE + "55006", // ERRCODE_OBJECT_IN_USE + "55P02", // ERRCODE_CANT_CHANGE_RUNTIME_PARAM + "55P03", // ERRCODE_LOCK_NOT_AVAILABLE + "55P04", // ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE + "57000", // ERRCODE_OPERATOR_INTERVENTION + "57014", // ERRCODE_QUERY_CANCELED + "57P01", // ERRCODE_ADMIN_SHUTDOWN + "57P02", // ERRCODE_CRASH_SHUTDOWN + "57P03", // ERRCODE_CANNOT_CONNECT_NOW + "57P04", // ERRCODE_DATABASE_DROPPED + "57P05", // ERRCODE_IDLE_SESSION_TIMEOUT + "58000", // ERRCODE_SYSTEM_ERROR + "58030", // ERRCODE_IO_ERROR + "58P01", // ERRCODE_UNDEFINED_FILE + "58P02", // ERRCODE_DUPLICATE_FILE + "F0000", // ERRCODE_CONFIG_FILE_ERROR + "F0001", // ERRCODE_LOCK_FILE_EXISTS + "HV000", // ERRCODE_FDW_ERROR + "HV005", // ERRCODE_FDW_COLUMN_NAME_NOT_FOUND + "HV002", // ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED + "HV010", // ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR + "HV021", // ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION + "HV024", // ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE + "HV007", // ERRCODE_FDW_INVALID_COLUMN_NAME + "HV008", // ERRCODE_FDW_INVALID_COLUMN_NUMBER + "HV004", // ERRCODE_FDW_INVALID_DATA_TYPE + "HV006", // ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS + "HV091", // ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER + "HV00B", // ERRCODE_FDW_INVALID_HANDLE + "HV00C", // ERRCODE_FDW_INVALID_OPTION_INDEX + "HV00D", // ERRCODE_FDW_INVALID_OPTION_NAME + "HV090", // ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH + "HV00A", // ERRCODE_FDW_INVALID_STRING_FORMAT + "HV009", // ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER + "HV014", // ERRCODE_FDW_TOO_MANY_HANDLES + "HV001", // ERRCODE_FDW_OUT_OF_MEMORY + "HV00P", // ERRCODE_FDW_NO_SCHEMAS + "HV00J", // ERRCODE_FDW_OPTION_NAME_NOT_FOUND + "HV00K", // ERRCODE_FDW_REPLY_HANDLE + "HV00Q", // ERRCODE_FDW_SCHEMA_NOT_FOUND + "HV00R", // ERRCODE_FDW_TABLE_NOT_FOUND + "HV00L", // ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION + "HV00M", // ERRCODE_FDW_UNABLE_TO_CREATE_REPLY + "HV00N", // ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION + "P0000", // ERRCODE_PLPGSQL_ERROR + "P0001", // ERRCODE_RAISE_EXCEPTION + "P0002", // ERRCODE_ASSERT_FAILURE + "XX000", // ERRCODE_INTERNAL_ERROR + "XX001", // ERRCODE_DATA_CORRUPTED + "XX002", // ERRCODE_INDEX_CORRUPTED + // Add more error codes here if needed. Make sure to update PGSQL_ERROR_CODES also. + "XXXXX", // ERRCODE_UNKNOWN + }; + + static_assert(static_cast(PGSQL_ERROR_CODES::PGSQL_ERROR_CODES_COUNT) == sizeof(error_code_str) / sizeof(char*), "Mismatch between PGSQL_ERROR_CODES_COUNT and error_code_str array size"); +}; + +#define PGSQL_GET_ERROR_CODE_STR(ENUM_CODE) PgSQL_Error_Helper::get_error_code(PGSQL_ERROR_CODES::ENUM_CODE) + +#endif /* __CLASS_PGSQL_ERROR_HELPER_H */ diff --git a/include/PgSQL_HostGroups_Manager.h b/include/PgSQL_HostGroups_Manager.h index 55f976ddc..6b9bd77c8 100644 --- a/include/PgSQL_HostGroups_Manager.h +++ b/include/PgSQL_HostGroups_Manager.h @@ -1,5 +1,5 @@ -#ifndef __CLASS_POSTGRESQL_HOSTGROUPS_MANAGER_H -#define __CLASS_POSTGRESQL_HOSTGROUPS_MANAGER_H +#ifndef __CLASS_PGSQL_HOSTGROUPS_MANAGER_H +#define __CLASS_PGSQL_HOSTGROUPS_MANAGER_H #include "proxysql.h" #include "cpp.h" #include "proxysql_gtid.h" @@ -983,4 +983,4 @@ private: }; -#endif /* __CLASS_POSTGRESQL_HOSTGROUPS_MANAGER_H */ +#endif /* __CLASS_PGSQL_HOSTGROUPS_MANAGER_H */ diff --git a/include/PgSQL_Logger.hpp b/include/PgSQL_Logger.hpp index 711b1c835..705446001 100644 --- a/include/PgSQL_Logger.hpp +++ b/include/PgSQL_Logger.hpp @@ -1,5 +1,5 @@ -#ifndef __CLASS_POSTGRESQL_LOGGER_H -#define __CLASS_POSTGRESQL_LOGGER_H +#ifndef __CLASS_PGSQL_LOGGER_H +#define __CLASS_PGSQL_LOGGER_H #include "proxysql.h" #include "cpp.h" @@ -96,5 +96,4 @@ class PgSQL_Logger { void wrunlock(); }; - -#endif /* __CLASS_POSTGRESQL_LOGGER_H */ +#endif /* __CLASS_PGSQL_LOGGER_H */ diff --git a/include/PgSQL_Protocol.h b/include/PgSQL_Protocol.h index abb5f2dd1..1b0f7db96 100644 --- a/include/PgSQL_Protocol.h +++ b/include/PgSQL_Protocol.h @@ -182,24 +182,33 @@ private: }; class PgSQL_Protocol; + +#define PGSQL_QUERY_RESULT_EMPTY 0x00 +#define PGSQL_QUERY_RESULT_TUPLE 0x01 +#define PGSQL_QUERY_RESULT_COMMAND 0x02 +#define PGSQL_QUERY_RESULT_READY 0x04 +#define PGSQL_QUERY_RESULT_ERROR 0x08 +#define PGSQL_QUERY_RESULT_WARNING 0x10 + class PgSQL_Query_Result { public: PgSQL_Query_Result(); ~PgSQL_Query_Result(); - void init(PgSQL_Protocol* _proto, PGconn* _conn); - unsigned int add_row_description(PGresult* result); - unsigned int add_row(PGresult* result); - unsigned int add_command_completion(PGresult* result); - unsigned int add_error(PgSQL_Data_Stream* _myds, PGresult* result); + void init(PgSQL_Protocol* _proto, PgSQL_Data_Stream* _myds, PgSQL_Connection* _conn); + unsigned int add_row_description(const PGresult* result); + unsigned int add_row(const PGresult* result); + unsigned int add_command_completion(const PGresult* result); + unsigned int add_error(const PGresult* result); + unsigned int add_ready_status(PGTransactionStatusType txn_status); bool get_resultset(PtrSizeArray* PSarrayFinal); unsigned long long current_size(); inline bool is_transfer_started() const { return transfer_started; } - inline bool is_resultset_completed() const { return resultset_completed; } inline unsigned long long get_num_rows() const { return num_rows; } inline unsigned int get_num_fields() const { return num_fields; } inline unsigned long long get_resultset_size() const { return resultset_size; } + inline uint8_t get_result_packet_type() const { return result_packet_type; } private: void buffer_init(); @@ -214,13 +223,12 @@ private: unsigned long long pkt_count; unsigned int num_fields; unsigned int buffer_used; - //ExecStatusType result_status; unsigned char* buffer; PgSQL_Protocol* proto; - PGconn* pgsql_conn; - //PGresult* result; + PgSQL_Data_Stream* myds; + PgSQL_Connection* conn; bool transfer_started; - bool resultset_completed; + uint8_t result_packet_type; friend class PgSQL_Protocol; }; @@ -240,15 +248,16 @@ public: EXECUTION_STATE process_handshake_response_packet(unsigned char* pkt, unsigned int len); void welcome_client(); - void generate_error_packet(bool send, bool ready, const char* msg, const char* code, bool fatal, PtrSize_t* _ptr = NULL); + void generate_error_packet(bool send, bool ready, const char* msg, PGSQL_ERROR_CODES code, bool fatal, bool track = false, PtrSize_t* _ptr = NULL); bool generate_ok_packet(bool send, bool ready, const char* msg, int rows, const char* query, PtrSize_t* _ptr = NULL); //bool generate_row_description(bool send, PgSQL_Query_Result* rs, const PG_Fields& fields, unsigned int size); - unsigned int copy_row_description_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result); - unsigned int copy_row_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result); - unsigned int copy_command_completion_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result); - unsigned int copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result); + unsigned int copy_row_description_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result); + unsigned int copy_row_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result); + unsigned int copy_command_completion_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result); + unsigned int copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result); + unsigned int copy_ready_status_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGTransactionStatusType txn_status); private: bool get_header(unsigned char* pkt, unsigned int len, pgsql_hdr* hdr); void load_conn_parameters(pgsql_hdr* pkt, bool startup); diff --git a/include/PgSQL_Session.h b/include/PgSQL_Session.h index e6b5f7f19..02bb94fb0 100644 --- a/include/PgSQL_Session.h +++ b/include/PgSQL_Session.h @@ -1,5 +1,5 @@ -#ifndef __CLASS_POSTGRESQL_SESSION_H -#define __CLASS_POSTGRESQL_SESSION_H +#ifndef __CLASS_PGSQL_SESSION_H +#define __CLASS_PGSQL_SESSION_H #include #include @@ -367,7 +367,7 @@ public: PgSQL_Backend* find_or_create_backend(int, PgSQL_Data_Stream* _myds = NULL); void SQLite3_to_MySQL(SQLite3_result*, char*, int, MySQL_Protocol*, bool in_transaction = false, bool deprecate_eof_active = false); - void PgSQL_Result_to_PgSQL_wire(PGconn* pgsql_conn, PgSQL_Query_Result* query_result, unsigned int warning_count, PgSQL_Data_Stream* _myds = NULL); + void PgSQL_Result_to_PgSQL_wire(PgSQL_Connection* conn, PgSQL_Data_Stream* _myds = NULL); void MySQL_Stmt_Result_to_MySQL_wire(MYSQL_STMT* stmt, PgSQL_Connection* myconn); unsigned int NumActiveTransactions(bool check_savpoint = false); bool HasOfflineBackends(); @@ -441,4 +441,4 @@ private: void* PgSQL_kill_query_thread(void* arg); -#endif /* __CLASS_POSTGRESQL_SESSION_H */ +#endif /* __CLASS_PGSQL_SESSION_H */ diff --git a/include/PgSQL_Variables.h b/include/PgSQL_Variables.h index ec639f254..aa54c02ec 100644 --- a/include/PgSQL_Variables.h +++ b/include/PgSQL_Variables.h @@ -1,5 +1,5 @@ -#ifndef POSTGRESQL_VARIABLES_H -#define POSTGRESQL_VARIABLES_H +#ifndef PGSQL_VARIABLES_H +#define PGSQL_VARIABLES_H #include "proxysql.h" #include "cpp.h" @@ -51,5 +51,5 @@ public: bool parse_variable_number(PgSQL_Session*sess, int idx, std::string &value1, bool* lock_hostgroup); }; -#endif // POSTGRESQL_VARIABLES_H +#endif // PGSQL_VARIABLES_H diff --git a/lib/Makefile b/lib/Makefile index da48bda21..a65e74ace 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -143,7 +143,7 @@ _OBJ_CXX := ProxySQL_GloVars.oo network.oo debug.oo configfile.oo Query_Cache.oo MySQL_encode.oo MySQL_ResultSet.oo \ ProxySQL_Admin_Tests.oo ProxySQL_Admin_Tests2.oo ProxySQL_Admin_Scheduler.oo ProxySQL_Admin_Disk_Upgrade.oo ProxySQL_Admin_Stats.oo \ Base_Session.oo Base_Thread.oo \ - proxysql_find_charset.oo ProxySQL_Poll.oo sha256crypt.oo PgSQL_Protocol.oo PgSQL_Thread.oo Client_Session.oo PgSQL_Data_Stream.oo PgSQL_Session.oo PgSQL_Variables.oo PgSQL_HostGroups_Manager.oo PgSQL_Connection.oo PgSQL_Backend.oo PgSQL_Logger.oo PgSQL_Authentication.oo + proxysql_find_charset.oo ProxySQL_Poll.oo sha256crypt.oo PgSQL_Protocol.oo PgSQL_Thread.oo Client_Session.oo PgSQL_Data_Stream.oo PgSQL_Session.oo PgSQL_Variables.oo PgSQL_HostGroups_Manager.oo PgSQL_Connection.oo PgSQL_Backend.oo PgSQL_Logger.oo PgSQL_Authentication.oo PgSQL_Error_Helper.oo OBJ_CXX := $(patsubst %,$(ODIR)/%,$(_OBJ_CXX)) HEADERS := ../include/*.h ../include/*.hpp diff --git a/lib/PgSQL_Connection.cpp b/lib/PgSQL_Connection.cpp index a1edefab4..af200ea00 100644 --- a/lib/PgSQL_Connection.cpp +++ b/lib/PgSQL_Connection.cpp @@ -1,19 +1,18 @@ + +#include +#include +#include #include "../deps/json/json.hpp" using json = nlohmann::json; #define PROXYJSON - #include "PgSQL_HostGroups_Manager.h" #include "proxysql.h" #include "cpp.h" -//#include "SpookyV2.h" -#include -#include - #include "MySQL_PreparedStatement.h" #include "PgSQL_Data_Stream.h" #include "query_processor.h" #include "MySQL_Variables.h" -#include + // some of the code that follows is from mariadb client library memory allocator typedef int myf; // Type of MyFlags in my_funcs @@ -3054,8 +3053,7 @@ handler_again: NEXT_IMMEDIATE(ASYNC_CONNECT_TIMEOUT); } next_event(ASYNC_CONNECT_CONT); - } - else { + } else { NEXT_IMMEDIATE(ASYNC_CONNECT_END); } break; @@ -3070,50 +3068,28 @@ handler_again: } if (is_error_present()) { // always increase the counter - proxy_error("Failed to PQconnectStart() on %u:%s:%d , FD (Conn:%d , MyDS:%d) , %d: %s.\n", parent->myhgc->hid, parent->address, parent->port, PQsocket(pgsql_conn), myds->fd, PQstatus(pgsql_conn), get_error_message().c_str()); + proxy_error("Failed to PQconnectStart() on %u:%s:%d , FD (Conn:%d , MyDS:%d) , %s.\n", parent->myhgc->hid, parent->address, parent->port, PQsocket(pgsql_conn), myds->fd, get_error_code_with_message().c_str()); NEXT_IMMEDIATE(ASYNC_CONNECT_FAILED); - } - else { + } else { + if (PQisnonblocking(pgsql_conn) == false) { + // Set non-blocking mode + if (PQsetnonblocking(pgsql_conn, 1) != 0) { + // WARNING: DO NOT RELEASE this PGresult + const PGresult* result = PQgetResultFromPGconn(pgsql_conn); + set_error_from_result(result); + proxy_error("Failed to set non-blocking mode: %s\n", get_error_code_with_message().c_str()); + NEXT_IMMEDIATE(ASYNC_CONNECT_FAILED); + } + } NEXT_IMMEDIATE(ASYNC_CONNECT_SUCCESSFUL); } break; case ASYNC_CONNECT_SUCCESSFUL: - if (is_connected()) { - //if (pgsql->options.use_ssl == 1) - // if (myds) - // if (myds->sess != NULL) - // if (myds->sess->session_fast_forward == true) { - // assert(myds->ssl == NULL); - // if (myds->ssl == NULL) { - // // check the definition of P_MARIADB_TLS - // /* P_MARIADB_TLS* matls = (P_MARIADB_TLS*)pgsql->net.pvio->ctls; - // if (matls != NULL) { - // myds->encrypted = true; - // myds->ssl = (SSL *)matls->ssl; - // myds->rbio_ssl = BIO_new(BIO_s_mem()); - // myds->wbio_ssl = BIO_new(BIO_s_mem()); - // SSL_set_bio(myds->ssl, myds->rbio_ssl, myds->wbio_ssl); - // } else { - // // if pgsql->options.use_ssl == 1 but matls == NULL - // // it means that ProxySQL tried to use SSL to connect to the backend - // // but the backend didn't support SSL - // } - // */ - // } - // } - } + if (!is_connected()) + assert(0); // shouldn't ever reach here, we have messed up the state machine + __sync_fetch_and_add(&PgHGM->status.server_connections_connected, 1); __sync_fetch_and_add(&parent->connect_OK, 1); - - if (PQisnonblocking(pgsql_conn) == false) { - // Set non-blocking mode - if (PQsetnonblocking(pgsql_conn, 1) != 0) { - const std::string errmsg = PQerrorMessage(pgsql_conn); - set_error(PG_CONNECT_FAILED, errmsg); - proxy_error("Failed to set non-blocking mode: %s\n", errmsg.c_str()); - NEXT_IMMEDIATE(ASYNC_CONNECT_FAILED); - } - } //MySQL_Monitor::update_dns_cache_from_mysql_conn(pgsql); break; case ASYNC_CONNECT_FAILED: @@ -3144,11 +3120,10 @@ handler_again: if (async_exit_status) { next_event(ASYNC_QUERY_CONT); } else { -#ifdef PROXYSQL_USE_RESULT + if (is_error_present()) { + NEXT_IMMEDIATE(ASYNC_QUERY_END); + } NEXT_IMMEDIATE(ASYNC_USE_RESULT_START); -#else - NEXT_IMMEDIATE(ASYNC_STORE_RESULT_START); -#endif } break; case ASYNC_QUERY_CONT: @@ -3158,54 +3133,47 @@ handler_again: if (async_exit_status) { next_event(ASYNC_QUERY_CONT); } else { -#ifdef PROXYSQL_USE_RESULT + if (is_error_present()) { + NEXT_IMMEDIATE(ASYNC_QUERY_END); + } NEXT_IMMEDIATE(ASYNC_USE_RESULT_START); -#else - NEXT_IMMEDIATE(ASYNC_STORE_RESULT_START); -#endif } break; case ASYNC_USE_RESULT_START: - if (is_error_present()) { - NEXT_IMMEDIATE(ASYNC_QUERY_END); - } fetch_result_start(); if (async_exit_status == PG_EVENT_NONE) { + if (is_error_present()) { + NEXT_IMMEDIATE(ASYNC_QUERY_END); + } first_result = true; if (myds->sess->mirror == false) { if (query_result_reuse == NULL) { query_result = new PgSQL_Query_Result(); - query_result->init(&myds->sess->client_myds->myprot, pgsql_conn); + query_result->init(&myds->sess->client_myds->myprot, myds, this); } else { query_result = query_result_reuse; query_result_reuse = NULL; - query_result->init(&myds->sess->client_myds->myprot, pgsql_conn); + query_result->init(&myds->sess->client_myds->myprot, myds, this); } - } - else { + } else { if (query_result_reuse == NULL) { query_result = new PgSQL_Query_Result(); - query_result->init(NULL, pgsql_conn); + query_result->init(NULL, myds, this); } else { query_result = query_result_reuse; query_result_reuse = NULL; - query_result->init(NULL, pgsql_conn); + query_result->init(NULL, myds, this); } } NEXT_IMMEDIATE(ASYNC_USE_RESULT_CONT); - } - else { + } else { assert(0); // shouldn't ever reach here } break; case ASYNC_USE_RESULT_CONT: { - if (is_error_present()) { - NEXT_IMMEDIATE(ASYNC_QUERY_END); - } - if (myds->sess && myds->sess->client_myds && myds->sess->mirror == false && myds->sess->status != SHOW_WARNINGS) { // see issue#4072 unsigned int buffered_data = 0; @@ -3223,8 +3191,7 @@ handler_again: break; } - PGresult* result = get_last_result(); - + const PGresult* result = get_last_result(); if (result) { switch (PQresultStatus(result)) { case PGRES_COMMAND_OK: @@ -3235,21 +3202,25 @@ handler_again: case PGRES_SINGLE_TUPLE: break; default: - //const std::string errmsg = PQresultErrorMessage(result); - //set_error(PG_RESULT_FAILED, errmsg); - //proxy_error("Failed condition in PQresultStatus(). %s\n", errmsg.c_str()); - //NEXT_IMMEDIATE(ASYNC_QUERY_END); - query_result->add_error(myds, result); + { + set_error_from_result(result, PGSQL_ERROR_FIELD_ALL); + query_result->add_error(result); + const PGSQL_ERROR_CATEGORY error_category = get_error_category(); + if (error_category != PGSQL_ERROR_CATEGORY::ERRCATEGORY_SYNTAX_ERROR && + error_category != PGSQL_ERROR_CATEGORY::ERRCATEGORY_STATUS && + error_category != PGSQL_ERROR_CATEGORY::ERRCATEGORY_DATA_ERROR) { + proxy_error("Error: %s\n", get_error_code_with_message().c_str()); + } + } NEXT_IMMEDIATE(ASYNC_USE_RESULT_CONT); } if (first_result == true) { - first_result = false; query_result->add_row_description(result); + first_result = false; } if (PQntuples(result) > 0) { - unsigned int br = query_result->add_row(result); __sync_fetch_and_add(&parent->bytes_recv, br); myds->sess->thread->status_variables.stvar[st_var_queries_backends_bytes_recv] += br; @@ -3262,8 +3233,7 @@ handler_again: (pgsql_thread___throttle_ratio_server_to_client && pgsql_thread___throttle_max_bytes_per_second_to_client && (processed_bytes > (unsigned long long)pgsql_thread___throttle_max_bytes_per_second_to_client / 10 * (unsigned long long)pgsql_thread___throttle_ratio_server_to_client)) ) { next_event(ASYNC_USE_RESULT_CONT); // we temporarily pause - } - else { + } else { NEXT_IMMEDIATE(ASYNC_USE_RESULT_CONT); // we continue looping } } else { @@ -3272,11 +3242,17 @@ handler_again: } } - if (query_result->is_resultset_completed() == false) { - if (myds) { - query_result->add_error(myds, NULL); - } + if ((query_result->get_result_packet_type() & (PGSQL_QUERY_RESULT_COMMAND | PGSQL_QUERY_RESULT_ERROR)) == 0) { + // if we reach here we assume that error_info is already set in previous call + if (!is_error_present()) + assert(0); // we might have missed setting error_info in previous call + + query_result->add_error(NULL); } + + // finally add ready for query packet + query_result->add_ready_status(PQtransactionStatus(pgsql_conn)); + NEXT_IMMEDIATE(ASYNC_QUERY_END); } break; @@ -3996,10 +3972,19 @@ void PgSQL_Connection::connect_start() { const std::string& conninfo_str = conninfo.str(); pgsql_conn = PQconnectStart(conninfo_str.c_str()); //pgsql_conn = PQconnectdb(conninfo_str.c_str()); + + //PQsetErrorVerbosity(pgsql_conn, PQERRORS_VERBOSE); + //PQsetErrorContextVisibility(pgsql_conn, PQSHOW_CONTEXT_ALWAYS); + if (pgsql_conn == NULL || PQstatus(pgsql_conn) == CONNECTION_BAD) { - const std::string errmsg = PQerrorMessage(pgsql_conn); - set_error(PG_CONNECT_FAILED, errmsg); - proxy_error("Connect failed. %s\n", errmsg.c_str()); + if (pgsql_conn) { + // WARNING: DO NOT RELEASE this PGresult + const PGresult* result = PQgetResultFromPGconn(pgsql_conn); + set_error_from_result(result); + } else { + set_error(PGSQL_GET_ERROR_CODE_STR(ERRCODE_OUT_OF_MEMORY), "Out of memory", false); + } + proxy_error("Connect failed. %s\n", get_error_code_with_message().c_str()); return; } fd = PQsocket(pgsql_conn); @@ -4012,6 +3997,7 @@ void PgSQL_Connection::connect_cont(short event) { reset_error(); async_exit_status = PG_EVENT_NONE; +// For troubleshooting connection issue #if 0 const char* message = nullptr; switch (PQstatus(pgsql_conn)) @@ -4056,17 +4042,16 @@ void PgSQL_Connection::connect_cont(short event) { case PGRES_POLLING_READING: async_exit_status = PG_EVENT_READ; break; - case PGRES_POLLING_FAILED: case PGRES_POLLING_OK: async_exit_status = PG_EVENT_NONE; break; + //case PGRES_POLLING_FAILED: default: - { - const std::string errmsg = PQerrorMessage(pgsql_conn); - set_error(PG_CONNECT_FAILED, errmsg); - proxy_error("Connect failed. %s\n", errmsg.c_str()); - return; - } + // WARNING: DO NOT RELEASE this PGresult + const PGresult* result = PQgetResultFromPGconn(pgsql_conn); + set_error_from_result(result); + proxy_error("Connect failed. %s\n", get_error_code_with_message().c_str()); + return; } } @@ -4075,9 +4060,10 @@ void PgSQL_Connection::query_start() { reset_error(); async_exit_status = PG_EVENT_NONE; if (PQsendQuery(pgsql_conn, query.ptr) == 0) { - const std::string errmsg = PQerrorMessage(pgsql_conn); - set_error(PG_QUERY_FAILED, errmsg); - proxy_error("Failed to send query. %s\n", errmsg.c_str()); + // WARNING: DO NOT RELEASE this PGresult + const PGresult* result = PQgetResultFromPGconn(pgsql_conn); + set_error_from_result(result); + proxy_error("Failed to send query. %s\n", get_error_code_with_message().c_str()); return; } flush(); @@ -4098,23 +4084,26 @@ void PgSQL_Connection::fetch_result_start() { reset_error(); async_exit_status = PG_EVENT_NONE; if (PQsetSingleRowMode(pgsql_conn) == 0) { - const std::string errmsg = PQerrorMessage(pgsql_conn); - set_error(PG_RESULT_FAILED, errmsg); - proxy_error("Failed to set single row mode. %s\n", errmsg.c_str()); + // WARNING: DO NOT RELEASE this PGresult + const PGresult* result = PQgetResultFromPGconn(pgsql_conn); + set_error_from_result(result); + proxy_error("Failed to set single row mode. %s\n", get_error_code_with_message().c_str()); return; } } void PgSQL_Connection::fetch_result_cont(short event) { PROXY_TRACE(); - reset_error(); + reset_last_result(); async_exit_status = PG_EVENT_NONE; if (PQconsumeInput(pgsql_conn) == 0) { - const std::string errmsg = PQerrorMessage(pgsql_conn); - set_error(PG_RESULT_FAILED, errmsg); - proxy_error("Failed to consume input. %s\n", errmsg.c_str()); - return; + // WARNING: DO NOT RELEASE this PGresult + const PGresult* result = PQgetResultFromPGconn(pgsql_conn); + set_error_from_result(result); + // this is not actually an error, a hint to the user + error_info.severity = PGSQL_ERROR_SEVERITY::ERRSEVERITY_INFO; + proxy_warning("Failed to consume input. %s\n", get_error_code_with_message().c_str()); } if (PQisBusy(pgsql_conn)) { @@ -4136,9 +4125,10 @@ void PgSQL_Connection::flush() { async_exit_status = PG_EVENT_READ; } else { - const std::string errmsg = PQerrorMessage(pgsql_conn); - set_error(PG_QUERY_FAILED, errmsg); - proxy_error("Failed to flush data backend. %s\n", errmsg.c_str()); + // WARNING: DO NOT RELEASE this PGresult + const PGresult* result = PQgetResultFromPGconn(pgsql_conn); + set_error_from_result(result); + proxy_error("Failed to flush data to backend. %s\n", get_error_code_with_message().c_str()); async_exit_status = PG_EVENT_NONE; } } @@ -4188,38 +4178,30 @@ bool PgSQL_Connection::is_connected() const { return true; } -std::string PgSQL_Connection::get_error_code_from_result() const { - assert(pgsql_conn); - std::string error_code{}; - if (last_result != nullptr) { - ExecStatusType status = PQresultStatus(last_result); - if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { - error_code = PQresultErrorField(last_result, PG_DIAG_SQLSTATE); - } - } - - return error_code; -} - void PgSQL_Connection::compute_unknown_transaction_status() { - if (is_connected()) { - const std::string& errocode = get_error_code_from_result(); - - if (errocode.empty()) { - unknown_transaction_status = false; // no error + if (pgsql_conn) { + // make sure we have not missed even a single error + if (is_error_present() == false) { + unknown_transaction_status = false; return; } - if (errocode[0] == 'C') { // client error - // do not change it + + if (is_connected() == false) { + unknown_transaction_status = true; return; } - if (errocode[0] == 'P') { // server error + + switch (PQtransactionStatus(pgsql_conn)) { + case PQTRANS_INTRANS: + case PQTRANS_INERROR: + case PQTRANS_UNKNOWN: + case PQTRANS_ACTIVE: unknown_transaction_status = true; - return; + break; + default: + unknown_transaction_status = false; } - // all other cases, server error - unknown_transaction_status = true; } } @@ -4257,8 +4239,8 @@ void PgSQL_Connection::async_free_result() { // } // query.stmt = NULL; //} - reset_last_result(); } + reset_last_result(); compute_unknown_transaction_status(); async_state_machine = ASYNC_IDLE; if (query_result) { @@ -4268,6 +4250,7 @@ void PgSQL_Connection::async_free_result() { query_result_reuse = query_result; query_result = NULL; } + first_result = false; } int PgSQL_Connection::async_set_autocommit(short event, bool ac) { @@ -4483,3 +4466,63 @@ int PgSQL_Connection::async_ping(short event) { return 1; } +bool PgSQL_Connection::IsKnownActiveTransaction() { + bool in_txn = false; + if (pgsql_conn) { + // Get the transaction status + if (PQtransactionStatus(pgsql_conn) == PQTRANS_INTRANS) { + in_txn = true; + } + } + return in_txn; +} + +bool PgSQL_Connection::IsActiveTransaction() { + bool in_txn = false; + if (pgsql_conn) { + + // Get the transaction status + PGTransactionStatusType status = PQtransactionStatus(pgsql_conn); + + switch (status) { + case PQTRANS_INTRANS: + case PQTRANS_INERROR: + case PQTRANS_UNKNOWN: + in_txn = true; + break; + default: + //case PQTRANS_IDLE: + //case PQTRANS_ACTIVE: + in_txn = false; + } + + if (in_txn == false && is_error_present() && unknown_transaction_status == true) { + in_txn = true; + } + /*if (ret == false) { + //bool r = ( mysql_thread___autocommit_false_is_transaction || mysql_thread___forward_autocommit ); // deprecated , see #3253 + bool r = (mysql_thread___autocommit_false_is_transaction); + if (r && (IsAutoCommit() == false)) { + ret = true; + } + }*/ + } + return in_txn; +} + +bool PgSQL_Connection::IsServerOffline() { + bool ret = false; + if (parent == NULL) + return ret; + server_status = parent->status; // we copy it here to avoid race condition. The caller will see this + if ( + (server_status == MYSQL_SERVER_STATUS_OFFLINE_HARD) // the server is OFFLINE as specific by the user + || + (server_status == MYSQL_SERVER_STATUS_SHUNNED && parent->shunned_automatic == true && parent->shunned_and_kill_all_connections == true) // the server is SHUNNED due to a serious issue + || + (server_status == MYSQL_SERVER_STATUS_SHUNNED_REPLICATION_LAG) // slave is lagging! see #774 + ) { + ret = true; + } + return ret; +} diff --git a/lib/PgSQL_Data_Stream.cpp b/lib/PgSQL_Data_Stream.cpp index d8a6b4c23..3de5baa56 100644 --- a/lib/PgSQL_Data_Stream.cpp +++ b/lib/PgSQL_Data_Stream.cpp @@ -167,14 +167,14 @@ static enum pgsql_sslstatus get_sslstatus(SSL* ssl, int n) ERR_clear_error(); switch (err) { case SSL_ERROR_NONE: - return POSTGRESQL_SSLSTATUS_OK; + return PGSQL_SSLSTATUS_OK; case SSL_ERROR_WANT_WRITE: case SSL_ERROR_WANT_READ: - return POSTGRESQL_SSLSTATUS_WANT_IO; + return PGSQL_SSLSTATUS_WANT_IO; case SSL_ERROR_ZERO_RETURN: case SSL_ERROR_SYSCALL: default: - return POSTGRESQL_SSLSTATUS_FAIL; + return PGSQL_SSLSTATUS_FAIL; } } @@ -232,14 +232,14 @@ enum pgsql_sslstatus PgSQL_Data_Stream::do_ssl_handshake() { long rc = SSL_get_verify_result(ssl); if (rc != X509_V_OK) { proxy_error("Disconnecting %s:%d: X509 client SSL certificate verify error: (%ld:%s)\n", addr.addr, addr.port, rc, X509_verify_cert_error_string(rc)); - return POSTGRESQL_SSLSTATUS_FAIL; + return PGSQL_SSLSTATUS_FAIL; } } } status = get_sslstatus(ssl, n); //proxy_info("SSL status = %d\n", status); /* Did SSL request to write bytes? */ - if (status == POSTGRESQL_SSLSTATUS_WANT_IO) { + if (status == PGSQL_SSLSTATUS_WANT_IO) { //proxy_info("SSL status is WANT_IO %d\n", status); do { n = BIO_read(wbio_ssl, buf, sizeof(buf)); @@ -250,7 +250,7 @@ enum pgsql_sslstatus PgSQL_Data_Stream::do_ssl_handshake() { } else if (!BIO_should_retry(wbio_ssl)) { //proxy_info("BIO_should_retry failed\n"); - return POSTGRESQL_SSLSTATUS_FAIL; + return PGSQL_SSLSTATUS_FAIL; } } while (n > 0); } @@ -621,7 +621,7 @@ int PgSQL_Data_Stream::read_from_net() { len -= n2; if (!SSL_is_init_finished(ssl)) { //proxy_info("SSL_is_init_finished NOT completed\n"); - if (do_ssl_handshake() == POSTGRESQL_SSLSTATUS_FAIL) { + if (do_ssl_handshake() == PGSQL_SSLSTATUS_FAIL) { //proxy_info("SSL_is_init_finished failed!!\n"); shut_soft(); return -1; @@ -651,7 +651,7 @@ int PgSQL_Data_Stream::read_from_net() { */ status = get_sslstatus(ssl, n2); //proxy_info("SSL status = %d\n", status); - if (status == POSTGRESQL_SSLSTATUS_WANT_IO) { + if (status == PGSQL_SSLSTATUS_WANT_IO) { do { n2 = BIO_read(wbio_ssl, buf2, sizeof(buf2)); //proxy_info("BIO_read with %d bytes\n", n2); @@ -664,7 +664,7 @@ int PgSQL_Data_Stream::read_from_net() { } } while (n2 > 0); } - if (status == POSTGRESQL_SSLSTATUS_FAIL) { + if (status == PGSQL_SSLSTATUS_FAIL) { shut_soft(); return -1; } @@ -865,7 +865,7 @@ void PgSQL_Data_Stream::set_pollout() { else { if (!SSL_is_init_finished(ssl)) { //proxy_info("SSL_is_init_finished NOT completed\n"); - if (do_ssl_handshake() == POSTGRESQL_SSLSTATUS_FAIL) { + if (do_ssl_handshake() == PGSQL_SSLSTATUS_FAIL) { //proxy_info("SSL_is_init_finished failed!!\n"); shut_soft(); return; @@ -903,7 +903,7 @@ int PgSQL_Data_Stream::write_to_net_poll() { if (encrypted) { if (!SSL_is_init_finished(ssl)) { //proxy_info("SSL_is_init_finished completed: NO!\n"); - if (do_ssl_handshake() == POSTGRESQL_SSLSTATUS_FAIL) { + if (do_ssl_handshake() == PGSQL_SSLSTATUS_FAIL) { //proxy_info("SSL_is_init_finished failed!!\n"); shut_soft(); return -1; diff --git a/lib/PgSQL_Error_Helper.cpp b/lib/PgSQL_Error_Helper.cpp new file mode 100644 index 000000000..f8e6ce1b5 --- /dev/null +++ b/lib/PgSQL_Error_Helper.cpp @@ -0,0 +1,409 @@ +#include "../deps/json/json.hpp" +using json = nlohmann::json; +#define PROXYJSON + +#include "PgSQL_Error_Helper.h" +#include "proxysql.h" +#include "cpp.h" +#include "libpq-fe.h" + +constexpr const char* PgSQL_Error_Helper::error_code_str[]; + +PGSQL_ERROR_CODES PgSQL_Error_Helper::identify_error_code(const char* errorCode) { + if (strlen(errorCode) != 5) + return PGSQL_ERROR_CODES::ERRCODE_UNKNOWN; + + for (uint8_t i = 0; i < static_cast(PGSQL_ERROR_CODES::PGSQL_ERROR_CODES_COUNT); i++) { + if (strncmp(errorCode, error_code_str[i], 5) == 0) { + return static_cast(i); + } + } + + return PGSQL_ERROR_CODES::ERRCODE_UNKNOWN; +} + +PGSQL_ERROR_CLASS PgSQL_Error_Helper::identify_error_class(const char* errorCode) { + if (strncmp(errorCode, "00", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_SUCCESS; + } + else if (strncmp(errorCode, "01", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_WARNING; + } + else if (strncmp(errorCode, "02", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_NO_DATA; + } + else if (strncmp(errorCode, "03", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_SQL_STATEMENT_NOT_YET_COMPLETE; + } + else if (strncmp(errorCode, "08", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_CONNECTION_EXCEPTION; + } + else if (strncmp(errorCode, "09", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_TRIGGERED_ACTION_EXCEPTION; + } + else if (strncmp(errorCode, "0A", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_FEATURE_NOT_SUPPORTED; + } + else if (strncmp(errorCode, "0B", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_TRANSACTION_INITIATION; + } + else if (strncmp(errorCode, "0F", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_LOCATOR_EXCEPTION; + } + else if (strncmp(errorCode, "0L", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_GRANTOR; + } + else if (strncmp(errorCode, "0P", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_ROLE_SPECIFICATION; + } + else if (strncmp(errorCode, "0Z", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_DIAGNOSTICS_EXCEPTION; + } + else if (strncmp(errorCode, "20", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_CASE_NOT_FOUND; + } + else if (strncmp(errorCode, "21", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_CARDINALITY_VIOLATION; + } + else if (strncmp(errorCode, "22", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_DATA_EXCEPTION; + } + else if (strncmp(errorCode, "23", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INTEGRITY_CONSTRAINT_VIOLATION; + } + else if (strncmp(errorCode, "24", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_CURSOR_STATE; + } + else if (strncmp(errorCode, "25", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_TRANSACTION_STATE; + } + else if (strncmp(errorCode, "26", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_SQL_STATEMENT_NAME; + } + else if (strncmp(errorCode, "27", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_TRIGGERED_DATA_CHANGE_VIOLATION; + } + else if (strncmp(errorCode, "28", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_AUTHORIZATION_SPECIFICATION; + } + else if (strncmp(errorCode, "2B", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST; + } + else if (strncmp(errorCode, "2D", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_TRANSACTION_TERMINATION; + } + else if (strncmp(errorCode, "2F", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_SQL_ROUTINE_EXCEPTION; + } + else if (strncmp(errorCode, "34", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_CURSOR_NAME; + } + else if (strncmp(errorCode, "38", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_EXTERNAL_ROUTINE_EXCEPTION; + } + else if (strncmp(errorCode, "39", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION; + } + else if (strncmp(errorCode, "3B", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_SAVEPOINT_EXCEPTION; + } + else if (strncmp(errorCode, "3D", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_CATALOG_NAME; + } + else if (strncmp(errorCode, "3F", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INVALID_SCHEMA_NAME; + } + else if (strncmp(errorCode, "40", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_TRANSACTION_ROLLBACK; + } + else if (strncmp(errorCode, "42", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION; + } + else if (strncmp(errorCode, "44", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_WITH_CHECK_OPTION_VIOLATION; + } + else if (strncmp(errorCode, "53", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INSUFFICIENT_RESOURCES; + } + else if (strncmp(errorCode, "54", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_PROGRAM_LIMIT_EXCEEDED; + } + else if (strncmp(errorCode, "55", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_OBJECT_NOT_IN_PREREQUISITE_STATE; + } + else if (strncmp(errorCode, "57", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_OPERATOR_INTERVENTION; + } + else if (strncmp(errorCode, "58", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_SYSTEM_ERROR_UNSPECIFIED; + } + else if (strncmp(errorCode, "72", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_CRASH_SHUTDOWN; + } + else if (strncmp(errorCode, "F0", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_CONFIG_FILE_ERROR; + } + else if (strncmp(errorCode, "HV", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_FOREIGN_DATA_WRAPPER_ERROR; + } + else if (strncmp(errorCode, "P0", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_PLPGSQL_ERROR; + } + else if (strncmp(errorCode, "XX", 2) == 0) { + return PGSQL_ERROR_CLASS::ERRCLASS_INTERNAL_ERROR; + } + else { + return PGSQL_ERROR_CLASS::ERRCLASS_UNKNOWN_ERROR; + } +} + +PGSQL_ERROR_CATEGORY PgSQL_Error_Helper::categorize_error_class(PGSQL_ERROR_CLASS err_class) { + switch (err_class) { + case PGSQL_ERROR_CLASS::ERRCLASS_SUCCESS: + case PGSQL_ERROR_CLASS::ERRCLASS_WARNING: + case PGSQL_ERROR_CLASS::ERRCLASS_NO_DATA: + case PGSQL_ERROR_CLASS::ERRCLASS_SQL_STATEMENT_NOT_YET_COMPLETE: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_STATUS; + + case PGSQL_ERROR_CLASS::ERRCLASS_CONNECTION_EXCEPTION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_CONNECTION_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_AUTHORIZATION_SPECIFICATION: + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_GRANTOR: + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_ROLE_SPECIFICATION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_AUTHORIZATION_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_INSUFFICIENT_RESOURCES: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_RESOURCE_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_CONFIG_FILE_ERROR: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_CONFIGURATION_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_SYNTAX_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_FEATURE_NOT_SUPPORTED: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_FEATURE_NOT_SUPPORTED; + + case PGSQL_ERROR_CLASS::ERRCLASS_TRIGGERED_ACTION_EXCEPTION: + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_TRANSACTION_INITIATION: + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_TRANSACTION_STATE: + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_TRANSACTION_TERMINATION: + case PGSQL_ERROR_CLASS::ERRCLASS_TRANSACTION_ROLLBACK: + case PGSQL_ERROR_CLASS::ERRCLASS_SAVEPOINT_EXCEPTION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_TRANSACTION_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_CASE_NOT_FOUND: + case PGSQL_ERROR_CLASS::ERRCLASS_CARDINALITY_VIOLATION: + case PGSQL_ERROR_CLASS::ERRCLASS_DATA_EXCEPTION: + case PGSQL_ERROR_CLASS::ERRCLASS_INTEGRITY_CONSTRAINT_VIOLATION: + case PGSQL_ERROR_CLASS::ERRCLASS_WITH_CHECK_OPTION_VIOLATION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_DATA_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_SQL_ROUTINE_EXCEPTION: + case PGSQL_ERROR_CLASS::ERRCLASS_TRIGGERED_DATA_CHANGE_VIOLATION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_ROUTINE_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_CURSOR_STATE: + case PGSQL_ERROR_CLASS::ERRCLASS_INVALID_CURSOR_NAME: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_CURSOR_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_EXTERNAL_ROUTINE_EXCEPTION: + case PGSQL_ERROR_CLASS::ERRCLASS_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_EXTERNAL_ROUTINE_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_PROGRAM_LIMIT_EXCEEDED: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_RESOURCE_LIMIT_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_OBJECT_NOT_IN_PREREQUISITE_STATE: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_OBJECT_STATE_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_OPERATOR_INTERVENTION: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_OPERATOR_INTERVENTION_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_FOREIGN_DATA_WRAPPER_ERROR: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_FDW_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_PLPGSQL_ERROR: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_PLPGSQL_ERROR; + + case PGSQL_ERROR_CLASS::ERRCLASS_INTERNAL_ERROR: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_INTERNAL_ERROR_CATEGORY; + + default: + return PGSQL_ERROR_CATEGORY::ERRCATEGORY_UNKNOWN_CATEGORY; + } +} + +PGSQL_ERROR_SEVERITY PgSQL_Error_Helper::identify_error_severity(const char* severity) { + + PGSQL_ERROR_SEVERITY ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_UNKNOWN_SEVERITY; + + if (strcasecmp(severity, "PANIC") == 0) { + ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_PANIC; + } else if (strcasecmp(severity, "FATAL") == 0) { + ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_FATAL; + } else if (strcasecmp(severity, "ERROR") == 0) { + ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_ERROR; + } else if (strcasecmp(severity, "WARNING") == 0) { + ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_WARNING; + } else if (strcasecmp(severity, "NOTICE") == 0) { + ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_NOTICE; + } else if (strcasecmp(severity, "DEBUG") == 0) { + ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_DEBUG; + } else if (strcasecmp(severity, "LOG") == 0) { + ret = PGSQL_ERROR_SEVERITY::ERRSEVERITY_LOG; + } + return ret; +} + + +void PgSQL_ErrorInfo_Ext::reset() { + text = PGSQL_ERROR_SEVERITY::ERRSEVERITY_UNKNOWN_SEVERITY; + detail.clear(); + hint.clear(); + position.clear(); + internal_position.clear(); + internal_query.clear(); + context.clear(); + schema_name.clear(); + table_name.clear(); + column_name.clear(); + datatype_name.clear(); + constraint_name.clear(); + source_file.clear(); + source_line.clear(); + source_function.clear(); +} + +void PgSQL_Error_Helper::fill_error_info(PgSQL_ErrorInfo& err_info, const char* code, const char* msg, const char* severity) { + strncpy(err_info.sqlstate, code, 5); + err_info.sqlstate[5] = '\0'; + err_info.severity = PgSQL_Error_Helper::identify_error_severity(severity); + err_info.code = PgSQL_Error_Helper::identify_error_code(code); + err_info.type = PgSQL_Error_Helper::identify_error_class(code); + err_info.category = PgSQL_Error_Helper::categorize_error_class(err_info.type); + err_info.message = msg; +} + +void reset_error_info(PgSQL_ErrorInfo& err_info, bool release_extented) { + err_info.sqlstate[0] = '\0'; + err_info.code = PGSQL_ERROR_CODES::ERRCODE_SUCCESSFUL_COMPLETION; + err_info.severity = PGSQL_ERROR_SEVERITY::ERRSEVERITY_UNKNOWN_SEVERITY; + err_info.type = PGSQL_ERROR_CLASS::ERRCLASS_UNKNOWN_ERROR; + err_info.category = PGSQL_ERROR_CATEGORY::ERRCATEGORY_UNKNOWN_CATEGORY; + err_info.message.clear(); + if (err_info.ext_info) { + if (release_extented) { + delete err_info.ext_info; + err_info.ext_info = NULL; + } else { + err_info.ext_info->reset(); + } + } +} + +void PgSQL_Error_Helper::fill_extended_error_info(PgSQL_ErrorInfo& err_info, const PGresult* result, uint16_t ext_fields) { + + if (ext_fields == 0) { + if (err_info.ext_info != NULL) { + delete err_info.ext_info; + err_info.ext_info = NULL; + } + return; + } + + char* val = NULL; + + if (err_info.ext_info == NULL) { + err_info.ext_info = new PgSQL_ErrorInfo_Ext(); + } else { + err_info.ext_info->reset(); + } + + if (ext_fields & PGSQL_ERROR_FIELD_TEXT) { + val = PQresultErrorField(result, PG_DIAG_SEVERITY_NONLOCALIZED); + err_info.ext_info->text = identify_error_severity(val ? val : ""); + } + + if (ext_fields & PGSQL_ERROR_FIELD_DETAIL) { + val = PQresultErrorField(result, PG_DIAG_MESSAGE_DETAIL); + err_info.ext_info->detail = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_HINT) { + val = PQresultErrorField(result, PG_DIAG_MESSAGE_HINT); + err_info.ext_info->hint = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_POSITION) { + val = PQresultErrorField(result, PG_DIAG_STATEMENT_POSITION); + err_info.ext_info->position = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_INTERNAL_POSITION) { + val = PQresultErrorField(result, PG_DIAG_INTERNAL_POSITION); + err_info.ext_info->internal_position = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_INTERNAL_QUERY) { + val = PQresultErrorField(result, PG_DIAG_INTERNAL_QUERY); + err_info.ext_info->internal_query = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_CONTEXT) { + val = PQresultErrorField(result, PG_DIAG_CONTEXT); + err_info.ext_info->context = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_SCHEMA_NAME) { + val = PQresultErrorField(result, PG_DIAG_SCHEMA_NAME); + err_info.ext_info->schema_name = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_TABLE_NAME) { + val = PQresultErrorField(result, PG_DIAG_TABLE_NAME); + err_info.ext_info->table_name = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_COLUMN_NAME) { + val = PQresultErrorField(result, PG_DIAG_COLUMN_NAME); + err_info.ext_info->column_name = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_DATA_TYPE_NAME) { + val = PQresultErrorField(result, PG_DIAG_DATATYPE_NAME); + err_info.ext_info->datatype_name = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_CONSTRAINT_NAME) { + val = PQresultErrorField(result, PG_DIAG_CONSTRAINT_NAME); + err_info.ext_info->constraint_name = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_FILE) { + val = PQresultErrorField(result, PG_DIAG_SOURCE_FILE); + err_info.ext_info->source_file = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_LINE) { + val = PQresultErrorField(result, PG_DIAG_SOURCE_LINE); + err_info.ext_info->source_line = val ? val : ""; + } + + if (ext_fields & PGSQL_ERROR_FIELD_ROUTINE) { + val = PQresultErrorField(result, PG_DIAG_SOURCE_FUNCTION); + err_info.ext_info->source_function = val ? val : ""; + } +} + +void PgSQL_Error_Helper::fill_error_info(PgSQL_ErrorInfo& err_info, const PGresult* result, uint16_t ext_fields) { + if (result == nullptr) { + return; + } + const char* sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE); + const char* message = PQresultErrorField(result, PG_DIAG_MESSAGE_PRIMARY); + const char* severity = PQresultErrorField(result, PG_DIAG_SEVERITY); + fill_error_info(err_info, sqlstate ? sqlstate : "00000", message ? message : "", severity ? severity : ""); + fill_extended_error_info(err_info, result, ext_fields); +} diff --git a/lib/PgSQL_Protocol.cpp b/lib/PgSQL_Protocol.cpp index 57353fd63..89f7d4053 100644 --- a/lib/PgSQL_Protocol.cpp +++ b/lib/PgSQL_Protocol.cpp @@ -684,7 +684,7 @@ bool PgSQL_Protocol::process_startup_packet(unsigned char* pkt, unsigned int len if (!user || *user == '\0') { proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p. no username supplied.\n", (*myds), (*myds)->sess); - generate_error_packet(true, false, "no username supplied", NULL, true); + generate_error_packet(true, false, "no username supplied", PGSQL_ERROR_CODES::ERRCODE_PROTOCOL_VIOLATION, true); return false; } @@ -731,7 +731,7 @@ EXECUTION_STATE PgSQL_Protocol::process_handshake_response_packet(unsigned char* if (!user || *user == '\0') { proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s'. Client password pkt before startup packet.\n", (*myds), (*myds)->sess, user); - generate_error_packet(true, false, "client password pkt before startup packet", NULL, true); + generate_error_packet(true, false, "client password pkt before startup packet", PGSQL_ERROR_CODES::ERRCODE_PROTOCOL_VIOLATION, true); goto __exit_process_pkt_handshake_response; } @@ -802,7 +802,7 @@ EXECUTION_STATE PgSQL_Protocol::process_handshake_response_packet(unsigned char* if (!pass || *pass == '\0') { proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s'. Empty password returned by client.\n", (*myds), (*myds)->sess, user); - generate_error_packet(true, false, "empty password returned by client", NULL, true); + generate_error_packet(true, false, "empty password returned by client", PGSQL_ERROR_CODES::ERRCODE_PROTOCOL_VIOLATION, true); break; } @@ -841,7 +841,8 @@ EXECUTION_STATE PgSQL_Protocol::process_handshake_response_packet(unsigned char* proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s'. Selected SASL mechanism: %s.\n", (*myds), (*myds)->sess, user, mech); if (strcmp(mech, "SCRAM-SHA-256") != 0) { proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s'. Client selected an invalid SASL authentication mechanism: %s.\n", (*myds), (*myds)->sess, user, mech); - generate_error_packet(true, false, "client selected an invalid SASL authentication mechanism", NULL, true); + generate_error_packet(true, false, "client selected an invalid SASL authentication mechanism", + PGSQL_ERROR_CODES::ERRCODE_PROTOCOL_VIOLATION, true); break; } @@ -861,7 +862,7 @@ EXECUTION_STATE PgSQL_Protocol::process_handshake_response_packet(unsigned char* if (!scram_handle_client_first((*myds)->scram_state, &stored_user_info, ((const unsigned char*)hdr.data.ptr) + read_pos, length)) { proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s'. SASL authentication failed\n", (*myds), (*myds)->sess, user); - generate_error_packet(true,false, "SASL authentication failed", NULL, true); + generate_error_packet(true, false, "SASL authentication failed", PGSQL_ERROR_CODES::ERRCODE_PROTOCOL_VIOLATION, true); break; } @@ -908,7 +909,7 @@ EXECUTION_STATE PgSQL_Protocol::process_handshake_response_packet(unsigned char* } } else { proxy_debug(PROXY_DEBUG_MYSQL_AUTH, 5, "Session=%p , DS=%p , user='%s'. User not found in the database.\n", (*myds), (*myds)->sess, user); - generate_error_packet(true,false, "User not found", NULL, true); + generate_error_packet(true, false, "User not found", PGSQL_ERROR_CODES::ERRCODE_PROTOCOL_VIOLATION, true); } // set the default session charset //(*myds)->sess->default_charset = charset; @@ -1010,10 +1011,15 @@ void PgSQL_Protocol::welcome_client() { //(*myds)->sess->status = WAITING_CLIENT_DATA; } -void PgSQL_Protocol::generate_error_packet(bool send, bool ready, const char* msg, const char* code, bool fatal, PtrSize_t* _ptr) { +void PgSQL_Protocol::generate_error_packet(bool send, bool ready, const char* msg, PGSQL_ERROR_CODES code, bool fatal, bool track, PtrSize_t* _ptr) { // to avoid memory leak assert(send == true || _ptr); + if (send) { + // in case of fatal error we dont generate ready packets + ready = !fatal; + } + PG_pkt pgpkt{}; if (ready) @@ -1022,7 +1028,7 @@ void PgSQL_Protocol::generate_error_packet(bool send, bool ready, const char* ms pgpkt.write_generic('E', "cscscscsc", 'S', fatal ? "FATAL" : "ERROR", 'V', fatal ? "FATAL" : "ERROR", - 'C', code ? code : "08P01", 'M', msg, 0); + 'C', PgSQL_Error_Helper::get_error_code(code), 'M', msg, 0); if (ready) { pgpkt.write_ReadyForQuery(); @@ -1058,6 +1064,12 @@ void PgSQL_Protocol::generate_error_packet(bool send, bool ready, const char* ms _ptr->ptr = buff.first; _ptr->size = buff.second; } + + if (track) { + if (*myds && (*myds)->sess && (*myds)->sess->thread) { + (*myds)->sess->thread->status_variables.stvar[st_var_generated_pkt_err]++; + } + } } bool PgSQL_Protocol::scram_handle_client_first(ScramState* scram_state, PgCredentials* user, const unsigned char* data, uint32_t datalen) @@ -1310,7 +1322,7 @@ bool PgSQL_Protocol::generate_ok_packet(bool send, bool ready, const char* msg, //} -unsigned int PgSQL_Protocol::copy_row_description_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result) { +unsigned int PgSQL_Protocol::copy_row_description_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result) { assert(pg_query_result); assert(result); @@ -1367,7 +1379,7 @@ unsigned int PgSQL_Protocol::copy_row_description_to_PgSQL_Query_Result(bool sen return size; } -unsigned int PgSQL_Protocol::copy_row_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result) { +unsigned int PgSQL_Protocol::copy_row_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result) { assert(pg_query_result); assert(result); assert(pg_query_result->num_fields); @@ -1428,18 +1440,16 @@ unsigned int PgSQL_Protocol::copy_row_to_PgSQL_Query_Result(bool send, PgSQL_Que return total_size; } -unsigned int PgSQL_Protocol::copy_command_completion_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result) { +unsigned int PgSQL_Protocol::copy_command_completion_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result) { assert(pg_query_result); assert(result); - const char* tag = PQcmdStatus(result); + const char* tag = PQcmdStatus((PGresult*)result); if (!tag) assert(0); // for testing it should not be null - const unsigned int tag_len = strlen(tag) + 1; - unsigned int size = 1 + 4 + tag_len + 1 + 4 + 1; // 'C', length, tag, Z, length, I + const unsigned int size = strlen(tag) + 1 + 1 + 4; // tag length, null byte, 'C', length, tag bool alloced_new_buffer = false; - unsigned char* _ptr = pg_query_result->buffer_reserve_space(size); // buffer is not enough to store the new row. Remember we have already pushed data to PSarrayOUT @@ -1451,11 +1461,8 @@ unsigned int PgSQL_Protocol::copy_command_completion_to_PgSQL_Query_Result(bool PG_pkt pgpkt(_ptr, size); pgpkt.put_char('C'); - pgpkt.put_uint32(tag_len + 4); + pgpkt.put_uint32(size - 1); pgpkt.put_string(tag); - pgpkt.put_char('Z'); - pgpkt.put_uint32(4 + 1); - pgpkt.put_char('I'); if (send == true) { // not supported @@ -1469,11 +1476,11 @@ unsigned int PgSQL_Protocol::copy_command_completion_to_PgSQL_Query_Result(bool //pg_query_result->buffer_to_PSarrayOut(); pg_query_result->PSarrayOUT.add(_ptr, size); } - pg_query_result->pkt_count+=2; + pg_query_result->pkt_count++; return size; } -unsigned int PgSQL_Protocol::copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGresult* result) { +unsigned int PgSQL_Protocol::copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, const PGresult* result) { assert(pg_query_result); assert(result); @@ -1496,7 +1503,7 @@ unsigned int PgSQL_Protocol::copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Q const char* source_line = PQresultErrorField(result, PG_DIAG_SOURCE_LINE); const char* source_function = PQresultErrorField(result, PG_DIAG_SOURCE_FUNCTION); - unsigned int size = 1 + 4 + 1 + 1 + 4 + 1; // 'E', length, null byte , Z, length, I + unsigned int size = 1 + 4 + 1; // 'E', length, null byte if (severity) size += strlen(severity) + 1 + 1; if (text) size += strlen(text) + 1 + 1; @@ -1529,7 +1536,7 @@ unsigned int PgSQL_Protocol::copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Q PG_pkt pgpkt(_ptr, size); pgpkt.put_char('E'); - pgpkt.put_uint32(size - (6 + 1)); // subtracting ready packet size + pgpkt.put_uint32(size - 1); if (severity) { pgpkt.put_char('S'); pgpkt.put_string(severity); @@ -1603,9 +1610,6 @@ unsigned int PgSQL_Protocol::copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Q pgpkt.put_string(source_function); } pgpkt.put_char('\0'); - pgpkt.put_char('Z'); - pgpkt.put_uint32(4 + 1); - pgpkt.put_char('I'); if (send == true) { // not supported @@ -1619,18 +1623,61 @@ unsigned int PgSQL_Protocol::copy_error_to_PgSQL_Query_Result(bool send, PgSQL_Q //pg_query_result->buffer_to_PSarrayOut(); pg_query_result->PSarrayOUT.add(_ptr, size); } - pg_query_result->pkt_count += 2; + pg_query_result->pkt_count++; + return size; +} + +unsigned int PgSQL_Protocol::copy_ready_status_to_PgSQL_Query_Result(bool send, PgSQL_Query_Result* pg_query_result, PGTransactionStatusType txn_status) { + assert(pg_query_result); + + char txn_state = 'I'; + if (txn_status == PQTRANS_INTRANS) + txn_state = 'T'; + else if (txn_status == PQTRANS_INERROR) + txn_state = 'E'; + + const unsigned int size = 1 + 4 + 1; // Z, length, I/T/E + bool alloced_new_buffer = false; + + unsigned char* _ptr = pg_query_result->buffer_reserve_space(size); + + // buffer is not enough to store the new row. Remember we have already pushed data to PSarrayOUT + if (_ptr == NULL) { + _ptr = (unsigned char*)l_alloc(size); + alloced_new_buffer = true; + } + + PG_pkt pgpkt(_ptr, size); + + pgpkt.put_char('Z'); + pgpkt.put_uint32(size - 1); + pgpkt.put_char(txn_state); + + if (send == true) { + // not supported + //(*myds)->PSarrayOUT->add((void*)_ptr, size); + } + + pg_query_result->resultset_size += size; + + if (alloced_new_buffer) { + // we created new buffer + //pg_query_result->buffer_to_PSarrayOut(); + pg_query_result->PSarrayOUT.add(_ptr, size); + } + pg_query_result->pkt_count++; return size; } PgSQL_Query_Result::PgSQL_Query_Result() { buffer = NULL; transfer_started = false; - resultset_completed = false; buffer_used = 0; resultset_size = 0; + num_fields = 0; num_rows = 0; pkt_count = 0; + result_packet_type = PGSQL_QUERY_RESULT_EMPTY; } PgSQL_Query_Result::~PgSQL_Query_Result() { @@ -1653,16 +1700,12 @@ void PgSQL_Query_Result::buffer_init() { buffer_used = 0; } -void PgSQL_Query_Result::init(PgSQL_Protocol* _proto, PGconn* _conn) { +void PgSQL_Query_Result::init(PgSQL_Protocol* _proto, PgSQL_Data_Stream* _myds, PgSQL_Connection* _conn) { PROXY_TRACE2(); transfer_started = false; - resultset_completed = false; - //resultset_size = 0; - //sid = 0; - num_rows = 0; proto = _proto; - //result = NULL; - pgsql_conn = _conn; + conn = _conn; + myds = _myds; buffer_init(); if (proto == NULL) { @@ -1670,57 +1713,71 @@ void PgSQL_Query_Result::init(PgSQL_Protocol* _proto, PGconn* _conn) { } } -unsigned int PgSQL_Query_Result::add_row_description(PGresult* result) { - return proto->copy_row_description_to_PgSQL_Query_Result(false, this, result); +unsigned int PgSQL_Query_Result::add_row_description(const PGresult* result) { + const unsigned int res = proto->copy_row_description_to_PgSQL_Query_Result(false, this, result); + result_packet_type |= PGSQL_QUERY_RESULT_TUPLE; + return res; } -unsigned int PgSQL_Query_Result::add_row(PGresult* result) { +unsigned int PgSQL_Query_Result::add_row(const PGresult* result) { + //result_type |= PGSQL_QUERY_RESULT_TUPLE; return proto->copy_row_to_PgSQL_Query_Result(false,this, result); } -unsigned int PgSQL_Query_Result::add_error(PgSQL_Data_Stream* _myds, PGresult* result) { +unsigned int PgSQL_Query_Result::add_error(const PGresult* result) { unsigned int size = 0; - if (_myds && _myds->killed_at) { // see case #750 - PtrSize_t pkt; - if (_myds->kill_type == 0) { - proto->generate_error_packet(false, true, (char*)"Query execution was interrupted, query_timeout exceeded", "57014", false, &pkt); - PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, 1907); - } - else { - proto->generate_error_packet(false, true, (char*)"Query execution was interrupted", "57014", false, &pkt); - PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, 1317); + if (result) { + size = proto->copy_error_to_PgSQL_Query_Result(false, this, result); + PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, conn->parent->myhgc->hid, conn->parent->address, conn->parent->port, 1907); + } + else { + PtrSize_t pkt; + if (myds && myds->killed_at) { // see case #750 + if (myds->kill_type == 0) { + proto->generate_error_packet(false, false, (char*)"Query execution was interrupted, query_timeout exceeded", + PGSQL_ERROR_CODES::ERRCODE_QUERY_CANCELED, false, false, &pkt); + PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, conn->parent->myhgc->hid, conn->parent->address, conn->parent->port, 1907); + } else { + proto->generate_error_packet(false, false, (char*)"Query execution was interrupted", + PGSQL_ERROR_CODES::ERRCODE_QUERY_CANCELED, false, false, &pkt); + PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, conn->parent->myhgc->hid, conn->parent->address, conn->parent->port, 1317); + } + } else if (conn->is_error_present()) { + proto->generate_error_packet(false, false, conn->get_error_message().c_str(), conn->get_error_code(), false, false, &pkt); + PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, conn->parent->myhgc->hid, conn->parent->address, conn->parent->port, 1907); + } else { + assert(0); // should never reach here } PSarrayOUT.add(pkt.ptr, pkt.size); resultset_size += pkt.size; size = pkt.size; - } else if (result) { - - size = proto->copy_error_to_PgSQL_Query_Result(false, this, result); - // get error from connection - //proto->generate_error_packet(false, true, "Unknown error", sqlstate, false, &pkt); - // TODO: Check this is a mysql error - PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _myds->myconn->parent->myhgc->hid, _myds->myconn->parent->address, _myds->myconn->parent->port, 1907); - } else { - } - buffer_to_PSarrayOut(); - resultset_completed = true; + result_packet_type |= PGSQL_QUERY_RESULT_ERROR; return size; } +unsigned int PgSQL_Query_Result::add_ready_status(PGTransactionStatusType txn_status) { + const unsigned int bytes = proto->copy_ready_status_to_PgSQL_Query_Result(false, this, txn_status); + buffer_to_PSarrayOut(); + result_packet_type |= PGSQL_QUERY_RESULT_READY; + return bytes; +} + bool PgSQL_Query_Result::get_resultset(PtrSizeArray* PSarrayFinal) { assert(buffer_used == 0); // we still have data in the buffer transfer_started = true; + // Ready packet confirms that the result is complete + bool result_complete = (result_packet_type & PGSQL_QUERY_RESULT_READY); if (proto) { PSarrayFinal->copy_add(&PSarrayOUT, 0, PSarrayOUT.len); while (PSarrayOUT.len) PSarrayOUT.remove_index(PSarrayOUT.len - 1, NULL); } reset(); - return resultset_completed; + return result_complete; } void PgSQL_Query_Result::buffer_to_PSarrayOut() { @@ -1755,10 +1812,9 @@ unsigned long long PgSQL_Query_Result::current_size() { return intsize; } -unsigned int PgSQL_Query_Result::add_command_completion(PGresult* result) { +unsigned int PgSQL_Query_Result::add_command_completion(const PGresult* result) { const unsigned int bytes = proto->copy_command_completion_to_PgSQL_Query_Result(false, this, result); - buffer_to_PSarrayOut(); - resultset_completed = true; + result_packet_type |= PGSQL_QUERY_RESULT_COMMAND; return bytes; } @@ -1787,4 +1843,5 @@ void PgSQL_Query_Result::reset() { num_fields = 0; num_rows = 0; pkt_count = 0; + result_packet_type = PGSQL_QUERY_RESULT_EMPTY; } diff --git a/lib/PgSQL_Session.cpp b/lib/PgSQL_Session.cpp index f1a54b0d4..1faf14f05 100644 --- a/lib/PgSQL_Session.cpp +++ b/lib/PgSQL_Session.cpp @@ -1327,7 +1327,7 @@ void PgSQL_Session::return_proxysql_internal(PtrSize_t* pkt) { } // default client_myds->DSS = STATE_QUERY_SENT_NET; - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 1064, (char*)"42000", (char*)"Unknown PROXYSQL INTERNAL command", true); + client_myds->myprot.generate_error_packet(true, true, "Unknown PROXYSQL INTERNAL command", PGSQL_ERROR_CODES::ERRCODE_SYNTAX_ERROR, false, true); if (mirror == false) { RequestEnd(NULL); } @@ -1686,7 +1686,8 @@ bool PgSQL_Session::handler_special_queries(PtrSize_t* pkt) { if ((pkt->size >= 22 + 5) && (strncasecmp((char*)"LOAD DATA LOCAL INFILE", (char*)pkt->ptr + 5, 22) == 0)) { if (mysql_thread___enable_load_data_local_infile == false) { client_myds->DSS = STATE_QUERY_SENT_NET; - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 1047, (char*)"HY000", (char*)"Unsupported 'LOAD DATA LOCAL INFILE' command", true); + client_myds->myprot.generate_error_packet(true, true, "Unsupported 'LOAD DATA LOCAL INFILE' command", + PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED, false, true); if (mirror == false) { RequestEnd(NULL); } @@ -3061,7 +3062,8 @@ bool PgSQL_Session::handler_again___status_CONNECTING_SERVER(int* _rc) { if (thread) { thread->status_variables.stvar[st_var_max_connect_timeout_err]++; } - client_myds->myprot.generate_error_packet(true, true, errmsg.c_str(), "57P03", false); // not sure if this is the right error code + client_myds->myprot.generate_error_packet(true, true, errmsg.c_str(), PGSQL_ERROR_CODES::ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, + false, true); RequestEnd(mybe->server_myds); string hg_status{}; @@ -3196,16 +3198,16 @@ bool PgSQL_Session::handler_again___status_CONNECTING_SERVER(int* _rc) { } else { __exit_handler_again___status_CONNECTING_SERVER_with_err: - int myerr = mysql_errno(myconn->pgsql); - if (myerr) { - char sqlstate[10]; - sprintf(sqlstate, "%s", mysql_sqlstate(myconn->pgsql)); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, mysql_errno(myconn->pgsql), sqlstate, mysql_error(myconn->pgsql), true); + bool is_error_present = myconn->is_error_present(); + if (is_error_present) { + client_myds->myprot.generate_error_packet(true, true, myconn->error_info.message.c_str(), + myconn->error_info.code, false, true); } else { char buf[256]; sprintf(buf, "Max connect failure while reaching hostgroup %d", current_hostgroup); - client_myds->myprot.generate_error_packet(true,true,buf, "57P03", false); // not sure if this is the right error code + client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION, + false, true); if (thread) { thread->status_variables.stvar[st_var_max_connect_timeout_err]++; } @@ -3221,7 +3223,7 @@ bool PgSQL_Session::handler_again___status_CONNECTING_SERVER(int* _rc) { if (mirror) { PROXY_TRACE(); } - myds->destroy_MySQL_Connection_From_Pool(myerr ? true : false); + myds->destroy_MySQL_Connection_From_Pool(is_error_present); myds->max_connect_time = 0; NEXT_IMMEDIATE_NEW(WAITING_CLIENT_DATA); } @@ -3481,7 +3483,8 @@ void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C char* err_msg = (char*)"Session trying to reach HG %d while locked on HG %d . Rejecting query: %s"; char* buf = (char*)malloc(strlen(err_msg) + strlen(nqn.c_str()) + strlen(end) + 64); sprintf(buf, err_msg, current_hostgroup, locked_on_hostgroup, nqn.c_str(), end); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 9005, (char*)"HY000", buf, true); + client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION, + false, true); thread->status_variables.stvar[st_var_hostgroup_locked_queries]++; RequestEnd(NULL); free(buf); @@ -3652,7 +3655,8 @@ void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C char* err_msg = (char*)"Session trying to reach HG %d while locked on HG %d . Rejecting query: %s"; char* buf = (char*)malloc(strlen(err_msg) + strlen(nqn.c_str()) + strlen(end) + 64); sprintf(buf, err_msg, current_hostgroup, locked_on_hostgroup, nqn.c_str(), end); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 9005, (char*)"HY000", buf, true); + client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION, + false, true); thread->status_variables.stvar[st_var_hostgroup_locked_queries]++; RequestEnd(NULL); free(buf); @@ -4188,7 +4192,8 @@ __get_pkts_from_client: char* err_msg = (char*)"Session trying to reach HG %d while locked on HG %d . Rejecting query: %s"; char* buf = (char*)malloc(strlen(err_msg) + strlen(nqn.c_str()) + strlen(end) + 64); sprintf(buf, err_msg, current_hostgroup, locked_on_hostgroup, nqn.c_str(), end); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 9005, (char*)"HY000", buf, true); + client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION, + false, true); thread->status_variables.stvar[st_var_hostgroup_locked_queries]++; RequestEnd(NULL); free(buf); @@ -4405,7 +4410,8 @@ __get_pkts_from_client: char* err_msg = (char*)"Session trying to reach HG %d while locked on HG %d . Rejecting query: %s"; char* buf = (char*)malloc(strlen(err_msg) + strlen(nqn.c_str()) + strlen(end) + 64); sprintf(buf, err_msg, current_hostgroup, locked_on_hostgroup, nqn.c_str(), end); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 9005, (char*)"HY000", buf, true); + client_myds->myprot.generate_error_packet(true, true, buf, PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION, + false, true); thread->status_variables.stvar[st_var_hostgroup_locked_queries]++; RequestEnd(NULL); free(buf); @@ -4909,10 +4915,10 @@ bool PgSQL_Session::handler_minus1_ClientLibraryError(PgSQL_Data_Stream* myds, i // this function was inline void PgSQL_Session::handler_minus1_LogErrorDuringQuery(PgSQL_Connection* myconn, int myerr, char* errmsg) { if (pgsql_thread___verbose_query_error) { - proxy_warning("Error during query on (%d,%s,%d,%lu) , user \"%s@%s\" , schema \"%s\" , %d, %s . digest_text = \"%s\"\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_mysql_thread_id(), client_myds->myconn->userinfo->username, (client_myds->addr.addr ? client_myds->addr.addr : (char*)"unknown"), client_myds->myconn->userinfo->schemaname, myerr, (errmsg ? errmsg : mysql_error(myconn->pgsql)), CurrentQuery.QueryParserArgs.digest_text); + proxy_warning("Error during query on (%d,%s,%d,%lu) , user \"%s@%s\" , schema \"%s\" , %s . digest_text = \"%s\"\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_mysql_thread_id(), client_myds->myconn->userinfo->username, (client_myds->addr.addr ? client_myds->addr.addr : (char*)"unknown"), client_myds->myconn->userinfo->schemaname, myconn->get_error_code_with_message().c_str(), CurrentQuery.QueryParserArgs.digest_text); } else { - proxy_warning("Error during query on (%d,%s,%d,%lu): %d, %s\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_mysql_thread_id(), myerr, (errmsg ? errmsg : mysql_error(myconn->pgsql))); + proxy_warning("Error during query on (%d,%s,%d,%lu): %s\n", myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, myconn->get_mysql_thread_id(), myconn->get_error_code_with_message().c_str()); } PgHGM->add_pgsql_errors(myconn->parent->myhgc->hid, myconn->parent->address, myconn->parent->port, client_myds->myconn->userinfo->username, (client_myds->addr.addr ? client_myds->addr.addr : (char*)"unknown"), client_myds->myconn->userinfo->schemaname, myerr, (char*)(errmsg ? errmsg : mysql_error(myconn->pgsql))); } @@ -5005,10 +5011,10 @@ void PgSQL_Session::handler_minus1_GenerateErrorMessage(PgSQL_Data_Stream* myds, switch (status) { case PROCESSING_QUERY: if (myconn) { - PgSQL_Result_to_PgSQL_wire(myconn->pgsql_conn, myconn->query_result, myconn->warning_count, myds); + PgSQL_Result_to_PgSQL_wire(myconn, myds); } else { - PgSQL_Result_to_PgSQL_wire(NULL, NULL, 0, myds); + PgSQL_Result_to_PgSQL_wire(NULL, myds); } break; case PROCESSING_STMT_PREPARE: @@ -5497,7 +5503,7 @@ handler_again: switch (status) { case PROCESSING_QUERY: - PgSQL_Result_to_PgSQL_wire(myconn->pgsql_conn, myconn->query_result, myconn->warning_count, myconn->myds); + PgSQL_Result_to_PgSQL_wire(myconn, myconn->myds); break; case PROCESSING_STMT_PREPARE: { @@ -5591,8 +5597,8 @@ handler_again: break; // rc==2 : a multi-resultset (or multi statement) was detected, and the current statement is completed case 2: - PgSQL_Result_to_PgSQL_wire(myconn->pgsql_conn, myconn->query_result, myconn->warning_count, myconn->myds); - if (myconn->query_result) { // we also need to clear query_result, so that the next staement will recreate it if needed + PgSQL_Result_to_PgSQL_wire(myconn, myconn->myds); + if (myconn->query_result) { // we also need to clear query_result, so that the next statement will recreate it if needed if (myconn->query_result_reuse) { delete myconn->query_result_reuse; } @@ -5998,7 +6004,8 @@ void PgSQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( if (client_myds->switching_auth_stage) _pid += 2; if (max_connections_reached == true) { proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 5, "Session=%p , DS=%p , Too many connections\n", this, client_myds); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, _pid, 1040, (char*)"08004", (char*)"Too many connections", true); + client_myds->myprot.generate_error_packet(true, false, "Too many connections", PGSQL_ERROR_CODES::ERRCODE_TOO_MANY_CONNECTIONS, + true, true); proxy_warning("pgsql-max_connections reached. Returning 'Too many connections'\n"); GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL, (char*)"pgsql-max_connections reached"); __sync_fetch_and_add(&PgHGM->status.access_denied_max_connections, 1); @@ -6010,7 +6017,8 @@ void PgSQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( char* b = (char*)malloc(strlen(a) + strlen(client_myds->myconn->userinfo->username) + 16); sprintf(b, a, client_myds->myconn->userinfo->username, used_users); GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL, b); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 2, 1226, (char*)"42000", b, true); + client_myds->myprot.generate_error_packet(true, false, b, PGSQL_ERROR_CODES::ERRCODE_TOO_MANY_CONNECTIONS, + true, true); proxy_warning("User '%s' has exceeded the 'max_user_connections' resource (current value: %d)\n", client_myds->myconn->userinfo->username, used_users); free(b); } @@ -6080,7 +6088,8 @@ void PgSQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( char* b = (char*)malloc(strlen(a) + strlen(client_myds->myconn->userinfo->username)); sprintf(b, a, client_myds->myconn->userinfo->username); GloPgSQL_Logger->log_audit_entry(PROXYSQL_MYSQL_AUTH_ERR, this, NULL, b); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, _pid, 1040, (char*)"42000", b, true); + client_myds->myprot.generate_error_packet(true, false, b, PGSQL_ERROR_CODES::ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION, + true, true); free(b); } free(addr); @@ -6106,7 +6115,8 @@ void PgSQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( char* _a = (char*)"ProxySQL Error: Access denied for user '%s' (using password: %s). SSL is required"; char* _s = (char*)malloc(strlen(_a) + strlen(client_myds->myconn->userinfo->username) + 32); sprintf(_s, _a, client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO")); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, _pid, 1045, (char*)"28000", _s, true); + client_myds->myprot.generate_error_packet(true, false, _s, PGSQL_ERROR_CODES::ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, + true, true); proxy_error("ProxySQL Error: Access denied for user '%s' (using password: %s). SSL is required\n", client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO")); proxy_debug(PROXY_DEBUG_MYSQL_CONNECTION, 8, "Session=%p , DS=%p . Access denied for user '%s' (using password: %s). SSL is required\n", this, client_myds, client_myds->myconn->userinfo->username, (client_myds->myconn->userinfo->password ? "YES" : "NO")); __sync_add_and_fetch(&PgHGM->status.client_connections_aborted, 1); @@ -6181,7 +6191,7 @@ void PgSQL_Session::handler___status_CONNECTING_CLIENT___STATE_SERVER_HANDSHAKE( } #endif // DEBUG sprintf(_s, "ProxySQL Error: Access denied for user '%s'@'%s' (using password: %s)", client_myds->myconn->userinfo->username, client_addr, (client_myds->myconn->userinfo->password ? "YES" : "NO")); - client_myds->myprot.generate_error_packet(true,false, _s, "28P01", true); + client_myds->myprot.generate_error_packet(true, false, _s, PGSQL_ERROR_CODES::ERRCODE_INVALID_PASSWORD, true, true); proxy_error("%s\n", _s); free(_s); __sync_fetch_and_add(&PgHGM->status.access_denied_wrong_password, 1); @@ -6246,13 +6256,15 @@ void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C /* FIXME: temporary */ l_free(pkt->size, pkt->ptr); client_myds->setDSS_STATE_QUERY_SENT_NET(); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 1045, (char*)"28000", (char*)"Command not supported", true); + client_myds->myprot.generate_error_packet(true, true, "Command not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED, + false, true); client_myds->DSS = STATE_SLEEP; } else { l_free(pkt->size, pkt->ptr); client_myds->setDSS_STATE_QUERY_SENT_NET(); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 1045, (char*)"28000", (char*)"Command not supported", true); + client_myds->myprot.generate_error_packet(true, true, "Command not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED, + false, true); client_myds->DSS = STATE_SLEEP; } } @@ -6260,7 +6272,7 @@ void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C void PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_COM_PROCESS_KILL(PtrSize_t* pkt) { l_free(pkt->size, pkt->ptr); client_myds->setDSS_STATE_QUERY_SENT_NET(); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 9003, (char*)"28000", (char*)"Command not supported"); + client_myds->myprot.generate_error_packet(true, true, "Command not supported", PGSQL_ERROR_CODES::ERRCODE_FEATURE_NOT_SUPPORTED, false); client_myds->DSS = STATE_SLEEP; } @@ -7040,7 +7052,8 @@ bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C sprintf(errmsg, m, value1.c_str()); } client_myds->DSS = STATE_QUERY_SENT_NET; - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 1115, (char*)"42000", errmsg, true); + client_myds->myprot.generate_error_packet(true, true, errmsg, + PGSQL_ERROR_CODES::ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, false, true); client_myds->DSS = STATE_SLEEP; status = WAITING_CLIENT_DATA; free(errmsg); @@ -7299,7 +7312,8 @@ bool PgSQL_Session::handler___status_WAITING_CLIENT_DATA___STATE_SLEEP___MYSQL_C errmsg = (char*)malloc(charset.length() + strlen(m)); sprintf(errmsg, m, charset.c_str()); client_myds->DSS = STATE_QUERY_SENT_NET; - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, 1, 1115, (char*)"42000", errmsg, true); + client_myds->myprot.generate_error_packet(true, true, errmsg, + PGSQL_ERROR_CODES::ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION, false, true); client_myds->DSS = STATE_SLEEP; status = WAITING_CLIENT_DATA; free(errmsg); @@ -7519,7 +7533,8 @@ __exit_set_destination_hostgroup: client_myds->DSS = STATE_QUERY_SENT_NET; char buf[140]; sprintf(buf, "ProxySQL Error: connection is locked to hostgroup %d but trying to reach hostgroup %d", locked_on_hostgroup, current_hostgroup); - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 9006, (char*)"Y0000", buf); + client_myds->myprot.generate_error_packet(true, true, buf, + PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION, false); thread->status_variables.stvar[st_var_hostgroup_locked_queries]++; RequestEnd(NULL); l_free(pkt->size, pkt->ptr); @@ -7897,58 +7912,81 @@ void PgSQL_Session::MySQL_Stmt_Result_to_MySQL_wire(MYSQL_STMT* stmt, PgSQL_Conn } } -void PgSQL_Session::PgSQL_Result_to_PgSQL_wire(PGconn* pgsql, PgSQL_Query_Result* query_result, unsigned int warning_count, PgSQL_Data_Stream* _myds) { - if (pgsql == NULL) { +void PgSQL_Session::PgSQL_Result_to_PgSQL_wire(PgSQL_Connection* _conn, PgSQL_Data_Stream* _myds) { + if (_conn == NULL) { // error - client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, 2013, (char*)"HY000", (char*)"Lost connection to MySQL server during query"); + client_myds->myprot.generate_error_packet(true, true, "Lost connection to PostgreSQL server during query", + PGSQL_ERROR_CODES::ERRCODE_CONNECTION_FAILURE, false); return; } - assert(query_result); + PgSQL_Query_Result* query_result = _conn->query_result; - bool transfer_started = query_result->is_transfer_started(); - bool resultset_completed = query_result->get_resultset(client_myds->PSarrayOUT); - CurrentQuery.rows_sent = query_result->get_num_rows(); - bool com_field_list = client_myds->com_field_list; - assert(resultset_completed); // the resultset should always be completed if PgSQL_Result_to_PgSQL_wire is called - if (transfer_started == false) { // we have all the resultset when PgSQL_Result_to_PgSQL_wire was called - /*if (qpo && qpo->cache_ttl > 0 && com_field_list == false) { // the resultset should be cached - if (mysql_errno(pgsql) == 0 && - (mysql_warning_count(pgsql) == 0 || - mysql_thread___query_cache_handle_warnings == 1)) { // no errors - if ( - (qpo->cache_empty_result == 1) - || ( - (qpo->cache_empty_result == -1) - && - (thread->variables.query_cache_stores_empty_result || query_result->num_rows) - ) - ) { - client_myds->resultset->copy_add(client_myds->PSarrayOUT, 0, client_myds->PSarrayOUT->len); - client_myds->resultset_length = query_result->resultset_size; - unsigned char* aa = client_myds->resultset2buffer(false); - while (client_myds->resultset->len) client_myds->resultset->remove_index(client_myds->resultset->len - 1, NULL); - bool deprecate_eof_active = client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF; - GloQC->set( - client_myds->myconn->userinfo->hash, - (const unsigned char*)CurrentQuery.QueryPointer, - CurrentQuery.QueryLength, - aa, - client_myds->resultset_length, - thread->curtime / 1000, - thread->curtime / 1000, - thread->curtime / 1000 + qpo->cache_ttl, - deprecate_eof_active - ); - l_free(client_myds->resultset_length, aa); - client_myds->resultset_length = 0; - } + if (query_result && query_result->get_result_packet_type() != PGSQL_QUERY_RESULT_EMPTY) { + bool transfer_started = query_result->is_transfer_started(); + // if there is an error, it will be false so results are not cached + bool is_tuple = query_result->get_result_packet_type() == (PGSQL_QUERY_RESULT_TUPLE | PGSQL_QUERY_RESULT_COMMAND | PGSQL_QUERY_RESULT_READY); + bool resultset_completed = query_result->get_resultset(client_myds->PSarrayOUT); + CurrentQuery.rows_sent = query_result->get_num_rows(); + assert(resultset_completed); // the resultset should always be completed if PgSQL_Result_to_PgSQL_wire is called + if (transfer_started == false) { // we have all the resultset when PgSQL_Result_to_PgSQL_wire was called + if (qpo && qpo->cache_ttl > 0 && is_tuple == true) { // the resultset should be cached + /*if (mysql_errno(pgsql) == 0 && + (mysql_warning_count(pgsql) == 0 || + mysql_thread___query_cache_handle_warnings == 1)) { // no errors + if ( + (qpo->cache_empty_result == 1) + || ( + (qpo->cache_empty_result == -1) + && + (thread->variables.query_cache_stores_empty_result || query_result->num_rows) + ) + ) { + client_myds->resultset->copy_add(client_myds->PSarrayOUT, 0, client_myds->PSarrayOUT->len); + client_myds->resultset_length = query_result->resultset_size; + unsigned char* aa = client_myds->resultset2buffer(false); + while (client_myds->resultset->len) client_myds->resultset->remove_index(client_myds->resultset->len - 1, NULL); + bool deprecate_eof_active = client_myds->myconn->options.client_flag & CLIENT_DEPRECATE_EOF; + GloQC->set( + client_myds->myconn->userinfo->hash, + (const unsigned char*)CurrentQuery.QueryPointer, + CurrentQuery.QueryLength, + aa, + client_myds->resultset_length, + thread->curtime / 1000, + thread->curtime / 1000, + thread->curtime / 1000 + qpo->cache_ttl, + deprecate_eof_active + ); + l_free(client_myds->resultset_length, aa); + client_myds->resultset_length = 0; + } + }*/ } - }*/ - } - - /*else { // no result set - int myerrno = mysql_errno(pgsql); + } + } else { // if query result is empty, means there was an error before query result was generated + + if (!_conn->is_error_present()) + assert(0); // if query result is empty, there should be an error present in connection. + + if (_myds && _myds->killed_at) { + if (_myds->kill_type == 0) { + client_myds->myprot.generate_error_packet(true, true, (char*)"Query execution was interrupted, query_timeout exceeded", + PGSQL_ERROR_CODES::ERRCODE_QUERY_CANCELED, false); + //PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _conn->parent->myhgc->hid, _conn->parent->address, _conn->parent->port, 1907); + } + else { + client_myds->myprot.generate_error_packet(true, true, (char*)"Query execution was interrupted", + PGSQL_ERROR_CODES::ERRCODE_QUERY_CANCELED, false); + //PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _conn->parent->myhgc->hid, _conn->parent->address, _conn->parent->port, 1317); + } + } + else { + client_myds->myprot.generate_error_packet(true, true, _conn->get_error_message().c_str(), _conn->get_error_code(), false); + //PgHGM->p_update_pgsql_error_counter(p_pgsql_error_type::proxysql, _conn->parent->myhgc->hid, _conn->parent->address, _conn->parent->port, 1907); + } + + /*int myerrno = mysql_errno(pgsql); if (myerrno == 0) { unsigned int num_rows = mysql_affected_rows(pgsql); uint16_t setStatus = (active_transactions ? SERVER_STATUS_IN_TRANS : 0); @@ -7976,8 +8014,9 @@ void PgSQL_Session::PgSQL_Result_to_PgSQL_wire(PGconn* pgsql, PgSQL_Query_Result client_myds->myprot.generate_pkt_ERR(true, NULL, NULL, client_myds->pkt_sid + 1, mysql_errno(pgsql), sqlstate, mysql_error(pgsql)); } //client_myds->pkt_sid++; - }*/ - //} + } + */ + } } void PgSQL_Session::SQLite3_to_MySQL(SQLite3_result* result, char* error, int affected_rows, MySQL_Protocol* myprot, bool in_transaction, bool deprecate_eof_active) { @@ -8050,10 +8089,12 @@ void PgSQL_Session::SQLite3_to_MySQL(SQLite3_result* result, char* error, int af if (error) { // there was an error if (strcmp(error, (char*)"database is locked") == 0) { - myprot->generate_pkt_ERR(true, NULL, NULL, sid, 1205, (char*)"HY000", error); + client_myds->myprot.generate_error_packet(true, true, error, + PGSQL_ERROR_CODES::ERRCODE_T_R_DEADLOCK_DETECTED, false); } else { - myprot->generate_pkt_ERR(true, NULL, NULL, sid, 1045, (char*)"28000", error); + client_myds->myprot.generate_error_packet(true, true, error, + PGSQL_ERROR_CODES::ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION, false); } } else { diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index a44539863..a7abfa9cf 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -10830,7 +10830,7 @@ void ProxySQL_Admin::send_error_msg_to_client(Client_Session& sess, const cha PgSQL_Data_Stream* myds = sess->client_myds; char* new_msg = (char*)malloc(strlen(msg) + sizeof(prefix_msg)); sprintf(new_msg, "%s%s", prefix_msg, msg); - myds->myprot.generate_error_packet(true,true, new_msg, NULL, false); + myds->myprot.generate_error_packet(true, true, new_msg, PGSQL_ERROR_CODES::ERRCODE_RAISE_EXCEPTION, false); free(new_msg); myds->DSS = STATE_SLEEP; }