Read-only transactions, as defined in SQL.
authorPeter Eisentraut
Fri, 10 Jan 2003 22:03:30 +0000 (22:03 +0000)
committerPeter Eisentraut
Fri, 10 Jan 2003 22:03:30 +0000 (22:03 +0000)
16 files changed:
doc/src/sgml/features.sgml
doc/src/sgml/ref/set_transaction.sgml
doc/src/sgml/ref/start_transaction.sgml
doc/src/sgml/release.sgml
src/backend/access/transam/xact.c
src/backend/catalog/namespace.c
src/backend/commands/copy.c
src/backend/executor/execMain.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/backend/utils/misc/guc.c
src/bin/psql/tab-complete.c
src/include/access/xact.h
src/include/catalog/namespace.h
src/test/regress/expected/transactions.out
src/test/regress/sql/transactions.sql

index fd01cd3cce62515d9fa2be8253eca695827afbae..9c7cc471935504028dea7898d33c4a20d80c7430 100644 (file)
@@ -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
index 315a999330f12478930a5e5931df1fdb5123c55e..f39e0eb7fe622af532ede620f11c57711b6fd971 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   2000-11-24
 
  
   
-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 (SELECTINSERT,
-   DELETEUPDATE,
-   FETCHCOPY) 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,
+   INSERTDELETE,
+   UPDATEFETCH,
+   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: INSERTUPDATE,
+   DELETE, and COPY TO if the
+   table they would write to is not a temporary table; all
+   CREATEALTER, and
+   DROP commands; COMMENT,
+   GRANTREVOKE,
+   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.
    
   
  
index fb8dd1319322a6e2074583b8b3af49c9b9bb265c..087a0df17746822fbbec80b847ef30e9b33a9dd4 100644 (file)
@@ -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 
+   endterm="sql-set-transaction-title"> was executed. In all other
+   respects, the behavior of this command is identical to the 
+   linkend="sql-begin" endterm="sql-begin-title"> command.
   
 
  
 
-  Notes
-
-  
-   The isolation level of a transaction can also be set with the 
-   linkend="sql-set-transaction" endterm="sql-set-transaction-title">
-   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 
+   linkend="sql-set-transaction" endterm="sql-set-transaction-title">.
+  
  
 
 
index 55007a7d627e8ac8c0ae0a18f440b4d24eb31de6..fd9a5e698f51a0bd7f4ad03e0ffa4565a972dff9 100644 (file)
@@ -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
 ]]>
 
  
index 0f30e13c84858cff1a27096d189f15d32be9d340..7150569a228950506d57399664772bbca46e9a89 100644 (file)
@@ -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
index 0c571ca2a4028ef7dd7a53bcd6e47f1da1709864..d58313a2ee073036311e1a1583c57981ce911799 100644 (file)
@@ -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
index fd8c6b83a82d3aa37f63cff00f1350db54ef6803..91386eeb2ccfa154e25c954926a839ebcffab6f9 100644 (file)
@@ -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);
index 613a62c3c372a83421fe6c735689e87d2c9630b0..a1a8134a6a713002d548b960837a5654f213dc1c 100644 (file)
@@ -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
  *
index fd33601cc08c98655ee9ebf352e1ee6726171d33..2703ae8a061399659acb07b16fe331dddc886f13 100644 (file)
@@ -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:
index 68c02c5f62d6a36aaf8d438a1736fcd8b7370ec7..45da9ba1c1d547836c5ecd98572493ccbb70c4f6 100644 (file)
@@ -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;
 
index bd63313cd23306647f99730013ab4299069ad5ab..471d895ea72dab384a46e2fbfe6f9d73d9b897b7 100644 (file)
@@ -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);
 
    /*
index 2e45278426c1211926a8fe4e12bfc46568998c77..e6972537536dc5fe3fbf86bacd1e1677333a5470 100644 (file)
@@ -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);
index fa30c3303b1b66618211dbdc8590a9dd75ec2a49..b3938c869e1f759a5df90b3ce6f7e16f37593935 100644 (file)
@@ -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
index 8bcd48767083edf20eb8a1903ca1456513a83d26..efd5ae3278671cd614923ec9c8491c29c6e2e5df 100644 (file)
@@ -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);
index 46afaa3aad4403dcfec365e62e392910a00ad152..b72ca5f36e58be9656adbd2f371b8f9c005059e7 100644 (file)
@@ -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;
index 6a84c6365ceb19efd5c3a16c1df6101ead2a335e..10ef759998bc610b05d0f70a2fbb8c3bca53de73 100644 (file)
@@ -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;