aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/access/transam/xact.c6
-rw-r--r--src/backend/catalog/namespace.c26
-rw-r--r--src/backend/commands/copy.c6
-rw-r--r--src/backend/executor/execMain.c50
-rw-r--r--src/backend/parser/gram.y87
-rw-r--r--src/backend/tcop/utility.c125
-rw-r--r--src/backend/utils/misc/guc.c18
-rw-r--r--src/bin/psql/tab-complete.c44
-rw-r--r--src/include/access/xact.h5
-rw-r--r--src/include/catalog/namespace.h3
-rw-r--r--src/test/regress/expected/transactions.out28
-rw-r--r--src/test/regress/sql/transactions.sql21
12 files changed, 352 insertions, 67 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 0f30e13c848..7150569a228 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 0c571ca2a40..d58313a2ee0 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 $
*
*-------------------------------------------------------------------------
*/
@@ -279,6 +279,30 @@ RelnameGetRelid(const char *relname)
}
/*
+ * 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
* current search path. Visible means "would be found by searching
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index fd8c6b83a82..91386eeb2cc 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 613a62c3c37..a1a8134a6a7 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);
@@ -202,6 +203,14 @@ ExecutorRun(QueryDesc *queryDesc,
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
*/
estate->es_processed = 0;
@@ -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 fd33601cc08..2703ae8a061 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 <defelt> createdb_opt_item copy_opt_item
%type <ival> opt_lock lock_type cast_context
-%type <boolean> opt_force opt_or_replace
+%type <boolean> opt_force opt_or_replace transaction_access_mode
%type <list> 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 68c02c5f62d..45da9ba1c1d 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 bd63313cd23..471d895ea72 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 <peter_e@gmx.net>.
@@ -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 2e45278426c..e6972537536 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 <foo> 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 fa30c3303b1..b3938c869e1 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 8bcd4876708..efd5ae32786 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 46afaa3aad4..b72ca5f36e5 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 6a84c6365ce..10ef759998b 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;