mirror of
https://github.com/postgres/postgres.git
synced 2025-07-15 19:21:59 +03:00
Read-only transactions, as defined in SQL.
This commit is contained in:
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user