From b65cd562402ed9d3206d501cc74dc38bc421b2ce Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Fri, 10 Jan 2003 22:03:30 +0000 Subject: [PATCH] Read-only transactions, as defined in SQL. --- doc/src/sgml/features.sgml | 28 ++--- doc/src/sgml/ref/set_transaction.sgml | 70 ++++++++---- doc/src/sgml/ref/start_transaction.sgml | 53 ++------- doc/src/sgml/release.sgml | 3 +- src/backend/access/transam/xact.c | 6 +- src/backend/catalog/namespace.c | 26 ++++- src/backend/commands/copy.c | 6 +- src/backend/executor/execMain.c | 50 ++++++++- src/backend/parser/gram.y | 87 ++++++++------ src/backend/tcop/utility.c | 125 +++++++++++++++++++-- src/backend/utils/misc/guc.c | 18 ++- src/bin/psql/tab-complete.c | 44 +++++--- src/include/access/xact.h | 5 +- src/include/catalog/namespace.h | 3 +- src/test/regress/expected/transactions.out | 28 +++++ src/test/regress/sql/transactions.sql | 21 ++++ 16 files changed, 427 insertions(+), 146 deletions(-) diff --git a/doc/src/sgml/features.sgml b/doc/src/sgml/features.sgml index fd01cd3cce..9c7cc47193 100644 --- a/doc/src/sgml/features.sgml +++ b/doc/src/sgml/features.sgml @@ -1,5 +1,5 @@ @@ -642,6 +642,12 @@ $Header: /cvsroot/pgsql/doc/src/sgml/features.sgml,v 2.13 2003/01/10 11:02:41 pe ROLLBACK statement + + E152 + Core + Basic SET TRANSACTION statement + + E152-01 Core @@ -649,6 +655,13 @@ $Header: /cvsroot/pgsql/doc/src/sgml/features.sgml,v 2.13 2003/01/10 11:02:41 pe clause + + E152-02 + Core + SET TRANSACTION statement: READ ONLY and READ WRITE + clauses + + E161 Core @@ -1598,19 +1611,6 @@ $Header: /cvsroot/pgsql/doc/src/sgml/features.sgml,v 2.13 2003/01/10 11:02:41 pe WITH HOLD cursors Cursor to stay open across transactions - - E152 - Core - Basic SET TRANSACTION statement - - - - E152-02 - Core - SET TRANSACTION statement: READ ONLY and READ WRITE - clauses - Syntax accepted; READ ONLY not supported - E153 Core diff --git a/doc/src/sgml/ref/set_transaction.sgml b/doc/src/sgml/ref/set_transaction.sgml index 315a999330..f39e0eb7fe 100644 --- a/doc/src/sgml/ref/set_transaction.sgml +++ b/doc/src/sgml/ref/set_transaction.sgml @@ -1,4 +1,4 @@ - + 2000-11-24 @@ -16,9 +16,10 @@ -SET TRANSACTION ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE } -SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL - { READ COMMITTED | SERIALIZABLE } +SET TRANSACTION + [ ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE } ] [ READ WRITE | READ ONLY ] +SET SESSION CHARACTERISTICS AS TRANSACTION + [ ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE } ] [ READ WRITE | READ ONLY ] @@ -26,17 +27,19 @@ SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL Description - This command sets the transaction isolation level. The - SET TRANSACTION command sets the characteristics - for the current SQL-transaction. It has no effect on any subsequent - transactions. This command cannot be used after the first query or data-modification - statement (SELECT, INSERT, - DELETE, UPDATE, - FETCH, COPY) of a transaction - has been executed. SET SESSION CHARACTERISTICS - sets the default transaction isolation level for each transaction - for a session. SET TRANSACTION can override it - for an individual transaction. + The SET TRANSACTION command sets the transaction + characteristics of the current SQL-transaction. It has no effect on + any subsequent transactions. SET SESSION + CHARACTERISTICS sets the default transaction + characteristics for each transaction of a session. SET + TRANSACTION can override it for an individual + transaction. + + + + The available transaction characteristics are the transaction + isolation level and the transaction access mode (read/write or + read-only). @@ -45,7 +48,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL - READ COMMITTED + READ COMMITTED A statement can only see rows committed before it began. This @@ -55,7 +58,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL - SERIALIZABLE + SERIALIZABLE The current transaction can only see rows committed before @@ -72,6 +75,28 @@ SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL + + The transaction isolation level cannot be set after the first query + or data-modification statement (SELECT, + INSERT, DELETE, + UPDATE, FETCH, + COPY) of a transaction has been executed. + + + + The transaction access mode determines whether the transaction is + read/write or read-only. Read/write is the default. When a + transaction is read-only, the following SQL commands are + disallowed: INSERT, UPDATE, + DELETE, and COPY TO if the + table they would write to is not a temporary table; all + CREATE, ALTER, and + DROP commands; COMMENT, + GRANT, REVOKE, + TRUNCATE; and EXPLAIN ANALYZE + and EXECUTE if the command they would execute is + among those listed. This is a high-level notion of read-only that + does not prevent writes to disk. @@ -97,7 +122,7 @@ SET default_transaction_isolation = 'value' SQL92, SQL99 - is the default level in + is the default transaction isolation level in SQL. PostgreSQL does not provide the isolation levels and . Because of multiversion @@ -107,11 +132,10 @@ SET default_transaction_isolation = 'value' - In SQL there are two other transaction - characteristics that can be set with these commands: whether the - transaction is read-only and the size of the diagnostics area. - Neither of these concepts are supported in - PostgreSQL. + In SQL there is one other transaction + characteristic that can be set with these commands: the size of + the diagnostics area. This concept is not supported in + PostgreSQL. diff --git a/doc/src/sgml/ref/start_transaction.sgml b/doc/src/sgml/ref/start_transaction.sgml index fb8dd13193..087a0df177 100644 --- a/doc/src/sgml/ref/start_transaction.sgml +++ b/doc/src/sgml/ref/start_transaction.sgml @@ -1,5 +1,5 @@ @@ -20,7 +20,7 @@ PostgreSQL documentation -START TRANSACTION [ ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE } ] +START TRANSACTION [ ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE } ] [ READ WRITE | READ ONLY ] @@ -77,52 +77,23 @@ WARNING: BEGIN: already a transaction in progress Description - This command begins a new transaction. If the isolation level is - specified, the new transaction has that isolation level. In all other - respects, the behavior of this command is identical to the - command. + This command begins a new transaction. If the isolation level or + read/write mode is specified, the new transaction has those + characteristics, as if was executed. In all other + respects, the behavior of this command is identical to the command. - - Notes - - - The isolation level of a transaction can also be set with the - command. If no isolation level is specified, the default isolation - level is used. - - - Compatibility - - SQL99 - - - is the default isolation level in - SQL99, but it is not the usual default in - PostgreSQL: the factory default setting - is READ COMMITTED. - PostgreSQL - does not provide the isolation levels - and . Because of lack of predicate - locking, the level is - not truly serializable. See the User's Guide - for details. - - - - In SQL99 this statement can specify two other - properties of the new transaction: whether the transaction is - read-only and the size of the diagnostics area. Neither of these - concepts are currently supported in - PostgreSQL. - - + + SQL99; but see also the compatibility section of . + diff --git a/doc/src/sgml/release.sgml b/doc/src/sgml/release.sgml index 55007a7d62..fd9a5e698f 100644 --- a/doc/src/sgml/release.sgml +++ b/doc/src/sgml/release.sgml @@ -1,5 +1,5 @@ @@ -36,6 +36,7 @@ System can use either hash- or sort-based strategy for grouped aggregation ON COMMIT options for temp tables extra_float_digits option allows pg_dump to dump float data accurately Long options for psql and pg_dump are now available on all platforms +Read-only transactions ]]> diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 0f30e13c84..7150569a22 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.140 2002/11/23 03:59:06 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.141 2003/01/10 22:03:27 petere Exp $ * * NOTES * Transaction aborts can now occur two ways: @@ -208,6 +208,9 @@ TransactionState CurrentTransactionState = &CurrentTransactionStateData; int DefaultXactIsoLevel = XACT_READ_COMMITTED; int XactIsoLevel; +bool DefaultXactReadOnly = false; +bool XactReadOnly; + bool autocommit = true; int CommitDelay = 0; /* precommit delay in microseconds */ @@ -848,6 +851,7 @@ StartTransaction(void) FreeXactSnapshot(); XactIsoLevel = DefaultXactIsoLevel; + XactReadOnly = DefaultXactReadOnly; /* * Check the current transaction state. If the transaction system is diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 0c571ca2a4..d58313a2ee 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -13,7 +13,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.43 2003/01/07 20:56:06 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/catalog/namespace.c,v 1.44 2003/01/10 22:03:27 petere Exp $ * *------------------------------------------------------------------------- */ @@ -278,6 +278,30 @@ RelnameGetRelid(const char *relname) return InvalidOid; } +/* + * RelidGetNamespaceId + * Given a relation OID, return the namespace OID. + */ +Oid +RelidGetNamespaceId(Oid relid) +{ + HeapTuple tuple; + Form_pg_class pg_class_form; + Oid result; + + tuple = SearchSysCache(RELOID, + ObjectIdGetDatum(relid), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for relation %u", relid); + pg_class_form = (Form_pg_class) GETSTRUCT(tuple); + + result = pg_class_form->relnamespace; + ReleaseSysCache(tuple); + return result; +} + + /* * RelationIsVisible * Determine whether a relation (identified by OID) is visible in the diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index fd8c6b83a8..91386eeb2c 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.187 2002/12/15 16:17:38 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.188 2003/01/10 22:03:27 petere Exp $ * *------------------------------------------------------------------------- */ @@ -348,6 +348,10 @@ DoCopy(const CopyStmt *stmt) */ rel = heap_openrv(relation, (is_from ? RowExclusiveLock : AccessShareLock)); + /* check read-only transaction */ + if (XactReadOnly && !is_from && !isTempNamespace(RelationGetNamespace(rel))) + elog(ERROR, "transaction is read-only"); + /* Check permissions. */ aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), required_access); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 613a62c3c3..a1a8134a6a 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -26,7 +26,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.196 2003/01/08 23:32:29 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.197 2003/01/10 22:03:27 petere Exp $ * *------------------------------------------------------------------------- */ @@ -85,6 +85,7 @@ static void ExecUpdate(TupleTableSlot *slot, ItemPointer tupleid, static TupleTableSlot *EvalPlanQualNext(EState *estate); static void EndEvalPlanQual(EState *estate); static void ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation); +static void ExecCheckXactReadOnly(Query *parsetree, CmdType operation); static void EvalPlanQualStart(evalPlanQual *epq, EState *estate, evalPlanQual *priorepq); static void EvalPlanQualStop(evalPlanQual *epq); @@ -201,6 +202,14 @@ ExecutorRun(QueryDesc *queryDesc, operation = queryDesc->operation; dest = queryDesc->dest; + /* + * If the transaction is read-only, we need to check if any writes + * are planned to non-temporary tables. This is done here at this + * rather late stage so that we can handle EXPLAIN vs. EXPLAIN + * ANALYZE easily. + */ + ExecCheckXactReadOnly(queryDesc->parsetree, operation); + /* * startup tuple receiver */ @@ -385,6 +394,45 @@ ExecCheckRTEPerms(RangeTblEntry *rte, CmdType operation) */ +static void +ExecCheckXactReadOnly(Query *parsetree, CmdType operation) +{ + if (!XactReadOnly) + return; + + /* CREATE TABLE AS or SELECT INTO */ + if (operation == CMD_SELECT && parsetree->into != NULL) + goto fail; + + if (operation == CMD_DELETE || operation == CMD_INSERT + || operation == CMD_UPDATE) + { + List *lp; + + foreach(lp, parsetree->rtable) + { + RangeTblEntry *rte = lfirst(lp); + + if (rte->rtekind != RTE_RELATION) + continue; + + if (!rte->checkForWrite) + continue; + + if (isTempNamespace(RelidGetNamespaceId(rte->relid))) + continue; + + goto fail; + } + } + + return; + +fail: + elog(ERROR, "transaction is read-only"); +} + + /* ---------------------------------------------------------------- * InitPlan * diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index fd33601cc0..2703ae8a06 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.394 2003/01/10 21:08:13 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.395 2003/01/10 22:03:27 petere Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -162,7 +162,7 @@ static void doNegateFloat(Value *v); %type createdb_opt_item copy_opt_item %type opt_lock lock_type cast_context -%type opt_force opt_or_replace +%type opt_force opt_or_replace transaction_access_mode %type user_list @@ -215,7 +215,8 @@ static void doNegateFloat(Value *v); target_list update_target_list insert_column_list insert_target_list def_list opt_indirection group_clause TriggerFuncArgs select_limit - opt_select_limit opclass_item_list trans_options + opt_select_limit opclass_item_list transaction_mode_list + transaction_mode_list_or_empty TableFuncElementList prep_type_clause prep_type_list execute_param_clause @@ -863,18 +864,18 @@ set_rest: ColId TO var_list_or_default n->args = makeList1($3); $$ = n; } - | TRANSACTION ISOLATION LEVEL iso_level opt_mode + | TRANSACTION transaction_mode_list { VariableSetStmt *n = makeNode(VariableSetStmt); - n->name = "TRANSACTION ISOLATION LEVEL"; - n->args = makeList1(makeStringConst($4, NULL)); + n->name = "TRANSACTION"; + n->args = $2; $$ = n; } - | SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL iso_level + | SESSION CHARACTERISTICS AS TRANSACTION transaction_mode_list { VariableSetStmt *n = makeNode(VariableSetStmt); - n->name = "default_transaction_isolation"; - n->args = makeList1(makeStringConst($7, NULL)); + n->name = "SESSION CHARACTERISTICS"; + n->args = $5; $$ = n; } | NAMES opt_encoding @@ -922,16 +923,6 @@ iso_level: READ COMMITTED { $$ = "read committed"; } | SERIALIZABLE { $$ = "serializable"; } ; -opt_mode: READ WRITE - {} - | READ ONLY - { - elog(ERROR, "SET TRANSACTION/READ ONLY not yet supported"); - } - | /*EMPTY*/ - {} - ; - opt_boolean: TRUE_P { $$ = "true"; } | FALSE_P { $$ = "false"; } @@ -1020,7 +1011,7 @@ VariableShowStmt: | SHOW TRANSACTION ISOLATION LEVEL { VariableShowStmt *n = makeNode(VariableShowStmt); - n->name = "TRANSACTION ISOLATION LEVEL"; + n->name = "transaction_isolation"; $$ = (Node *) n; } | SHOW SESSION AUTHORIZATION @@ -1053,7 +1044,7 @@ VariableResetStmt: | RESET TRANSACTION ISOLATION LEVEL { VariableResetStmt *n = makeNode(VariableResetStmt); - n->name = "TRANSACTION ISOLATION LEVEL"; + n->name = "transaction_isolation"; $$ = (Node *) n; } | RESET SESSION AUTHORIZATION @@ -3500,42 +3491,42 @@ UnlistenStmt: *****************************************************************************/ TransactionStmt: - ABORT_TRANS opt_trans + ABORT_TRANS opt_transaction { TransactionStmt *n = makeNode(TransactionStmt); n->command = ROLLBACK; n->options = NIL; $$ = (Node *)n; } - | BEGIN_TRANS opt_trans + | BEGIN_TRANS opt_transaction { TransactionStmt *n = makeNode(TransactionStmt); n->command = BEGIN_TRANS; n->options = NIL; $$ = (Node *)n; } - | START TRANSACTION trans_options + | START TRANSACTION transaction_mode_list_or_empty { TransactionStmt *n = makeNode(TransactionStmt); n->command = START; n->options = $3; $$ = (Node *)n; } - | COMMIT opt_trans + | COMMIT opt_transaction { TransactionStmt *n = makeNode(TransactionStmt); n->command = COMMIT; n->options = NIL; $$ = (Node *)n; } - | END_TRANS opt_trans + | END_TRANS opt_transaction { TransactionStmt *n = makeNode(TransactionStmt); n->command = COMMIT; n->options = NIL; $$ = (Node *)n; } - | ROLLBACK opt_trans + | ROLLBACK opt_transaction { TransactionStmt *n = makeNode(TransactionStmt); n->command = ROLLBACK; @@ -3544,16 +3535,46 @@ TransactionStmt: } ; -trans_options: ISOLATION LEVEL iso_level - { $$ = makeList1(makeStringConst($3, NULL)); } - | /* EMPTY */ { $$ = NIL; } - ; - -opt_trans: WORK {} +opt_transaction: WORK {} | TRANSACTION {} | /*EMPTY*/ {} ; +transaction_mode_list: + ISOLATION LEVEL iso_level + { $$ = makeList1(makeDefElem("transaction_isolation", + makeStringConst($3, NULL))); } + | transaction_access_mode + { $$ = makeList1(makeDefElem("transaction_read_only", + makeIntConst($1))); } + | ISOLATION LEVEL iso_level transaction_access_mode + { + $$ = makeList2(makeDefElem("transaction_isolation", + makeStringConst($3, NULL)), + makeDefElem("transaction_read_only", + makeIntConst($4))); + } + | transaction_access_mode ISOLATION LEVEL iso_level + { + $$ = makeList2(makeDefElem("transaction_read_only", + makeIntConst($1)), + makeDefElem("transaction_isolation", + makeStringConst($4, NULL))); + } + ; + +transaction_mode_list_or_empty: + transaction_mode_list + | /* EMPTY */ + { $$ = NIL; } + ; + +transaction_access_mode: + READ ONLY { $$ = TRUE; } + | READ WRITE { $$ = FALSE; } + ; + + /***************************************************************************** * * QUERY: diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 68c02c5f62..45da9ba1c1 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.188 2003/01/06 00:31:44 tgl Exp $ + * $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.189 2003/01/10 22:03:28 petere Exp $ * *------------------------------------------------------------------------- */ @@ -168,6 +168,66 @@ CheckOwnership(RangeVar *rel, bool noCatalogs) } +static void +check_xact_readonly(Node *parsetree) +{ + if (!XactReadOnly) + return; + + /* + * Note: Commands that need to do more complicated checking are + * handled elsewhere. + */ + + switch (nodeTag(parsetree)) + { + case T_AlterDatabaseSetStmt: + case T_AlterDomainStmt: + case T_AlterGroupStmt: + case T_AlterTableStmt: + case T_RenameStmt: + case T_AlterUserStmt: + case T_AlterUserSetStmt: + case T_CommentStmt: + case T_DefineStmt: + case T_CreateCastStmt: + case T_CreateConversionStmt: + case T_CreatedbStmt: + case T_CreateDomainStmt: + case T_CreateFunctionStmt: + case T_CreateGroupStmt: + case T_IndexStmt: + case T_CreatePLangStmt: + case T_CreateOpClassStmt: + case T_RuleStmt: + case T_CreateSchemaStmt: + case T_CreateSeqStmt: + case T_CreateStmt: + case T_CreateTrigStmt: + case T_CompositeTypeStmt: + case T_CreateUserStmt: + case T_ViewStmt: + case T_RemoveAggrStmt: + case T_DropCastStmt: + case T_DropStmt: + case T_DropdbStmt: + case T_RemoveFuncStmt: + case T_DropGroupStmt: + case T_DropPLangStmt: + case T_RemoveOperStmt: + case T_RemoveOpClassStmt: + case T_DropPropertyStmt: + case T_DropUserStmt: + case T_GrantStmt: + case T_TruncateStmt: + elog(ERROR, "transaction is read-only"); + break; + default: + /*nothing*/; + } +} + + /* * ProcessUtility * general utility function invoker @@ -187,6 +247,8 @@ ProcessUtility(Node *parsetree, CommandDest dest, char *completionTag) { + check_xact_readonly(parsetree); + if (completionTag) completionTag[0] = '\0'; @@ -214,16 +276,21 @@ ProcessUtility(Node *parsetree, { BeginTransactionBlock(); - /* - * Currently, the only option that can be set - * by START TRANSACTION is the isolation - * level. - */ if (stmt->options) { - SetPGVariable("TRANSACTION ISOLATION LEVEL", - stmt->options, - false); + List *head; + + foreach(head, stmt->options) + { + DefElem *item = (DefElem *) lfirst(head); + + if (strcmp(item->defname, "transaction_isolation")==0) + SetPGVariable("transaction_isolation", + makeList1(item->arg), false); + else if (strcmp(item->defname, "transaction_read_only")==0) + SetPGVariable("transaction_read_only", + makeList1(item->arg), false); + } } } break; @@ -765,7 +832,45 @@ ProcessUtility(Node *parsetree, { VariableSetStmt *n = (VariableSetStmt *) parsetree; - SetPGVariable(n->name, n->args, n->is_local); + /* + * Special cases for special SQL syntax that + * effectively sets more than one variable per + * statement. + */ + if (strcmp(n->name, "TRANSACTION")==0) + { + List *head; + + foreach(head, n->args) + { + DefElem *item = (DefElem *) lfirst(head); + + if (strcmp(item->defname, "transaction_isolation")==0) + SetPGVariable("transaction_isolation", + makeList1(item->arg), n->is_local); + else if (strcmp(item->defname, "transaction_read_only")==0) + SetPGVariable("transaction_read_only", + makeList1(item->arg), n->is_local); + } + } + else if (strcmp(n->name, "SESSION CHARACTERISTICS")==0) + { + List *head; + + foreach(head, n->args) + { + DefElem *item = (DefElem *) lfirst(head); + + if (strcmp(item->defname, "transaction_isolation")==0) + SetPGVariable("default_transaction_isolation", + makeList1(item->arg), n->is_local); + else if (strcmp(item->defname, "transaction_read_only")==0) + SetPGVariable("default_transaction_read_only", + makeList1(item->arg), n->is_local); + } + } + else + SetPGVariable(n->name, n->args, n->is_local); } break; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index bd63313cd2..471d895ea7 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -5,7 +5,7 @@ * command, configuration file, and command line options. * See src/backend/utils/misc/README for more information. * - * $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.109 2002/12/27 14:06:34 momjian Exp $ + * $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.110 2003/01/10 22:03:29 petere Exp $ * * Copyright 2000 by PostgreSQL Global Development Group * Written by Peter Eisentraut . @@ -516,6 +516,14 @@ static struct config_bool {"autocommit", PGC_USERSET}, &autocommit, true, NULL, NULL }, + { + {"default_transaction_read_only", PGC_USERSET}, &DefaultXactReadOnly, + false, NULL, NULL + }, + { + {"transaction_read_only", PGC_USERSET, GUC_NO_RESET_ALL}, &XactReadOnly, + false, NULL, NULL + }, { {NULL, 0}, NULL, false, NULL, NULL @@ -841,7 +849,7 @@ static struct config_string }, { - {"TRANSACTION ISOLATION LEVEL", PGC_USERSET, GUC_NO_RESET_ALL}, + {"transaction_isolation", PGC_USERSET, GUC_NO_RESET_ALL}, &XactIsoLevel_string, NULL, assign_XactIsoLevel, show_XactIsoLevel }, @@ -1157,10 +1165,12 @@ InitializeGUCOptions(void) guc_string_workspace = NULL; /* - * Prevent any attempt to override TRANSACTION ISOLATION LEVEL from + * Prevent any attempt to override the transaction modes from * non-interactive sources. */ - SetConfigOption("TRANSACTION ISOLATION LEVEL", "default", + SetConfigOption("transaction_isolation", "default", + PGC_POSTMASTER, PGC_S_OVERRIDE); + SetConfigOption("transaction_read_only", "no", PGC_POSTMASTER, PGC_S_OVERRIDE); /* diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 2e45278426..e697253753 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3,7 +3,7 @@ * * Copyright 2000-2002 by PostgreSQL Global Development Group * - * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.70 2002/12/13 05:36:24 momjian Exp $ + * $Header: /cvsroot/pgsql/src/bin/psql/tab-complete.c,v 1.71 2003/01/10 22:03:30 petere Exp $ */ /*---------------------------------------------------------------------- @@ -212,13 +212,7 @@ psql_completion(char *text, int start, int end) "CONSTRAINTS", "NAMES", "SESSION", - "TRANSACTION ISOLATION LEVEL", - /* these are treated in backend/commands/variable.c */ - "DateStyle", - "TimeZone", - "client_encoding", - "server_encoding", - "seed", + "TRANSACTION", /* * the rest should match USERSET entries in @@ -229,12 +223,14 @@ psql_completion(char *text, int start, int end) "autocommit", "checkpoint_segments", "checkpoint_timeout", + "client_encoding", "client_min_messages", "commit_delay", "commit_siblings", "cpu_index_tuple_cost", "cpu_operator_cost", "cpu_tuple_cost", + "DateStyle", "db_user_namespace", "deadlock_timeout", "debug_pretty_print", @@ -269,7 +265,7 @@ psql_completion(char *text, int start, int end) "lc_messages", "lc_monetary", "lc_numeric", - "lc_timeC", + "lc_time", "log_connections", "log_duration", "log_min_error_statement", @@ -294,6 +290,8 @@ psql_completion(char *text, int start, int end) "log_planner_stats", "log_source_port", "log_statement_stats", + "seed", + "server_encoding", "silent_mode", "sort_mem", "sql_inheritance", @@ -309,6 +307,7 @@ psql_completion(char *text, int start, int end) "syslog_facility", "syslog_ident", "tcpip_socket", + "TimeZone", "trace_notify", "transform_null_equals", "unix_socket_directory", @@ -817,10 +816,18 @@ psql_completion(char *text, int start, int end) strcasecmp(prev_wd, "RESET") == 0 || strcasecmp(prev_wd, "SHOW") == 0) COMPLETE_WITH_LIST(pgsql_variables); - /* Complete "SET TRANSACTION ISOLOLATION LEVEL" */ - else if (strcasecmp(prev2_wd, "SET") == 0 && - strcasecmp(prev_wd, "TRANSACTION") == 0) - COMPLETE_WITH_CONST("ISOLATION"); + /* Complete "SET TRANSACTION" */ + else if ((strcasecmp(prev2_wd, "SET") == 0 && + strcasecmp(prev_wd, "TRANSACTION") == 0) || + (strcasecmp(prev4_wd, "SESSION") == 0 && + strcasecmp(prev3_wd, "CHARACTERISTICS") == 0 && + strcasecmp(prev2_wd, "AS") == 0 && + strcasecmp(prev_wd, "TRANSACTION") == 0)) + { + char *my_list[] = {"ISOLATION", "READ", NULL}; + + COMPLETE_WITH_LIST(my_list); + } else if (strcasecmp(prev3_wd, "SET") == 0 && strcasecmp(prev2_wd, "TRANSACTION") == 0 && strcasecmp(prev_wd, "ISOLATION") == 0) @@ -840,6 +847,15 @@ psql_completion(char *text, int start, int end) strcasecmp(prev2_wd, "LEVEL") == 0 && strcasecmp(prev_wd, "READ") == 0) COMPLETE_WITH_CONST("COMMITTED"); + else if ((strcasecmp(prev3_wd, "SET") == 0 || + strcasecmp(prev3_wd, "AS") == 0) && + strcasecmp(prev2_wd, "TRANSACTION") == 0 && + strcasecmp(prev_wd, "READ") == 0) + { + char *my_list[] = {"ONLY", "WRITE", NULL}; + + COMPLETE_WITH_LIST(my_list); + } /* Complete SET CONSTRAINTS with DEFERRED|IMMEDIATE */ else if (strcasecmp(prev3_wd, "SET") == 0 && strcasecmp(prev2_wd, "CONSTRAINTS") == 0) @@ -853,7 +869,7 @@ psql_completion(char *text, int start, int end) strcasecmp(prev_wd, "SESSION") == 0) { char *my_list[] = {"AUTHORIZATION", - "CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL", + "CHARACTERISTICS AS TRANSACTION", NULL}; COMPLETE_WITH_LIST(my_list); diff --git a/src/include/access/xact.h b/src/include/access/xact.h index fa30c3303b..b3938c869e 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: xact.h,v 1.48 2002/11/18 01:17:39 tgl Exp $ + * $Id: xact.h,v 1.49 2003/01/10 22:03:30 petere Exp $ * *------------------------------------------------------------------------- */ @@ -29,6 +29,9 @@ extern int DefaultXactIsoLevel; extern int XactIsoLevel; +extern bool DefaultXactReadOnly; +extern bool XactReadOnly; + /* ---------------- * transaction state structure diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 8bcd487670..efd5ae3278 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $Id: namespace.h,v 1.24 2003/01/07 20:56:07 tgl Exp $ + * $Id: namespace.h,v 1.25 2003/01/10 22:03:30 petere Exp $ * *------------------------------------------------------------------------- */ @@ -51,6 +51,7 @@ typedef struct _OpclassCandidateList extern Oid RangeVarGetRelid(const RangeVar *relation, bool failOK); extern Oid RangeVarGetCreationNamespace(const RangeVar *newRelation); extern Oid RelnameGetRelid(const char *relname); +extern Oid RelidGetNamespaceId(Oid relid); extern bool RelationIsVisible(Oid relid); extern Oid TypenameGetTypid(const char *typname); diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out index 46afaa3aad..b72ca5f36e 100644 --- a/src/test/regress/expected/transactions.out +++ b/src/test/regress/expected/transactions.out @@ -40,3 +40,31 @@ SELECT * FROM aggtest; 42 | 324.78 (4 rows) +-- Read-only tests +CREATE TABLE writetest (a int); +CREATE TEMPORARY TABLE temptest (a int); +SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY; +DROP TABLE writetest; -- fail +ERROR: transaction is read-only +INSERT INTO writetest VALUES (1); -- fail +ERROR: transaction is read-only +SELECT * FROM writetest; -- ok + a +--- +(0 rows) + +DELETE FROM temptest; -- ok +UPDATE temptest SET a = 0 WHERE a = 1 AND writetest.a = temptest.a; -- ok +PREPARE test AS UPDATE writetest SET a = 0; -- ok +EXECUTE test; -- fail +ERROR: transaction is read-only +SELECT * FROM writetest, temptest; -- ok + a | a +---+--- +(0 rows) + +CREATE TABLE test AS SELECT * FROM writetest; -- fail +ERROR: transaction is read-only +START TRANSACTION READ WRITE; +DROP TABLE writetest; -- ok +COMMIT; diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql index 6a84c6365c..10ef759998 100644 --- a/src/test/regress/sql/transactions.sql +++ b/src/test/regress/sql/transactions.sql @@ -33,3 +33,24 @@ SELECT oid FROM pg_class WHERE relname = 'disappear'; -- should have members again SELECT * FROM aggtest; + +-- Read-only tests + +CREATE TABLE writetest (a int); +CREATE TEMPORARY TABLE temptest (a int); + +SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY; + +DROP TABLE writetest; -- fail +INSERT INTO writetest VALUES (1); -- fail +SELECT * FROM writetest; -- ok +DELETE FROM temptest; -- ok +UPDATE temptest SET a = 0 WHERE a = 1 AND writetest.a = temptest.a; -- ok +PREPARE test AS UPDATE writetest SET a = 0; -- ok +EXECUTE test; -- fail +SELECT * FROM writetest, temptest; -- ok +CREATE TABLE test AS SELECT * FROM writetest; -- fail + +START TRANSACTION READ WRITE; +DROP TABLE writetest; -- ok +COMMIT;