mirror of
https://github.com/postgres/postgres.git
synced 2026-04-05 17:25:55 -04:00
postgres_fdw: Inherit the local transaction's access/deferrable modes.
READ ONLY transactions should prevent modifications to foreign data as well as local data, but postgres_fdw transactions declared as READ ONLY that reference foreign tables mapped to a remote view executing volatile functions would modify data on remote servers, as it would open remote transactions in READ WRITE mode. Similarly, DEFERRABLE transactions should not abort due to a serialization failure even when accessing foreign data, but postgres_fdw transactions declared as DEFERRABLE would abort due to that failure in a remote server, as it would open remote transactions in NOT DEFERRABLE mode. To fix, modify postgres_fdw to open remote transactions in the same access/deferrable modes as the local transaction. This commit also modifies it to open remote subtransactions in the same access mode as the local subtransaction. This commit changes the behavior of READ ONLY/DEFERRABLE transactions using postgres_fdw; in particular, it doesn't allow the READ ONLY transactions to modify data on remote servers anymore, so such transactions should be redeclared as READ WRITE or rewritten using other tools like dblink. The release notes should note this as an incompatibility. These issues exist since the introduction of postgres_fdw, but to avoid the incompatibility in the back branches, fix them in master only. Author: Etsuro Fujita <etsuro.fujita@gmail.com> Reviewed-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Fujii Masao <masao.fujii@gmail.com> Discussion: https://postgr.es/m/CAPmGK16n_hcUUWuOdmeUS%2Bw4Q6dZvTEDHb%3DOP%3D5JBzo-M3QmpQ%40mail.gmail.com Discussion: https://postgr.es/m/E1uLe9X-000zsY-2g%40gemulon.postgresql.org
This commit is contained in:
parent
fc44f10665
commit
de28140ded
6 changed files with 378 additions and 8 deletions
|
|
@ -60,6 +60,7 @@ typedef struct ConnCacheEntry
|
|||
/* Remaining fields are invalid when conn is NULL: */
|
||||
int xact_depth; /* 0 = no xact open, 1 = main xact open, 2 =
|
||||
* one level of subxact open, etc */
|
||||
bool xact_read_only; /* xact r/o state */
|
||||
bool have_prep_stmt; /* have we prepared any stmts in this xact? */
|
||||
bool have_error; /* have any subxacts aborted in this xact? */
|
||||
bool changing_xact_state; /* xact state change in process */
|
||||
|
|
@ -86,6 +87,12 @@ static unsigned int prep_stmt_number = 0;
|
|||
/* tracks whether any work is needed in callback functions */
|
||||
static bool xact_got_connection = false;
|
||||
|
||||
/*
|
||||
* tracks the topmost read-only local transaction's nesting level determined
|
||||
* by GetTopReadOnlyTransactionNestLevel()
|
||||
*/
|
||||
static int read_only_level = 0;
|
||||
|
||||
/* custom wait event values, retrieved from shared memory */
|
||||
static uint32 pgfdw_we_cleanup_result = 0;
|
||||
static uint32 pgfdw_we_connect = 0;
|
||||
|
|
@ -378,6 +385,7 @@ make_new_connection(ConnCacheEntry *entry, UserMapping *user)
|
|||
|
||||
/* Reset all transient state fields, to be sure all are clean */
|
||||
entry->xact_depth = 0;
|
||||
entry->xact_read_only = false;
|
||||
entry->have_prep_stmt = false;
|
||||
entry->have_error = false;
|
||||
entry->changing_xact_state = false;
|
||||
|
|
@ -871,29 +879,106 @@ do_sql_command_end(PGconn *conn, const char *sql, bool consume_input)
|
|||
* those scans. A disadvantage is that we can't provide sane emulation of
|
||||
* READ COMMITTED behavior --- it would be nice if we had some other way to
|
||||
* control which remote queries share a snapshot.
|
||||
*
|
||||
* Note also that we always start the remote transaction with the same
|
||||
* read/write and deferrable properties as the local transaction, and start
|
||||
* the remote subtransaction with the same read/write property as the local
|
||||
* subtransaction.
|
||||
*/
|
||||
static void
|
||||
begin_remote_xact(ConnCacheEntry *entry)
|
||||
{
|
||||
int curlevel = GetCurrentTransactionNestLevel();
|
||||
|
||||
/* Start main transaction if we haven't yet */
|
||||
/*
|
||||
* If the current local (sub)transaction is read-only, set the topmost
|
||||
* read-only local transaction's nesting level if we haven't yet.
|
||||
*
|
||||
* Note: once it's set, it's retained until the topmost read-only local
|
||||
* transaction is committed/aborted (see pgfdw_xact_callback and
|
||||
* pgfdw_subxact_callback).
|
||||
*/
|
||||
if (XactReadOnly)
|
||||
{
|
||||
if (read_only_level == 0)
|
||||
read_only_level = GetTopReadOnlyTransactionNestLevel();
|
||||
Assert(read_only_level > 0);
|
||||
}
|
||||
else
|
||||
Assert(read_only_level == 0);
|
||||
|
||||
/*
|
||||
* Start main transaction if we haven't yet; otherwise, change the current
|
||||
* remote (sub)transaction's read/write mode if needed.
|
||||
*/
|
||||
if (entry->xact_depth <= 0)
|
||||
{
|
||||
const char *sql;
|
||||
/*
|
||||
* This is the case when we haven't yet started a main transaction.
|
||||
*/
|
||||
StringInfoData sql;
|
||||
bool ro = (read_only_level == 1);
|
||||
|
||||
elog(DEBUG3, "starting remote transaction on connection %p",
|
||||
entry->conn);
|
||||
|
||||
initStringInfo(&sql);
|
||||
appendStringInfoString(&sql, "START TRANSACTION ISOLATION LEVEL ");
|
||||
if (IsolationIsSerializable())
|
||||
sql = "START TRANSACTION ISOLATION LEVEL SERIALIZABLE";
|
||||
appendStringInfoString(&sql, "SERIALIZABLE");
|
||||
else
|
||||
sql = "START TRANSACTION ISOLATION LEVEL REPEATABLE READ";
|
||||
appendStringInfoString(&sql, "REPEATABLE READ");
|
||||
if (ro)
|
||||
appendStringInfoString(&sql, " READ ONLY");
|
||||
if (XactDeferrable)
|
||||
appendStringInfoString(&sql, " DEFERRABLE");
|
||||
entry->changing_xact_state = true;
|
||||
do_sql_command(entry->conn, sql);
|
||||
do_sql_command(entry->conn, sql.data);
|
||||
entry->xact_depth = 1;
|
||||
if (ro)
|
||||
{
|
||||
Assert(!entry->xact_read_only);
|
||||
entry->xact_read_only = true;
|
||||
}
|
||||
entry->changing_xact_state = false;
|
||||
}
|
||||
else if (!entry->xact_read_only)
|
||||
{
|
||||
/*
|
||||
* The remote (sub)transaction has been opened in read-write mode.
|
||||
*/
|
||||
Assert(read_only_level == 0 ||
|
||||
entry->xact_depth <= read_only_level);
|
||||
|
||||
/*
|
||||
* If its nesting depth matches read_only_level, it means that the
|
||||
* local read-write (sub)transaction that started it has changed to
|
||||
* read-only after that; in which case change it to read-only as well.
|
||||
* Otherwise, the local (sub)transaction is still read-write, so there
|
||||
* is no need to do anything.
|
||||
*/
|
||||
if (entry->xact_depth == read_only_level)
|
||||
{
|
||||
entry->changing_xact_state = true;
|
||||
do_sql_command(entry->conn, "SET transaction_read_only = on");
|
||||
entry->xact_read_only = true;
|
||||
entry->changing_xact_state = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* The remote (sub)transaction has been opened in read-only mode.
|
||||
*/
|
||||
Assert(read_only_level > 0 &&
|
||||
entry->xact_depth >= read_only_level);
|
||||
|
||||
/*
|
||||
* The local read-only (sub)transaction that started it is guaranteed
|
||||
* to be still read-only (see check_transaction_read_only), so there
|
||||
* is no need to do anything.
|
||||
*/
|
||||
}
|
||||
|
||||
/*
|
||||
* If we're in a subtransaction, stack up savepoints to match our level.
|
||||
|
|
@ -902,12 +987,21 @@ begin_remote_xact(ConnCacheEntry *entry)
|
|||
*/
|
||||
while (entry->xact_depth < curlevel)
|
||||
{
|
||||
char sql[64];
|
||||
StringInfoData sql;
|
||||
bool ro = (entry->xact_depth + 1 == read_only_level);
|
||||
|
||||
snprintf(sql, sizeof(sql), "SAVEPOINT s%d", entry->xact_depth + 1);
|
||||
initStringInfo(&sql);
|
||||
appendStringInfo(&sql, "SAVEPOINT s%d", entry->xact_depth + 1);
|
||||
if (ro)
|
||||
appendStringInfoString(&sql, "; SET transaction_read_only = on");
|
||||
entry->changing_xact_state = true;
|
||||
do_sql_command(entry->conn, sql);
|
||||
do_sql_command(entry->conn, sql.data);
|
||||
entry->xact_depth++;
|
||||
if (ro)
|
||||
{
|
||||
Assert(!entry->xact_read_only);
|
||||
entry->xact_read_only = true;
|
||||
}
|
||||
entry->changing_xact_state = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -1212,6 +1306,9 @@ pgfdw_xact_callback(XactEvent event, void *arg)
|
|||
|
||||
/* Also reset cursor numbering for next transaction */
|
||||
cursor_number = 0;
|
||||
|
||||
/* Likewise for read_only_level */
|
||||
read_only_level = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1310,6 +1407,10 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
|
|||
false);
|
||||
}
|
||||
}
|
||||
|
||||
/* If in read_only_level, reset it */
|
||||
if (curlevel == read_only_level)
|
||||
read_only_level = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1412,6 +1513,9 @@ pgfdw_reset_xact_state(ConnCacheEntry *entry, bool toplevel)
|
|||
/* Reset state to show we're out of a transaction */
|
||||
entry->xact_depth = 0;
|
||||
|
||||
/* Reset xact r/o state */
|
||||
entry->xact_read_only = false;
|
||||
|
||||
/*
|
||||
* If the connection isn't in a good idle state, it is marked as
|
||||
* invalid or keep_connections option of its server is disabled, then
|
||||
|
|
@ -1432,6 +1536,10 @@ pgfdw_reset_xact_state(ConnCacheEntry *entry, bool toplevel)
|
|||
{
|
||||
/* Reset state to show we're out of a subtransaction */
|
||||
entry->xact_depth--;
|
||||
|
||||
/* If in read_only_level, reset xact r/o state */
|
||||
if (entry->xact_depth + 1 == read_only_level)
|
||||
entry->xact_read_only = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12575,6 +12575,142 @@ SELECT count(*) FROM remote_application_name
|
|||
DROP FOREIGN TABLE remote_application_name;
|
||||
DROP VIEW my_application_name;
|
||||
-- ===================================================================
|
||||
-- test read-only and/or deferrable transactions
|
||||
-- ===================================================================
|
||||
CREATE TABLE loct (f1 int, f2 text);
|
||||
CREATE FUNCTION locf() RETURNS SETOF loct LANGUAGE SQL AS
|
||||
'UPDATE public.loct SET f2 = f2 || f2 RETURNING *';
|
||||
CREATE VIEW locv AS SELECT t.* FROM locf() t;
|
||||
CREATE FOREIGN TABLE remt (f1 int, f2 text)
|
||||
SERVER loopback OPTIONS (table_name 'locv');
|
||||
CREATE FOREIGN TABLE remt2 (f1 int, f2 text)
|
||||
SERVER loopback2 OPTIONS (table_name 'locv');
|
||||
INSERT INTO loct VALUES (1, 'foo'), (2, 'bar');
|
||||
START TRANSACTION READ ONLY;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK;
|
||||
START TRANSACTION;
|
||||
SAVEPOINT s;
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK;
|
||||
START TRANSACTION;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
f1 | f2
|
||||
----+--------
|
||||
1 | foofoo
|
||||
2 | barbar
|
||||
(2 rows)
|
||||
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
f1 | f2
|
||||
----+--------
|
||||
1 | foofoo
|
||||
2 | barbar
|
||||
(2 rows)
|
||||
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK;
|
||||
-- Exercise abort code paths in pgfdw_xact_callback/pgfdw_subxact_callback
|
||||
-- in situations where multiple connections are involved
|
||||
START TRANSACTION;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
f1 | f2
|
||||
----+--------
|
||||
1 | foofoo
|
||||
2 | barbar
|
||||
(2 rows)
|
||||
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt2; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
f1 | f2
|
||||
----+--------
|
||||
1 | foofoo
|
||||
2 | barbar
|
||||
(2 rows)
|
||||
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt2; -- should fail
|
||||
ERROR: cannot execute UPDATE in a read-only transaction
|
||||
CONTEXT: SQL function "locf" statement 1
|
||||
remote SQL command: SELECT f1, f2 FROM public.locv
|
||||
ROLLBACK;
|
||||
DROP FOREIGN TABLE remt;
|
||||
CREATE FOREIGN TABLE remt (f1 int, f2 text)
|
||||
SERVER loopback OPTIONS (table_name 'loct');
|
||||
START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
|
||||
SELECT * FROM remt;
|
||||
f1 | f2
|
||||
----+-----
|
||||
1 | foo
|
||||
2 | bar
|
||||
(2 rows)
|
||||
|
||||
COMMIT;
|
||||
START TRANSACTION ISOLATION LEVEL SERIALIZABLE DEFERRABLE;
|
||||
SELECT * FROM remt;
|
||||
f1 | f2
|
||||
----+-----
|
||||
1 | foo
|
||||
2 | bar
|
||||
(2 rows)
|
||||
|
||||
COMMIT;
|
||||
START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;
|
||||
SELECT * FROM remt;
|
||||
f1 | f2
|
||||
----+-----
|
||||
1 | foo
|
||||
2 | bar
|
||||
(2 rows)
|
||||
|
||||
COMMIT;
|
||||
-- Clean up
|
||||
DROP FOREIGN TABLE remt;
|
||||
DROP FOREIGN TABLE remt2;
|
||||
DROP VIEW locv;
|
||||
DROP FUNCTION locf();
|
||||
DROP TABLE loct;
|
||||
-- ===================================================================
|
||||
-- test parallel commit and parallel abort
|
||||
-- ===================================================================
|
||||
ALTER SERVER loopback OPTIONS (ADD parallel_commit 'true');
|
||||
|
|
|
|||
|
|
@ -4328,6 +4328,86 @@ SELECT count(*) FROM remote_application_name
|
|||
DROP FOREIGN TABLE remote_application_name;
|
||||
DROP VIEW my_application_name;
|
||||
|
||||
-- ===================================================================
|
||||
-- test read-only and/or deferrable transactions
|
||||
-- ===================================================================
|
||||
CREATE TABLE loct (f1 int, f2 text);
|
||||
CREATE FUNCTION locf() RETURNS SETOF loct LANGUAGE SQL AS
|
||||
'UPDATE public.loct SET f2 = f2 || f2 RETURNING *';
|
||||
CREATE VIEW locv AS SELECT t.* FROM locf() t;
|
||||
CREATE FOREIGN TABLE remt (f1 int, f2 text)
|
||||
SERVER loopback OPTIONS (table_name 'locv');
|
||||
CREATE FOREIGN TABLE remt2 (f1 int, f2 text)
|
||||
SERVER loopback2 OPTIONS (table_name 'locv');
|
||||
INSERT INTO loct VALUES (1, 'foo'), (2, 'bar');
|
||||
|
||||
START TRANSACTION READ ONLY;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ROLLBACK;
|
||||
|
||||
START TRANSACTION;
|
||||
SAVEPOINT s;
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ROLLBACK;
|
||||
|
||||
START TRANSACTION;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt; -- should fail
|
||||
ROLLBACK;
|
||||
|
||||
-- Exercise abort code paths in pgfdw_xact_callback/pgfdw_subxact_callback
|
||||
-- in situations where multiple connections are involved
|
||||
START TRANSACTION;
|
||||
SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt2; -- should fail
|
||||
ROLLBACK TO s;
|
||||
RELEASE SAVEPOINT s;
|
||||
SELECT * FROM remt; -- should work
|
||||
SET transaction_read_only = on;
|
||||
SELECT * FROM remt2; -- should fail
|
||||
ROLLBACK;
|
||||
|
||||
DROP FOREIGN TABLE remt;
|
||||
CREATE FOREIGN TABLE remt (f1 int, f2 text)
|
||||
SERVER loopback OPTIONS (table_name 'loct');
|
||||
|
||||
START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY;
|
||||
SELECT * FROM remt;
|
||||
COMMIT;
|
||||
|
||||
START TRANSACTION ISOLATION LEVEL SERIALIZABLE DEFERRABLE;
|
||||
SELECT * FROM remt;
|
||||
COMMIT;
|
||||
|
||||
START TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;
|
||||
SELECT * FROM remt;
|
||||
COMMIT;
|
||||
|
||||
-- Clean up
|
||||
DROP FOREIGN TABLE remt;
|
||||
DROP FOREIGN TABLE remt2;
|
||||
DROP VIEW locv;
|
||||
DROP FUNCTION locf();
|
||||
DROP TABLE loct;
|
||||
|
||||
-- ===================================================================
|
||||
-- test parallel commit and parallel abort
|
||||
-- ===================================================================
|
||||
|
|
|
|||
|
|
@ -1103,6 +1103,23 @@ CREATE SUBSCRIPTION my_subscription SERVER subscription_server PUBLICATION testp
|
|||
<productname>PostgreSQL</productname> release might modify these rules.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The remote transaction is opened in the same read/write mode as the local
|
||||
transaction: if the local transaction is <literal>READ ONLY</literal>,
|
||||
the remote transaction is opened in <literal>READ ONLY</literal> mode,
|
||||
otherwise it is opened in <literal>READ WRITE</literal> mode.
|
||||
(This rule is also applied to remote and local subtransactions.)
|
||||
Note that this does not prevent login triggers executed on the remote
|
||||
server from writing.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The remote transaction is also opened in the same deferrable mode as the
|
||||
local transaction: if the local transaction is <literal>DEFERRABLE</literal>,
|
||||
the remote transaction is opened in <literal>DEFERRABLE</literal> mode,
|
||||
otherwise it is opened in <literal>NOT DEFERRABLE</literal> mode.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note that it is currently not supported by
|
||||
<filename>postgres_fdw</filename> to prepare the remote transaction for
|
||||
|
|
|
|||
|
|
@ -1046,6 +1046,34 @@ TransactionStartedDuringRecovery(void)
|
|||
return CurrentTransactionState->startedInRecovery;
|
||||
}
|
||||
|
||||
/*
|
||||
* GetTopReadOnlyTransactionNestLevel
|
||||
*
|
||||
* Note: this will return zero when not inside any transaction or when neither
|
||||
* a top-level transaction nor subtransactions are read-only, one when the
|
||||
* top-level transaction is read-only, two when one level of subtransaction is
|
||||
* read-only, etc.
|
||||
*
|
||||
* Note: subtransactions of the topmost read-only transaction are also
|
||||
* read-only, because they inherit read-only mode from the transaction, and
|
||||
* thus can't change to read-write mode (see check_transaction_read_only).
|
||||
*/
|
||||
int
|
||||
GetTopReadOnlyTransactionNestLevel(void)
|
||||
{
|
||||
TransactionState s = CurrentTransactionState;
|
||||
|
||||
if (!XactReadOnly)
|
||||
return 0;
|
||||
while (s->nestingLevel > 1)
|
||||
{
|
||||
if (!s->prevXactReadOnly)
|
||||
return s->nestingLevel;
|
||||
s = s->parent;
|
||||
}
|
||||
return s->nestingLevel;
|
||||
}
|
||||
|
||||
/*
|
||||
* EnterParallelMode
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -459,6 +459,7 @@ extern TimestampTz GetCurrentTransactionStopTimestamp(void);
|
|||
extern void SetCurrentStatementStartTimestamp(void);
|
||||
extern int GetCurrentTransactionNestLevel(void);
|
||||
extern bool TransactionIdIsCurrentTransactionId(TransactionId xid);
|
||||
extern int GetTopReadOnlyTransactionNestLevel(void);
|
||||
extern void CommandCounterIncrement(void);
|
||||
extern void ForceSyncCommit(void);
|
||||
extern void StartTransactionCommand(void);
|
||||
|
|
|
|||
Loading…
Reference in a new issue