Hi Most important features: >> >> 1. the values are stored in native types >> 2. access to content is protected by ACL - like the content of tables >> 3. the content is not MVCC based - no any cost of UPDATE >> 4. simple API allows access to content of variables from any supported >> environment. >> > > next update - setattr, getattr functions are working now >
new update - rebased after partitioning patch Regards Pavel > > notes, comments? > > Regards > > Pavel > > >> >> Regards >> >> Pavel >> >> >>> -- >>> Craig Ringer http://www.2ndQuadrant.com/ >>> PostgreSQL Development, 24x7 Support, Training & Services >>> >> >> >
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 3086021..6bd88b8 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -274,6 +274,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, case ACL_KIND_TYPE: whole_mask = ACL_ALL_RIGHTS_TYPE; break; + case ACL_KIND_VARIABLE: + whole_mask = ACL_ALL_RIGHTS_VARIABLE; + break; default: elog(ERROR, "unrecognized object kind: %d", objkind); /* not reached, but keep compiler quiet */ @@ -488,6 +491,10 @@ ExecuteGrantStmt(GrantStmt *stmt) all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER; errormsg = gettext_noop("invalid privilege type %s for foreign server"); break; + case ACL_OBJECT_VARIABLE: + all_privileges = ACL_ALL_RIGHTS_VARIABLE; + errormsg = gettext_noop("invalid privilege type %s for variable"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) stmt->objtype); @@ -558,6 +565,7 @@ ExecGrantStmt_oids(InternalGrant *istmt) { case ACL_OBJECT_RELATION: case ACL_OBJECT_SEQUENCE: + case ACL_OBJECT_VARIABLE: ExecGrant_Relation(istmt); break; case ACL_OBJECT_DATABASE: @@ -625,6 +633,7 @@ objectNamesToOids(GrantObjectType objtype, List *objnames) { case ACL_OBJECT_RELATION: case ACL_OBJECT_SEQUENCE: + case ACL_OBJECT_VARIABLE: foreach(cell, objnames) { RangeVar *relvar = (RangeVar *) lfirst(cell); @@ -775,6 +784,10 @@ objectsInSchemaToOids(GrantObjectType objtype, List *nspnames) objs = getRelationsInNamespace(namespaceId, RELKIND_SEQUENCE); objects = list_concat(objects, objs); break; + case ACL_OBJECT_VARIABLE: + objs = getRelationsInNamespace(namespaceId, RELKIND_VARIABLE); + objects = list_concat(objects, objs); + break; case ACL_OBJECT_FUNCTION: { ScanKeyData key[1]; @@ -950,6 +963,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s all_privileges = ACL_ALL_RIGHTS_TYPE; errormsg = gettext_noop("invalid privilege type %s for type"); break; + case ACL_OBJECT_VARIABLE: + all_privileges = ACL_ALL_RIGHTS_VARIABLE; + errormsg = gettext_noop("invalid privilege type %s for variable"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) action->objtype); @@ -1137,6 +1154,12 @@ SetDefaultACL(InternalDefaultACL *iacls) this_privileges = ACL_ALL_RIGHTS_TYPE; break; + case ACL_OBJECT_VARIABLE: + objtype = DEFACLOBJ_VARIABLE; + if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS) + this_privileges = ACL_ALL_RIGHTS_VARIABLE; + break; + default: elog(ERROR, "unrecognized objtype: %d", (int) iacls->objtype); @@ -1363,6 +1386,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) case DEFACLOBJ_TYPE: iacls.objtype = ACL_OBJECT_TYPE; break; + case DEFACLOBJ_VARIABLE: + iacls.objtype = ACL_OBJECT_VARIABLE; + break; default: /* Shouldn't get here */ elog(ERROR, "unexpected default ACL type: %d", @@ -1710,7 +1736,7 @@ ExecGrant_Attribute(InternalGrant *istmt, Oid relOid, const char *relname, } /* - * This processes both sequences and non-sequences. + * This processes both sequences, variables and others. */ static void ExecGrant_Relation(InternalGrant *istmt) @@ -1767,11 +1793,21 @@ ExecGrant_Relation(InternalGrant *istmt) errmsg("\"%s\" is not a sequence", NameStr(pg_class_tuple->relname)))); + /* Used GRANT VARIABLE on a non-variable? */ + if (istmt->objtype == ACL_OBJECT_VARIABLE && + pg_class_tuple->relkind != RELKIND_VARIABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a variable", + NameStr(pg_class_tuple->relname)))); + /* Adjust the default permissions based on object type */ if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS) { if (pg_class_tuple->relkind == RELKIND_SEQUENCE) this_privileges = ACL_ALL_RIGHTS_SEQUENCE; + else if (pg_class_tuple->relkind == RELKIND_VARIABLE) + this_privileges = ACL_ALL_RIGHTS_VARIABLE; else this_privileges = ACL_ALL_RIGHTS_RELATION; } @@ -1864,6 +1900,9 @@ ExecGrant_Relation(InternalGrant *istmt) case RELKIND_SEQUENCE: old_acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId); break; + case RELKIND_VARIABLE: + old_acl = acldefault(ACL_OBJECT_VARIABLE, ownerId); + break; default: old_acl = acldefault(ACL_OBJECT_RELATION, ownerId); break; @@ -1908,6 +1947,9 @@ ExecGrant_Relation(InternalGrant *istmt) case RELKIND_SEQUENCE: aclkind = ACL_KIND_SEQUENCE; break; + case RELKIND_VARIABLE: + aclkind = ACL_KIND_VARIABLE; + break; default: aclkind = ACL_KIND_CLASS; break; @@ -3343,6 +3385,8 @@ static const char *const no_priv_msg[MAX_ACL_KIND] = gettext_noop("permission denied for event trigger %s"), /* ACL_KIND_EXTENSION */ gettext_noop("permission denied for extension %s"), + /* ACL_KIND_VARIABLE */ + gettext_noop("permission denied for variable %s"), }; static const char *const not_owner_msg[MAX_ACL_KIND] = @@ -3389,6 +3433,8 @@ static const char *const not_owner_msg[MAX_ACL_KIND] = gettext_noop("must be owner of event trigger %s"), /* ACL_KIND_EXTENSION */ gettext_noop("must be owner of extension %s"), + /* ACL_KIND_VARIABLE */ + gettext_noop("must be owner of variable %s"), }; @@ -3474,6 +3520,7 @@ pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum, Oid roleid, pg_attribute_aclmask(table_oid, attnum, roleid, mask, how); case ACL_KIND_CLASS: case ACL_KIND_SEQUENCE: + case ACL_KIND_VARIABLE: return pg_class_aclmask(table_oid, roleid, mask, how); case ACL_KIND_DATABASE: return pg_database_aclmask(table_oid, roleid, mask, how); @@ -3681,6 +3728,9 @@ pg_class_aclmask(Oid table_oid, Oid roleid, case RELKIND_SEQUENCE: acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId); break; + case RELKIND_VARIABLE: + acl = acldefault(ACL_OBJECT_VARIABLE, ownerId); + break; default: acl = acldefault(ACL_OBJECT_RELATION, ownerId); break; @@ -5191,6 +5241,10 @@ get_user_default_acl(GrantObjectType objtype, Oid ownerId, Oid nsp_oid) defaclobjtype = DEFACLOBJ_TYPE; break; + case ACL_OBJECT_VARIABLE: + defaclobjtype = DEFACLOBJ_VARIABLE; + break; + default: return NULL; } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index c09c9f2..818f866 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -291,6 +291,7 @@ heap_create(const char *relname, case RELKIND_VIEW: case RELKIND_COMPOSITE_TYPE: case RELKIND_FOREIGN_TABLE: + case RELKIND_VARIABLE: create_storage = false; /* @@ -421,10 +422,10 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind, /* * first check for collision with system attribute names * - * Skip this for a view or type relation, since those don't have system + * Skip this for a view, variable or type relation, since those don't have system * attributes. */ - if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE) + if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_VARIABLE) { for (i = 0; i < natts; i++) { @@ -1115,7 +1116,7 @@ heap_create_with_catalog(const char *relname, (relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE || relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW || relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE || - relkind == RELKIND_PARTITIONED_TABLE)) + relkind == RELKIND_PARTITIONED_TABLE || relkind == RELKIND_VARIABLE)) { if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid)) ereport(ERROR, @@ -1157,6 +1158,10 @@ heap_create_with_catalog(const char *relname, relacl = get_user_default_acl(ACL_OBJECT_SEQUENCE, ownerid, relnamespace); break; + case RELKIND_VARIABLE: + relacl = get_user_default_acl(ACL_OBJECT_VARIABLE, ownerid, + relnamespace); + break; default: relacl = NULL; break; diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index bb4b080..d66d6fa 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -656,6 +656,10 @@ static const struct object_type_map /* OCLASS_TRANSFORM */ { "transform", OBJECT_TRANSFORM + }, + /* OCLASS_VARIABLE */ + { + "variable", OBJECT_VARIABLE } }; @@ -762,6 +766,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, case OBJECT_VIEW: case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: + case OBJECT_VARIABLE: address = get_relation_by_qualified_name(objtype, objname, &relation, lockmode, @@ -1232,6 +1237,12 @@ get_relation_by_qualified_name(ObjectType objtype, List *objname, errmsg("\"%s\" is not a foreign table", RelationGetRelationName(relation)))); break; + case OBJECT_VARIABLE: + if (relation->rd_rel->relkind != RELKIND_VARIABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a variable", + RelationGetRelationName(relation)))); default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); break; @@ -2081,6 +2092,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, case OBJECT_TRIGGER: case OBJECT_POLICY: case OBJECT_TABCONSTRAINT: + case OBJECT_VARIABLE: if (!pg_class_ownercheck(RelationGetRelid(relation), roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, RelationGetRelationName(relation)); @@ -3277,6 +3289,10 @@ getRelationDescription(StringInfo buffer, Oid relid) appendStringInfo(buffer, _("foreign table %s"), relname); break; + case RELKIND_VARIABLE: + appendStringInfo(buffer, _("variable %s"), + relname); + break; default: /* shouldn't get here */ appendStringInfo(buffer, _("relation %s"), diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 6b3742c..9be5174 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -18,8 +18,8 @@ OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \ policy.o portalcmds.o prepare.o proclang.o \ - schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ - tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \ - variable.o view.o + schemacmds.o seclabel.o sequence.o session_variable.o tablecmds.o \ + tablespace.o trigger.o tsearchcmds.o typecmds.o user.o vacuum.o \ + vacuumlazy.o variable.o view.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 03c0433..9e76b2d 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -329,6 +329,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_MATVIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: + case OBJECT_VARIABLE: return RenameRelation(stmt); case OBJECT_COLUMN: @@ -454,6 +455,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, case OBJECT_TABLE: case OBJECT_VIEW: case OBJECT_MATVIEW: + case OBJECT_VARIABLE: address = AlterTableNamespace(stmt, oldSchemaAddr ? &oldNspOid : NULL); break; diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c index 5b8bd67..143e938 100644 --- a/src/backend/commands/discard.c +++ b/src/backend/commands/discard.c @@ -19,6 +19,7 @@ #include "commands/discard.h" #include "commands/prepare.h" #include "commands/sequence.h" +#include "commands/session_variable.h" #include "utils/guc.h" #include "utils/portal.h" @@ -75,4 +76,5 @@ DiscardAll(bool isTopLevel) ResetPlanCache(); ResetTempTableNamespace(); ResetSequenceCaches(); + ResetVariablesCache(); } diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index e87fce7..0b8ce34 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -122,6 +122,7 @@ static event_trigger_support_data event_trigger_support[] = { {"TYPE", true}, {"USER MAPPING", true}, {"VIEW", true}, + {"VARIABLE", true}, {NULL, false} }; @@ -1117,6 +1118,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_TYPE: case OBJECT_USER_MAPPING: case OBJECT_VIEW: + case OBJECT_VARIABLE: return true; } return true; @@ -1195,6 +1197,7 @@ EventTriggerSupportsGrantObjectType(GrantObjectType objtype) case ACL_OBJECT_LARGEOBJECT: case ACL_OBJECT_NAMESPACE: case ACL_OBJECT_TYPE: + case ACL_OBJECT_VARIABLE: return true; default: Assert(false); @@ -2221,6 +2224,8 @@ stringify_grantobjtype(GrantObjectType objtype) return "TABLESPACE"; case ACL_OBJECT_TYPE: return "TYPE"; + case ACL_OBJECT_VARIABLE: + return "VARIABLE"; default: elog(ERROR, "unrecognized type %d", objtype); return "???"; /* keep compiler quiet */ @@ -2248,6 +2253,8 @@ stringify_adefprivs_objtype(GrantObjectType objtype) case ACL_OBJECT_TYPE: return "TYPES"; break; + case ACL_OBJECT_VARIABLE: + return "VARIABLES"; default: elog(ERROR, "unrecognized type %d", objtype); return "???"; /* keep compiler quiet */ diff --git a/src/backend/commands/session_variable.c b/src/backend/commands/session_variable.c new file mode 100644 index 0000000..9422b53 --- /dev/null +++ b/src/backend/commands/session_variable.c @@ -0,0 +1,499 @@ +/*------------------------------------------------------------------------- + * + * session_variable.c + * PostgreSQL session variable support code. + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/session_variable.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/dependency.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_type.h" +#include "commands/defrem.h" +#include "commands/session_variable.h" +#include "commands/tablecmds.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/makefuncs.h" +#include "parser/parse_type.h" +#include "storage/lmgr.h" +#include "storage/proc.h" +#include "storage/smgr.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/memutils.h" +#include "utils/lsyscache.h" +#include "utils/resowner.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +typedef struct VarTableData +{ + Oid varid; /* pg_class OID of this sequence (hash key) */ + Oid typid; /* OID of the data type */ + int32 typmod; + int16 typlen; + bool typbyval; + bool isnull; + bool freeval; + Datum value; + TupleDesc tupdesc; /* when value is composite, then related tuple desc */ +} VarTableData; + +typedef VarTableData *VarTable; + +static HTAB *varhashtab = NULL; /* hash table for session variables */ + +/* + * Create the hash table for storing session variables + */ +static void +create_var_hashtable(void) +{ + HASHCTL ctl; + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(VarTableData); + + varhashtab = hash_create("Session variables", 64, &ctl, + HASH_ELEM | HASH_BLOBS); +} + +static void +SetAttribute(VarTable var, const char *attrname, Datum value, bool isNull, + Oid typid, int32 typmod, + int16 typlen, bool typbyval, char typalign) +{ + AttrNumber attrno; + HeapTuple tuple; + HeapTupleHeader result; + MemoryContext oldcxt; + TupleDesc tupDesc; + Datum *values; + bool *nulls; + int natts; + int i; + + /* fast leaving, when there are not any change */ + if (var->isnull && isNull) + return; + + tupDesc = lookup_rowtype_tupdesc(var->typid, var->typmod); + natts = tupDesc->natts; + + attrno = InvalidAttrNumber; + for (i = 0; i < natts; i++) + { + if (namestrcmp(&(tupDesc->attrs[i]->attname), attrname) == 0 && + !tupDesc->attrs[i]->attisdropped) + { + attrno = tupDesc->attrs[i]->attnum; + if (typid != tupDesc->attrs[i]->atttypid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("variable has wrong type"), + errdetail("value has type %s, but query expects %s.", + format_type_be(tupDesc->attrs[i]->atttypid), + format_type_be(typid)))); + break; + } + } + + if (attrno == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute \"%s\" of variable \"%s\" does not exist", + attrname, get_rel_name(var->varid)))); + + values = (Datum *) palloc0(natts * sizeof(Datum)); + nulls = (bool *) palloc0(natts * sizeof(bool)); + + if (!var->isnull) + { + HeapTupleHeader rec = DatumGetHeapTupleHeader(var->value); + HeapTupleData tup; + + /* Build a temporary HeapTuple control structure */ + tup.t_len = HeapTupleHeaderGetDatumLength(rec); + ItemPointerSetInvalid(&(tup.t_self)); + tup.t_tableOid = InvalidOid; + tup.t_data = rec; + + heap_deform_tuple(&tup, tupDesc, values, nulls); + } + else + { + for(i = 0; i < natts; i++) + nulls[i] = true; + } + + values[attrno - 1] = value; + nulls[attrno - 1] = isNull; + + tuple = heap_form_tuple(tupDesc, values, nulls); + + /* alloc result in persistent memory */ + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + result = (HeapTupleHeader) palloc(tuple->t_len); + MemoryContextSwitchTo(oldcxt); + + memcpy(result, tuple->t_data, tuple->t_len); + + heap_freetuple(tuple); + pfree(values); + pfree(nulls); + + if (var->freeval) + pfree(DatumGetPointer(var->value)); + + var->value = HeapTupleHeaderGetDatum(result); + var->isnull = false; + var->freeval = true; + var->typid = tupDesc->tdtypeid; + var->typmod = tupDesc->tdtypmod; + + ReleaseTupleDesc(tupDesc); + + return; +} + +static void +SetValue(VarTable var, Datum value, bool isNull, + Oid typid, int32 typmod, + int16 typlen, bool typbyval, char typalign) +{ + if (var->freeval) + { + pfree(DatumGetPointer(var->value)); + var->freeval = false; + } + + if (!isNull) + { + MemoryContext oldcxt; + + var->isnull = false; + var->typid = typid; + var->typmod = typmod; + var->typbyval = typbyval; + var->typlen = typlen; + + oldcxt = MemoryContextSwitchTo(TopMemoryContext); + + var->value = datumCopy(value, typbyval, typlen); + if (var->value != value) + var->freeval = true; + + MemoryContextSwitchTo(oldcxt); + } + else + { + var->value = (Datum) 0; + var->isnull = true; + } +} + +/* + * Access functions to session variables. + */ +void +SetSessionVariable(Oid varid, char *attrname, Datum value, bool isNull, + Oid typid, int32 typmod, + int16 typlen, bool typbyval, char typalign) +{ + VarTable var; + bool found; + + if (varhashtab == NULL) + { + /* don't init hashtable for NULL values */ + if (isNull) + return; + + create_var_hashtable(); + } + + var = (VarTable) hash_search(varhashtab, &varid, HASH_ENTER, &found); + if (!found) + { + var->value = (Datum) 0; + var->isnull = true; + var->freeval = false; + } + + if (attrname != NULL) + SetAttribute(var, attrname, value, isNull, + typid, typmod, typlen, typbyval, typalign); + else + SetValue(var, value, isNull, typid, typmod, + typlen, typbyval, typalign); + +} + +Datum +GetSessionVariable(Oid varid, char *attrname, bool *isNull, + Oid typid, int32 typmod, + int16 typlen, bool typbyval, char typalign) +{ + VarTable var; + Datum result = (Datum) 0; + Oid result_typid; + bool found; + + if (varhashtab == NULL) + create_var_hashtable(); + + var = (VarTable) hash_search(varhashtab, &varid, HASH_FIND, &found); + if (found) + { + if (!var->isnull) + { + if (attrname != NULL) + { + /* This code is based on GetAttributeBy... function */ + TupleDesc tupDesc; + HeapTupleHeader tuple; + AttrNumber attrno; + HeapTupleData tmptup; + int i; + + tuple = DatumGetHeapTupleHeader(var->value); + tupDesc = lookup_rowtype_tupdesc(var->typid, var->typmod); + + attrno = InvalidAttrNumber; + for (i = 0; i < tupDesc->natts; i++) + { + if (namestrcmp(&(tupDesc->attrs[i]->attname), attrname) == 0 && + !tupDesc->attrs[i]->attisdropped) + { + attrno = tupDesc->attrs[i]->attnum; + result_typid = tupDesc->attrs[i]->atttypid; + break; + } + } + + if (attrno == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute \"%s\" of variable \"%s\" does not exist", + attrname, get_rel_name(varid)))); + + /* + * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all + * the fields in the struct just in case user tries to inspect system + * columns. + */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = tuple; + + result = heap_getattr(&tmptup, + attrno, + tupDesc, + isNull); + + ReleaseTupleDesc(tupDesc); + } + else + { + result = datumCopy(var->value, var->typbyval, var->typlen); + result_typid = var->typid; + } + + *isNull = false; + } + else + *isNull = true; + } + else + *isNull = true; + + /* recheck result */ + if (!*isNull && result_typid != typid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("variable has wrong type"), + errdetail("value has type %s, but query expects %s.", + format_type_be(result_typid), + format_type_be(typid)))); + + return result; +} + +/* + * DefineSessionVariable + * Creates a new variable related relation + */ +ObjectAddress +DefineSessionVariable(ParseState *pstate, CreateVarStmt *var) +{ + CreateStmt *stmt = makeNode(CreateStmt); + Oid varoid; + ObjectAddress address; + Type tup; + int32 typmod; + Form_pg_type typeStruct; + bool is_composite; + Oid typrelid = InvalidOid; + + /* + * If if_not_exists was given and a relation with the same name already + * exists, bail out. (Note: we needn't check this when not if_not_exists, + * because DefineRelation will complain anyway.) + */ + if (var->if_not_exists) + { + RangeVarGetAndCheckCreationNamespace(var->variable, NoLock, &varoid); + if (OidIsValid(varoid)) + { + ereport(NOTICE, + (errcode(ERRCODE_DUPLICATE_TABLE), + errmsg("relation \"%s\" already exists, skipping", + var->variable->relname))); + return InvalidObjectAddress; + } + } + + tup = LookupTypeName(pstate, var->typeName, &typmod, false); + if (tup == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("type \"%s\" does not exist", + TypeNameToString(var->typeName)), + parser_errposition(pstate, var->typeName->location))); + + typeStruct = (Form_pg_type) GETSTRUCT(tup); + //typoid = HeapTupleGetOid(tup); + + switch (typeStruct->typtype) + { + case TYPTYPE_BASE: + case TYPTYPE_DOMAIN: + case TYPTYPE_ENUM: + case TYPTYPE_RANGE: + is_composite = false; + break; + case TYPTYPE_COMPOSITE: + Assert(OidIsValid(typeStruct->typrelid)); + typrelid = typeStruct->typrelid; + is_composite = true; + break; + case TYPTYPE_PSEUDO: + elog(ERROR, "variable cannot cannot be pseudotype"); + default: + elog(ERROR, "unrecognized typtype: %d", + (int) typeStruct->typtype); + } + + ReleaseSysCache(tup); + + stmt->tableElts = NIL; + + /* + * When variable type is not composite, then the related relation will have + * one column named "value", else the created relation is based on unpacked + * composite type. + */ + if (!is_composite) + { + ColumnDef *coldef = makeNode(ColumnDef); + + coldef->inhcount = 0; + coldef->is_local = true; + coldef->is_not_null = false; + coldef->is_from_type = false; + coldef->storage = 0; + coldef->raw_default = NULL; + coldef->cooked_default = NULL; + coldef->collClause = NULL; + coldef->collOid = InvalidOid; + coldef->constraints = NIL; + coldef->location = -1; + + coldef->colname = "value"; + coldef->typeName = var->typeName; + + stmt->tableElts = lappend(stmt->tableElts, coldef); + } + else + { + TupleDesc tupdesc; + Relation rel; + int i; + + rel = relation_open(typrelid, AccessShareLock); + tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); + + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute attrStruct; + + attrStruct = tupdesc->attrs[i]; + + if (!attrStruct->attisdropped) + { + ColumnDef *coldef = makeNode(ColumnDef); + + coldef->inhcount = 0; + coldef->is_local = true; + coldef->is_not_null = false; + coldef->is_from_type = false; + coldef->storage = 0; + coldef->raw_default = NULL; + coldef->cooked_default = NULL; + coldef->collClause = NULL; + coldef->collOid = InvalidOid; + coldef->constraints = NIL; + coldef->location = -1; + + coldef->colname = pstrdup(NameStr(attrStruct->attname)); + coldef->typeName = makeTypeNameFromOid(attrStruct->atttypid, + attrStruct->atttypmod); + + stmt->tableElts = lappend(stmt->tableElts, coldef); + } + } + + FreeTupleDesc(tupdesc); + relation_close(rel, AccessShareLock); + + stmt->ofTypename = var->typeName; + } + + stmt->relation = var->variable; + stmt->inhRelations = NIL; + stmt->constraints = NIL; + stmt->options = NIL; + stmt->oncommit = ONCOMMIT_NOOP; + stmt->tablespacename = NULL; + stmt->if_not_exists = var->if_not_exists; + + address = DefineRelation(stmt, RELKIND_VARIABLE, InvalidOid, NULL, NULL); + varoid = address.objectId; + Assert(varoid != InvalidOid); + + return address; +} + +void +ResetVariablesCache(void) +{ + if (varhashtab) + { + hash_destroy(varhashtab); + varhashtab = NULL; + } +} \ No newline at end of file diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 7a574dc..feccf7c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -263,6 +263,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = { gettext_noop("table \"%s\" does not exist, skipping"), gettext_noop("\"%s\" is not a table"), gettext_noop("Use DROP TABLE to remove a table.")}, + {RELKIND_VARIABLE, + ERRCODE_UNDEFINED_TABLE, + gettext_noop("variable \"%s\" does not exist"), + gettext_noop("variable \"%s\" does not exist, skipping"), + gettext_noop("\"%s\" is not a variable"), + gettext_noop("Use DROP VARIABLE to remove a variable.")}, {'\0', 0, NULL, NULL, NULL, NULL} }; @@ -1010,6 +1016,10 @@ RemoveRelations(DropStmt *drop) relkind = RELKIND_FOREIGN_TABLE; break; + case OBJECT_VARIABLE: + relkind = RELKIND_VARIABLE; + break; + default: elog(ERROR, "unrecognized drop object type: %d", (int) drop->removeType); @@ -12702,6 +12712,11 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not an index", rv->relname))); + if (reltype == OBJECT_VARIABLE && relkind != RELKIND_VARIABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a variable", rv->relname))); + /* * Don't allow ALTER TABLE on composite types. We want people to use ALTER * TYPE for that. diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index d43a204..182bb12 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -1036,6 +1036,11 @@ CheckValidResultRel(Relation resultRel, CmdType operation) errmsg("cannot change TOAST relation \"%s\"", RelationGetRelationName(resultRel)))); break; + case RELKIND_VARIABLE: + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot change session variable \"%s\"", + RelationGetRelationName(resultRel)))); case RELKIND_VIEW: /* @@ -1165,6 +1170,13 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType) errmsg("cannot lock rows in sequence \"%s\"", RelationGetRelationName(rel)))); break; + case RELKIND_VARIABLE: + /* Must disallow this because we cannot locks session variables */ + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot lock rows in session variable \"%s\"", + RelationGetRelationName(rel)))); + break; case RELKIND_TOASTVALUE: /* We could allow this, but there seems no good reason to */ ereport(ERROR, diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 743e7d6..a18620e 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -41,6 +41,7 @@ #include "access/tupconvert.h" #include "catalog/objectaccess.h" #include "catalog/pg_type.h" +#include "commands/session_variable.h" #include "executor/execdebug.h" #include "executor/nodeSubplan.h" #include "funcapi.h" @@ -189,7 +190,9 @@ static Datum ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, static Datum ExecEvalGroupingFuncExpr(GroupingFuncExprState *gstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone); - +static Datum ExecEvalUseVarExpr(UseVarExprState *usevarstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone); /* ---------------------------------------------------------------- * ExecEvalExpr routines @@ -3994,6 +3997,44 @@ ExecEvalNullTest(NullTestState *nstate, } /* ---------------------------------------------------------------- + * ExecEvalUseVarExpr + * + * Evaluate a access to session variable node. + * ---------------------------------------------------------------- + */ +static Datum +ExecEvalUseVarExpr(UseVarExprState *usevarstate, + ExprContext *econtext, + bool *isNull, ExprDoneCond *isDone) +{ + Datum value = (Datum) 0; + + if (isDone) + *isDone = ExprSingleResult; + + if (usevarstate->expr != NULL) + { + /* setvar, setattr */ + value = ExecEvalExpr(usevarstate->expr, econtext, isNull, NULL); + + SetSessionVariable(usevarstate->varid, usevarstate->attrname, + value, *isNull, + usevarstate->typid, usevarstate->typmod, + usevarstate->typlen, usevarstate->typbyval, usevarstate->typalign); + } + else + { + /* getvar, getattr */ + value = GetSessionVariable(usevarstate->varid, usevarstate->attrname, + isNull, + usevarstate->typid, usevarstate->typmod, + usevarstate->typlen, usevarstate->typbyval, usevarstate->typalign); + } + + return value; +} + +/* ---------------------------------------------------------------- * ExecEvalBooleanTest * * Evaluate a BooleanTest node. @@ -5197,6 +5238,34 @@ ExecInitExpr(Expr *node, PlanState *parent) state = (ExprState *) xstate; } break; + case T_UseVarExpr: + { + AclResult aclresult; + UseVarExpr *uvexpr = (UseVarExpr *) node; + UseVarExprState *uvstate = makeNode(UseVarExprState); + + /* Recheck permissions */ + aclresult = pg_class_aclcheck(uvexpr->varid, GetUserId(), + uvexpr->expr != NULL ? ACL_UPDATE : ACL_SELECT); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_VARIABLE, + get_rel_name(uvexpr->varid)); + + uvstate->varid = uvexpr->varid; + uvstate->attrname = uvexpr->attrname; + + /* get info about expected type */ + uvstate->typid = uvexpr->typid; + uvstate->typmod = uvexpr->typmod; + get_typlenbyvalalign(uvexpr->typid, + &uvstate->typlen, &uvstate->typbyval, &uvstate->typalign); + + uvstate->expr = ExecInitExpr((Expr *) uvexpr->expr, parent); + uvstate->xprstate.evalfunc = (ExprStateEvalFunc) ExecEvalUseVarExpr; + + state = (ExprState *) uvstate; + } + break; case T_NullTest: { NullTest *ntest = (NullTest *) node; diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index a3bcb10..020e942 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -802,6 +802,15 @@ ExecOpenScanRelation(EState *estate, Index scanrelid, int eflags) rel = heap_open(reloid, lockmode); /* + * In this moment, the session variables relation is not scannable + */ + if (rel->rd_rel->relkind == RELKIND_VARIABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot read session variable \"%s\" as relation", + RelationGetRelationName(rel)))); + + /* * Complain if we're attempting a scan of an unscannable relation, except * when the query won't actually be run. This is a slightly klugy place * to do this, perhaps, but there is no better place. diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index d973225..b03b6fc 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1987,6 +1987,25 @@ _copyOnConflictExpr(const OnConflictExpr *from) return newnode; } +/* + * _SetVarExpr + */ +static UseVarExpr * +_copyUseVarExpr(const UseVarExpr *from) +{ + UseVarExpr *newnode = makeNode(UseVarExpr); + + COPY_SCALAR_FIELD(varid); + COPY_STRING_FIELD(attrname); + COPY_NODE_FIELD(expr); + COPY_SCALAR_FIELD(typid); + COPY_SCALAR_FIELD(typmod); + COPY_SCALAR_FIELD(collation); + COPY_LOCATION_FIELD(location); + + return newnode; +} + /* **************************************************************** * relation.h copy functions * @@ -2455,6 +2474,19 @@ _copyA_ArrayExpr(const A_ArrayExpr *from) return newnode; } +static UseVar * +_copyUseVar(const UseVar *from) +{ + UseVar *newnode = makeNode(UseVar); + + COPY_STRING_FIELD(name); + COPY_STRING_FIELD(attrname); + COPY_NODE_FIELD(expr); + COPY_LOCATION_FIELD(location); + + return newnode; +} + static ResTarget * _copyResTarget(const ResTarget *from) { @@ -3630,6 +3662,18 @@ _copyAlterSeqStmt(const AlterSeqStmt *from) return newnode; } +static CreateVarStmt * +_copyCreateVarStmt(const CreateVarStmt *from) +{ + CreateVarStmt *newnode = makeNode(CreateVarStmt); + + COPY_NODE_FIELD(variable); + COPY_NODE_FIELD(typeName); + COPY_SCALAR_FIELD(if_not_exists); + + return newnode; +} + static VariableSetStmt * _copyVariableSetStmt(const VariableSetStmt *from) { @@ -4666,6 +4710,9 @@ copyObject(const void *from) case T_OnConflictExpr: retval = _copyOnConflictExpr(from); break; + case T_UseVarExpr: + retval = _copyUseVarExpr(from); + break; /* * RELATION NODES @@ -4912,6 +4959,9 @@ copyObject(const void *from) case T_AlterSeqStmt: retval = _copyAlterSeqStmt(from); break; + case T_CreateVarStmt: + retval = _copyCreateVarStmt(from); + break; case T_VariableSetStmt: retval = _copyVariableSetStmt(from); break; @@ -5074,6 +5124,9 @@ copyObject(const void *from) case T_A_ArrayExpr: retval = _copyA_ArrayExpr(from); break; + case T_UseVar: + retval = _copyUseVar(from); + break; case T_ResTarget: retval = _copyResTarget(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index edc1797..af275bc 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -783,6 +783,20 @@ _equalOnConflictExpr(const OnConflictExpr *a, const OnConflictExpr *b) return true; } +static bool +_equalUseVarExpr(const UseVarExpr *a, const UseVarExpr *b) +{ + COMPARE_SCALAR_FIELD(varid); + COMPARE_STRING_FIELD(attrname); + COMPARE_NODE_FIELD(expr); + COMPARE_SCALAR_FIELD(typid); + COMPARE_SCALAR_FIELD(typmod); + COMPARE_SCALAR_FIELD(collation); + COMPARE_LOCATION_FIELD(location); + + return true; +} + /* * Stuff from relation.h */ @@ -1673,6 +1687,17 @@ _equalAlterSeqStmt(const AlterSeqStmt *a, const AlterSeqStmt *b) } static bool +_equalCreateVarStmt(const CreateVarStmt *a, const CreateVarStmt *b) +{ + COMPARE_NODE_FIELD(variable); + COMPARE_NODE_FIELD(typeName); + COMPARE_SCALAR_FIELD(if_not_exists); + + return true; +} + + +static bool _equalVariableSetStmt(const VariableSetStmt *a, const VariableSetStmt *b) { COMPARE_SCALAR_FIELD(kind); @@ -2239,6 +2264,17 @@ _equalA_ArrayExpr(const A_ArrayExpr *a, const A_ArrayExpr *b) } static bool +_equalUseVar(const UseVar *a, const UseVar *b) +{ + COMPARE_STRING_FIELD(name); + COMPARE_STRING_FIELD(attrname); + COMPARE_NODE_FIELD(expr); + COMPARE_LOCATION_FIELD(location); + + return true; +} + +static bool _equalResTarget(const ResTarget *a, const ResTarget *b) { COMPARE_STRING_FIELD(name); @@ -2962,6 +2998,9 @@ equal(const void *a, const void *b) case T_OnConflictExpr: retval = _equalOnConflictExpr(a, b); break; + case T_UseVarExpr: + retval = _equalUseVarExpr(a, b); + break; case T_JoinExpr: retval = _equalJoinExpr(a, b); break; @@ -3198,6 +3237,9 @@ equal(const void *a, const void *b) case T_AlterSeqStmt: retval = _equalAlterSeqStmt(a, b); break; + case T_CreateVarStmt: + retval = _equalCreateVarStmt(a, b); + break; case T_VariableSetStmt: retval = _equalVariableSetStmt(a, b); break; @@ -3360,6 +3402,9 @@ equal(const void *a, const void *b) case T_A_ArrayExpr: retval = _equalA_ArrayExpr(a, b); break; + case T_UseVar: + retval = _equalUseVar(a, b); + break; case T_ResTarget: retval = _equalResTarget(a, b); break; diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c index 973fb15..69a111d 100644 --- a/src/backend/nodes/nodeFuncs.c +++ b/src/backend/nodes/nodeFuncs.c @@ -257,6 +257,9 @@ exprType(const Node *expr) case T_PlaceHolderVar: type = exprType((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_UseVarExpr: + type = ((const UseVarExpr *) expr)->typid; + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); type = InvalidOid; /* keep compiler quiet */ @@ -492,6 +495,8 @@ exprTypmod(const Node *expr) return ((const SetToDefault *) expr)->typeMod; case T_PlaceHolderVar: return exprTypmod((Node *) ((const PlaceHolderVar *) expr)->phexpr); + case T_UseVarExpr: + return ((const UseVarExpr *) expr)->typmod; default: break; } @@ -727,6 +732,8 @@ expression_returns_set_walker(Node *node, void *context) return false; if (IsA(node, XmlExpr)) return false; + if (IsA(node, UseVarExpr)) + return false; return expression_tree_walker(node, expression_returns_set_walker, context); @@ -929,6 +936,9 @@ exprCollation(const Node *expr) case T_PlaceHolderVar: coll = exprCollation((Node *) ((const PlaceHolderVar *) expr)->phexpr); break; + case T_UseVarExpr: + coll = ((const UseVarExpr *) expr)->collation; + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); coll = InvalidOid; /* keep compiler quiet */ @@ -1127,6 +1137,9 @@ exprSetCollation(Node *expr, Oid collation) case T_CurrentOfExpr: Assert(!OidIsValid(collation)); /* result is always boolean */ break; + case T_UseVarExpr: + ((UseVarExpr *) expr)->collation = collation; + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); break; @@ -1557,6 +1570,8 @@ exprLocation(const Node *expr) break; case T_PartitionRangeDatum: loc = ((const PartitionRangeDatum *) expr)->location; + case T_UseVarExpr: + loc = ((const UseVarExpr *) expr)->location; break; default: /* for any other node type it's just unknown... */ @@ -2217,6 +2232,8 @@ expression_tree_walker(Node *node, return true; } break; + case T_UseVarExpr: + return walker(((UseVarExpr *) node)->expr, context); default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3013,6 +3030,16 @@ expression_tree_mutator(Node *node, return (Node *) newnode; } break; + case T_UseVarExpr: + { + UseVarExpr *varexpr = (UseVarExpr *) node; + UseVarExpr *newnode; + + FLATCOPY(newnode, varexpr, UseVarExpr); + MUTATE(newnode->expr, varexpr->expr, Node *); + return (Node *) newnode; + } + break; default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -3480,6 +3507,8 @@ raw_expression_tree_walker(Node *node, break; case T_A_ArrayExpr: return walker(((A_ArrayExpr *) node)->elements, context); + case T_UseVarExpr: + return walker(((UseVarExpr *) node)->expr, context); case T_ResTarget: { ResTarget *rt = (ResTarget *) node; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 7258c03..58fa46d 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1574,6 +1574,20 @@ _outOnConflictExpr(StringInfo str, const OnConflictExpr *node) WRITE_NODE_FIELD(exclRelTlist); } +static void +_outUseVarExpr(StringInfo str, const UseVarExpr *node) +{ + WRITE_NODE_TYPE("USEVAREXPR"); + + WRITE_OID_FIELD(varid); + WRITE_STRING_FIELD(attrname); + WRITE_NODE_FIELD(expr); + WRITE_OID_FIELD(typid); + WRITE_INT_FIELD(typmod); + WRITE_OID_FIELD(collation); + WRITE_LOCATION_FIELD(location); +} + /***************************************************************************** * * Stuff from relation.h. @@ -3079,6 +3093,17 @@ _outA_ArrayExpr(StringInfo str, const A_ArrayExpr *node) } static void +_outUseVar(StringInfo str, const UseVar *node) +{ + WRITE_NODE_TYPE("UseVAR"); + + WRITE_STRING_FIELD(name); + WRITE_STRING_FIELD(attrname); + WRITE_NODE_FIELD(expr); + WRITE_LOCATION_FIELD(location); +} + +static void _outResTarget(StringInfo str, const ResTarget *node) { WRITE_NODE_TYPE("RESTARGET"); @@ -3620,6 +3645,9 @@ outNode(StringInfo str, const void *obj) case T_OnConflictExpr: _outOnConflictExpr(str, obj); break; + case T_UseVarExpr: + _outUseVarExpr(str, obj); + break; case T_Path: _outPath(str, obj); break; @@ -3865,6 +3893,9 @@ outNode(StringInfo str, const void *obj) case T_A_ArrayExpr: _outA_ArrayExpr(str, obj); break; + case T_UseVar: + _outUseVar(str, obj); + break; case T_ResTarget: _outResTarget(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index d608530..909436b 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -1277,6 +1277,25 @@ _readOnConflictExpr(void) } /* + * _readUseVarExpr + */ +static UseVarExpr * +_readUseVarExpr(void) +{ + READ_LOCALS(UseVarExpr); + + READ_OID_FIELD(varid); + READ_STRING_FIELD(attrname); + READ_NODE_FIELD(expr); + READ_OID_FIELD(typid); + READ_INT_FIELD(typmod); + READ_OID_FIELD(collation); + READ_LOCATION_FIELD(location); + + READ_DONE(); +} + +/* * Stuff from parsenodes.h. */ @@ -2427,6 +2446,8 @@ parseNodeString(void) return_value = _readFromExpr(); else if (MATCH("ONCONFLICTEXPR", 14)) return_value = _readOnConflictExpr(); + else if (MATCH("USEVAREXPR", 10)) + return_value = _readUseVarExpr(); else if (MATCH("RTE", 3)) return_value = _readRangeTblEntry(); else if (MATCH("RANGETBLFUNCTION", 16)) diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 9af29dd..b723106 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -861,6 +861,8 @@ expression_returns_set_rows_walker(Node *node, double *count) return false; if (IsA(node, XmlExpr)) return false; + if (IsA(node, UseVarExpr)) + return false; return expression_tree_walker(node, expression_returns_set_rows_walker, (void *) count); @@ -1405,6 +1407,8 @@ contain_nonstrict_functions_walker(Node *node, void *context) return true; if (IsA(node, BooleanTest)) return true; + if (IsA(node, UseVarExpr)) + return true; /* * Check other function-containing nodes; but ArrayCoerceExpr is strict at diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 2ed7b52..58cd915 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -248,7 +248,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt - CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt + CreateSchemaStmt CreateSeqStmt CreateVarStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt @@ -610,7 +610,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); FALSE_P FAMILY FETCH FILTER FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS - GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING + GETATTR GETVAR GLOBAL GRANT GRANTED GREATEST GROUP_P GROUPING HANDLER HAVING HEADER_P HOLD HOUR_P @@ -648,9 +648,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ROW ROWS RULE SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES - SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW - SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START - STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING + SERIALIZABLE SERVER SESSION SESSION_USER SET SETATTR SETS SETOF SETVAR SHARE + SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P + START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING SYMMETRIC SYSID SYSTEM_P TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN @@ -660,8 +660,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED UNTIL UPDATE USER USING - VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING - VERBOSE VERSION_P VIEW VIEWS VOLATILE + VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIABLE VARIADIC + VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE @@ -833,6 +833,7 @@ stmt : | CreatePLangStmt | CreateSchemaStmt | CreateSeqStmt + | CreateVarStmt | CreateStmt | CreateTableSpaceStmt | CreateTransformStmt @@ -1343,6 +1344,7 @@ schema_stmt: CreateStmt | IndexStmt | CreateSeqStmt + | CreateVarStmt | CreateTrigStmt | GrantStmt | ViewStmt @@ -3977,6 +3979,32 @@ NumericOnly_list: NumericOnly { $$ = list_make1($1); } /***************************************************************************** * + * QUERY : + * CREATE VARIABLE varname + * + *****************************************************************************/ + +CreateVarStmt: + CREATE VARIABLE qualified_name opt_as Typename + { + CreateVarStmt *n = makeNode(CreateVarStmt); + n->variable = $3; + n->typeName = $5; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename + { + CreateVarStmt *n = makeNode(CreateVarStmt); + n->variable = $6; + n->typeName = $8; + n->if_not_exists = true; + $$ = (Node *)n; + } + ; + +/***************************************************************************** + * * QUERIES : * CREATE [OR REPLACE] [TRUSTED] [PROCEDURAL] LANGUAGE ... * DROP [PROCEDURAL] LANGUAGE ... @@ -6050,6 +6078,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; } | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } | TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; any_name_list: @@ -6318,6 +6347,7 @@ comment_type: | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; comment_text: @@ -6431,6 +6461,7 @@ security_label_type: | TABLESPACE { $$ = OBJECT_TABLESPACE; } | VIEW { $$ = OBJECT_VIEW; } | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; security_label: Sconst { $$ = $1; } @@ -6848,6 +6879,14 @@ privilege_target: n->objs = $5; $$ = n; } + | VARIABLE qualified_name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + n->targtype = ACL_TARGET_OBJECT; + n->objtype = ACL_OBJECT_VARIABLE; + n->objs = $2; + $$ = n; + } ; @@ -12779,6 +12818,27 @@ func_expr_common_subexpr: { $$ = (Node *) makeFuncCall(SystemFuncName("date_part"), $3, @1); } + | GETVAR '(' Sconst ')' + { + /* + * The name of session variable must be known in transformation + * time - it is same situation like any other database object. + * The qualified_name is not used to be consistent with any other + * function like syntax. + */ + UseVar *g = makeNode(UseVar); + g->name = $3; + g->location = @1; + $$ = (Node *) g; + } + | GETATTR '(' Sconst ',' Sconst ')' + { + UseVar *g = makeNode(UseVar); + g->name = $3; + g->attrname = $5; + g->location = @1; + $$ = (Node *) g; + } | OVERLAY '(' overlay_list ')' { /* overlay(A PLACING B FROM C FOR D) is converted to @@ -12793,6 +12853,23 @@ func_expr_common_subexpr: /* position(A in B) is converted to position(B, A) */ $$ = (Node *) makeFuncCall(SystemFuncName("position"), $3, @1); } + | SETATTR '(' Sconst ',' Sconst ',' a_expr ')' + { + UseVar *s = makeNode(UseVar); + s->name = $3; + s->attrname = $5; + s->expr = $7; + s->location = @1; + $$ = (Node *) s; + } + | SETVAR '(' Sconst ',' a_expr ')' + { + UseVar *s = makeNode(UseVar); + s->name = $3; + s->expr = $5; + s->location = @1; + $$ = (Node *) s; + } | SUBSTRING '(' substr_list ')' { /* substring(A from B for C) is converted to @@ -14261,6 +14338,7 @@ unreserved_keyword: | VALIDATE | VALIDATOR | VALUE_P + | VARIABLE | VARYING | VERSION_P | VIEW @@ -14301,6 +14379,8 @@ col_name_keyword: | EXISTS | EXTRACT | FLOAT_P + | GETATTR + | GETVAR | GREATEST | GROUPING | INOUT @@ -14319,7 +14399,9 @@ col_name_keyword: | PRECISION | REAL | ROW + | SETATTR | SETOF + | SETVAR | SMALLINT | SUBSTRING | TIME diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 8a2bdf0..a8f722c 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -15,7 +15,9 @@ #include "postgres.h" +#include "access/htup_details.h" #include "catalog/pg_type.h" +#include "catalog/pg_class.h" #include "commands/dbcommands.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -36,6 +38,7 @@ #include "utils/builtins.h" #include "utils/date.h" #include "utils/lsyscache.h" +#include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/xml.h" @@ -122,6 +125,7 @@ static Node *transformIndirection(ParseState *pstate, Node *basenode, List *indirection); static Node *transformTypeCast(ParseState *pstate, TypeCast *tc); static Node *transformCollateClause(ParseState *pstate, CollateClause *c); +static Node *transformUseVar(ParseState *pstate, UseVar *varexpr); static Node *make_row_comparison_op(ParseState *pstate, List *opname, List *largs, List *rargs, int location); static Node *make_row_distinct_op(ParseState *pstate, List *opname, @@ -376,6 +380,10 @@ transformExprRecurse(ParseState *pstate, Node *expr) break; } + case T_UseVar: + result = transformUseVar(pstate, (UseVar *) expr); + break; + default: /* should not reach here */ elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr)); @@ -2768,6 +2776,144 @@ transformCollateClause(ParseState *pstate, CollateClause *c) } /* + * Transform access to session variables - setvar, setattr, getvar, getattr + * + */ +static Node * +transformUseVar(ParseState *pstate, UseVar *varexpr) +{ + UseVarExpr *newe = makeNode(UseVarExpr); + char *class_name_or_oid = varexpr->name; + Oid varid = InvalidOid; + HeapTuple tuple; + Form_pg_class classForm; + Oid expected_typid; + int32 expected_typmod; + Oid expected_collid; + AclResult aclresult; + + /* + * Currently regclass functions are not used. The main reason in this + * moment is a messy error messagers - related to relations. Should be + * fixed. + */ + + /* Numeric OID? */ + if (class_name_or_oid[0] >= '0' && + class_name_or_oid[0] <= '9' && + strspn(class_name_or_oid, "0123456789") == strlen(class_name_or_oid)) + { + varid = DatumGetObjectId(DirectFunctionCall1(oidin, + CStringGetDatum(class_name_or_oid))); + } + else + { + List *names; + + names = stringToQualifiedNameList(class_name_or_oid); + varid = RangeVarGetRelid(makeRangeVarFromNameList(names), NoLock, true); + if (!OidIsValid(varid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("session variable \"%s\" does not exits", + class_name_or_oid), + parser_errposition(pstate, varexpr->location))); + } + + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(varid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for session variable %u", varid); + + classForm = (Form_pg_class) GETSTRUCT(tuple); + + if (classForm->relkind != RELKIND_VARIABLE) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a session variable", + class_name_or_oid), + parser_errposition(pstate, varexpr->location))); + + /* Check permissions */ + aclresult = pg_class_aclcheck(varid, GetUserId(), + varexpr->expr != NULL ? ACL_UPDATE : ACL_SELECT); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, ACL_KIND_VARIABLE, + class_name_or_oid); + + /* + * Design of session variables allows a variables of composite types. The + * read of this variable can be composite type, when GETVAR is used or + * some other type, when GETATTR is used. The identifier of composite + * variable is valid reloftype attribute. + */ + if (!OidIsValid(classForm->reloftype)) + { + /* scalar variable */ + if (varexpr->attrname != NULL) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("session variable \"%s\" is not a composite variable", + class_name_or_oid), + parser_errposition(pstate, varexpr->location))); + + get_atttypetypmodcoll(varid, (AttrNumber) 1, + &expected_typid, + &expected_typmod, + &expected_collid); + } + else + { + /* composite variable */ + if (varexpr->attrname != NULL) + { + AttrNumber attnum = get_attnum(varid, varexpr->attrname); + + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute \"%s\" of variable \"%s\" does not exist", + varexpr->attrname, class_name_or_oid), + parser_errposition(pstate, varexpr->location))); + + get_atttypetypmodcoll(varid, attnum, + &expected_typid, + &expected_typmod, + &expected_collid); + } + else + { + expected_typid = classForm->reloftype; + expected_typmod = -1; + + /* result is a composite */ + expected_collid = InvalidOid; + } + } + + /* enforce casting of expression to expected type */ + if (varexpr->expr) + { + /* Note: can be used coerce_to_specific_type_typmod when it will be available */ + + newe->expr = coerce_to_specific_type(pstate, + transformExprRecurse(pstate, (Node *) varexpr->expr), + expected_typid, "transform variable"); + } + + ReleaseSysCache(tuple); + + newe->varid = varid; + newe->attrname = varexpr->attrname; + newe->typid = expected_typid; + newe->typmod = expected_typmod; + newe->collation = expected_collid; + + newe->location = varexpr->location; + + return (Node *) newe; +} + +/* * Transform a "row compare-op row" construct * * The inputs are lists of already-transformed expressions. diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index d440dec..b8e8cc9 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -1868,6 +1868,20 @@ FigureColnameInternal(Node *node, char **name) case T_XmlSerialize: *name = "xmlserialize"; return 2; + case T_UseVar: + { + UseVar *usevar = (UseVar *) node; + + if (usevar->attrname != NULL) + { + *name = usevar->attrname; + return 2; + } + + *name = usevar->name; + return 2; + } + default: break; } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index fd4eff4..b7ab53d 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -47,6 +47,7 @@ #include "commands/schemacmds.h" #include "commands/seclabel.h" #include "commands/sequence.h" +#include "commands/session_variable.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" #include "commands/trigger.h" @@ -174,6 +175,7 @@ check_xact_readonly(Node *parsetree) case T_RuleStmt: case T_CreateSchemaStmt: case T_CreateSeqStmt: + case T_CreateVarStmt: case T_CreateStmt: case T_CreateTableAsStmt: case T_RefreshMatViewStmt: @@ -1404,6 +1406,10 @@ ProcessUtilitySlow(ParseState *pstate, address = AlterSequence(pstate, (AlterSeqStmt *) parsetree); break; + case T_CreateVarStmt: + address = DefineSessionVariable(pstate, (CreateVarStmt *) parsetree); + break; + case T_CreateTableAsStmt: address = ExecCreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, completionTag); @@ -1607,6 +1613,7 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel) case OBJECT_VIEW: case OBJECT_MATVIEW: case OBJECT_FOREIGN_TABLE: + case OBJECT_VARIABLE: RemoveRelations(stmt); break; default: @@ -2199,6 +2206,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_ACCESS_METHOD: tag = "DROP ACCESS METHOD"; break; + case OBJECT_VARIABLE: + tag = "DROP VARIABLE"; + break; default: tag = "???"; } @@ -2347,6 +2357,10 @@ CreateCommandTag(Node *parsetree) tag = "ALTER SEQUENCE"; break; + case T_CreateVarStmt: + tag = "CREATE VARIABLE"; + break; + case T_DoStmt: tag = "DO"; break; @@ -2939,6 +2953,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_DDL; break; + case T_CreateVarStmt: + lev = LOGSTMT_DDL; + break; + case T_DoStmt: lev = LOGSTMT_ALL; break; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 025a99e..79658cf 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -791,6 +791,10 @@ acldefault(GrantObjectType objtype, Oid ownerId) world_default = ACL_USAGE; owner_default = ACL_ALL_RIGHTS_TYPE; break; + case ACL_OBJECT_VARIABLE: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_SEQUENCE; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ @@ -886,6 +890,9 @@ acldefault_sql(PG_FUNCTION_ARGS) case 'T': objtype = ACL_OBJECT_TYPE; break; + case 'v': + objtype = ACL_OBJECT_VARIABLE; + break; default: elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec); } diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 4e2ba19..2f493b1 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -8547,6 +8547,49 @@ get_rule_expr(Node *node, deparse_context *context, } break; + case T_UseVarExpr: + { + UseVarExpr *varexpr = (UseVarExpr *) node; + char *varname = generate_relation_name(varexpr->varid, NIL); + + if (varexpr->expr != NULL) + { + if (varexpr->attrname != NULL) + appendStringInfoString(buf, "SETATTR("); + else + appendStringInfoString(buf, "SETVAR("); + + simple_quote_literal(buf, varname); + + if (varexpr->attrname != NULL) + { + appendStringInfoChar(buf, ','); + simple_quote_literal(buf, varexpr->attrname); + } + + appendStringInfoChar(buf, ','); + get_rule_expr(varexpr->expr, context, showimplicit); + } + else + { + if (varexpr->attrname != NULL) + appendStringInfoString(buf, "GETATTR("); + else + appendStringInfoString(buf, "GETVAR("); + + simple_quote_literal(buf, varname); + + if (varexpr->attrname != NULL) + { + appendStringInfoChar(buf, ','); + simple_quote_literal(buf, varexpr->attrname); + } + } + + appendStringInfoChar(buf, ')'); + } + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); break; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index a582a37..f3d5626 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1676,6 +1676,10 @@ describeOneTableDetails(const char *schemaname, printfPQExpBuffer(&title, _("Table \"%s.%s\""), schemaname, relationname); break; + case 'V': + printfPQExpBuffer(&title, _("Variable \"%s.%s\""), + schemaname, relationname); + break; default: /* untranslated unknown relkind */ printfPQExpBuffer(&title, "?%c? \"%s.%s\"", diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index cd64c39..d9b4242 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -584,6 +584,21 @@ static const SchemaQuery Query_for_list_of_matviews = { NULL }; +static const SchemaQuery Query_for_list_of_variables = { + /* catname */ + "pg_catalog.pg_class c", + /* selcondition */ + "c.relkind IN ('V')", + /* viscondition */ + "pg_catalog.pg_table_is_visible(c.oid)", + /* namespace */ + "c.relnamespace", + /* result */ + "pg_catalog.quote_ident(c.relname)", + /* qualresult */ + NULL +}; + /* * Queries to get lists of names of various kinds of things, possibly @@ -977,6 +992,7 @@ static const pgsql_thing_t words_after_create[] = { * ... */ {"USER", Query_for_list_of_roles}, {"USER MAPPING FOR", NULL, NULL}, + {"VARIABLE", NULL, &Query_for_list_of_variables}, {"VIEW", NULL, &Query_for_list_of_views}, {NULL} /* end of list */ }; @@ -2390,6 +2406,10 @@ psql_completion(const char *text, int start, int end) else if (Matches5("CREATE", "EVENT", "TRIGGER", MatchAny, "ON")) COMPLETE_WITH_LIST3("ddl_command_start", "ddl_command_end", "sql_drop"); +/* CREATE VARIABLE */ + else if (Matches4("CREATE", "VARIABLE", MatchAny, "AS")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + /* DECLARE */ else if (Matches2("DECLARE", MatchAny)) COMPLETE_WITH_LIST5("BINARY", "INSENSITIVE", "SCROLL", "NO SCROLL", @@ -2511,6 +2531,8 @@ psql_completion(const char *text, int start, int end) } else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny)) COMPLETE_WITH_LIST2("CASCADE", "RESTRICT"); + else if (Matches2("DROP", "VARIABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); /* EXECUTE */ else if (Matches1("EXECUTE")) diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index a61b7a2..42ec21d 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -166,6 +166,7 @@ DESCR(""); #define RELKIND_FOREIGN_TABLE 'f' /* foreign table */ #define RELKIND_MATVIEW 'm' /* materialized view */ #define RELKIND_PARTITIONED_TABLE 'P' /* partitioned table */ +#define RELKIND_VARIABLE 'V' /* session variable */ #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h index 06aaaba..6e47c06 100644 --- a/src/include/catalog/pg_default_acl.h +++ b/src/include/catalog/pg_default_acl.h @@ -70,5 +70,6 @@ typedef FormData_pg_default_acl *Form_pg_default_acl; #define DEFACLOBJ_SEQUENCE 'S' /* sequence */ #define DEFACLOBJ_FUNCTION 'f' /* function */ #define DEFACLOBJ_TYPE 'T' /* type */ +#define DEFACLOBJ_VARIABLE 'v' /* session variable */ #endif /* PG_DEFAULT_ACL_H */ diff --git a/src/include/commands/session_variable.h b/src/include/commands/session_variable.h new file mode 100644 index 0000000..d7c5a39 --- /dev/null +++ b/src/include/commands/session_variable.h @@ -0,0 +1,31 @@ +/*------------------------------------------------------------------------- + * + * session_variable.h + * prototypes for session_variable.c. + * + * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/session_variable.h + * + *------------------------------------------------------------------------- + */ +#ifndef SESSION_VARIABLE_H +#define SESSION_VARIABLE_H + +#include "catalog/objectaddress.h" +#include "nodes/parsenodes.h" + +extern void SetSessionVariable(Oid varid, char *attrname, Datum value, bool isNull, + Oid typid, int32 typmod, + int16 typlen, bool typbyval, char typalign); + +extern Datum GetSessionVariable(Oid varid, char *attrname, bool *isNull, + Oid typid, int32 typmod, + int16 typlen, bool typbyval, char typalign); + +extern ObjectAddress DefineSessionVariable(ParseState *pstate, CreateVarStmt *stmt); + +extern void ResetVariablesCache(void); + +#endif /* SESSION_VARIABLE_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 1de5c81..89b328b 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -2082,4 +2082,23 @@ typedef struct LimitState TupleTableSlot *subSlot; /* tuple last obtained from subplan */ } LimitState; +/* ---------------- + * UseVarExprState information + * + * Controlled access to session variables + * ---------------- + */ +typedef struct UseVarExprState +{ + ExprState xprstate; + ExprState *expr; + Oid varid; + char *attrname; + Oid typid; + int32 typmod; + int16 typlen; + bool typbyval; + char typalign; +} UseVarExprState; + #endif /* EXECNODES_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index c514d3f..b0bc214 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -181,6 +181,7 @@ typedef enum NodeTag T_FromExpr, T_OnConflictExpr, T_IntoClause, + T_UseVarExpr, /* * TAGS FOR EXPRESSION STATE NODES (execnodes.h) @@ -216,6 +217,7 @@ typedef enum NodeTag T_NullTestState, T_CoerceToDomainState, T_DomainConstraintState, + T_UseVarExprState, /* * TAGS FOR PLANNER NODES (relation.h) @@ -342,6 +344,7 @@ typedef enum NodeTag T_ExplainStmt, T_CreateTableAsStmt, T_CreateSeqStmt, + T_CreateVarStmt, T_AlterSeqStmt, T_VariableSetStmt, T_VariableShowStmt, @@ -420,6 +423,7 @@ typedef enum NodeTag T_A_Indices, T_A_Indirection, T_A_ArrayExpr, + T_UseVar, T_ResTarget, T_MultiAssignRef, T_TypeCast, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index fc532fb..f0ad2b4 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -400,6 +400,18 @@ typedef struct A_ArrayExpr } A_ArrayExpr; /* + * SETVAR(string, expr), SETATTR(string, string, expr) + */ +typedef struct UseVar +{ + NodeTag type; + char *name; /* Sconst of session variable name */ + char *attrname; /* Sconst of attr name or NULL */ + Node *expr; /* future value, NULL for GETVAR, GETATTR */ + int location; /* token location */ +} UseVar; + +/* * ResTarget - * result target (used in target list of pre-transformed parse trees) * @@ -1527,6 +1539,7 @@ typedef enum ObjectType OBJECT_TSTEMPLATE, OBJECT_TYPE, OBJECT_USER_MAPPING, + OBJECT_VARIABLE, OBJECT_VIEW } ObjectType; @@ -1706,7 +1719,8 @@ typedef enum GrantObjectType ACL_OBJECT_LARGEOBJECT, /* largeobject */ ACL_OBJECT_NAMESPACE, /* namespace */ ACL_OBJECT_TABLESPACE, /* tablespace */ - ACL_OBJECT_TYPE /* type */ + ACL_OBJECT_TYPE, /* type */ + ACL_OBJECT_VARIABLE /* session variable */ } GrantObjectType; typedef struct GrantStmt @@ -2331,6 +2345,19 @@ typedef struct AlterSeqStmt } AlterSeqStmt; /* ---------------------- + * Create VARIABLE Statement + * ---------------------- + */ + +typedef struct CreateVarStmt +{ + NodeTag type; + RangeVar *variable; /* the variable to create */ + TypeName *typeName; /* the variable type */ + bool if_not_exists; /* just do nothing if it already exists? */ +} CreateVarStmt; + +/* ---------------------- * Create {Aggregate|Operator|Type} Statement * ---------------------- */ diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 65510b0..58c0c69 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -1467,4 +1467,21 @@ typedef struct OnConflictExpr List *exclRelTlist; /* tlist of the EXCLUDED pseudo relation */ } OnConflictExpr; +/*---------- + * UseVarExpr used for manipulation with session variables + * + *---------- + */ +typedef struct UseVarExpr +{ + NodeTag type; + Oid varid; + char *attrname; + Node *expr; /* NULL, when read session var */ + Oid typid; /* typid of result (var or varattr) */ + int32 typmod; /* typmod of result (var or varattr) */ + Oid collation; + int location; +} UseVarExpr; + #endif /* PRIMNODES_H */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index 581ff6e..6253dda 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -173,6 +173,8 @@ PG_KEYWORD("from", FROM, RESERVED_KEYWORD) PG_KEYWORD("full", FULL, TYPE_FUNC_NAME_KEYWORD) PG_KEYWORD("function", FUNCTION, UNRESERVED_KEYWORD) PG_KEYWORD("functions", FUNCTIONS, UNRESERVED_KEYWORD) +PG_KEYWORD("getattr", GETATTR, COL_NAME_KEYWORD) +PG_KEYWORD("getvar", GETVAR, COL_NAME_KEYWORD) PG_KEYWORD("global", GLOBAL, UNRESERVED_KEYWORD) PG_KEYWORD("grant", GRANT, RESERVED_KEYWORD) PG_KEYWORD("granted", GRANTED, UNRESERVED_KEYWORD) @@ -353,8 +355,10 @@ PG_KEYWORD("server", SERVER, UNRESERVED_KEYWORD) PG_KEYWORD("session", SESSION, UNRESERVED_KEYWORD) PG_KEYWORD("session_user", SESSION_USER, RESERVED_KEYWORD) PG_KEYWORD("set", SET, UNRESERVED_KEYWORD) +PG_KEYWORD("setattr", SETATTR, COL_NAME_KEYWORD) PG_KEYWORD("setof", SETOF, COL_NAME_KEYWORD) PG_KEYWORD("sets", SETS, UNRESERVED_KEYWORD) +PG_KEYWORD("setvar", SETVAR, COL_NAME_KEYWORD) PG_KEYWORD("share", SHARE, UNRESERVED_KEYWORD) PG_KEYWORD("show", SHOW, UNRESERVED_KEYWORD) PG_KEYWORD("similar", SIMILAR, TYPE_FUNC_NAME_KEYWORD) @@ -420,6 +424,7 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD) PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD) PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD) PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD) +PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD) PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD) PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD) PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD) diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index fda75bb..41c98a9 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -158,6 +158,7 @@ typedef ArrayType Acl; #define ACL_ALL_RIGHTS_NAMESPACE (ACL_USAGE|ACL_CREATE) #define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE) #define ACL_ALL_RIGHTS_TYPE (ACL_USAGE) +#define ACL_ALL_RIGHTS_VARIABLE (ACL_SELECT|ACL_UPDATE) /* operation codes for pg_*_aclmask */ typedef enum @@ -199,6 +200,7 @@ typedef enum AclObjectKind ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */ ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */ ACL_KIND_EXTENSION, /* pg_extension */ + ACL_KIND_VARIABLE, /* session variable */ MAX_ACL_KIND /* MUST BE LAST */ } AclObjectKind; diff --git a/src/test/regress/expected/variables.out b/src/test/regress/expected/variables.out new file mode 100644 index 0000000..20db9d5 --- /dev/null +++ b/src/test/regress/expected/variables.out @@ -0,0 +1,256 @@ +create type composite_type as (x integer, y integer, z integer); +create variable var1 as integer; +create variable var2 as numeric; +create variable var3 as text; +create variable var4 as composite_type; +create variable var5 as integer[]; +create variable var6 as composite_type; +select getvar('var1'), getvar('var2'), getvar('var3'), getvar('var4'), getvar('var5'), getvar('var6'); + var1 | var2 | var3 | var4 | var5 | var6 +------+------+------+------+------+------ + | | | | | +(1 row) + +select setvar('var1', 1); + var1 +------ + 1 +(1 row) + +select setvar('var2', 12222222222.1234); + var2 +------------------ + 12222222222.1234 +(1 row) + +select setvar('var3', 'today is a pretty day'); + var3 +----------------------- + today is a pretty day +(1 row) + +select setvar('var4', '(1,2,3)'); + var4 +--------- + (1,2,3) +(1 row) + +select setvar('var5', ARRAY[9,8,7,6,5,4,3,2,1]); + var5 +--------------------- + {9,8,7,6,5,4,3,2,1} +(1 row) + +select setvar('var6', ROW(10,20,30)); + var6 +------------ + (10,20,30) +(1 row) + +select getvar('var1'), pg_typeof(getvar('var1')); + var1 | pg_typeof +------+----------- + 1 | integer +(1 row) + +select getvar('var2'), pg_typeof(getvar('var2')); + var2 | pg_typeof +------------------+----------- + 12222222222.1234 | numeric +(1 row) + +select getvar('var3'), pg_typeof(getvar('var3')); + var3 | pg_typeof +-----------------------+----------- + today is a pretty day | text +(1 row) + +select getvar('var4'), pg_typeof(getvar('var4')); + var4 | pg_typeof +---------+---------------- + (1,2,3) | composite_type +(1 row) + +select getvar('var5'), pg_typeof(getvar('var5')); + var5 | pg_typeof +---------------------+----------- + {9,8,7,6,5,4,3,2,1} | integer[] +(1 row) + +select getvar('var6'), pg_typeof(getvar('var6')); + var6 | pg_typeof +------------+---------------- + (10,20,30) | composite_type +(1 row) + +-- second setting should to work too +select setvar('var3', 'today is a super pretty day'); + var3 +----------------------------- + today is a super pretty day +(1 row) + +select getvar('var3'), pg_typeof(getvar('var3')); + var3 | pg_typeof +-----------------------------+----------- + today is a super pretty day | text +(1 row) + +explain (verbose, cost off) select getvar('var6'), pg_typeof(getvar('var6')); +ERROR: unrecognized EXPLAIN option "cost" +LINE 1: explain (verbose, cost off) select getvar('var6'), pg_typeof... + ^ +select (getvar('var6')).y; + y +---- + 20 +(1 row) + +select (getvar('var5'))[2]; + var5 +------ + 8 +(1 row) + +-- access to attributes of composite fields +create type composite_type_2 as (a int, b text); +create variable var7 composite_type_2; +select getvar('var7'); + var7 +------ + +(1 row) + +select * from getvar('var7'); + a | b +---+--- + | +(1 row) + +select getattr('var7', 'b'); + b +--- + +(1 row) + +select setvar('var7','(10,Hello world\, Hello world\, Hello world)'); + var7 +---------------------------------------------- + (10,"Hello world, Hello world, Hello world") +(1 row) + +select * from getvar('var7'); + a | b +----+--------------------------------------- + 10 | Hello world, Hello world, Hello world +(1 row) + +select getattr('var7','a'); + a +---- + 10 +(1 row) + +select getattr('var7','b'); + b +--------------------------------------- + Hello world, Hello world, Hello world +(1 row) + +select setattr('var7', 'a', 1000); + a +------ + 1000 +(1 row) + +select * from getvar('var7'); + a | b +------+--------------------------------------- + 1000 | Hello world, Hello world, Hello world +(1 row) + +select setattr('var7', 'b', 'Hola, hola!'); + b +------------- + Hola, hola! +(1 row) + +select * from getvar('var7'); + a | b +------+------------- + 1000 | Hola, hola! +(1 row) + +select getattr('var7','b'); + b +------------- + Hola, hola! +(1 row) + +drop variable var7; +drop type composite_type_2; +-- getvar, setvar are volatile functions +create variable _id as integer; +select setvar('_id', coalesce(getvar('_id') + 1,1)) from generate_series(1,10); + _id +----- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + +explain verbose select setvar('_id', coalesce(getvar('_id') + 1,1)) from generate_series(1,10); + QUERY PLAN +----------------------------------------------------------------------------------- + Function Scan on pg_catalog.generate_series (cost=0.00..12.50 rows=1000 width=4) + Output: SETVAR('_id',COALESCE((GETVAR('_id') + 1), 1)) + Function Call: generate_series(1, 10) +(3 rows) + +drop variable _id; +-- should fail +drop variable var1; +select getvar('var1'), pg_typeof(getvar('var1')); +ERROR: session variable "var1" does not exits +LINE 1: select getvar('var1'), pg_typeof(getvar('var1')); + ^ +-- SQL access is not supported yet, should fail (but syntax is valid, +-- and should be supported in future time) +select value from var3; +ERROR: cannot read session variable "var3" as relation +select x from var4; +ERROR: cannot read session variable "var4" as relation +update var3 set value = 'Hello, Hello'; +ERROR: cannot change session variable "var3" +update var4 set x = 100; +ERROR: cannot change session variable "var4" +-- access tests +create role var_test_role login; +grant select on variable var2 to var_test_role; +grant update on variable var3 to var_test_role; +set role var_test_role; +-- should fail +select setvar('var2', 3.3); +ERROR: permission denied for variable var2 +select getvar('var3'); +ERROR: permission denied for variable var3 +-- should be ok +select getvar('var2'); + var2 +------------------ + 12222222222.1234 +(1 row) + +select setvar('var3', 'Hello, Hello, Hello'); + var3 +--------------------- + Hello, Hello, Hello +(1 row) + diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 8641769..d6a5b9d 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -106,7 +106,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- -test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml variables # event triggers cannot run concurrently with any test that runs DDL test: event_trigger diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 835cf35..90ce8c9 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -169,3 +169,4 @@ test: with test: xml test: event_trigger test: stats +test: variables diff --git a/src/test/regress/sql/variables.sql b/src/test/regress/sql/variables.sql new file mode 100644 index 0000000..0d502aa --- /dev/null +++ b/src/test/regress/sql/variables.sql @@ -0,0 +1,85 @@ +create type composite_type as (x integer, y integer, z integer); + +create variable var1 as integer; +create variable var2 as numeric; +create variable var3 as text; +create variable var4 as composite_type; +create variable var5 as integer[]; +create variable var6 as composite_type; + +select getvar('var1'), getvar('var2'), getvar('var3'), getvar('var4'), getvar('var5'), getvar('var6'); + +select setvar('var1', 1); +select setvar('var2', 12222222222.1234); +select setvar('var3', 'today is a pretty day'); +select setvar('var4', '(1,2,3)'); +select setvar('var5', ARRAY[9,8,7,6,5,4,3,2,1]); +select setvar('var6', ROW(10,20,30)); + +select getvar('var1'), pg_typeof(getvar('var1')); +select getvar('var2'), pg_typeof(getvar('var2')); +select getvar('var3'), pg_typeof(getvar('var3')); +select getvar('var4'), pg_typeof(getvar('var4')); +select getvar('var5'), pg_typeof(getvar('var5')); +select getvar('var6'), pg_typeof(getvar('var6')); + +-- second setting should to work too +select setvar('var3', 'today is a super pretty day'); +select getvar('var3'), pg_typeof(getvar('var3')); + +explain (verbose, cost off) select getvar('var6'), pg_typeof(getvar('var6')); + +select (getvar('var6')).y; +select (getvar('var5'))[2]; + +-- access to attributes of composite fields +create type composite_type_2 as (a int, b text); +create variable var7 composite_type_2; +select getvar('var7'); +select * from getvar('var7'); +select getattr('var7', 'b'); +select setvar('var7','(10,Hello world\, Hello world\, Hello world)'); +select * from getvar('var7'); +select getattr('var7','a'); +select getattr('var7','b'); +select setattr('var7', 'a', 1000); +select * from getvar('var7'); +select setattr('var7', 'b', 'Hola, hola!'); +select * from getvar('var7'); +select getattr('var7','b'); + +drop variable var7; +drop type composite_type_2; + +-- getvar, setvar are volatile functions +create variable _id as integer; +select setvar('_id', coalesce(getvar('_id') + 1,1)) from generate_series(1,10); +explain verbose select setvar('_id', coalesce(getvar('_id') + 1,1)) from generate_series(1,10); +drop variable _id; + +-- should fail +drop variable var1; +select getvar('var1'), pg_typeof(getvar('var1')); + +-- SQL access is not supported yet, should fail (but syntax is valid, +-- and should be supported in future time) +select value from var3; +select x from var4; +update var3 set value = 'Hello, Hello'; +update var4 set x = 100; + +-- access tests +create role var_test_role login; + +grant select on variable var2 to var_test_role; +grant update on variable var3 to var_test_role; + +set role var_test_role; + +-- should fail +select setvar('var2', 3.3); +select getvar('var3'); + +-- should be ok +select getvar('var2'); +select setvar('var3', 'Hello, Hello, Hello');
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers