else if (MAIN_REPLICA && query_context->is_multi_statement)
{
/*
- * If we are in native replication mode and we have multi statement query,
+ * If we are in streaming replication mode and we have multi statement query,
* we should send it to primary server only. Otherwise it is possible
* to send a write query to standby servers because we only use the
* first element of the multi statement query and don't care about the
/* Should be sent to both primary and standby? */
else if (dest == POOL_BOTH)
{
- pool_setall_node_to_be_sent(query_context);
+ if (is_tx_started_by_multi_statement_query())
+ {
+ /*
+ * If we are in an explicit transaction and the transaction
+ * was started by a multi statement query, we should send
+ * query to primary node only (which was supposed to be sent
+ * to all nodes) until the transaction gets committed or
+ * aborted.
+ */
+ pool_set_node_to_be_sent(query_context, PRIMARY_NODE_ID);
+ }
+ else
+ {
+ pool_setall_node_to_be_sent(query_context);
+ }
}
else if (pool_is_writing_transaction() &&
pool_config->disable_load_balance_on_write == DLBOW_ALWAYS)
}
/* SAVEPOINT related commands are sent to both primary and standby */
else if (is_savepoint_query(node))
+ {
+ if (SL_MODE && is_tx_started_by_multi_statement_query())
+ {
+ /*
+ * But in streaming replication mode, if a transaction was
+ * started by a multi statement query, SAVEPOINT should be
+ * sent to primary because the transaction was started on
+ * primary only.
+ */
+ return POOL_PRIMARY;
+ }
return POOL_BOTH;
-
+ }
/*
* 2PC commands
*/
* pgpool: a language independent connection pool server for PostgreSQL
* written by Tatsuo Ishii
*
- * Copyright (c) 2003-2022 PgPool Global Development Group
+ * Copyright (c) 2003-2023 PgPool Global Development Group
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby
session_context->transaction_read_only = false;
dml_adaptive_init();
+
+ unset_tx_started_by_multi_statement_query();
}
/*
#endif
}
+
+/*
+ * Return true if an explicit transaction has been started by a
+ * multi-statement-query
+ */
+bool
+is_tx_started_by_multi_statement_query(void)
+{
+ if (!session_context)
+ ereport(ERROR,
+ (errmsg("is_tx_started_by_multi_statement_query: session context is not initialized")));
+
+ return session_context->is_tx_started_by_multi_statement;
+}
+
+/*
+ * Remember that an explicit transaction has been started by a
+ * multi-statement-query
+ */
+void
+set_tx_started_by_multi_statement_query(void)
+{
+ if (!session_context)
+ ereport(ERROR,
+ (errmsg("set_tx_started_by_multi_statement_query: session context is not initialized")));
+
+ session_context->is_tx_started_by_multi_statement = true;
+}
+
+/*
+ * Forget that an explicit transaction has been started by a
+ * multi-statement-query
+ */
+void
+unset_tx_started_by_multi_statement_query(void)
+{
+ if (!session_context)
+ ereport(ERROR,
+ (errmsg("unset_tx_started_by_multi_statement_query: session context is not initialized")));
+
+ session_context->is_tx_started_by_multi_statement = false;
+}
* pgpool: a language independent connection pool server for PostgreSQL
* written by Tatsuo Ishii
*
- * Copyright (c) 2003-2022 PgPool Global Development Group
+ * Copyright (c) 2003-2023 PgPool Global Development Group
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby
* message for frontend are sent out.
*/
bool flush_pending;
+
+ bool is_tx_started_by_multi_statement; /* true if an explicit transaction has been strated by
+ multi statement query */
} POOL_PENDING_MESSAGE;
typedef enum {
* Set by read_kind_from_backend and reset by SimpleForwardToFrontend.
*/
bool flush_pending;
+
+ bool is_tx_started_by_multi_statement; /* True if an explicit
+ * transaction has been
+ * started by a
+ * multi-statement-query */
} POOL_SESSION_CONTEXT;
extern void pool_init_session_context(POOL_CONNECTION * frontend, POOL_CONNECTION_POOL * backend);
extern void pool_temp_tables_remove_pending(void);
extern void pool_temp_tables_dump(void);
+extern bool is_tx_started_by_multi_statement_query(void);
+extern void set_tx_started_by_multi_statement_query(void);
+extern void unset_tx_started_by_multi_statement_query(void);
+
#ifdef NOT_USED
extern void pool_set_preferred_main_node_id(int node_id);
extern int pool_get_preferred_main_node_id(void);
* pgpool: a language independent connection pool server for PostgreSQL
* written by Tatsuo Ishii
*
- * Copyright (c) 2003-2022 PgPool Global Development Group
+ * Copyright (c) 2003-2023 PgPool Global Development Group
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby
memcpy(p1, p, len);
}
+ /*
+ * Check if a transaction start command was issued by a multi statement
+ * query by looking at the command tag. We cannot look into the parse tree
+ * because our parse tree is only the first query in the multi statement.
+ */
+ if (SL_MODE && session_context->query_context &&
+ session_context->query_context->is_multi_statement)
+ {
+ if (!strcmp(p1, "BEGIN"))
+ {
+ /*
+ * If the query was a transaction starting command, remember it
+ * until it gets committed or roll backed.
+ */
+ elog(DEBUG1, "Call set_tx_started_by_multi_statement_query() in CommandComplete");
+ set_tx_started_by_multi_statement_query();
+ }
+ else if (!strcmp(p1, "COMMIT") || !strcmp(p1, "ROLLBACK"))
+ {
+ /*
+ * It is possible that the multi statement query included both
+ * BEGIN and COMMIT/ROLLBACK, or just COMMIT/ROLLBACK command. In
+ * this case we forget that a transaction was started by multi
+ * statement query.
+ */
+ elog(DEBUG1, "Call unset_tx_started_by_multi_statement_query() in CommandComplete");
+ unset_tx_started_by_multi_statement_query();
+ }
+ }
+
/*
* If operated in streaming replication mode and extended query mode, just
* forward the packet to frontend and we are done. Otherwise, we need to
{
/* Commit ongoing CREATE/DROP temp table status */
pool_temp_tables_commit_pending();
+
+ /* Forget a transaction was started by multi statement query */
+ unset_tx_started_by_multi_statement_query();
}
else if (stmt->kind == TRANS_STMT_ROLLBACK)
{
/* Remove ongoing CREATE/DROP temp table status */
pool_temp_tables_remove_pending();
+
+ /* Forget a transaction was started by multi statement query */
+ elog(LOG, "unset_tx_started_by_multi_statement_query is called in CommandComplete");
+ unset_tx_started_by_multi_statement_query();
}
}
else if (IsA(node, CreateStmt))
INTERNAL_TRANSACTION_STARTED(backend, i) = false;
+ /*
+ * Explicitly set the tx state to 'Idle'. This is necessary
+ * because ReadyForQuery only takes care VALID_BACKEND.
+ */
+ TSTATE(backend, i) = 'I';
+
if (MAJOR(backend) == PROTO_MAJOR_V3 && !VALID_BACKEND(i))
{
/*
PG_END_TRY();
INTERNAL_TRANSACTION_STARTED(backend, MAIN_NODE_ID) = false;
+ /*
+ * Explicitly set the tx state to 'Idle'. This is necessary
+ * because ReadyForQuery only takes care VALID_BACKEND.
+ */
+ TSTATE(backend, MAIN_NODE_ID) = 'I';
+
if (MAJOR(backend) == PROTO_MAJOR_V3 && !VALID_BACKEND(MAIN_NODE_ID))
{
/*
TSTATE(backend, i) = kind;
ereport(DEBUG5,
(errmsg("processing ReadyForQuery"),
- errdetail("transaction state '%c'(%02x)", state, state)));
+ errdetail("transaction state of node %d '%c'(%02x)", i, kind , kind)));
/*
* The transaction state to be returned to frontend is main node's.
{
int len;
+ if (TSTATE(backend, MAIN_NODE_ID) != 'E')
+ return true;
+
/*
* Are we in failed transaction and the command is not a transaction close
* command?