diff --git a/doc/src/sgml/ref/declare.sgml b/doc/src/sgml/ref/declare.sgml new file mode 100644 index d500faa..7b8c6b6 *** a/doc/src/sgml/ref/declare.sgml --- b/doc/src/sgml/ref/declare.sgml *************** DECLARE n *** 179,187 **** created by this command can only be used within the current transaction. Thus, DECLARE without WITH HOLD is useless outside a transaction block: the cursor would ! survive only to the completion of the statement. Therefore ! PostgreSQL reports an error if such a ! command is used outside a transaction block. Use and --- 179,185 ---- created by this command can only be used within the current transaction. Thus, DECLARE without WITH HOLD is useless outside a transaction block: the cursor would ! survive only to the completion of the statement. Use and diff --git a/doc/src/sgml/ref/lock.sgml b/doc/src/sgml/ref/lock.sgml new file mode 100644 index 95d6767..6a0ad20 *** a/doc/src/sgml/ref/lock.sgml --- b/doc/src/sgml/ref/lock.sgml *************** LOCK [ TABLE ] [ ONLY ] LOCK TABLE is useless outside a transaction block: the lock ! would remain held only to the completion of the statement. Therefore ! PostgreSQL reports an error if LOCK ! is used outside a transaction block. Use and --- 168,174 ---- LOCK TABLE is useless outside a transaction block: the lock ! would remain held only to the completion of the statement. Use and diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml new file mode 100644 index 6290c9d..aaaf186 *** a/doc/src/sgml/ref/set.sgml --- b/doc/src/sgml/ref/set.sgml *************** SET [ SESSION | LOCAL ] TIME ZONE { COMMIT or ROLLBACK, the session-level setting takes effect again. ! PostgreSQL reports an error if SET LOCAL is used outside a transaction block. --- 111,117 ---- Specifies that the command takes effect for only the current transaction. After COMMIT or ROLLBACK, the session-level setting takes effect again. ! PostgreSQL generates a warning if SET LOCAL is used outside a transaction block. diff --git a/doc/src/sgml/ref/set_constraints.sgml b/doc/src/sgml/ref/set_constraints.sgml new file mode 100644 index 895a5fd..2fd228c *** a/doc/src/sgml/ref/set_constraints.sgml --- b/doc/src/sgml/ref/set_constraints.sgml *************** SET CONSTRAINTS { ALL | This command only alters the behavior of constraints within the ! current transaction. Thus, if you execute this command outside of a ! transaction block ! (BEGIN/COMMIT pair), it will ! generate an error. --- 99,105 ---- This command only alters the behavior of constraints within the ! current transaction and is useless outside a transaction block. diff --git a/doc/src/sgml/ref/set_transaction.sgml b/doc/src/sgml/ref/set_transaction.sgml new file mode 100644 index 391464a..a568ab7 *** a/doc/src/sgml/ref/set_transaction.sgml --- b/doc/src/sgml/ref/set_transaction.sgml *************** SET SESSION CHARACTERISTICS AS TRANSACTI *** 183,194 **** Notes - If SET TRANSACTION is executed without a prior - START TRANSACTION or BEGIN, - it will generate an error. - - - It is possible to dispense with SET TRANSACTION by instead specifying the desired transaction_modes in --- 183,188 ---- diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c new file mode 100644 index 0591f3f..4e492c4 *** a/src/backend/access/transam/xact.c --- b/src/backend/access/transam/xact.c *************** PreventTransactionChain(bool isTopLevel, *** 2949,2955 **** } /* ! * RequireTransactionChain * * This routine is to be called by statements that must run inside * a transaction block, because they have no effects that persist past --- 2949,2955 ---- } /* ! * IsTransactionChain * * This routine is to be called by statements that must run inside * a transaction block, because they have no effects that persist past *************** PreventTransactionChain(bool isTopLevel, *** 2963,2996 **** * * isTopLevel: passed down from ProcessUtility to determine whether we are * inside a function. ! * stmtType: statement type name, for error messages. */ ! void ! RequireTransactionChain(bool isTopLevel, const char *stmtType) { /* * xact block already started? */ if (IsTransactionBlock()) ! return; /* * subtransaction? */ if (IsSubTransaction()) ! return; /* * inside a function call? */ if (!isTopLevel) ! return; ! ereport(ERROR, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */ errmsg("%s can only be used in transaction blocks", stmtType))); } /* --- 2963,2997 ---- * * isTopLevel: passed down from ProcessUtility to determine whether we are * inside a function. ! * stmtType: statement type name, for warning messages. */ ! bool ! IsTransactionChain(bool isTopLevel, const char *stmtType) { /* * xact block already started? */ if (IsTransactionBlock()) ! return true; /* * subtransaction? */ if (IsSubTransaction()) ! return true; /* * inside a function call? */ if (!isTopLevel) ! return true; ! ereport(WARNING, (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), /* translator: %s represents an SQL statement name */ errmsg("%s can only be used in transaction blocks", stmtType))); + return false; } /* diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c new file mode 100644 index 5c3f42c..6be3fa1 *** a/src/backend/commands/portalcmds.c --- b/src/backend/commands/portalcmds.c *************** PerformCursorOpen(PlannedStmt *stmt, Par *** 66,72 **** * user-visible effect). */ if (!(cstmt->options & CURSOR_OPT_HOLD)) ! RequireTransactionChain(isTopLevel, "DECLARE CURSOR"); /* * Create a portal and copy the plan and queryString into its memory. --- 66,72 ---- * user-visible effect). */ if (!(cstmt->options & CURSOR_OPT_HOLD)) ! IsTransactionChain(isTopLevel, "DECLARE CURSOR"); /* * Create a portal and copy the plan and queryString into its memory. diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c new file mode 100644 index 6a7bf0d..0566e59 *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** standard_ProcessUtility(Node *parsetree, *** 461,490 **** ListCell *cell; char *name = NULL; ! RequireTransactionChain(isTopLevel, "SAVEPOINT"); ! ! foreach(cell, stmt->options) { ! DefElem *elem = lfirst(cell); ! ! if (strcmp(elem->defname, "savepoint_name") == 0) ! name = strVal(elem->arg); } - - Assert(PointerIsValid(name)); - - DefineSavepoint(name); } break; case TRANS_STMT_RELEASE: ! RequireTransactionChain(isTopLevel, "RELEASE SAVEPOINT"); ! ReleaseSavepoint(stmt->options); break; case TRANS_STMT_ROLLBACK_TO: ! RequireTransactionChain(isTopLevel, "ROLLBACK TO SAVEPOINT"); ! RollbackToSavepoint(stmt->options); /* * CommitTransactionCommand is in charge of --- 461,491 ---- ListCell *cell; char *name = NULL; ! if (IsTransactionChain(isTopLevel, "SAVEPOINT")) { ! foreach(cell, stmt->options) ! { ! DefElem *elem = lfirst(cell); ! ! if (strcmp(elem->defname, "savepoint_name") == 0) ! name = strVal(elem->arg); ! } ! ! Assert(PointerIsValid(name)); ! ! DefineSavepoint(name); } } break; case TRANS_STMT_RELEASE: ! if (IsTransactionChain(isTopLevel, "RELEASE SAVEPOINT")) ! ReleaseSavepoint(stmt->options); break; case TRANS_STMT_ROLLBACK_TO: ! if (IsTransactionChain(isTopLevel, "ROLLBACK TO SAVEPOINT")) ! RollbackToSavepoint(stmt->options); /* * CommitTransactionCommand is in charge of *************** standard_ProcessUtility(Node *parsetree, *** 749,760 **** * Since the lock would just get dropped immediately, LOCK TABLE * outside a transaction block is presumed to be user error. */ ! RequireTransactionChain(isTopLevel, "LOCK TABLE"); LockTableCommand((LockStmt *) parsetree); break; case T_ConstraintsSetStmt: ! RequireTransactionChain(isTopLevel, "SET CONSTRAINTS"); AfterTriggerSetState((ConstraintsSetStmt *) parsetree); break; --- 750,761 ---- * Since the lock would just get dropped immediately, LOCK TABLE * outside a transaction block is presumed to be user error. */ ! IsTransactionChain(isTopLevel, "LOCK TABLE"); LockTableCommand((LockStmt *) parsetree); break; case T_ConstraintsSetStmt: ! IsTransactionChain(isTopLevel, "SET CONSTRAINTS"); AfterTriggerSetState((ConstraintsSetStmt *) parsetree); break; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c new file mode 100644 index 54d8078..2437007 *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** ExecSetVariableStmt(VariableSetStmt *stm *** 6274,6280 **** case VAR_SET_VALUE: case VAR_SET_CURRENT: if (stmt->is_local) ! RequireTransactionChain(isTopLevel, "SET LOCAL"); (void) set_config_option(stmt->name, ExtractSetVariableArgs(stmt), (superuser() ? PGC_SUSET : PGC_USERSET), --- 6274,6280 ---- case VAR_SET_VALUE: case VAR_SET_CURRENT: if (stmt->is_local) ! IsTransactionChain(isTopLevel, "SET LOCAL"); (void) set_config_option(stmt->name, ExtractSetVariableArgs(stmt), (superuser() ? PGC_SUSET : PGC_USERSET), *************** ExecSetVariableStmt(VariableSetStmt *stm *** 6295,6301 **** { ListCell *head; ! RequireTransactionChain(isTopLevel, "SET TRANSACTION"); foreach(head, stmt->args) { --- 6295,6301 ---- { ListCell *head; ! IsTransactionChain(isTopLevel, "SET TRANSACTION"); foreach(head, stmt->args) { *************** ExecSetVariableStmt(VariableSetStmt *stm *** 6346,6352 **** (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented"))); ! RequireTransactionChain(isTopLevel, "SET TRANSACTION"); Assert(IsA(con, A_Const)); Assert(nodeTag(&con->val) == T_String); ImportSnapshot(strVal(&con->val)); --- 6346,6352 ---- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented"))); ! IsTransactionChain(isTopLevel, "SET TRANSACTION"); Assert(IsA(con, A_Const)); Assert(nodeTag(&con->val) == T_String); ImportSnapshot(strVal(&con->val)); *************** ExecSetVariableStmt(VariableSetStmt *stm *** 6357,6367 **** break; case VAR_SET_DEFAULT: if (stmt->is_local) ! RequireTransactionChain(isTopLevel, "SET LOCAL"); /* fall through */ case VAR_RESET: if (strcmp(stmt->name, "transaction_isolation") == 0) ! RequireTransactionChain(isTopLevel, "RESET TRANSACTION"); (void) set_config_option(stmt->name, NULL, --- 6357,6367 ---- break; case VAR_SET_DEFAULT: if (stmt->is_local) ! IsTransactionChain(isTopLevel, "SET LOCAL"); /* fall through */ case VAR_RESET: if (strcmp(stmt->name, "transaction_isolation") == 0) ! IsTransactionChain(isTopLevel, "RESET TRANSACTION"); (void) set_config_option(stmt->name, NULL, diff --git a/src/include/access/xact.h b/src/include/access/xact.h new file mode 100644 index 835f6ac..690b50d *** a/src/include/access/xact.h --- b/src/include/access/xact.h *************** extern bool IsTransactionOrTransactionBl *** 244,250 **** extern char TransactionBlockStatusCode(void); extern void AbortOutOfAnyTransaction(void); extern void PreventTransactionChain(bool isTopLevel, const char *stmtType); ! extern void RequireTransactionChain(bool isTopLevel, const char *stmtType); extern bool IsInTransactionChain(bool isTopLevel); extern void RegisterXactCallback(XactCallback callback, void *arg); extern void UnregisterXactCallback(XactCallback callback, void *arg); --- 244,250 ---- extern char TransactionBlockStatusCode(void); extern void AbortOutOfAnyTransaction(void); extern void PreventTransactionChain(bool isTopLevel, const char *stmtType); ! extern bool IsTransactionChain(bool isTopLevel, const char *stmtType); extern bool IsInTransactionChain(bool isTopLevel); extern void RegisterXactCallback(XactCallback callback, void *arg); extern void UnregisterXactCallback(XactCallback callback, void *arg); diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out new file mode 100644 index 203fa6e..4f0065c *** a/src/test/regress/expected/guc.out --- b/src/test/regress/expected/guc.out *************** SELECT '2006-08-13 12:34:56'::timestampt *** 29,35 **** -- SET LOCAL has no effect outside of a transaction SET LOCAL vacuum_cost_delay TO 50; ! ERROR: SET LOCAL can only be used in transaction blocks SHOW vacuum_cost_delay; vacuum_cost_delay ------------------- --- 29,35 ---- -- SET LOCAL has no effect outside of a transaction SET LOCAL vacuum_cost_delay TO 50; ! WARNING: SET LOCAL can only be used in transaction blocks SHOW vacuum_cost_delay; vacuum_cost_delay ------------------- *************** SHOW vacuum_cost_delay; *** 37,43 **** (1 row) SET LOCAL datestyle = 'SQL'; ! ERROR: SET LOCAL can only be used in transaction blocks SHOW datestyle; DateStyle ----------- --- 37,43 ---- (1 row) SET LOCAL datestyle = 'SQL'; ! WARNING: SET LOCAL can only be used in transaction blocks SHOW datestyle; DateStyle ----------- diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out new file mode 100644 index 5c84ec5..45bbe8a *** a/src/test/regress/expected/transactions.out --- b/src/test/regress/expected/transactions.out *************** SELECT a FROM savepoints WHERE a BETWEEN *** 363,373 **** DROP TABLE savepoints; -- only in a transaction block: SAVEPOINT one; ! ERROR: SAVEPOINT can only be used in transaction blocks ROLLBACK TO SAVEPOINT one; ! ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks RELEASE SAVEPOINT one; ! ERROR: RELEASE SAVEPOINT can only be used in transaction blocks -- Only "rollback to" allowed in aborted state BEGIN; SAVEPOINT one; --- 363,373 ---- DROP TABLE savepoints; -- only in a transaction block: SAVEPOINT one; ! WARNING: SAVEPOINT can only be used in transaction blocks ROLLBACK TO SAVEPOINT one; ! WARNING: ROLLBACK TO SAVEPOINT can only be used in transaction blocks RELEASE SAVEPOINT one; ! WARNING: RELEASE SAVEPOINT can only be used in transaction blocks -- Only "rollback to" allowed in aborted state BEGIN; SAVEPOINT one;