diff --git a/test/tap/tests/pgsql-extended_query_protocol_test-t.cpp b/test/tap/tests/pgsql-extended_query_protocol_test-t.cpp index 8586b5cd5..e5eeaa1f0 100644 --- a/test/tap/tests/pgsql-extended_query_protocol_test-t.cpp +++ b/test/tap/tests/pgsql-extended_query_protocol_test-t.cpp @@ -538,7 +538,7 @@ void test_empty_stmt() { } } -void test_prepare_statment_mix() { +void test_prepare_statement_mix() { diag("Test %d: Prepare statement + Query", test_count++); auto conn = create_connection(); if (!conn) return; @@ -3312,6 +3312,302 @@ void test_set_statement_untracked() { ok(false, "Extended Query SET Statement UnTracked test failed: %s", e.what()); } } + + +void test_deallocate_having_stmt_name_via_simple_query() { + diag("Test %d: Simple Query - DEALLOCATE named statement", test_count++); + auto conn = create_connection(); if (!conn) return; + + try { + // Prepare a valid statement + conn->prepareStatement("deallocate_existing_stmt", "SELECT 1", true); + + // Close the statement + conn->execute("DEALLOCATE PREPARE deallocate_existing_stmt"); + + // Verify response + char type; + std::vector buffer; + conn->readMessage(type, buffer); + ok(type == PgConnection::COMMAND_COMPLETE, "CommandComplete was received after DEALLOCATE"); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "ReadyForQuery was received after DEALLOCATE"); + + // Verify statement is actually closed + conn->describeStatement("deallocate_existing_stmt", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed as expected after DEALLOCATE"); + std::string errormsg; + std::string errorcode; + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + } + catch (const PgException& e) { + ok(false, "Simple Query - DEALLOCATE named statement failed with error:%s", e.what()); + } +} + +void test_deallocate_having_stmt_name_via_prepared() { + diag("Test %d: Extended Query - DEALLOCATE named statement via prepared execution", test_count++); + auto conn = create_connection(); if (!conn) return; + if (!conn) return; + + try { + // Prepare a valid statement + conn->prepareStatement("deallocate_existing_stmt2", "DEALLOCATE deallocate_existing_stmt2", false); + conn->sendSync(); + + // Verify response + char type; + std::vector buffer; + conn->readMessage(type, buffer); + ok(type == PgConnection::PARSE_COMPLETE, "ParseComplete was received for DEALLOCATE"); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + // should be present in prepared statements + conn->describeStatement("deallocate_existing_stmt2", true); + conn->readMessage(type, buffer); + ok(type == PgConnection::PARAMETER_DESCRIPTION, "ParameterDescription was received for prepared DEALLOCATE"); + conn->readMessage(type, buffer); + ok(type == PgConnection::NO_DATA, "Received NoData"); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + conn->bindStatement("deallocate_existing_stmt2", "", {}, {}, false); + conn->executeStatement(0, false); + conn->sendSync(); + + // Verify response + conn->readMessage(type, buffer); + ok(type == PgConnection::BIND_COMPLETE, "BindComplete was received for DEALLOCATE"); + conn->readMessage(type, buffer); + ok(type == PgConnection::COMMAND_COMPLETE, "CommandComplete was received after DEALLOCATE execution"); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query after deallocate existing statement"); + + // Verify statement is actually closed + conn->describeStatement("deallocate_existing_stmt2", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed as expected after DEALLOCATE"); + std::string errormsg; + std::string errorcode; + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query after deallocate existing statement"); + } + catch (const PgException& e) { + ok(false, "Extended Query - DEALLOCATE named statement via prepared execution failed with error:%s", e.what()); + } +} + +void test_deallocate_all_via_simple_query() { + diag("Test %d: Simple Query - DEALLOCATE ALL", test_count++); + auto conn = create_connection(); if (!conn) return; + if (!conn) return; + + try { + // Prepare a valid statement + conn->prepareStatement("deallocate_all_1", "SELECT 1", true); + conn->prepareStatement("deallocate_all_2", "SELECT 2", true); + conn->prepareStatement("deallocate_all_3", "SELECT 3", true); + + // Close the statement + conn->execute("DEALLOCATE PREPARE ALL"); + + // Verify response + char type; + std::vector buffer; + conn->readMessage(type, buffer); + ok(type == PgConnection::COMMAND_COMPLETE, "CommandComplete was received after DEALLOCATE ALL"); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + // Verify statement is actually closed + conn->describeStatement("deallocate_all_1", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed for deallocate_all_1 as expected"); + std::string errormsg; + std::string errorcode; + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + // Verify statement is actually closed + conn->describeStatement("deallocate_all_2", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed for deallocate_all_2 as expected"); + + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + // Verify statement is actually closed + conn->describeStatement("deallocate_all_3", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed for deallocate_all_3 as expected"); + + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + } + catch (const PgException& e) { + ok(false, "Extended Query DEALLOCATE having Statement Name failed with error:%s", e.what()); + } +} + +void test_deallocate_all_via_prepared() { + diag("Test %d: Extended Query - DEALLOCATE ALL via prepared execution", test_count++); + auto conn = create_connection(); if (!conn) return; + if (!conn) return; + try { + conn->execute("SET dummy TO 'test'"); // intentionally lock on hostgroup. DEALLOCATE should work regardless + + // Verify response + char type; + std::vector buffer; + conn->readMessage(type, buffer); + ok(type == PgConnection::ERROR_RESPONSE, "Session Locked On Hostgroup as expected"); + std::string errormsg; + std::string errorcode; + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "42704", "Received ERRCODE_UNDEFINED_OBJECT Error:%s", errormsg.c_str()); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query after SET dummy"); + + // Prepare a valid statement + conn->prepareStatement("deallocate_all_1", "SELECT 1", true); + conn->prepareStatement("deallocate_all_2", "SELECT 2", true); + conn->prepareStatement("deallocate_all_3", "SELECT 3", true); + + // Bind and execute DEALLOCATE ALL + conn->prepareStatement("", "DEALLOCATE ALL", true); + conn->bindStatement("", "", {}, {}, false); + conn->executeStatement(0, false); + conn->sendSync(); + + conn->readMessage(type, buffer); + ok(type == PgConnection::BIND_COMPLETE, "BindComplete was received for DEALLOCATE ALL"); + conn->readMessage(type, buffer); + ok(type == PgConnection::COMMAND_COMPLETE, "CommandComplete was received for DEALLOCATE ALL"); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + // Verify statement is actually closed + conn->describeStatement("deallocate_all_1", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed for deallocate_all_1 as expected"); + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + + // Verify statement is actually closed + conn->describeStatement("deallocate_all_2", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed for deallocate_all_2 as expected"); + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + + // Verify statement is actually closed + conn->describeStatement("deallocate_all_3", true); + conn->readMessage(type, buffer); // Should get error + ok(type == PgConnection::ERROR_RESPONSE, "Describe failed for deallocate_all_3 as expected"); + + if (type == PgConnection::ERROR_RESPONSE) { + BufferReader reader(buffer); + char field; + while (reader.remaining() > 0 && (field = reader.readByte()) != 0) { + if (field == 'M') errormsg = reader.readString(); + else if (field == 'C') errorcode = reader.readString(); + else reader.readString(); + } + } + ok(errorcode == "26000", "Received ERRCODE_INVALID_SQL_STATEMENT_NAME Error:%s", errormsg.c_str()); + conn->readMessage(type, buffer); + ok(type == PgConnection::READY_FOR_QUERY, "Received ready for query"); + } + catch (const PgException& e) { + ok(false, "Extended Query DEALLOCATE ALL via Prepared failed with error:%s", e.what()); + } +} /* void test_update_delete_commands_extended() { diag("Test %d: Extended Query UPDATE and DELETE tags", test_count++); @@ -3507,7 +3803,7 @@ int main(int argc, char** argv) { return exit_status(); } - plan(366); // Adjust based on number of tests + plan(408); // Adjust based on number of tests auto admin_conn = createNewConnection(ConnType::ADMIN, "", false); @@ -3531,7 +3827,7 @@ int main(int argc, char** argv) { test_multiple_parse(); test_only_sync(); test_empty_stmt(); - //test_prepare_statment_mix(); + //test_prepare_statement_mix(); test_invalid_query_parse_packet(); test_parse_use_same_stmt_name(); test_parse_use_unnamed_stmt(); @@ -3589,9 +3885,15 @@ int main(int argc, char** argv) { test_insert_command_complete(); test_parse_with_param_type(); - // set statements tracking + // SET statement tracking test_set_statement_tracked(); test_set_statement_untracked(); + + // DEALLOCATE statements + test_deallocate_having_stmt_name_via_simple_query(); + test_deallocate_having_stmt_name_via_prepared(); + test_deallocate_all_via_simple_query(); + test_deallocate_all_via_prepared(); } catch (const std::exception& e) { diag("Fatal error: %s",e.what());