diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 3bb48d4ccf..b863823160 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -359,6 +359,11 @@ pg_user_mapping mappings of users to foreign servers + + + pg_variable + schema variables + @@ -11311,4 +11316,104 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx + + <structname>pg_variable</structname> + + + pg_variable + + + + The table pg_variable holds metadata + of schema variables. + + + + <structname>pg_views</structname> Columns + + + + + Name + Type + References + Description + + + + + oid + oid + + Row identifier (hidden attribute; must be explicitly selected) + + + + varname + name + + Name of the schema variable + + + + varnamespace + oid + pg_namespace.oid + + The OID of the namespace that contains this variable + + + + + vartype + oid + pg_type.oid + + The OID of the data type of this variable. + + + + + vartypmod + int4 + + + vartypmod records type-specific data + supplied at table creation time (for example, the maximum + length of a varchar column). It is passed to + type-specific input functions and length coercion functions. + The value will generally be -1 for types that do not need vartypmod. + + + + + varowner + oid + pg_authid.oid + Owner of the variable + + + + vardefexpr + pg_node_tree + + The internal representation of the variable default value + + + + varacl + aclitem[] + + + Access privileges; see + and + + for details + + + + +
+
+ diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index c81c87ef41..0631c9ed56 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -47,6 +47,7 @@ Complete list of usable sgml source files in this directory. + @@ -99,6 +100,7 @@ Complete list of usable sgml source files in this directory. + @@ -148,6 +150,7 @@ Complete list of usable sgml source files in this directory. + @@ -155,6 +158,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml new file mode 100644 index 0000000000..6376ac716b --- /dev/null +++ b/doc/src/sgml/ref/alter_variable.sgml @@ -0,0 +1,170 @@ + + + + + ALTER VARIABLE + + + + ALTER VARIABLE + 7 + SQL - Language Statements + + + + ALTER VARIABLE + + change the definition of a variable + + + + + +ALTER VARIABLE name OWNER TO { new_owner | CURRENT_USER | SESSION_USER } +ALTER VARIABLE name RENAME TO new_name +ALTER VARIABLE name SET SCHEMA new_schema + + + + + Description + + + ALTER VARIABLE changes the definition of an existing variable. + There are several subforms: + + + + OWNER + + + This form changes the owner of the variable. + + + + + + RENAME + + + This form changes the name of the variable. + + + + + + SET SCHEMA + + + This form moves the variable into another schema. + + + + + + + + + You must own the variable to use ALTER VARIABLE. + To change the schema of a variable, you must also have + CREATE privilege on the new schema. + To alter the owner, you must also be a direct or indirect member of the new + owning role, and that role must have CREATE privilege on + the variable's schema. (These restrictions enforce that altering the owner + doesn't do anything you couldn't do by dropping and recreating the variable. + However, a superuser can alter ownership of any type anyway.) + + + + + Parameters + + + + + name + + + The name (possibly schema-qualified) of an existing variable to + alter. + + + + + + new_name + + + The new name for the variable. + + + + + + new_owner + + + The user name of the new owner of the variable. + + + + + + new_schema + + + The new schema for the variable. + + + + + + + + + Examples + + + To rename a variable: + +ALTER VARIABLE foo RENAME TO boo; + + + + + To change the owner of the variable boo + to joe: + +ALTER VARIABLE boo OWNER TO joe; + + + + + To change the schema of the variable boo + to private: + +ALTER VARIABLE boo SET SCHEMA private; + + + + + + Compatibility + + + This comman is a PostgreSQL extension. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml new file mode 100644 index 0000000000..6099538813 --- /dev/null +++ b/doc/src/sgml/ref/create_variable.sgml @@ -0,0 +1,134 @@ + + + + + CREATE VARIABLE + + + + CREATE VARIABLE + 7 + SQL - Language Statements + + + + CREATE VARIABLE + define a new permissioned typed schema variable + + + + +CREATE VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ] + + + + Description + + + CREATE VARIABLE creates a new schema variable. + These variables are scalar typed, non-transactional, and, like relations, + exist within a schema with access controlled via + GRANT and REVOKE. + + + + The value of a schema variable is session-local. Retrieving + a variable's value will return NULL unless its value has been set + to something else in the current session. + + + + Retrieval is done via the get_schema_variabledunxrion or the SQL + command SELECT. Setting of values is done via the + set_schema_variable function or the SQL command + LET. + Notably, while schema variables are in many ways a kind of table you cannot use + UPDATE on them. + + + + For purposes of name uniqueness relation-like objects (e.g., tables, indexes) + within the same schema are considered. i.e., you cannot give a table and a + schema variable the same name. This is a consequence of them being treated + like relations for purposes of SELECT. + + + + + Parameters + + + + IF NOT EXISTS + + + Do not throw an error if the name already exists. A notice is issued in this case. + Note that type of the variable is not considered, nor could it be since the namespace + searched contains non-variable objects. + + + + + + name + + + The name (optionally schema-qualified) of the variable to be created. + + + + + + data_type + + + The name (optionally schema-qualified) of the data type of the variable to be created. + + + + + + + + Notes + + + Use DROP VARIABLE to remove a variable. + + + + + Examples + + + Create an integer variable var1: + +CREATE VARIABLE var1 AS integer; +SELECT var1; + + + + + + + Compatibility + + + CREATE VARIABLE is a PostgreSQL feature. + + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml index 6b909b7232..d83ad811fd 100644 --- a/doc/src/sgml/ref/discard.sgml +++ b/doc/src/sgml/ref/discard.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP } +DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP | VARIABLES } @@ -75,6 +75,17 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP } + + VARIABLES + + + Resets the value of all schema variables. When variables + will be used later, then will be initialized again to + NULL or default value. + + + + ALL diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml new file mode 100644 index 0000000000..c1c1a2bd67 --- /dev/null +++ b/doc/src/sgml/ref/drop_variable.sgml @@ -0,0 +1,93 @@ + + + + + DROP VARIABLE + + + + DROP VARIABLE + 7 + SQL - Language Statements + + + + DROP VARIABLE + remove a schema variable + + + + +DROP VARIABLE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ] + + + + + Description + + + DROP VARIABLE removes a schema variable. + A variable can only be dropped by its owner or a superuser. + + + + + + Parameters + + + + IF EXISTS + + + Do not throw an error if the variable does not exist. A notice is issued + in this case. + + + + + + name + + + The name (optionally schema-qualified) of a schema variable. + + + + + + + + Examples + + + To remove the schema variable var1: + + +DROP VARIABLE var1; + + + + + Compatibility + + + DROP VARIABLE is proprietary PostgreSQL command. + + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index ff64c7a3ba..a83920a7a1 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -79,6 +79,10 @@ GRANT { USAGE | ALL [ PRIVILEGES ] } ON TYPE type_name [, ...] TO role_specification [, ...] [ WITH GRANT OPTION ] +GRANT { READ | WRITE | ALL [ PRIVILEGES ] } + ON VARIABLE variable_name [, ...] + TO role_specification [, ...] [ WITH GRANT OPTION ] + where role_specification can be: [ GROUP ] role_name @@ -167,6 +171,7 @@ GRANT role_name [, ...] TO PUBLIC are as follows: @@ -385,6 +390,24 @@ GRANT role_name [, ...] TO + + READ + + + Allows to read a schema variable. + + + + + + WRITE + + + Allows to set a schema variable. + + + + ALL PRIVILEGES @@ -550,6 +573,8 @@ rolename=xxxx -- privileges granted to a role C -- CREATE c -- CONNECT T -- TEMPORARY + S -- READ + w -- WRITE arwdDxt -- ALL PRIVILEGES (for tables, varies for other objects) * -- grant option for preceding privilege diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml new file mode 100644 index 0000000000..e8bf3f6dd4 --- /dev/null +++ b/doc/src/sgml/ref/let.sgml @@ -0,0 +1,90 @@ + + + + + LET + + + + LET + 7 + SQL - Language Statements + + + + LET + change a schema variable's value + + + + +LET schema_variable = sql_expression + + + + + Description + + + The LET command updates the specified schema variable' value. + + + + + + Parameters + + + + schema_variable + + + The name of schema variable. + + + + + + sql expression + + + An SQL expression, the result is cast to the schema variable's type. + + + + + + + Example: + +CREATE VARIABLE myvar AS integer; +LET myvar = 10; +LET myvar = (SELECT sum(val) FROM tab); + + + + + + Compatibility + + + + LET extends syntax defined in the SQL + standard. The standard knows SET command, + that is used for different purpouse in PostgreSQL. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 5317f8ccba..8435e05957 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -108,6 +108,12 @@ REVOKE [ GRANT OPTION FOR ] REVOKE [ ADMIN OPTION FOR ] role_name [, ...] FROM role_name [, ...] [ CASCADE | RESTRICT ] + +REVOKE [ GRANT OPTION FOR ] + { { READ | WRITE } [, ...] | ALL [ PRIVILEGES ] } + ON VARIABLE variable_name [, ...] + FROM { [ GROUP ] role_name | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index db4f4167e3..5fb82df51e 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -75,6 +75,7 @@ &alterType; &alterUser; &alterUserMapping; + &alterVariable; &alterView; &analyze; &begin; @@ -127,6 +128,7 @@ &createType; &createUser; &createUserMapping; + &createVariable; &createView; &deallocate; &declare; @@ -175,6 +177,7 @@ &dropType; &dropUser; &dropUserMapping; + &dropVariable; &dropView; &end; &execute; @@ -183,6 +186,7 @@ &grant; &importForeignSchema; &insert; + &let; &listen; &load; &lock; diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 0865240f11..1f7c4d1223 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -19,7 +19,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ pg_operator.o pg_proc.o pg_publication.o pg_range.o \ pg_db_role_setting.o pg_shdepend.o pg_subscription.o pg_type.o \ - storage.o toasting.o + pg_variable.o storage.o toasting.o BKIFILES = postgres.bki postgres.description postgres.shdescription @@ -46,7 +46,7 @@ CATALOG_HEADERS := \ pg_default_acl.h pg_init_privs.h pg_seclabel.h pg_shseclabel.h \ pg_collation.h pg_partitioned_table.h pg_range.h pg_transform.h \ pg_sequence.h pg_publication.h pg_publication_rel.h pg_subscription.h \ - pg_subscription_rel.h + pg_subscription_rel.h pg_variable.h GENERATED_HEADERS := $(CATALOG_HEADERS:%.h=%_d.h) schemapg.h diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 578e4c6592..86917e15a8 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -57,6 +57,7 @@ #include "catalog/pg_ts_parser.h" #include "catalog/pg_ts_template.h" #include "catalog/pg_transform.h" +#include "catalog/pg_variable.h" #include "commands/dbcommands.h" #include "commands/event_trigger.h" #include "commands/extension.h" @@ -112,6 +113,7 @@ static void ExecGrant_Largeobject(InternalGrant *grantStmt); static void ExecGrant_Namespace(InternalGrant *grantStmt); static void ExecGrant_Tablespace(InternalGrant *grantStmt); static void ExecGrant_Type(InternalGrant *grantStmt); +static void ExecGrant_Variable(InternalGrant *grantStmt); static void SetDefaultACLsInSchemas(InternalDefaultACL *iacls, List *nspnames); static void SetDefaultACL(InternalDefaultACL *iacls); @@ -284,6 +286,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, case OBJECT_TYPE: whole_mask = ACL_ALL_RIGHTS_TYPE; break; + case OBJECT_VARIABLE: + whole_mask = ACL_ALL_RIGHTS_VARIABLE; + break; default: elog(ERROR, "unrecognized object type: %d", objtype); /* not reached, but keep compiler quiet */ @@ -507,6 +512,10 @@ ExecuteGrantStmt(GrantStmt *stmt) all_privileges = ACL_ALL_RIGHTS_FOREIGN_SERVER; errormsg = gettext_noop("invalid privilege type %s for foreign server"); break; + case OBJECT_VARIABLE: + all_privileges = ACL_ALL_RIGHTS_VARIABLE; + errormsg = gettext_noop("invalid privilege type %s for schema variable"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) stmt->objtype); @@ -609,6 +618,9 @@ ExecGrantStmt_oids(InternalGrant *istmt) case OBJECT_TABLESPACE: ExecGrant_Tablespace(istmt); break; + case OBJECT_VARIABLE: + ExecGrant_Variable(istmt); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) istmt->objtype); @@ -768,6 +780,16 @@ objectNamesToOids(ObjectType objtype, List *objnames) objects = lappend_oid(objects, srvid); } break; + case OBJECT_VARIABLE: + foreach(cell, objnames) + { + RangeVar *varvar = (RangeVar *) lfirst(cell); + Oid relOid; + + relOid = lookup_variable(varvar->schemaname, varvar->relname, false); + objects = lappend_oid(objects, relOid); + } + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) objtype); @@ -855,6 +877,31 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames) heap_close(rel, AccessShareLock); } break; + case OBJECT_VARIABLE: + { + ScanKeyData key; + Relation rel; + HeapScanDesc scan; + HeapTuple tuple; + + ScanKeyInit(&key, + Anum_pg_variable_varnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + + rel = heap_open(VariableRelationId, AccessShareLock); + scan = heap_beginscan_catalog(rel, 1, &key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + objects = lappend_oid(objects, HeapTupleGetOid(tuple)); + } + + heap_endscan(scan); + heap_close(rel, AccessShareLock); + } + break; + default: /* should not happen */ elog(ERROR, "unrecognized GrantStmt.objtype: %d", @@ -1018,6 +1065,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s all_privileges = ACL_ALL_RIGHTS_SCHEMA; errormsg = gettext_noop("invalid privilege type %s for schema"); break; + case OBJECT_VARIABLE: + all_privileges = ACL_ALL_RIGHTS_VARIABLE; + errormsg = gettext_noop("invalid privilege type %s for schema variable"); + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) action->objtype); @@ -1215,6 +1266,12 @@ SetDefaultACL(InternalDefaultACL *iacls) this_privileges = ACL_ALL_RIGHTS_SCHEMA; break; + case 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); @@ -1441,6 +1498,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) case DEFACLOBJ_NAMESPACE: iacls.objtype = OBJECT_SCHEMA; break; + case DEFACLOBJ_VARIABLE: + iacls.objtype = OBJECT_VARIABLE; + break; default: /* Shouldn't get here */ elog(ERROR, "unexpected default ACL type: %d", @@ -3266,6 +3326,129 @@ ExecGrant_Type(InternalGrant *istmt) heap_close(relation, RowExclusiveLock); } +static void +ExecGrant_Variable(InternalGrant *istmt) +{ + Relation relation; + ListCell *cell; + + if (istmt->all_privs && istmt->privileges == ACL_NO_RIGHTS) + istmt->privileges = ACL_ALL_RIGHTS_VARIABLE; + + relation = heap_open(VariableRelationId, RowExclusiveLock); + + foreach(cell, istmt->objects) + { + Oid varId = lfirst_oid(cell); + Form_pg_variable pg_variable_tuple; + Datum aclDatum; + bool isNull; + AclMode avail_goptions; + AclMode this_privileges; + Acl *old_acl; + Acl *new_acl; + Oid grantorId; + Oid ownerId; + HeapTuple tuple; + HeapTuple newtuple; + Datum values[Natts_pg_variable]; + bool nulls[Natts_pg_variable]; + bool replaces[Natts_pg_variable]; + int noldmembers; + int nnewmembers; + Oid *oldmembers; + Oid *newmembers; + + tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varId)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for schema variables %u", varId); + + pg_variable_tuple = (Form_pg_variable) GETSTRUCT(tuple); + + /* + * Get owner ID and working copy of existing ACL. If there's no ACL, + * substitute the proper default. + */ + ownerId = pg_variable_tuple->varowner; + aclDatum = SysCacheGetAttr(VARIABLEOID, tuple, Anum_pg_variable_varacl, + &isNull); + if (isNull) + { + old_acl = acldefault(OBJECT_VARIABLE, ownerId); + /* There are no old member roles according to the catalogs */ + noldmembers = 0; + oldmembers = NULL; + } + else + { + old_acl = DatumGetAclPCopy(aclDatum); + /* Get the roles mentioned in the existing ACL */ + noldmembers = aclmembers(old_acl, &oldmembers); + } + + /* Determine ID to do the grant as, and available grant options */ + select_best_grantor(GetUserId(), istmt->privileges, + old_acl, ownerId, + &grantorId, &avail_goptions); + + /* + * Restrict the privileges to what we can actually grant, and emit the + * standards-mandated warning and error messages. + */ + this_privileges = + restrict_and_check_grant(istmt->is_grant, avail_goptions, + istmt->all_privs, istmt->privileges, + varId, grantorId, OBJECT_VARIABLE, + NameStr(pg_variable_tuple->varname), + 0, NULL); + + /* + * Generate new ACL. + */ + new_acl = merge_acl_with_grant(old_acl, istmt->is_grant, + istmt->grant_option, istmt->behavior, + istmt->grantees, this_privileges, + grantorId, ownerId); + + /* + * We need the members of both old and new ACLs so we can correct the + * shared dependency information. + */ + nnewmembers = aclmembers(new_acl, &newmembers); + + /* finished building new ACL value, now insert it */ + MemSet(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + MemSet(replaces, false, sizeof(replaces)); + + replaces[Anum_pg_variable_varacl - 1] = true; + values[Anum_pg_variable_varacl - 1] = PointerGetDatum(new_acl); + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, + nulls, replaces); + + CatalogTupleUpdate(relation, &newtuple->t_self, newtuple); + + /* Update initial privileges for extensions */ + recordExtensionInitPriv(varId, VariableRelationId, 0, new_acl); + + /* Update the shared dependency ACL info */ + updateAclDependencies(VariableRelationId, varId, 0, + ownerId, + noldmembers, oldmembers, + nnewmembers, newmembers); + + ReleaseSysCache(tuple); + + pfree(new_acl); + + /* prevent error when processing duplicate objects */ + CommandCounterIncrement(); + } + + heap_close(relation, RowExclusiveLock); +} + static AclMode string_to_privilege(const char *privname) @@ -3298,6 +3481,10 @@ string_to_privilege(const char *privname) return ACL_CONNECT; if (strcmp(privname, "rule") == 0) return 0; /* ignore old RULE privileges */ + if (strcmp(privname, "read") == 0) + return ACL_READ; + if (strcmp(privname, "write") == 0) + return ACL_WRITE; ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized privilege type \"%s\"", privname))); @@ -3333,6 +3520,10 @@ privilege_to_string(AclMode privilege) return "TEMP"; case ACL_CONNECT: return "CONNECT"; + case ACL_READ: + return "READ"; + case ACL_WRITE: + return "WRITE"; default: elog(ERROR, "unrecognized privilege: %d", (int) privilege); } @@ -3456,6 +3647,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_TYPE: msg = gettext_noop("permission denied for type %s"); break; + case OBJECT_VARIABLE: + msg = gettext_noop("permission denied for schema variable %s"); + break; case OBJECT_VIEW: msg = gettext_noop("permission denied for view %s"); break; @@ -3566,6 +3760,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype, case OBJECT_TYPE: msg = gettext_noop("must be owner of type %s"); break; + case OBJECT_VARIABLE: + msg = gettext_noop("must be owner of schema variable %s"); + break; case OBJECT_VIEW: msg = gettext_noop("must be owner of view %s"); break; @@ -3710,6 +3907,8 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid, return ACL_NO_RIGHTS; case OBJECT_TYPE: return pg_type_aclmask(table_oid, roleid, mask, how); + case OBJECT_VARIABLE: + return pg_variable_aclmask(table_oid, roleid, mask, how); default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); @@ -4499,6 +4698,67 @@ pg_type_aclmask(Oid type_oid, Oid roleid, AclMode mask, AclMaskHow how) return result; } +/* + * Exported routine for examining a user's privileges for a variable. + */ +AclMode +pg_variable_aclmask(Oid var_oid, Oid roleid, AclMode mask, AclMaskHow how) +{ + AclMode result; + HeapTuple tuple; + Datum aclDatum; + bool isNull; + Acl *acl; + Oid ownerId; + + Form_pg_variable varForm; + + /* Bypass permission checks for superusers */ + if (superuser_arg(roleid)) + return mask; + + /* + * Must get the type's tuple from pg_type + */ + tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("variable with OID %u does not exist", + var_oid))); + varForm = (Form_pg_variable) GETSTRUCT(tuple); + + /* + * Now get the type's owner and ACL from the tuple + */ + ownerId = varForm->varowner; + + aclDatum = SysCacheGetAttr(VARIABLEOID, tuple, + Anum_pg_variable_varacl, &isNull); + if (isNull) + { + /* No ACL, so build default ACL */ + acl = acldefault(OBJECT_VARIABLE, ownerId); + aclDatum = (Datum) 0; + } + else + { + /* detoast rel's ACL if necessary */ + acl = DatumGetAclP(aclDatum); + } + + result = aclmask(acl, roleid, ownerId, mask, how); + + /* if we have a detoasted copy, free it */ + if (acl && (Pointer) acl != DatumGetPointer(aclDatum)) + pfree(acl); + + ReleaseSysCache(tuple); + + return result; +} + + /* * Exported routine for checking a user's access privileges to a column * @@ -4744,6 +5004,18 @@ pg_type_aclcheck(Oid type_oid, Oid roleid, AclMode mode) return ACLCHECK_NO_PRIV; } +/* + * Exported routine for checking a user's access privileges to a variable + */ +AclResult +pg_variable_aclcheck(Oid type_oid, Oid roleid, AclMode mode) +{ + if (pg_variable_aclmask(type_oid, roleid, mode, ACLMASK_ANY) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} + /* * Ownership check for a relation (specified by OID). */ @@ -5361,6 +5633,33 @@ pg_statistics_object_ownercheck(Oid stat_oid, Oid roleid) return has_privs_of_role(roleid, ownerId); } +/* + * Ownership check for a schema variables (specified by OID). + */ +bool +pg_variable_ownercheck(Oid db_oid, Oid roleid) +{ + HeapTuple tuple; + Oid ownerId; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + tuple = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(db_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("variable with OID %u does not exist", db_oid))); + + ownerId = ((Form_pg_variable) GETSTRUCT(tuple))->varowner; + + ReleaseSysCache(tuple); + + return has_privs_of_role(roleid, ownerId); +} + + /* * Check whether specified role has CREATEROLE privilege (or is a superuser) * @@ -5486,6 +5785,10 @@ get_user_default_acl(ObjectType objtype, Oid ownerId, Oid nsp_oid) defaclobjtype = DEFACLOBJ_NAMESPACE; break; + case OBJECT_VARIABLE: + defaclobjtype = DEFACLOBJ_VARIABLE; + break; + default: return NULL; } diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 4f1d365357..782ddb1655 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -59,6 +59,7 @@ #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" +#include "catalog/pg_variable.h" #include "commands/comment.h" #include "commands/defrem.h" #include "commands/event_trigger.h" @@ -67,6 +68,7 @@ #include "commands/proclang.h" #include "commands/publicationcmds.h" #include "commands/schemacmds.h" +#include "commands/schemavariable.h" #include "commands/seclabel.h" #include "commands/sequence.h" #include "commands/trigger.h" @@ -1280,6 +1282,10 @@ doDeletion(const ObjectAddress *object, int flags) DropTransformById(object->objectId); break; + case OCLASS_VARIABLE: + RemoveVariableById(object->objectId); + break; + /* * These global object types are not supported here. */ @@ -2537,6 +2543,9 @@ getObjectClass(const ObjectAddress *object) case TransformRelationId: return OCLASS_TRANSFORM; + + case VariableRelationId: + return OCLASS_VARIABLE; } /* shouldn't get here */ diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 0f67a122ed..81aaf454a8 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -39,6 +39,7 @@ #include "catalog/pg_ts_parser.h" #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "commands/dbcommands.h" #include "funcapi.h" #include "mb/pg_wchar.h" @@ -755,6 +756,71 @@ RelationIsVisible(Oid relid) return visible; } +/* + * VariableIsVisible + * Determine whether a variable (identified by OID) is visible in the + * current search path. Visible means "would be found by searching + * for the unqualified variable name". + */ +bool +VariableIsVisible(Oid varid) +{ + HeapTuple vartup; + Form_pg_variable varform; + Oid varnamespace; + bool visible; + + vartup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + if (!HeapTupleIsValid(vartup)) + elog(ERROR, "cache lookup failed for schema variable %u", varid); + varform = (Form_pg_variable) GETSTRUCT(vartup); + + recomputeNamespacePath(); + + /* + * Quick check: if it ain't in the path at all, it ain't visible. Items in + * the system namespace are surely in the path and so we needn't even do + * list_member_oid() for them. + */ + varnamespace = varform->varnamespace; + if (varnamespace != PG_CATALOG_NAMESPACE && + !list_member_oid(activeSearchPath, varnamespace)) + visible = false; + else + { + /* + * If it is in the path, it might still not be visible; it could be + * hidden by another relation of the same name earlier in the path. So + * we must do a slow check for conflicting relations. + */ + char *varname = NameStr(varform->varname); + ListCell *l; + + visible = false; + foreach(l, activeSearchPath) + { + Oid namespaceId = lfirst_oid(l); + + if (namespaceId == varnamespace) + { + /* Found it first in path */ + visible = true; + break; + } + if (OidIsValid(get_varname_varid(varname, namespaceId))) + { + /* Found something else first in path */ + break; + } + } + } + + ReleaseSysCache(vartup); + + return visible; +} + + /* * TypenameGetTypid @@ -2776,6 +2842,202 @@ TSConfigIsVisible(Oid cfgid) return visible; } +/* + * When we know a variable name, then we can find variable simply + */ +Oid +lookup_variable(const char *nspname, const char *varname, bool missing_ok) +{ + Oid namespaceId; + Oid varoid = InvalidOid; + ListCell *l; + + if (nspname) + { + namespaceId = LookupExplicitNamespace(nspname, missing_ok); + if (!OidIsValid(namespaceId)) + return InvalidOid; + + varoid = GetSysCacheOid2(VARIABLENAMENSP, + PointerGetDatum(varname), + ObjectIdGetDatum(namespaceId)); + } + else + { + /* search for it in search path */ + recomputeNamespacePath(); + + foreach(l, activeSearchPath) + { + namespaceId = lfirst_oid(l); + + varoid = GetSysCacheOid2(VARIABLENAMENSP, + PointerGetDatum(varname), + ObjectIdGetDatum(namespaceId)); + + if (OidIsValid(varoid)) + break; + } + } + + if (!OidIsValid(varoid) && !missing_ok) + { + if (nspname) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("variable \"%s\".\"%s\" does not exist", + nspname, varname))); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("variable \"%s\" does not exist", + varname))); + } + + return varoid; +} + +List * +NamesFromList(List *names) +{ + ListCell *l; + List *result = NIL; + + foreach(l, names) + { + Node *n = lfirst(l); + + if (IsA(n, String)) + { + result = lappend(result, n); + } + else + break; + } + + return result; +} + +/* + * identify_variable + * + * Returns oid of not ambigonuous variable specified by qualified path + * or InvalidOid. When the path is ambigonuous, then not_uniq flag is + * is true. + */ +Oid +identify_variable(List *names, char **attrname, bool *not_uniq) +{ + char *a = NULL; + char *b = NULL; + char *c = NULL; + char *d = NULL; + Oid varoid_without_attr; + Oid varoid_with_attr; + + *not_uniq = false; + + switch (list_length(names)) + { + case 1: + a = strVal(linitial(names)); + return lookup_variable(NULL, a, true); + + case 2: + a = strVal(linitial(names)); + b = strVal(lsecond(names)); + + /* + * a.b can mean "schema"."variable" or "variable"."field", + * Check both variants, and returns InvalidOid with not_uniq + * flag, when both interpretations are possible. + */ + varoid_without_attr = lookup_variable(a, b, true); + varoid_with_attr = lookup_variable(NULL, a, true); + + if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr)) + { + *not_uniq = true; + return InvalidOid; + } + else if (OidIsValid(varoid_without_attr)) + { + *attrname = NULL; + return varoid_without_attr; + } + else + { + *attrname = b; + return varoid_with_attr; + } + break; + + case 3: + a = strVal(linitial(names)); + b = strVal(lsecond(names)); + c = strVal(lthird(names)); + + /* + * a.b.c can mean "catalog"."schema"."variable" or "schema"."variable"."field", + * Check both variants, and returns InvalidOid with not_uniq + * flag, when both interpretations are possible. + */ + varoid_without_attr = lookup_variable(b, c, true); + varoid_with_attr = lookup_variable(a, b, true); + + if (OidIsValid(varoid_without_attr) && OidIsValid(varoid_with_attr)) + { + *not_uniq = true; + return InvalidOid; + } + else if (OidIsValid(varoid_without_attr)) + { + *attrname = NULL; + + /* + * We in this case a "a" is used as catalog name, check it. + */ + if (strcmp(a, get_database_name(MyDatabaseId)) != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cross-database references are not implemented: %s", + NameListToString(names)))); + + return varoid_without_attr; + } + else + { + *attrname = c; + return varoid_with_attr; + } + break; + + case 4: + a = strVal(linitial(names)); + b = strVal(lsecond(names)); + c = strVal(lthird(names)); + d = strVal(lfourth(names)); + + /* + * We in this case a "a" is used as catalog name, check it. + */ + if (strcmp(a, get_database_name(MyDatabaseId)) != 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cross-database references are not implemented: %s", + NameListToString(names)))); + + *attrname = d; + return lookup_variable(b, c, true); + + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("improper qualified name (too many dotted names): %s", + NameListToString(names)))); + break; + } +} /* * DeconstructQualifiedName @@ -4416,3 +4678,14 @@ pg_is_other_temp_schema(PG_FUNCTION_ARGS) PG_RETURN_BOOL(isOtherTempNamespace(oid)); } + +Datum +pg_variable_is_visible(PG_FUNCTION_ARGS) +{ + Oid oid = PG_GETARG_OID(0); + + if (!SearchSysCacheExists1(VARIABLEOID, ObjectIdGetDatum(oid))) + PG_RETURN_NULL(); + + PG_RETURN_BOOL(VariableIsVisible(oid)); +} diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 7db942dcba..cc3d415e61 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -58,6 +58,7 @@ #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" +#include "catalog/pg_variable.h" #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/event_trigger.h" @@ -489,6 +490,18 @@ static const ObjectPropertyType ObjectProperty[] = InvalidAttrNumber, /* no ACL (same as relation) */ OBJECT_STATISTIC_EXT, true + }, + { + VariableRelationId, + VariableObjectIndexId, + VARIABLEOID, + VARIABLENAMENSP, + Anum_pg_variable_varname, + Anum_pg_variable_varnamespace, + Anum_pg_variable_varowner, + Anum_pg_variable_varacl, + OBJECT_VARIABLE, + true } }; @@ -714,6 +727,10 @@ static const struct object_type_map /* OBJECT_STATISTIC_EXT */ { "statistics object", OBJECT_STATISTIC_EXT + }, + /* OCLASS_VARIABLE */ + { + "schema variable", OBJECT_VARIABLE } }; @@ -739,6 +756,7 @@ static ObjectAddress get_object_address_attrdef(ObjectType objtype, bool missing_ok); static ObjectAddress get_object_address_type(ObjectType objtype, TypeName *typename, bool missing_ok); +static ObjectAddress get_object_address_variable(List *object, bool missing_ok); static ObjectAddress get_object_address_opcf(ObjectType objtype, List *object, bool missing_ok); static ObjectAddress get_object_address_opf_member(ObjectType objtype, @@ -996,6 +1014,10 @@ get_object_address(ObjectType objtype, Node *object, missing_ok); address.objectSubId = 0; break; + case OBJECT_VARIABLE: + address = get_object_address_variable(castNode(List, object), missing_ok); + break; + default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); /* placate compiler, in case it thinks elog might return */ @@ -1848,16 +1870,20 @@ get_object_address_defacl(List *object, bool missing_ok) case DEFACLOBJ_NAMESPACE: objtype_str = "schemas"; break; + case DEFACLOBJ_VARIABLE: + objtype_str = "variables"; + break; default: ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized default ACL object type \"%c\"", objtype), - errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".", + errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".", DEFACLOBJ_RELATION, DEFACLOBJ_SEQUENCE, DEFACLOBJ_FUNCTION, DEFACLOBJ_TYPE, - DEFACLOBJ_NAMESPACE))); + DEFACLOBJ_NAMESPACE, + DEFACLOBJ_VARIABLE))); } /* @@ -1942,6 +1968,24 @@ textarray_to_strvaluelist(ArrayType *arr) return list; } +/* + * Find the ObjectAddress for a type or domain + */ +static ObjectAddress +get_object_address_variable(List *object, bool missing_ok) +{ + ObjectAddress address; + char *nspname = NULL; + char *varname = NULL; + + ObjectAddressSet(address, VariableRelationId, InvalidOid); + + DeconstructQualifiedName(object, &nspname, &varname); + address.objectId = lookup_variable(nspname, varname, missing_ok); + + return address; +} + /* * SQL-callable version of get_object_address */ @@ -2131,6 +2175,7 @@ pg_get_object_address(PG_FUNCTION_ARGS) case OBJECT_TABCONSTRAINT: case OBJECT_OPCLASS: case OBJECT_OPFAMILY: + case OBJECT_VARIABLE: objnode = (Node *) name; break; case OBJECT_ACCESS_METHOD: @@ -2415,6 +2460,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, if (!pg_statistics_object_ownercheck(address.objectId, roleid)) aclcheck_error_type(ACLCHECK_NOT_OWNER, address.objectId); break; + case OBJECT_VARIABLE: + if (!pg_variable_ownercheck(address.objectId, roleid)) + aclcheck_error(ACLCHECK_NOT_OWNER, objtype, + NameListToString(castNode(List, object))); + break; default: elog(ERROR, "unrecognized object type: %d", (int) objtype); @@ -3157,6 +3207,32 @@ getObjectDescription(const ObjectAddress *object) break; } + case OCLASS_VARIABLE: + { + char *nspname; + HeapTuple tup; + Form_pg_variable varform; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for schema variable %u", + object->objectId); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + if (VariableIsVisible(object->objectId)) + nspname = NULL; + else + nspname = get_namespace_name(varform->varnamespace); + + appendStringInfo(&buffer, _("schema variable %s"), + quote_qualified_identifier(nspname, + NameStr(varform->varname))); + + ReleaseSysCache(tup); + break; + } + case OCLASS_TSPARSER: { HeapTuple tup; @@ -3422,6 +3498,16 @@ getObjectDescription(const ObjectAddress *object) _("default privileges on new schemas belonging to role %s"), rolename); break; + case DEFACLOBJ_VARIABLE: + if (nspname) + appendStringInfo(&buffer, + _("default privileges on new variables belonging to role %s in schema %s"), + rolename, nspname); + else + appendStringInfo(&buffer, + _("default privileges on new variables belonging to role %s"), + rolename); + break; default: /* shouldn't get here */ if (nspname) @@ -4070,6 +4156,10 @@ getObjectTypeDescription(const ObjectAddress *object) appendStringInfoString(&buffer, "transform"); break; + case OCLASS_VARIABLE: + appendStringInfoString(&buffer, "schema variable"); + break; + /* * There's intentionally no default: case here; we want the * compiler to warn if a new OCLASS hasn't been handled above. @@ -4962,6 +5052,10 @@ getObjectIdentityParts(const ObjectAddress *object, appendStringInfoString(&buffer, " on schemas"); break; + case DEFACLOBJ_VARIABLE: + appendStringInfoString(&buffer, + " on variables"); + break; } if (objname) @@ -5121,6 +5215,33 @@ getObjectIdentityParts(const ObjectAddress *object, } break; + case OCLASS_VARIABLE: + { + char *schema; + char *varname; + HeapTuple tup; + Form_pg_variable varform; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for schema variable %u", + object->objectId); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + schema = get_namespace_name_or_temp(varform->varnamespace); + varname = NameStr(varform->varname); + + appendStringInfo(&buffer, "%s", + quote_qualified_identifier(schema, varname)); + + if (objname) + *objname = list_make2(schema, varname); + + ReleaseSysCache(tup); + break; + } + /* * There's intentionally no default: case here; we want the * compiler to warn if a new OCLASS hasn't been handled above. diff --git a/src/backend/catalog/pg_variable.c b/src/backend/catalog/pg_variable.c new file mode 100644 index 0000000000..ff71f8bf6a --- /dev/null +++ b/src/backend/catalog/pg_variable.c @@ -0,0 +1,305 @@ +/*------------------------------------------------------------------------- + * + * pg_variable.c + * schema variables + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/catalog/pg_variable.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "miscadmin.h" + +#include "access/genam.h" +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/xact.h" + +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_type.h" +#include "catalog/pg_variable.h" + +#include "nodes/makefuncs.h" + +#include "storage/lmgr.h" + +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/pg_lsn.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* + * Returns name of schema variable. When variable is not on path, + * then the name is qualified. + */ +char * +schema_variable_get_name(Oid varid) +{ + HeapTuple tup; + Form_pg_variable varform; + char *varname; + char *nspname; + char *result; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for variable %u", varid); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + varname = NameStr(varform->varname); + + if (VariableIsVisible(varid)) + nspname = NULL; + else + nspname = get_namespace_name(varform->varnamespace); + + result = quote_qualified_identifier(nspname, varname); + + ReleaseSysCache(tup); + + return result; +} + +/* + * Returns varname field of pg_variable + */ +char * +get_schema_variable_name(Oid varid) +{ + HeapTuple tup; + Form_pg_variable varform; + char *varname; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for variable %u", varid); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + varname = NameStr(varform->varname); + + ReleaseSysCache(tup); + + return varname; +} + +/* + * Returns type, typmod of schema variable + */ +void +get_schema_variable_type_typmod(Oid varid, Oid *typid, int32 *typmod) +{ + HeapTuple tup; + Form_pg_variable varform; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for variable %u", varid); + + varform = (Form_pg_variable) GETSTRUCT(tup); + + *typid = varform->vartype; + *typmod = varform->vartypmod; + + ReleaseSysCache(tup); + + return; +} + +/* + * Fetch all fields of schema variable from the syscache. + */ +Variable * +GetVariable(Oid varid, bool missing_ok) +{ + HeapTuple tup; + Variable *var; + Form_pg_variable varform; + Datum aclDatum; + Datum defexprDatum; + bool isnull; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + { + if (missing_ok) + return NULL; + + elog(ERROR, "cache lookup failed for variable %u", varid); + } + + varform = (Form_pg_variable) GETSTRUCT(tup); + + var = (Variable *) palloc(sizeof(Variable)); + var->oid = varid; + var->name = pstrdup(NameStr(varform->varname)); + var->namespace = varform->varnamespace; + var->typid = varform->vartype; + var->typmod = varform->vartypmod; + var->owner = varform->varowner; + + /* Get defexpr */ + defexprDatum = SysCacheGetAttr(VARIABLEOID, + tup, + Anum_pg_variable_vardefexpr, + &isnull); + + if (!isnull) + var->defexpr = stringToNode(TextDatumGetCString(defexprDatum)); + else + var->defexpr = NULL; + + /* Get varacl */ + aclDatum = SysCacheGetAttr(VARIABLEOID, + tup, + Anum_pg_variable_varacl, + &isnull); + if (!isnull) + var->acl = DatumGetAclPCopy(aclDatum); + else + var->acl = NULL; + + ReleaseSysCache(tup); + + return var; +} + +ObjectAddress +VariableCreate(const char *varName, + Oid varNamespace, + Oid varType, + int32 varTypmod, + Oid varOwner, + Node *varDefexpr, + bool if_not_exists) +{ + Acl *varacl; + NameData varname; + bool nulls[Natts_pg_variable]; + Datum values[Natts_pg_variable]; + Relation rel; + HeapTuple tup, + oldtup; + TupleDesc tupdesc; + ObjectAddress myself, + referenced; + Oid retval; + int i; + + for (i = 0; i < Natts_pg_variable; i++) + { + nulls[i] = false; + values[i] = (Datum) 0; + } + + namestrcpy(&varname, varName); + values[Anum_pg_variable_varname - 1] = NameGetDatum(&varname); + values[Anum_pg_variable_varnamespace - 1] = ObjectIdGetDatum(varNamespace); + values[Anum_pg_variable_vartype - 1] = ObjectIdGetDatum(varType); + values[Anum_pg_variable_vartypmod - 1] = Int32GetDatum(varTypmod); + values[Anum_pg_variable_varowner - 1] = ObjectIdGetDatum(varOwner); + /* proacl will be determined later */ + + if (varDefexpr) + values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr)); + else + nulls[Anum_pg_variable_vardefexpr - 1] = true; + + rel = heap_open(VariableRelationId, RowExclusiveLock); + tupdesc = RelationGetDescr(rel); + + oldtup = SearchSysCache2(VARIABLENAMENSP, + PointerGetDatum(varName), + ObjectIdGetDatum(varNamespace)); + + if (HeapTupleIsValid(oldtup)) + { + if (if_not_exists) + ereport(NOTICE, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("schema variable \"%s\" already exists, skipping", + varName))); + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("schema variable \"%s\" already exists", + varName))); + + heap_freetuple(oldtup); + heap_close(rel, RowExclusiveLock); + + return InvalidObjectAddress; + } + + varacl = get_user_default_acl(OBJECT_VARIABLE, varOwner, + varNamespace); + + if (varacl != NULL) + values[Anum_pg_variable_varacl - 1] = PointerGetDatum(varacl); + else + nulls[Anum_pg_variable_varacl - 1] = true; + + tup = heap_form_tuple(tupdesc, values, nulls); + CatalogTupleInsert(rel, tup); + + retval = HeapTupleGetOid(tup); + + myself.classId = VariableRelationId; + myself.objectId = retval; + myself.objectSubId = 0; + + /* dependency on namespace */ + referenced.classId = NamespaceRelationId; + referenced.objectId = varNamespace; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on used type */ + referenced.classId = TypeRelationId; + referenced.objectId = varType; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on any roles mentioned in ACL */ + if (varacl != NULL) + { + int nnewmembers; + Oid *newmembers; + + nnewmembers = aclmembers(varacl, &newmembers); + updateAclDependencies(VariableRelationId, retval, 0, + varOwner, + 0, NULL, + nnewmembers, newmembers); + } + + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself, false); + + heap_freetuple(tup); + + /* Post creation hook for new function */ + InvokeObjectPostCreateHook(VariableRelationId, retval, 0); + + heap_close(rel, RowExclusiveLock); + + return myself; +} diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 4a6c99e090..2cb5b1172d 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -18,7 +18,7 @@ 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 publicationcmds.o \ - schemacmds.o seclabel.o sequence.o statscmds.o subscriptioncmds.o \ + schemacmds.o seclabel.o sequence.o schemavariable.o statscmds.o subscriptioncmds.o \ tablecmds.o tablespace.o trigger.o tsearchcmds.o typecmds.o user.o \ vacuum.o vacuumlazy.o variable.o view.o diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index eff325cc7d..a9d5e5e0ad 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -387,6 +387,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_TSTEMPLATE: case OBJECT_PUBLICATION: case OBJECT_SUBSCRIPTION: + case OBJECT_VARIABLE: { ObjectAddress address; Relation catalog; @@ -504,6 +505,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, case OBJECT_TSDICTIONARY: case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: + case OBJECT_VARIABLE: { Relation catalog; Relation relation; @@ -594,6 +596,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, case OCLASS_TSDICT: case OCLASS_TSTEMPLATE: case OCLASS_TSCONFIG: + case OCLASS_VARIABLE: { Relation catalog; @@ -852,6 +855,7 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt) case OBJECT_TABLESPACE: case OBJECT_TSDICTIONARY: case OBJECT_TSCONFIGURATION: + case OBJECT_VARIABLE: { Relation catalog; Relation relation; diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c index 01a999c2ac..fec2495e93 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/schemavariable.h" #include "utils/guc.h" #include "utils/portal.h" @@ -48,6 +49,10 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel) ResetTempTableNamespace(); break; + case DISCARD_VARIABLES: + ResetSchemaVariableCache(); + break; + default: elog(ERROR, "unrecognized DISCARD target: %d", stmt->target); } @@ -75,4 +80,5 @@ DiscardAll(bool isTopLevel) ResetPlanCache(); ResetTempTableNamespace(); ResetSequenceCaches(); + ResetSchemaVariableCache(); } diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index eecc85d14e..426df246b3 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -126,6 +126,7 @@ static event_trigger_support_data event_trigger_support[] = { {"TEXT SEARCH TEMPLATE", true}, {"TYPE", true}, {"USER MAPPING", true}, + {"VARIABLE", true}, {"VIEW", true}, {NULL, false} }; @@ -297,7 +298,8 @@ check_ddl_tag(const char *tag) pg_strcasecmp(tag, "REVOKE") == 0 || pg_strcasecmp(tag, "DROP OWNED") == 0 || pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 || - pg_strcasecmp(tag, "SECURITY LABEL") == 0) + pg_strcasecmp(tag, "SECURITY LABEL") == 0 || + pg_strcasecmp(tag, "CREATE VARIABLE") == 0) return EVENT_TRIGGER_COMMAND_TAG_OK; /* @@ -1146,6 +1148,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_TSTEMPLATE: case OBJECT_TYPE: case OBJECT_USER_MAPPING: + case OBJECT_VARIABLE: case OBJECT_VIEW: return true; @@ -1209,6 +1212,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_PUBLICATION_REL: case OCLASS_SUBSCRIPTION: case OCLASS_TRANSFORM: + case OCLASS_VARIABLE: return true; /* @@ -2244,6 +2248,8 @@ stringify_grant_objtype(ObjectType objtype) return "TABLESPACE"; case OBJECT_TYPE: return "TYPE"; + case OBJECT_VARIABLE: + return "VARIABLE"; /* these currently aren't used */ case OBJECT_ACCESS_METHOD: case OBJECT_AGGREGATE: @@ -2326,6 +2332,8 @@ stringify_adefprivs_objtype(ObjectType objtype) return "TABLESPACES"; case OBJECT_TYPE: return "TYPES"; + case OBJECT_VARIABLE: + return "VARIABLES"; /* these currently aren't used */ case OBJECT_ACCESS_METHOD: case OBJECT_AGGREGATE: diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index b945b1556a..eb8c08baf3 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -151,6 +151,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString, case CMD_INSERT: case CMD_UPDATE: case CMD_DELETE: + case CMD_PLAN_UTILITY: /* OK */ break; default: diff --git a/src/backend/commands/schemavariable.c b/src/backend/commands/schemavariable.c new file mode 100644 index 0000000000..3d39f5fd41 --- /dev/null +++ b/src/backend/commands/schemavariable.c @@ -0,0 +1,467 @@ +#include "postgres.h" +#include "miscadmin.h" + +#include "access/heapam.h" +#include "access/htup_details.h" +#include "access/xact.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/pg_variable.h" +#include "commands/schemavariable.h" +#include "executor/executor.h" +#include "executor/svariableReceiver.h" +#include "nodes/execnodes.h" +#include "optimizer/planner.h" +#include "parser/parse_coerce.h" +#include "parser/parse_expr.h" +#include "parser/parse_type.h" +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/lsyscache.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +/* + * The content of variables is not transactional. Due this fact the + * implementation of DROP can be simple, because although DROP VARIABLE + * can be reverted, the content of variable can be lost. In this example, + * DROP VARIABLE is same like reset variable. + */ + +typedef struct SchemaVariableData +{ + Oid varid; /* pg_variable 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; + bool is_rowtype; /* true when variable is composite */ + bool is_valid; /* true when variable was successfuly initialized */ +} SchemaVariableData; + +typedef SchemaVariableData *SchemaVariable; + +static HTAB *schemavarhashtab = NULL; /* hash table for session variables */ +static MemoryContext SchemaVariableMemoryContext = NULL; + +static bool first_time = true; +static void create_schemavar_hashtable(void); +static bool clean_cache_req = false; + +static void clean_cache(void); +static void force_clean_cache(XactEvent event, void *arg); + + +/* + * Save info about ncessity to clean hash table, because some + * schema variable was dropped. Don't do here more, recheck + * needs to be in transaction state. + */ +static void +InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + if (cacheid != VARIABLEOID) + return; + + clean_cache_req = true; +} + +static void +force_clean_cache(XactEvent event, void *arg) +{ + /* + * should continue only in transaction time, when + * syscache is available. + */ + if (clean_cache_req && IsTransactionState()) + { + clean_cache(); + clean_cache_req = false; + } +} + +static void +clean_cache(void) +{ + HASH_SEQ_STATUS status; + SchemaVariable var; + + if (!schemavarhashtab) + return; + + hash_seq_init(&status, schemavarhashtab); + + /* + * Every valid variable have to have entry in system + * catalog. Removed if there is nothing. + */ + while ((var = (SchemaVariable) hash_seq_search(&status)) != NULL) + { + HeapTuple tp = InvalidOid; + + tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(var->varid)); + if (!HeapTupleIsValid(tp)) + { + elog(DEBUG1, "variable %d is removed from cache", var->varid); + + if (var->freeval) + { + pfree(DatumGetPointer(var->value)); + var->freeval = false; + } + + if (hash_search(schemavarhashtab, + (void *) &var->varid, + HASH_REMOVE, + NULL) == NULL) + elog(DEBUG1, "hash table corrupted"); + } + else + ReleaseSysCache(tp); + } +} + +/* + * Create the hash table for storing schema variables + */ +static void +create_schemavar_hashtable(void) +{ + HASHCTL ctl; + + /* set callbacks */ + if (first_time) + { + CacheRegisterSyscacheCallback(VARIABLEOID, + InvalidateSchemaVarCacheCallback, + (Datum) 0); + + RegisterXactCallback(force_clean_cache, NULL); + + first_time = false; + } + + /* needs own long life memory context */ + if (SchemaVariableMemoryContext == NULL) + { + SchemaVariableMemoryContext = AllocSetContextCreate(TopMemoryContext, + "schema variables", + ALLOCSET_START_SMALL_SIZES); + } + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(SchemaVariableData); + ctl.hcxt = SchemaVariableMemoryContext; + + schemavarhashtab = hash_create("Schema variables", 64, &ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +/* + * Fast drop complete content of schema variables + */ +void +ResetSchemaVariableCache(void) +{ + if (schemavarhashtab) + { + hash_destroy(schemavarhashtab); + schemavarhashtab = NULL; + } + + if (SchemaVariableMemoryContext != NULL) + { + MemoryContextReset(SchemaVariableMemoryContext); + } +} + +/* + * Drop variable by OID + */ +void +RemoveVariableById(Oid varid) +{ + Relation rel; + HeapTuple tup; + + rel = heap_open(VariableRelationId, RowExclusiveLock); + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for variable %u", varid); + + CatalogTupleDelete(rel, &tup->t_self); + + ReleaseSysCache(tup); + + heap_close(rel, RowExclusiveLock); +} + +/* + * Creates new variable - entry in pg_catalog.pg_variable table + */ +ObjectAddress +DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt) +{ + Oid namespaceid; + AclResult aclresult; + Oid typid; + int32 typmod; + Oid varowner = GetUserId(); + + Node *cooked_default = NULL; + + namespaceid = + RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL); + + typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod); + + aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, typid); + + if (stmt->defexpr) + { + cooked_default = transformExpr(pstate, stmt->defexpr, + EXPR_KIND_VARIABLE_DEFAULT); + + cooked_default = coerce_to_specific_type(pstate, + cooked_default, typid, "DEFAULT"); + } + + return VariableCreate(stmt->variable->relname, + namespaceid, + typid, + typmod, + varowner, + cooked_default, + stmt->if_not_exists); +} + +/* + * Try to search value in hash table. If doesn't + * exists insert it (and calculate defexpr if exists. + */ +static SchemaVariable +PrepareSchemaVariableForReading(Oid varid) +{ + SchemaVariable svar; + Variable *var; + bool found; + + if (schemavarhashtab == NULL) + create_schemavar_hashtable(); + + svar = (SchemaVariable) hash_search(schemavarhashtab, &varid, + HASH_ENTER, &found); + if (!found) + { + var = GetVariable(varid, false); + get_typlenbyval(var->typid, &svar->typlen, &svar->typbyval); + + svar->varid = varid; + svar->typid = var->typid; + svar->typmod = var->typmod; + svar->isnull = true; + svar->freeval = false; + svar->value = (Datum) 0; + svar->is_rowtype = type_is_rowtype(var->typid); + + /* when we don't need calculate defexpr, value is valid already */ + svar->is_valid = var->defexpr ? false : true; + } + else if (!svar->is_valid) + { + /* we need var to recalculate defexpr */ + var = GetVariable(varid, false); + } + else + /* we don't need to go to sys cache */ + var = NULL; + + /* + * Initialize variable when it is necessary. It is fresh + * or last initialization was not successfull. + */ + if (var != NULL && var->defexpr && !svar->is_valid) + { + MemoryContext oldcontext = NULL; + + Datum value = (Datum) 0; + bool null; + EState *estate = NULL; + Expr *defexpr; + ExprState *defexprs; + + /* Prepare default expr */ + estate = CreateExecutorState(); + oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + defexpr = expression_planner((Expr *) var->defexpr); + defexprs = ExecInitExpr(defexpr, NULL); + value = ExecEvalExprSwitchContext(defexprs, GetPerTupleExprContext(estate), &null); + + MemoryContextSwitchTo(SchemaVariableMemoryContext); + + if (!null) + { + svar->value = datumCopy(value, svar->typbyval, svar->typlen); + svar->freeval = svar->value != value; + svar->isnull = false; + svar->is_valid = true; + } + else + { + svar->isnull = true; + svar->is_valid = true; + } + + MemoryContextSwitchTo(oldcontext); + + FreeExecutorState(estate); + } + + if (!svar->is_valid) + elog(ERROR, "the content of variable is not valid"); + + return svar; +} + +/* + * Returns content of variable. We expext secured access now. + * Secure check should be done before. + */ +Datum +GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy) +{ + SchemaVariable svar; + Datum value; + bool isnull; + + svar = PrepareSchemaVariableForReading(varid); + Assert(svar != NULL); + + if (expected_typid != svar->typid) + elog(ERROR, "type of variable \"%s\" is different than expected", + schema_variable_get_name(varid)); + + value = svar->value; + isnull = svar->isnull; + + *isNull = isnull; + + if (!isnull && copy) + return datumCopy(value, svar->typbyval, svar->typlen); + + return value; +} + +/* + * Write value to variable. We expect secured access in this moment. + * In this time, we recheck syschache about used type. + */ +void +SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod) +{ + MemoryContext oldcontext = NULL; + + SchemaVariable svar; + Oid var_typid; + int32 var_typmod; + bool found; + + if (schemavarhashtab == NULL) + create_schemavar_hashtable(); + + svar = (SchemaVariable) hash_search(schemavarhashtab, &varid, + HASH_ENTER, &found); + + get_schema_variable_type_typmod(varid, &var_typid, &var_typmod); + + /* check types first */ + if (var_typid != typid) + elog(ERROR, "type of expression is different than schema variable type"); + + if (found) + { + /* release current content first */ + if (svar->freeval) + { + pfree(DatumGetPointer(svar->value)); + svar->value = (Datum) 0; + svar->isnull = true; + svar->freeval = false; + } + } + + get_typlenbyval(typid, &svar->typlen, &svar->typbyval); + + svar->varid = varid; + svar->typid = typid; + svar->typmod = typmod; + + svar->isnull = true; + svar->freeval = false; + svar->value = (Datum) 0; + + svar->is_rowtype = type_is_rowtype(typid); + svar->is_valid = false; + + oldcontext = MemoryContextSwitchTo(SchemaVariableMemoryContext); + + if (!isNull) + { + svar->value = datumCopy(value, svar->typbyval, svar->typlen); + svar->freeval = svar->value != value; + svar->isnull = false; + svar->is_valid = true; + } + else + { + svar->isnull = true; + svar->is_valid = true; + } + + MemoryContextSwitchTo(oldcontext); +} + +void +doLetStmt(PlannedStmt *pstmt, + ParamListInfo params, + QueryEnvironment *queryEnv, + const char *queryString) +{ + QueryDesc *queryDesc; + DestReceiver *dest; + + PushCopiedSnapshot(GetActiveSnapshot()); + UpdateActiveSnapshotCommandId(); + + /* Create dest receiver for LET */ + dest = CreateDestReceiver(DestVariable); + + SetVariableDestReceiverParams(dest, pstmt->resultVariable); + + /* Create a QueryDesc requesting no output */ + queryDesc = CreateQueryDesc(pstmt, queryString, + GetActiveSnapshot(), + InvalidSnapshot, + dest, params, queryEnv, 0); + + ExecutorStart(queryDesc, 0); + ExecutorRun(queryDesc, ForwardScanDirection, 2L, true); + ExecutorFinish(queryDesc); + ExecutorEnd(queryDesc); + + FreeQueryDesc(queryDesc); + + PopActiveSnapshot(); +} + diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cef6632840..30e6c1290b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9656,6 +9656,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, case OCLASS_PUBLICATION_REL: case OCLASS_SUBSCRIPTION: case OCLASS_TRANSFORM: + case OCLASS_VARIABLE: /* * We don't expect any of these sorts of objects to depend on diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile index cc09895fa5..ee8ff7da9e 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -29,6 +29,6 @@ OBJS = execAmi.o execCurrent.o execExpr.o execExprInterp.o \ nodeCtescan.o nodeNamedtuplestorescan.o nodeWorktablescan.o \ nodeGroup.o nodeSubplan.o nodeSubqueryscan.o nodeTidscan.o \ nodeForeignscan.o nodeWindowAgg.o tstoreReceiver.o tqueue.o spi.o \ - nodeTableFuncscan.o + nodeTableFuncscan.o svariableReceiver.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index e284fd71d7..bb9bf53e1c 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -33,6 +33,7 @@ #include "access/nbtree.h" #include "catalog/objectaccess.h" #include "catalog/pg_type.h" +#include "commands/schemavariable.h" #include "executor/execExpr.h" #include "executor/nodeSubplan.h" #include "funcapi.h" @@ -727,6 +728,7 @@ ExecInitExprRec(Expr *node, ExprState *state, { Param *param = (Param *) node; ParamListInfo params; + AclResult aclresult; switch (param->paramkind) { @@ -736,6 +738,28 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.d.param.paramtype = param->paramtype; ExprEvalPushStep(state, &scratch); break; + + case PARAM_VARIABLE: + + /* Check permission to read schema variable */ + aclresult = pg_variable_aclcheck(param->paramid, GetUserId(), ACL_READ); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_VARIABLE, + schema_variable_get_name(param->paramid)); + + /* + * Using varoid as paramid is not practical. Better to recount + * used schema variables from zero, and later to use paramid like + * offset. + */ + scratch.opcode = EEOP_PARAM_VARIABLE; + scratch.d.vparam.paramid = state->nvariables++; + scratch.d.vparam.varoid = param->paramid; + scratch.d.vparam.paramtype = param->paramtype; + + ExprEvalPushStep(state, &scratch); + break; + case PARAM_EXTERN: /* diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 9d6e25aae5..4462dcc952 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -59,6 +59,7 @@ #include "access/tuptoaster.h" #include "catalog/pg_type.h" #include "commands/sequence.h" +#include "commands/schemavariable.h" #include "executor/execExpr.h" #include "executor/nodeSubplan.h" #include "funcapi.h" @@ -351,6 +352,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_PARAM_EXEC, &&CASE_EEOP_PARAM_EXTERN, &&CASE_EEOP_PARAM_CALLBACK, + &&CASE_EEOP_PARAM_VARIABLE, &&CASE_EEOP_CASE_TESTVAL, &&CASE_EEOP_MAKE_READONLY, &&CASE_EEOP_IOCOERCE, @@ -1007,6 +1009,13 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_PARAM_VARIABLE) + { + /* iut of line implementation; too large */ + ExecEvalParamVariable(state, op, econtext); + EEO_NEXT(); + } + EEO_CASE(EEOP_CASE_TESTVAL) { /* @@ -2323,6 +2332,79 @@ ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext) errmsg("no value found for parameter %d", paramId))); } +/* + * Evaluate a PARAM_VARIABLE parameter + */ +void +ExecEvalParamVariable(ExprState *state, ExprEvalStep *op, ExprContext *econtext) +{ + EState *estate = econtext->ecxt_estate; + + /* + * We should to ensure stable behave of schema variables in queries. It is + * important, because optimizer uses these values as stable, like extern + * parameters, what is nice, because queries are optimized well. So, don't + * try to access variables directly, use this query variable cache. + * This cache cannot be used when EState is shared - PLpgSQL did it for + * simple expressions. + */ + if (estate && !estate->es_shared) + { + int paramid = op->d.vparam.paramid; + + if (estate->es_nvariables == 0) + { + MemoryContext old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + + /* initialize estate schema variable cache */ + + estate->es_nvariables = state->nvariables; + estate->es_varnulls = palloc(sizeof(bool) * state->nvariables); + estate->es_vartypes = palloc0(sizeof(Oid) * state->nvariables); + estate->es_varvalues = palloc(sizeof(Datum) * state->nvariables); + + MemoryContextSwitchTo(old_cxt); + } + + Assert(estate->es_nvariables == state->nvariables); + Assert(estate->es_nvariables > paramid); + + if (!OidIsValid(estate->es_vartypes[paramid])) + { + MemoryContext old_cxt = MemoryContextSwitchTo(econtext->ecxt_per_query_memory); + + /* copy variable to estate schema variable cache */ + estate->es_varvalues[paramid] = + GetSchemaVariable(op->d.vparam.varoid, + &estate->es_varnulls[paramid], + op->d.vparam.paramtype, + true); + estate->es_vartypes[paramid] = op->d.vparam.paramtype; + + MemoryContextSwitchTo(old_cxt); + } + + Assert(OidIsValid(estate->es_vartypes[paramid])); + + *op->resvalue = estate->es_varvalues[paramid]; + *op->resnull = estate->es_varnulls[paramid]; + } + else + { + Datum d; + bool isnull; + + /* read content of variable directly */ + d = GetSchemaVariable(op->d.vparam.varoid, + &isnull, + op->d.vparam.paramtype, + false); + + *op->resvalue = d; + *op->resnull = isnull; + } +} + /* * Evaluate a SQLValueFunction expression. */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index b797d064b7..a49deb810c 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -43,9 +43,11 @@ #include "access/xact.h" #include "catalog/namespace.h" #include "catalog/pg_publication.h" +#include "catalog/pg_variable.h" #include "commands/matview.h" #include "commands/trigger.h" #include "executor/execdebug.h" +#include "executor/svariableReceiver.h" #include "foreign/fdwapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" @@ -204,12 +206,18 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) */ estate->es_queryEnv = queryDesc->queryEnv; + /* + * Result can be stored in schema variable. + */ + estate->es_result_variable = queryDesc->plannedstmt->resultVariable; + /* * If non-read-only query, set the command ID to mark output tuples with */ switch (queryDesc->operation) { case CMD_SELECT: + case CMD_PLAN_UTILITY: /* * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark @@ -345,6 +353,7 @@ standard_ExecutorRun(QueryDesc *queryDesc, estate->es_lastoid = InvalidOid; sendTuples = (operation == CMD_SELECT || + OidIsValid(estate->es_result_variable) || queryDesc->plannedstmt->hasReturning); if (sendTuples) @@ -924,6 +933,17 @@ InitPlan(QueryDesc *queryDesc, int eflags) estate->es_num_root_result_relations = 0; } + if (OidIsValid(estate->es_result_variable)) + { + AclResult aclresult; + Oid varid = estate->es_result_variable; + + /* Ensure this variable is writeable */ + aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_VARIABLE, schema_variable_get_name(varid)); + } + /* * Similarly, we have to lock relations selected FOR [KEY] UPDATE/SHARE * before we initialize the plan tree, else we'd be risking lock upgrades. diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 5b3eaec80b..eca7805517 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -102,6 +102,7 @@ CreateExecutorState(void) /* * Initialize all fields of the Executor State structure */ + estate->es_shared = false; estate->es_direction = ForwardScanDirection; estate->es_snapshot = InvalidSnapshot; /* caller must initialize this */ estate->es_crosscheck_snapshot = InvalidSnapshot; /* no crosscheck */ diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c new file mode 100644 index 0000000000..0eac4b5d0c --- /dev/null +++ b/src/backend/executor/svariableReceiver.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * svariableReceiver.c + * An implementation of DestReceiver that stores the result value in + * a schema variable. + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/executor/svariableReceiver.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/tuptoaster.h" +#include "executor/svariableReceiver.h" +#include "commands/schemavariable.h" + +typedef struct +{ + DestReceiver pub; + Oid varid; + Oid typid; + int32 typmod; + int typlen; + int slot_offset; + int rows; +} svariableState; + + +/* + * Prepare to receive tuples from executor. + */ +static void +svariableStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo) +{ + svariableState *myState = (svariableState *) self; + int natts = typeinfo->natts; + int outcols = 0; + int i; + + for (i = 0; i < natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(typeinfo, i); + + if (attr->attisdropped) + continue; + + if (++outcols > 1) + elog(ERROR, "svariable DestReceiver can take only one attribute"); + + myState->typid = attr->atttypid; + myState->typmod = attr->atttypmod; + myState->typlen = attr->attlen; + myState->slot_offset = i; + } + + myState->rows = 0; +} + +/* + * Receive a tuple from the executor and store it in schema variable. + */ +static bool +svariableReceiveSlot(TupleTableSlot *slot, DestReceiver *self) +{ + svariableState *myState = (svariableState *) self; + Datum value; + bool isnull; + bool freeval = false; + + /* Make sure the tuple is fully deconstructed */ + slot_getallattrs(slot); + + value = slot->tts_values[myState->slot_offset]; + isnull = slot->tts_isnull[myState->slot_offset]; + + if (myState->typlen == -1 && !isnull && VARATT_IS_EXTERNAL(DatumGetPointer(value))) + { + value = PointerGetDatum(heap_tuple_fetch_attr((struct varlena *) + DatumGetPointer(value))); + freeval = true; + } + + SetSchemaVariable(myState->varid, value, isnull, myState->typid, myState->typmod); + + if (freeval) + pfree(DatumGetPointer(value)); + + return true; +} + +/* + * Clean up at end of an executor run + */ +static void +svariableShutdownReceiver(DestReceiver *self) +{ + /* Do nothing */ +} + +/* + * Destroy receiver when done with it + */ +static void +svariableDestroyReceiver(DestReceiver *self) +{ + pfree(self); +} + +/* + * Initially create a DestReceiver object. + */ +DestReceiver * +CreateVariableDestReceiver(void) +{ + svariableState *self = (svariableState *) palloc0(sizeof(svariableState)); + + self->pub.receiveSlot = svariableReceiveSlot; + self->pub.rStartup = svariableStartupReceiver; + self->pub.rShutdown = svariableShutdownReceiver; + self->pub.rDestroy = svariableDestroyReceiver; + self->pub.mydest = DestVariable; + + /* private fields will be set by SetVariableDestReceiverParams */ + + return (DestReceiver *) self; +} + +/* + * Set parameters for a VariableDestReceiver + */ +void +SetVariableDestReceiverParams(DestReceiver *self, Oid varid) +{ + svariableState *myState = (svariableState *) self; + + Assert(myState->pub.mydest == DestVariable); + Assert(OidIsValid(varid)); + + myState->varid = varid; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 7c8220cf65..fcaa2db51a 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -93,6 +93,7 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_NODE_FIELD(resultRelations); COPY_NODE_FIELD(nonleafResultRelations); COPY_NODE_FIELD(rootResultRelations); + COPY_SCALAR_FIELD(resultVariable); COPY_NODE_FIELD(subplans); COPY_BITMAPSET_FIELD(rewindPlanIDs); COPY_NODE_FIELD(rowMarks); @@ -3000,6 +3001,7 @@ _copyQuery(const Query *from) COPY_SCALAR_FIELD(canSetTag); COPY_NODE_FIELD(utilityStmt); COPY_SCALAR_FIELD(resultRelation); + COPY_SCALAR_FIELD(resultVariable); COPY_SCALAR_FIELD(hasAggs); COPY_SCALAR_FIELD(hasWindowFuncs); COPY_SCALAR_FIELD(hasTargetSRFs); @@ -3118,6 +3120,18 @@ _copySelectStmt(const SelectStmt *from) return newnode; } +static LetStmt * +_copyLetStmt(const LetStmt *from) +{ + LetStmt *newnode = makeNode(LetStmt); + + COPY_NODE_FIELD(target); + COPY_NODE_FIELD(selectStmt); + COPY_LOCATION_FIELD(location); + + return newnode; +} + static SetOperationStmt * _copySetOperationStmt(const SetOperationStmt *from) { @@ -5166,6 +5180,9 @@ copyObjectImpl(const void *from) case T_SelectStmt: retval = _copySelectStmt(from); break; + case T_LetStmt: + retval = _copyLetStmt(from); + break; case T_SetOperationStmt: retval = _copySetOperationStmt(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 378f2facb8..3ec472e19b 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -949,6 +949,7 @@ _equalQuery(const Query *a, const Query *b) COMPARE_SCALAR_FIELD(canSetTag); COMPARE_NODE_FIELD(utilityStmt); COMPARE_SCALAR_FIELD(resultRelation); + COMPARE_SCALAR_FIELD(resultVariable); COMPARE_SCALAR_FIELD(hasAggs); COMPARE_SCALAR_FIELD(hasWindowFuncs); COMPARE_SCALAR_FIELD(hasTargetSRFs); @@ -1057,6 +1058,16 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b) return true; } +static bool +_equalLetStmt(const LetStmt *a, const LetStmt *b) +{ + COMPARE_NODE_FIELD(target); + COMPARE_NODE_FIELD(selectStmt); + + return true; +} + + static bool _equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b) { @@ -3225,6 +3236,9 @@ equal(const void *a, const void *b) case T_SelectStmt: retval = _equalSelectStmt(a, b); break; + case T_LetStmt: + retval = _equalLetStmt(a, b); + break; case T_SetOperationStmt: retval = _equalSetOperationStmt(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 6269f474d2..46404ff9ac 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -278,6 +278,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_NODE_FIELD(resultRelations); WRITE_NODE_FIELD(nonleafResultRelations); WRITE_NODE_FIELD(rootResultRelations); + WRITE_OID_FIELD(resultVariable); WRITE_NODE_FIELD(subplans); WRITE_BITMAPSET_FIELD(rewindPlanIDs); WRITE_NODE_FIELD(rowMarks); @@ -2793,6 +2794,16 @@ _outSelectStmt(StringInfo str, const SelectStmt *node) WRITE_NODE_FIELD(rarg); } +static void +_outLetStmt(StringInfo str, const LetStmt *node) +{ + WRITE_NODE_TYPE("LET"); + + WRITE_NODE_FIELD(target); + WRITE_NODE_FIELD(selectStmt); + WRITE_LOCATION_FIELD(location); +} + static void _outFuncCall(StringInfo str, const FuncCall *node) { @@ -2971,6 +2982,7 @@ _outQuery(StringInfo str, const Query *node) appendStringInfoString(str, " :utilityStmt <>"); WRITE_INT_FIELD(resultRelation); + WRITE_INT_FIELD(resultVariable); WRITE_BOOL_FIELD(hasAggs); WRITE_BOOL_FIELD(hasWindowFuncs); WRITE_BOOL_FIELD(hasTargetSRFs); @@ -4191,6 +4203,9 @@ outNode(StringInfo str, const void *obj) case T_SelectStmt: _outSelectStmt(str, obj); break; + case T_LetStmt: + _outLetStmt(str, obj); + break; case T_ColumnDef: _outColumnDef(str, obj); break; diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index 3254524223..4454327549 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -242,6 +242,7 @@ _readQuery(void) READ_BOOL_FIELD(canSetTag); READ_NODE_FIELD(utilityStmt); READ_INT_FIELD(resultRelation); + READ_INT_FIELD(resultVariable); READ_BOOL_FIELD(hasAggs); READ_BOOL_FIELD(hasWindowFuncs); READ_BOOL_FIELD(hasTargetSRFs); @@ -1485,6 +1486,7 @@ _readPlannedStmt(void) READ_NODE_FIELD(resultRelations); READ_NODE_FIELD(nonleafResultRelations); READ_NODE_FIELD(rootResultRelations); + READ_OID_FIELD(resultVariable); READ_NODE_FIELD(subplans); READ_BITMAPSET_FIELD(rewindPlanIDs); READ_NODE_FIELD(rowMarks); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index fd06da98b9..01f97f2d86 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -335,7 +335,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) */ if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 && IsUnderPostmaster && - parse->commandType == CMD_SELECT && + (parse->commandType == CMD_SELECT || + parse->commandType == CMD_PLAN_UTILITY) && !parse->hasModifyingCTE && max_parallel_workers_per_gather > 0 && !IsParallelWorker() && @@ -352,6 +353,8 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) glob->parallelModeOK = false; } + + /* * glob->parallelModeNeeded is normally set to false here and changed to * true during plan creation if a Gather or Gather Merge plan is actually @@ -521,6 +524,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->resultRelations = glob->resultRelations; result->nonleafResultRelations = glob->nonleafResultRelations; result->rootResultRelations = glob->rootResultRelations; + result->resultVariable = parse->resultVariable; result->subplans = glob->subplans; result->rewindPlanIDs = glob->rewindPlanIDs; result->rowMarks = glob->finalrowmarks; @@ -2167,7 +2171,7 @@ grouping_planner(PlannerInfo *root, bool inheritance_update, * If this is an INSERT/UPDATE/DELETE, and we're not being called from * inheritance_planner, add the ModifyTable node. */ - if (parse->commandType != CMD_SELECT && !inheritance_update) + if (parse->commandType != CMD_SELECT && parse->commandType != CMD_PLAN_UTILITY && !inheritance_update) { List *withCheckOptionLists; List *returningLists; diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 8603feef2b..2923e3fcc7 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -71,6 +71,7 @@ preprocess_targetlist(PlannerInfo *root) { Query *parse = root->parse; int result_relation = parse->resultRelation; + int result_variable = parse->resultVariable; List *range_table = parse->rtable; CmdType command_type = parse->commandType; RangeTblEntry *target_rte = NULL; @@ -96,6 +97,10 @@ preprocess_targetlist(PlannerInfo *root) target_relation = heap_open(target_rte->relid, NoLock); } + else if (result_variable) + { + Assert(command_type == CMD_PLAN_UTILITY); + } else Assert(command_type == CMD_SELECT); diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index a04ad6e99e..8f023225c6 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1254,7 +1254,8 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context) { Param *param = (Param *) node; - if (param->paramkind == PARAM_EXTERN) + if (param->paramkind == PARAM_EXTERN || + param->paramkind == PARAM_VARIABLE) return false; if (param->paramkind != PARAM_EXEC || @@ -4799,7 +4800,7 @@ substitute_actual_parameters_mutator(Node *node, { if (node == NULL) return NULL; - if (IsA(node, Param)) + if (IsA(node, Param) && ((Param *) node)->paramkind != PARAM_VARIABLE) { Param *param = (Param *) node; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 8369e3ad62..fc0cf34c7d 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1272,7 +1272,7 @@ get_relation_constraints(PlannerInfo *root, * descriptor, instead of constraint exclusion which is driven by the * individual partition's partition constraint. */ - if (enable_partition_pruning && root->parse->commandType != CMD_SELECT) + if (enable_partition_pruning && root->parse->commandType != CMD_SELECT && root->parse->commandType != CMD_PLAN_UTILITY) { List *pcqual = RelationGetPartitionQual(relation); diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index c601b6d40d..53ce2435d6 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -25,7 +25,10 @@ #include "postgres.h" #include "access/sysattr.h" +#include "catalog/namespace.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" +#include "commands/schemavariable.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -44,6 +47,8 @@ #include "parser/parse_target.h" #include "parser/parsetree.h" #include "rewrite/rewriteManip.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" #include "utils/rel.h" @@ -78,6 +83,8 @@ static Query *transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt); static Query *transformCallStmt(ParseState *pstate, CallStmt *stmt); +static Query *transformLetStmt(ParseState *pstate, + LetStmt *stmt); static void transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc, bool pushedDown); #ifdef RAW_EXPRESSION_COVERAGE_TEST @@ -267,6 +274,7 @@ transformStmt(ParseState *pstate, Node *parseTree) case T_InsertStmt: case T_UpdateStmt: case T_DeleteStmt: + case T_LetStmt: (void) test_raw_expression_coverage(parseTree, NULL); break; default: @@ -327,6 +335,11 @@ transformStmt(ParseState *pstate, Node *parseTree) (CallStmt *) parseTree); break; + case T_LetStmt: + result = transformLetStmt(pstate, + (LetStmt *) parseTree); + break; + default: /* @@ -367,6 +380,7 @@ analyze_requires_snapshot(RawStmt *parseTree) case T_DeleteStmt: case T_UpdateStmt: case T_SelectStmt: + case T_LetStmt: result = true; break; @@ -1567,6 +1581,203 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) return qry; } +/* + * transformLetStmt - + * transform an Let Statement + */ +static Query * +transformLetStmt(ParseState *pstate, LetStmt *stmt) +{ + Query *qry = makeNode(Query); + List *exprList = NIL; + List *exprListCoer = NIL; + List *indirection = NIL; + ListCell *lc; + Query *selectQuery; + int i = 0; + + Oid varid; + + ParseExprKind sv_expr_kind; + char *attrname = NULL; + bool not_unique; + bool is_rowtype; + Oid typid; + int32 typmod; + + AclResult aclresult; + List *names = NULL; + int indirection_start; + + sv_expr_kind = pstate->p_expr_kind; + pstate->p_expr_kind = EXPR_KIND_LET; + + /* There can't be any outer WITH to worry about */ + Assert(pstate->p_ctenamespace == NIL); + + /* Exec this command as utility */ + qry->commandType = CMD_PLAN_UTILITY; + qry->utilityStmt = (Node *) stmt; + + names = NamesFromList(stmt->target); + + varid = identify_variable(names, &attrname, ¬_unique); + if (not_unique) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_PARAMETER), + errmsg("target \"%s\" of LET command is ambiguous", + NameListToString(names)), + parser_errposition(pstate, stmt->location))); + + if (!OidIsValid(varid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("schema variable \"%s\" doesn't exists", + NameListToString(names)), + parser_errposition(pstate, stmt->location))); + + qry->resultVariable = varid; + + get_schema_variable_type_typmod(varid, &typid, &typmod); + + is_rowtype = type_is_rowtype(typid); + + if (attrname && !is_rowtype) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("target variable \"%s\" is not row type", + schema_variable_get_name(varid)), + parser_errposition(pstate, stmt->location))); + + aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_WRITE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_VARIABLE, NameListToString(names)); + + selectQuery = transformStmt(pstate, stmt->selectStmt); + + /* The grammar should have produced a SELECT */ + if (!IsA(selectQuery, Query) || + selectQuery->commandType != CMD_SELECT) + elog(ERROR, "unexpected non-SELECT command in LET ... SELECT"); + + /*---------- + * Generate an expression list for the LET that selects all the + * non-resjunk columns from the subquery. + *---------- + */ + exprList = NIL; + foreach(lc, selectQuery->targetList) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + + if (tle->resjunk) + continue; + + exprList = lappend(exprList, tle->expr); + } + + /* + * Because doesn't support pattern matching, don't allow multicolumn result + */ + if (list_length(exprList) != 1) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("expression is not scalar value"), + parser_errposition(pstate, + exprLocation((Node *) exprList)))); + + indirection_start = list_length(names) - (attrname ? 1 : 0); + indirection = list_copy_tail(stmt->target, indirection_start); + + exprListCoer = NIL; + foreach(lc, exprList) + { + Node *orig_expr = (Node*) lfirst(lc); + Oid exprtypid = exprType((Node *) orig_expr); + Param *param = makeNode(Param); + Expr *expr = NULL; + + param->paramkind = PARAM_VARIABLE; + param->paramid = varid; + param->paramtype = typid; + param->paramtypmod = typmod; + + if (indirection != NULL) + { + bool targetIsArray; + char *targetName; + + targetName = attrname != NULL ? attrname : get_schema_variable_name(varid); + targetIsArray = OidIsValid(get_element_type(typid)); + + expr = (Expr *) + transformAssignmentIndirection(pstate, + (Node *) param, + targetName, + targetIsArray, + typid, + typmod, + InvalidOid, + list_head(indirection), + (Node *) orig_expr, + stmt->location); + } + else + expr = (Expr *) + coerce_to_target_type(pstate, + (Node *) orig_expr, + exprtypid, + typid, typmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + stmt->location); + + if (expr == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("variable \"%s\" is of type %s," + " but expression is of type %s", + schema_variable_get_name(varid), + format_type_be(typid), + format_type_be(exprtypid)), + errhint("You will need to rewrite or cast the expression."), + parser_errposition(pstate, exprLocation((Node *) orig_expr)))); + + exprListCoer = lappend(exprListCoer, expr); + } + + /* + * Generate query's target list using the computed list of expressions. + * Also, mark all the target columns as needing insert permissions. + */ + qry->targetList = NIL; + foreach(lc, exprListCoer) + { + Expr *expr = (Expr *) lfirst(lc); + TargetEntry *tle; + + tle = makeTargetEntry(expr, + i + 1, + FigureColname((Node *)expr), + false); + qry->targetList = lappend(qry->targetList, tle); + } + + /* done building the range table and jointree */ + qry->rtable = pstate->p_rtable; + qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); + + qry->hasTargetSRFs = pstate->p_hasTargetSRFs; + qry->hasSubLinks = pstate->p_hasSubLinks; + + assign_query_collations(pstate, qry); + + pstate->p_expr_kind = sv_expr_kind; + + return qry; +} + + /* * transformSetOperationStmt - * transforms a set-operations tree diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 87f5e95827..25036669c1 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -257,8 +257,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt - CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt - CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt + CreateSchemaStmt CreateSchemaVarStmt CreateSeqStmt CreateStmt CreateStatsStmt + CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt @@ -268,7 +268,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); DropTransformStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt - ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt + LetStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt RuleActionStmt RuleActionStmtOrEmpty RuleStmt @@ -400,6 +400,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TriggerTransitions TriggerReferencing publication_name_list vacuum_relation_list opt_vacuum_relation_list + let_target %type group_by_list %type group_by_item empty_grouping_set rollup_clause cube_clause @@ -584,6 +585,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type partbound_datum PartitionRangeDatum %type hash_partbound partbound_datum_list range_datum_list %type hash_partbound_elem +%type optSchemaVarDefExpr /* * Non-keyword token types. These are hard-wired into the "flex" lexer. @@ -649,7 +651,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); KEY LABEL LANGUAGE LARGE_P LAST_P LATERAL_P - LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL + LEADING LEAKPROOF LEAST LEFT LET LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE @@ -687,8 +689,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 VARIABLES + VARIADIC VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE @@ -878,6 +880,7 @@ stmt : | CreatePolicyStmt | CreatePLangStmt | CreateSchemaStmt + | CreateSchemaVarStmt | CreateSeqStmt | CreateStmt | CreateSubscriptionStmt @@ -917,6 +920,7 @@ stmt : | ImportForeignSchemaStmt | IndexStmt | InsertStmt + | LetStmt | ListenStmt | RefreshMatViewStmt | LoadStmt @@ -1808,7 +1812,12 @@ DiscardStmt: n->target = DISCARD_SEQUENCES; $$ = (Node *) n; } - + | DISCARD VARIABLES + { + DiscardStmt *n = makeNode(DiscardStmt); + n->target = DISCARD_VARIABLES; + $$ = (Node *) n; + } ; @@ -4479,6 +4488,42 @@ create_extension_opt_item: } ; +/***************************************************************************** + * + * QUERY : + * CREATE VARIABLE varname [AS] type + * + *****************************************************************************/ + +CreateSchemaVarStmt: + CREATE OptTemp VARIABLE qualified_name opt_as Typename optSchemaVarDefExpr + { + CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt); + $4->relpersistence = $2; + n->variable = $4; + n->typeName = $6; + n->defexpr = $7; + n->if_not_exists = false; + $$ = (Node *) n; + } + | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename optSchemaVarDefExpr + { + CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt); + $7->relpersistence = $2; + n->variable = $7; + n->typeName = $9; + n->defexpr = $10; + n->if_not_exists = true; + $$ = (Node *) n; + } + ; + +optSchemaVarDefExpr: DEFAULT b_expr { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; + + + /***************************************************************************** * * ALTER EXTENSION name UPDATE [ TO version ] @@ -6335,6 +6380,7 @@ drop_type_any_name: | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } | TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; /* object types taking name_list */ @@ -6604,6 +6650,7 @@ comment_type_any_name: | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; /* object types taking name */ @@ -6742,6 +6789,7 @@ security_label_type_any_name: | TABLE { $$ = OBJECT_TABLE; } | VIEW { $$ = OBJECT_VIEW; } | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; /* object types taking name */ @@ -7163,6 +7211,14 @@ privilege_target: n->objs = $2; $$ = n; } + | VARIABLE qualified_name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + n->targtype = ACL_TARGET_OBJECT; + n->objtype = OBJECT_VARIABLE; + n->objs = $2; + $$ = n; + } | ALL TABLES IN_P SCHEMA name_list { PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); @@ -7203,6 +7259,14 @@ privilege_target: n->objs = $5; $$ = n; } + | ALL VARIABLES IN_P SCHEMA name_list + { + PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget)); + n->targtype = ACL_TARGET_ALL_IN_SCHEMA; + n->objtype = OBJECT_VARIABLE; + n->objs = $5; + $$ = n; + } ; @@ -7363,6 +7427,7 @@ defacl_privilege_target: | SEQUENCES { $$ = OBJECT_SEQUENCE; } | TYPES_P { $$ = OBJECT_TYPE; } | SCHEMAS { $$ = OBJECT_SCHEMA; } + | VARIABLES { $$ = OBJECT_VARIABLE; } ; @@ -8959,6 +9024,25 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name n->missing_ok = false; $$ = (Node *)n; } + | ALTER VARIABLE any_name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_VARIABLE; + n->object = (Node *) $3; + n->newname = $6; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER VARIABLE IF_P EXISTS any_name RENAME TO name + { + RenameStmt *n = makeNode(RenameStmt); + n->renameType = OBJECT_VARIABLE; + n->object = (Node *) $5; + n->newname = $8; + n->missing_ok = true; + $$ = (Node *)n; + } + ; opt_column: COLUMN { $$ = COLUMN; } @@ -9277,6 +9361,25 @@ AlterObjectSchemaStmt: n->missing_ok = false; $$ = (Node *)n; } + | ALTER VARIABLE any_name SET SCHEMA name + { + AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); + n->objectType = OBJECT_VARIABLE; + n->object = (Node *) $3; + n->newschema = $6; + n->missing_ok = false; + $$ = (Node *)n; + } + | ALTER VARIABLE IF_P EXISTS any_name SET SCHEMA name + { + AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt); + n->objectType = OBJECT_VARIABLE; + n->object = (Node *) $5; + n->newschema = $8; + n->missing_ok = true; + $$ = (Node *)n; + } + ; /***************************************************************************** @@ -9512,6 +9615,14 @@ AlterOwnerStmt: ALTER AGGREGATE aggregate_with_argtypes OWNER TO RoleSpec n->newowner = $6; $$ = (Node *)n; } + | ALTER VARIABLE any_name OWNER TO RoleSpec + { + AlterOwnerStmt *n = makeNode(AlterOwnerStmt); + n->objectType = OBJECT_VARIABLE; + n->object = (Node *) $3; + n->newowner = $6; + $$ = (Node *)n; + } ; @@ -10693,6 +10804,7 @@ ExplainableStmt: | CreateMatViewStmt | RefreshMatViewStmt | ExecuteStmt /* by default all are $$=$1 */ + | LetStmt ; explain_option_list: @@ -10750,6 +10862,7 @@ PreparableStmt: | InsertStmt | UpdateStmt | DeleteStmt /* by default all are $$=$1 */ + | LetStmt ; /***************************************************************************** @@ -11148,6 +11261,44 @@ opt_hold: /* EMPTY */ { $$ = 0; } | WITHOUT HOLD { $$ = 0; } ; +/***************************************************************************** + * + * QUERY: + * LET STATEMENTS + * + *****************************************************************************/ +LetStmt: LET let_target '=' a_expr + { + LetStmt *n = makeNode(LetStmt); + SelectStmt *select = makeNode(SelectStmt); + ResTarget *res = makeNode(ResTarget); + + n->target = $2; + + /* Create target list for implicit query */ + res->name = NULL; + res->indirection = NIL; + res->val = (Node *) $4; + res->location = @4; + + select->targetList = list_make1(res); + n->selectStmt = (Node *) select; + + n->location = @2; + + $$ = (Node *) n; + } + ; + +let_target: + ColId opt_indirection + { + $$ = list_make1(makeString($1)); + if ($2) + $$ = list_concat($$, + check_indirection($2, yyscanner)); + } + /***************************************************************************** * * QUERY: @@ -15127,6 +15278,7 @@ unreserved_keyword: | LARGE_P | LAST_P | LEAKPROOF + | LET | LEVEL | LISTEN | LOAD @@ -15275,6 +15427,8 @@ unreserved_keyword: | VALIDATE | VALIDATOR | VALUE_P + | VARIABLE + | VARIABLES | VARYING | VERSION_P | VIEW diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 61727e1d71..6823612fba 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -349,6 +349,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) Assert(false); /* can't happen */ break; case EXPR_KIND_OTHER: + case EXPR_KIND_LET: /* * Accept aggregate/grouping here; caller must throw error if @@ -465,6 +466,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: if (isAgg) err = _("aggregate functions are not allowed in DEFAULT expressions"); @@ -879,6 +881,7 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: err = _("window functions are not allowed in DEFAULT expressions"); break; case EXPR_KIND_INDEX_EXPRESSION: @@ -902,6 +905,8 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_CALL_ARGUMENT: err = _("window functions are not allowed in CALL arguments"); break; + case EXPR_KIND_LET: + err = _("window functions are not allowed in LET statement"); /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 385e54a9b6..6ea194b563 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -16,6 +16,7 @@ #include "postgres.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "commands/dbcommands.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -37,6 +38,7 @@ #include "utils/date.h" #include "utils/lsyscache.h" #include "utils/timestamp.h" +#include "utils/typcache.h" #include "utils/xml.h" @@ -116,6 +118,9 @@ static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs); static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); static Node *transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr); static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); +static Node *makeParamSchemaVariable(ParseState *pstate, + Oid varid, Oid typid, int32 typmod, + char *attrname, int location); static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location); static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); @@ -512,6 +517,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) char *nspname = NULL; char *relname = NULL; char *colname = NULL; + Oid varid = InvalidOid; + char *attrname = NULL; + bool not_unique; + RangeTblEntry *rte; int levels_up; enum @@ -749,6 +758,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) break; } + varid = identify_variable(cref->fields, &attrname, ¬_unique); + + if (not_unique) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_PARAMETER), + errmsg("schema variable reference \"%s\" is ambiguous", + NameListToString(cref->fields)), + parser_errposition(pstate, cref->location))); + /* * Now give the PostParseColumnRefHook, if any, a chance. We pass the * translation-so-far so that it can throw an error if it wishes in the @@ -773,6 +791,71 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) parser_errposition(pstate, cref->location))); } + if (OidIsValid(varid)) + { + Oid typid; + int32 typmod; + + get_schema_variable_type_typmod(varid, &typid, &typmod); + + if (node != NULL) + { + /* + * some collision can be solved simply here to reduce errors + * based on simply existence of some variables. Often error + * can be using alias same like variable name. In this case, + * when we found column reference, and we found reference to + * possible composite variable, but the variable is not composite, + * then we can ignore the variable as simply improper, and we + * use column reference only. + */ + if (attrname) + { + if (type_is_rowtype(typid)) + { + TupleDesc tupdesc; + bool found = false; + int i; + + /* slow part, I hope it will not be to often */ + tupdesc = lookup_rowtype_tupdesc(typid, typmod); + for (i = 0; i < tupdesc->natts; i++) + { + if (namestrcmp(&(TupleDescAttr(tupdesc, i)->attname), attrname) == 0 && + !TupleDescAttr(tupdesc, i)->attisdropped) + { + found = true; + break; + } + } + + FreeTupleDesc(tupdesc); + + /* there are not composite variable with this field */ + if (!found) + varid = InvalidOid; + } + else + /* there are not composite variable with this name */ + varid = InvalidOid; + } + + /* Raise error if varid is still valid. It should be really amigonuous */ + if (OidIsValid(varid)) + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_COLUMN), + errmsg("column reference \"%s\" is ambiguous", + NameListToString(cref->fields)), + errdetail("The qualified identifier can be column reference or schema variable reference"), + parser_errposition(pstate, cref->location))); + } + + if (OidIsValid(varid)) + node = makeParamSchemaVariable(pstate, + varid, typid, typmod, + attrname, cref->location); + } + /* * Throw error if no translation found. */ @@ -807,6 +890,59 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) return node; } +/* + * Generate param variable for reference to schema variable + */ +static Node * +makeParamSchemaVariable(ParseState *pstate, Oid varid, Oid typid, int32 typmod, char *attrname, int location) +{ + Param *param; + + param = makeNode(Param); + + param->paramkind = PARAM_VARIABLE; + param->paramid = varid; + param->paramtype = typid; + param->paramtypmod = typmod; + + if (attrname != NULL) + { + TupleDesc tupdesc; + int i; + + tupdesc = lookup_rowtype_tupdesc(typid, typmod); + + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute att = TupleDescAttr(tupdesc, i); + + if (strcmp(attrname, NameStr(att->attname)) == 0 && + !att->attisdropped) + { + /* Success, so generate a FieldSelect expression */ + FieldSelect *fselect = makeNode(FieldSelect); + + fselect->arg = (Expr *) param; + fselect->fieldnum = i + 1; + fselect->resulttype = att->atttypid; + fselect->resulttypmod = att->atttypmod; + /* save attribute's collation for parse_collate.c */ + fselect->resultcollid = att->attcollation; + + ReleaseTupleDesc(tupdesc); + return (Node *) fselect; + } + } + + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("could not identify column \"%s\" in variable", attrname), + parser_errposition(pstate, location))); + } + + return (Node *) param; +} + static Node * transformParamRef(ParseState *pstate, ParamRef *pref) { @@ -1818,6 +1954,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_RETURNING: case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: + case EXPR_KIND_LET: /* okay */ break; case EXPR_KIND_CHECK_CONSTRAINT: @@ -1826,6 +1963,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: err = _("cannot use subquery in DEFAULT expression"); break; case EXPR_KIND_INDEX_EXPRESSION: @@ -3460,6 +3598,7 @@ ParseExprKindName(ParseExprKind exprKind) return "CHECK"; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: return "DEFAULT"; case EXPR_KIND_INDEX_EXPRESSION: return "index expression"; @@ -3475,6 +3614,8 @@ ParseExprKindName(ParseExprKind exprKind) return "PARTITION BY"; case EXPR_KIND_CALL_ARGUMENT: return "CALL"; + case EXPR_KIND_LET: + return "LET"; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c index 44257154b8..b2c9900e00 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2347,6 +2347,7 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) break; case EXPR_KIND_COLUMN_DEFAULT: case EXPR_KIND_FUNCTION_DEFAULT: + case EXPR_KIND_VARIABLE_DEFAULT: err = _("set-returning functions are not allowed in DEFAULT expressions"); break; case EXPR_KIND_INDEX_EXPRESSION: @@ -2370,6 +2371,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) case EXPR_KIND_CALL_ARGUMENT: err = _("set-returning functions are not allowed in CALL arguments"); break; + case EXPR_KIND_LET: + err = _("set-returning functions are not allowed in CALL arguments"); + break; /* * There is intentionally no default: case here, so that the diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 4932e58022..c60fe011f7 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -35,16 +35,6 @@ static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle, Var *var, int levelsup); -static Node *transformAssignmentIndirection(ParseState *pstate, - Node *basenode, - const char *targetName, - bool targetIsArray, - Oid targetTypeId, - int32 targetTypMod, - Oid targetCollation, - ListCell *indirection, - Node *rhs, - int location); static Node *transformAssignmentSubscripts(ParseState *pstate, Node *basenode, const char *targetName, @@ -672,7 +662,7 @@ updateTargetListEntry(ParseState *pstate, * might want to decorate indirection cells with their own location info, * in which case the location argument could probably be dropped.) */ -static Node * +Node * transformAssignmentIndirection(ParseState *pstate, Node *basenode, const char *targetName, diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 3123ee274d..10737d422d 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -3350,7 +3350,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events) * get executed. Also, utilities aren't rewritten at all (do we still * need that check?) */ - if (event != CMD_SELECT && event != CMD_UTILITY) + if (event != CMD_SELECT && event != CMD_UTILITY && event != CMD_PLAN_UTILITY) { int result_relation; RangeTblEntry *rt_entry; diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c index 61ef396d8a..6a068af799 100644 --- a/src/backend/rewrite/rowsecurity.c +++ b/src/backend/rewrite/rowsecurity.c @@ -212,7 +212,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, } /* - * For SELECT, UPDATE and DELETE, add security quals to enforce the USING + * For SELECT, LET, UPDATE and DELETE, add security quals to enforce the USING * policies. These security quals control access to existing table rows. * Restrictive policies are combined together using AND, and permissive * policies are combined together using OR. @@ -222,6 +222,7 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, &restrictive_policies); if (commandType == CMD_SELECT || + commandType == CMD_PLAN_UTILITY || commandType == CMD_UPDATE || commandType == CMD_DELETE) add_security_quals(rt_index, @@ -423,6 +424,7 @@ get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id, switch (cmd) { case CMD_SELECT: + case CMD_PLAN_UTILITY: if (policy->polcmd == ACL_SELECT_CHR) cmd_matches = true; break; diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index c95a4d519d..47fb0f38b1 100644 --- a/src/backend/tcop/dest.c +++ b/src/backend/tcop/dest.c @@ -37,6 +37,7 @@ #include "executor/functions.h" #include "executor/tqueue.h" #include "executor/tstoreReceiver.h" +#include "executor/svariableReceiver.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "utils/portal.h" @@ -143,6 +144,9 @@ CreateDestReceiver(CommandDest dest) case DestTupleQueue: return CreateTupleQueueDestReceiver(NULL); + + case DestVariable: + return CreateVariableDestReceiver(); } /* should never get here */ @@ -178,6 +182,7 @@ EndCommand(const char *commandTag, CommandDest dest) case DestSQLFunction: case DestTransientRel: case DestTupleQueue: + case DestVariable: break; } } @@ -222,6 +227,7 @@ NullCommand(CommandDest dest) case DestSQLFunction: case DestTransientRel: case DestTupleQueue: + case DestVariable: break; } } @@ -268,6 +274,7 @@ ReadyForQuery(CommandDest dest) case DestSQLFunction: case DestTransientRel: case DestTupleQueue: + case DestVariable: break; } } diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index b5804f64ad..35199fd0dc 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -47,6 +47,7 @@ #include "commands/proclang.h" #include "commands/publicationcmds.h" #include "commands/schemacmds.h" +#include "commands/schemavariable.h" #include "commands/seclabel.h" #include "commands/sequence.h" #include "commands/subscriptioncmds.h" @@ -344,7 +345,7 @@ ProcessUtility(PlannedStmt *pstmt, char *completionTag) { Assert(IsA(pstmt, PlannedStmt)); - Assert(pstmt->commandType == CMD_UTILITY); + Assert(pstmt->commandType == CMD_UTILITY || pstmt->commandType == CMD_PLAN_UTILITY); Assert(queryString != NULL); /* required as of 8.4 */ /* @@ -915,6 +916,14 @@ standard_ProcessUtility(PlannedStmt *pstmt, break; } + case T_LetStmt: + { + doLetStmt(pstmt, params, queryEnv, queryString); + if (completionTag) + strcpy(completionTag, "LET"); + } + break; + default: /* All other statement types have event trigger support */ ProcessUtilitySlow(pstate, pstmt, queryString, @@ -1221,6 +1230,10 @@ ProcessUtilitySlow(ParseState *pstate, } break; + case T_CreateSchemaVarStmt: + address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree); + break; + /* * ************* object creation / destruction ************** */ @@ -2055,6 +2068,9 @@ AlterObjectTypeCommandTag(ObjectType objtype) case OBJECT_STATISTIC_EXT: tag = "ALTER STATISTICS"; break; + case OBJECT_VARIABLE: + tag = "ALTER VARIABLE"; + break; default: tag = "???"; break; @@ -2104,6 +2120,10 @@ CreateCommandTag(Node *parsetree) tag = "SELECT"; break; + case T_LetStmt: + tag = "LET"; + break; + /* utility statements --- same whether raw or cooked */ case T_TransactionStmt: { @@ -2358,6 +2378,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_STATISTIC_EXT: tag = "DROP STATISTICS"; break; + case OBJECT_VARIABLE: + tag = "DROP VARIABLE"; + break; default: tag = "???"; } @@ -2639,6 +2662,9 @@ CreateCommandTag(Node *parsetree) case DISCARD_SEQUENCES: tag = "DISCARD SEQUENCES"; break; + case DISCARD_VARIABLES: + tag = "DISCARD VARIABLES"; + break; default: tag = "???"; } @@ -2844,6 +2870,7 @@ CreateCommandTag(Node *parsetree) tag = "DELETE"; break; case CMD_UTILITY: + case CMD_PLAN_UTILITY: tag = CreateCommandTag(stmt->utilityStmt); break; default: @@ -2915,6 +2942,10 @@ CreateCommandTag(Node *parsetree) } break; + case T_CreateSchemaVarStmt: + tag = "CREATE VARIABLE"; + break; + default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); @@ -2961,6 +2992,10 @@ GetCommandLogLevel(Node *parsetree) lev = LOGSTMT_ALL; break; + case T_LetStmt: + lev = LOGSTMT_ALL; + break; + /* utility statements --- same whether raw or cooked */ case T_TransactionStmt: lev = LOGSTMT_ALL; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index a45e093de7..952c0d9628 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -315,6 +315,12 @@ aclparse(const char *s, AclItem *aip) case ACL_CONNECT_CHR: read = ACL_CONNECT; break; + case ACL_READ_CHR: + read = ACL_READ; + break; + case ACL_WRITE_CHR: + read = ACL_WRITE; + break; case 'R': /* ignore old RULE privileges */ read = 0; break; @@ -808,6 +814,10 @@ acldefault(ObjectType objtype, Oid ownerId) world_default = ACL_USAGE; owner_default = ACL_ALL_RIGHTS_TYPE; break; + case OBJECT_VARIABLE: + world_default = ACL_NO_RIGHTS; + owner_default = ACL_ALL_RIGHTS_VARIABLE; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); world_default = ACL_NO_RIGHTS; /* keep compiler quiet */ @@ -903,6 +913,9 @@ acldefault_sql(PG_FUNCTION_ARGS) case 'T': objtype = OBJECT_TYPE; break; + case 'V': + objtype = OBJECT_VARIABLE; + break; default: elog(ERROR, "unrecognized objtype abbreviation: %c", objtypec); } @@ -1627,6 +1640,10 @@ convert_priv_string(text *priv_type_text) return ACL_CONNECT; if (pg_strcasecmp(priv_type, "RULE") == 0) return 0; /* ignore old RULE privileges */ + if (pg_strcasecmp(priv_type, "READ") == 0) + return ACL_READ; + if (pg_strcasecmp(priv_type, "WRITE") == 0) + return ACL_WRITE; ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -1721,6 +1738,10 @@ convert_aclright_to_string(int aclright) return "TEMPORARY"; case ACL_CONNECT: return "CONNECT"; + case ACL_READ: + return "READ"; + case ACL_WRITE: + return "WRITE"; default: elog(ERROR, "unrecognized aclright: %d", aclright); return NULL; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 03e9a28a63..cc8f3326ac 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -38,6 +38,7 @@ #include "catalog/pg_statistic_ext.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "commands/defrem.h" #include "commands/tablespace.h" #include "common/keywords.h" @@ -7362,6 +7363,14 @@ get_parameter(Param *param, deparse_context *context) return; } + /* translate paramid to original schema variable name */ + if (param->paramkind == PARAM_VARIABLE) + { + appendStringInfo(context->buf, "%s", + schema_variable_get_name(param->paramid)); + return; + } + /* * Not PARAM_EXEC, or couldn't find referent: just print $N. */ diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index bba595ad1d..858a6dd4be 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -1691,6 +1691,18 @@ get_relname_relid(const char *relname, Oid relnamespace) ObjectIdGetDatum(relnamespace)); } +/* + * get_varname_varid + * Given name and namespace of variable, look up the OID. + */ +Oid +get_varname_varid(const char *varname, Oid varnamespace) +{ + return GetSysCacheOid2(VARIABLENAMENSP, + PointerGetDatum(varname), + ObjectIdGetDatum(varnamespace)); +} + #ifdef NOT_USED /* * get_relnatts diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 2b381782a3..35dc32f649 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -73,6 +73,7 @@ #include "catalog/pg_ts_template.h" #include "catalog/pg_type.h" #include "catalog/pg_user_mapping.h" +#include "catalog/pg_variable.h" #include "utils/rel.h" #include "utils/catcache.h" #include "utils/syscache.h" @@ -968,6 +969,28 @@ static const struct cachedesc cacheinfo[] = { 0 }, 2 + }, + {VariableRelationId, /* VARIABLENAMENSP */ + VariableNameNspIndexId, + 2, + { + Anum_pg_variable_varname, + Anum_pg_variable_varnamespace, + 0, + 0 + }, + 8 + }, + {VariableRelationId, /* VARIABLEOID */ + VariableObjectIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 } }; diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 0d147cb08d..6d97931d85 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -296,6 +296,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) write_msg(NULL, "reading subscriptions\n"); getSubscriptions(fout); + if (g_verbose) + write_msg(NULL, "reading variables\n"); + getVariables(fout); + *numTablesPtr = numTables; return tblinfo; } diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 83c976eaf7..c9bc91ca68 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3471,6 +3471,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH) strcmp(type, "TEXT SEARCH DICTIONARY") == 0 || strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 || strcmp(type, "STATISTICS") == 0 || + strcmp(type, "VARIABLE") == 0 || /* non-schema-specified objects */ strcmp(type, "DATABASE") == 0 || strcmp(type, "PROCEDURAL LANGUAGE") == 0 || @@ -3670,7 +3671,8 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) strcmp(te->desc, "SERVER") == 0 || strcmp(te->desc, "STATISTICS") == 0 || strcmp(te->desc, "PUBLICATION") == 0 || - strcmp(te->desc, "SUBSCRIPTION") == 0) + strcmp(te->desc, "SUBSCRIPTION") == 0 || + strcmp(te->desc, "VARIABLE") == 0) { PQExpBuffer temp = createPQExpBuffer(); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 9baf7b2fde..f825a00c9d 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -260,6 +260,7 @@ static void dumpPolicy(Archive *fout, PolicyInfo *polinfo); static void dumpPublication(Archive *fout, PublicationInfo *pubinfo); static void dumpPublicationTable(Archive *fout, PublicationRelInfo *pubrinfo); static void dumpSubscription(Archive *fout, SubscriptionInfo *subinfo); +static void dumpVariable(Archive *fout, VariableInfo *varinfo); static void dumpDatabase(Archive *AH); static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf, const char *dbname, Oid dboid); @@ -4221,6 +4222,208 @@ dumpSubscription(Archive *fout, SubscriptionInfo *subinfo) free(qsubname); } +/* + * getVariables + * get information about variables + */ +void +getVariables(Archive *fout) +{ + DumpOptions *dopt = fout->dopt; + PQExpBuffer query; + PQExpBuffer acl_subquery = createPQExpBuffer(); + PQExpBuffer racl_subquery = createPQExpBuffer(); + PQExpBuffer init_acl_subquery = createPQExpBuffer(); + PQExpBuffer init_racl_subquery = createPQExpBuffer(); + PGresult *res; + VariableInfo *varinfo; + int i_tableoid; + int i_oid; + int i_varname; + int i_varnamespace; + int i_vartype; + int i_vartypname; + int i_vardefexpr; + int i_rolname; + int i_varacl; + int i_rvaracl; + int i_initvaracl; + int i_initrvaracl; + int i, + ntups; + + if (fout->remoteVersion <= 110000) + return; + + acl_subquery = createPQExpBuffer(); + racl_subquery = createPQExpBuffer(); + init_acl_subquery = createPQExpBuffer(); + init_racl_subquery = createPQExpBuffer(); + + buildACLQueries(acl_subquery, racl_subquery, init_acl_subquery, + init_racl_subquery, "v.varacl", "v.varowner", "'V'", + dopt->binary_upgrade); + + query = createPQExpBuffer(); + + resetPQExpBuffer(query); + + /* Get the variables in current database. */ + appendPQExpBuffer(query, + "SELECT v.tableoid, v.oid, v.varname, " + "v.varnamespace," + "(%s varowner) AS rolname, " + "%s as varacl, " + "%s as rvaracl, " + "%s as initvaracl, " + "%s as initrvaracl, " + "v.vartype, " + "pg_catalog.format_type(v.vartype, v.vartypmod) as vartypname, " + "pg_catalog.pg_get_expr(v.vardefexpr,0) as vardefexpr " + "FROM pg_variable v " + "LEFT JOIN pg_init_privs pip " + "ON (v.oid = pip.objoid " + "AND pip.classoid = 'pg_variable'::regclass " + "AND pip.objsubid = 0)", + username_subquery, + acl_subquery->data, + racl_subquery->data, + init_acl_subquery->data, + init_racl_subquery->data); + + destroyPQExpBuffer(acl_subquery); + destroyPQExpBuffer(racl_subquery); + destroyPQExpBuffer(init_acl_subquery); + destroyPQExpBuffer(init_racl_subquery); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_varname = PQfnumber(res, "varname"); + i_varnamespace = PQfnumber(res, "varnamespace"); + i_rolname = PQfnumber(res, "rolname"); + i_vartype = PQfnumber(res, "vartype"); + i_vartypname = PQfnumber(res, "vartypname"); + i_vardefexpr = PQfnumber(res, "vardefexpr"); + i_varacl = PQfnumber(res, "varacl"); + i_rvaracl = PQfnumber(res, "rvaracl"); + i_initvaracl = PQfnumber(res, "initvaracl"); + i_initrvaracl = PQfnumber(res, "initrvaracl"); + + varinfo = pg_malloc(ntups * sizeof(VariableInfo)); + + for (i = 0; i < ntups; i++) + { + TypeInfo *vtype; + + varinfo[i].dobj.objType = DO_VARIABLE; + varinfo[i].dobj.catId.tableoid = + atooid(PQgetvalue(res, i, i_tableoid)); + varinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&varinfo[i].dobj); + varinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_varname)); + varinfo[i].dobj.namespace = + findNamespace(fout, + atooid(PQgetvalue(res, i, i_varnamespace))); + + varinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname)); + varinfo[i].vartype = atooid(PQgetvalue(res, i, i_vartype)); + varinfo[i].vartypname = pg_strdup(PQgetvalue(res, i, i_vartypname)); + + varinfo[i].varacl = pg_strdup(PQgetvalue(res, i, i_varacl)); + varinfo[i].rvaracl = pg_strdup(PQgetvalue(res, i, i_rvaracl)); + varinfo[i].initvaracl = pg_strdup(PQgetvalue(res, i, i_initvaracl)); + varinfo[i].initrvaracl = pg_strdup(PQgetvalue(res, i, i_initrvaracl)); + + /* Decide whether we want to dump it */ + selectDumpableObject(&(varinfo[i].dobj), fout); + + /* Do not try to dump ACL if no ACL exists. */ + if (PQgetisnull(res, i, i_varacl) && PQgetisnull(res, i, i_rvaracl) && + PQgetisnull(res, i, i_initvaracl) && + PQgetisnull(res, i, i_initrvaracl)) + varinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL; + + if (PQgetisnull(res, i, i_vardefexpr)) + varinfo[i].vardefexpr = NULL; + else + varinfo[i].vardefexpr = pg_strdup(PQgetvalue(res, i, i_vardefexpr)); + + if (strlen(varinfo[i].rolname) == 0) + write_msg(NULL, "WARNING: owner of variable \"%s\" appears to be invalid\n", + varinfo[i].dobj.name); + + /* Decide whether we want to dump it */ + selectDumpableObject(&(varinfo[i].dobj), fout); + + vtype = findTypeByOid(varinfo[i].vartype); + addObjectDependency(&varinfo[i].dobj, vtype->dobj.dumpId); + } + PQclear(res); + + destroyPQExpBuffer(query); +} + +/* + * dumpVariable + * dump the definition of the given variables + */ +static void +dumpVariable(Archive *fout, VariableInfo *varinfo) +{ + DumpOptions *dopt = fout->dopt; + + PQExpBuffer delq; + PQExpBuffer query; + const char *varname; + const char *vartypname; + const char *vardefexpr; + + /* Skip if not to be dumped */ + if (!varinfo->dobj.dump || dopt->dataOnly) + return; + + delq = createPQExpBuffer(); + query = createPQExpBuffer(); + + varname = fmtQualifiedDumpable(varinfo); + vartypname = varinfo->vartypname; + vardefexpr = varinfo->vardefexpr; + + appendPQExpBuffer(delq, "DROP VARIABLE %s;\n", + varname); + + appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s", + varname, vartypname); + + if (vardefexpr) + appendPQExpBuffer(query, " DEFAULT %s", + vardefexpr); + + appendPQExpBuffer(query, ";\n"); + + ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId, + varinfo->dobj.name, + NULL, + NULL, + varinfo->rolname, false, + "VARIABLE", SECTION_PRE_DATA, + query->data, delq->data, NULL, + NULL, 0, + NULL, NULL); + + if (varinfo->dobj.dump & DUMP_COMPONENT_COMMENT) + dumpComment(fout, "VARIABLE", varname, + NULL, varinfo->rolname, + varinfo->dobj.catId, 0, varinfo->dobj.dumpId); + + destroyPQExpBuffer(delq); + destroyPQExpBuffer(query); +} + static void binary_upgrade_set_type_oids_by_type_oid(Archive *fout, PQExpBuffer upgrade_buffer, @@ -9849,6 +10052,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) case DO_SUBSCRIPTION: dumpSubscription(fout, (SubscriptionInfo *) dobj); break; + case DO_VARIABLE: + dumpVariable(fout, (VariableInfo *) dobj); + break; case DO_PRE_DATA_BOUNDARY: case DO_POST_DATA_BOUNDARY: /* never dumped, nothing to do */ @@ -17935,6 +18141,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs, case DO_OPFAMILY: case DO_COLLATION: case DO_CONVERSION: + case DO_VARIABLE: case DO_TABLE: case DO_ATTRDEF: case DO_PROCLANG: diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 1448005f30..0d49bb7ed7 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -84,7 +84,8 @@ typedef enum DO_POLICY, DO_PUBLICATION, DO_PUBLICATION_REL, - DO_SUBSCRIPTION + DO_SUBSCRIPTION, + DO_VARIABLE } DumpableObjectType; /* component types of an object which can be selected for dumping */ @@ -625,6 +626,22 @@ typedef struct _SubscriptionInfo char *subpublications; } SubscriptionInfo; +/* + * The VariableInfo struct is used to represent schema variables + */ +typedef struct _VariableInfo +{ + DumpableObject dobj; + Oid vartype; + char *vartypname; + char *rolname; /* name of owner, or empty string */ + char *vardefexpr; + char *varacl; + char *rvaracl; + char *initvaracl; + char *initrvaracl; +} VariableInfo; + /* * We build an array of these with an entry for each object that is an * extension member according to pg_depend. @@ -725,5 +742,6 @@ extern void getPublications(Archive *fout); extern void getPublicationTables(Archive *fout, TableInfo tblinfo[], int numTables); extern void getSubscriptions(Archive *fout); +extern void getVariables(Archive *fout); #endif /* PG_DUMP_H */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 6227a8fd26..969a021771 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -1477,6 +1477,10 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "POST-DATA BOUNDARY (ID %d)", obj->dumpId); return; + case DO_VARIABLE: + snprintf(buf, bufsize, + "VARIABLE %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); } /* shouldn't get here */ snprintf(buf, bufsize, diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index ec751a7c23..2a67766ed4 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2601,6 +2601,38 @@ my %tests = ( }, }, + 'CREATE VARIABLE test_variable' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE VARIABLE dump_test.variable AS integer DEFAULT 0;', + regexp => qr/^ + \QCREATE VARIABLE dump_test.variable AS integer DEFAULT 0;\E/xm, + like => { + binary_upgrade => 1, + clean => 1, + clean_if_exists => 1, + createdb => 1, + defaults => 1, + exclude_test_table => 1, + exclude_test_table_data => 1, + no_blobs => 1, + no_privs => 1, + no_owner => 1, + only_dump_test_schema => 1, + pg_dumpall_dbprivs => 1, + schema_only => 1, + section_pre_data => 1, + test_schema_plus_blobs => 1, + with_oids => 1, }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_test_table => 1, + pg_dumpall_globals => 1, + pg_dumpall_globals_clean => 1, + role => 1, + section_post_data => 1, }, }, + 'CREATE VIEW test_view' => { create_order => 61, create_sql => 'CREATE VIEW dump_test.test_view diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 5b4d54a442..73a752fd7e 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -853,6 +853,9 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd) break; } break; + case 'V': /* Variables */ + success = listVariables(pattern, show_verbose); + break; case 'x': /* Extensions */ if (show_verbose) success = listExtensionContents(pattern); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 80d8338b96..d645bba7af 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -4178,6 +4178,80 @@ listSchemas(const char *pattern, bool verbose, bool showSystem) return true; } +/* + * \dV + * + * listVariables() + */ +bool +listVariables(const char *pattern, bool verbose) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + static const bool translate_columns[] = {false, false, false, false, false, false, false}; + + initPQExpBuffer(&buf); + + printfPQExpBuffer(&buf, + "SELECT n.nspname as \"%s\",\n" + " v.varname as \"%s\",\n" + " pg_catalog.format_type(v.vartype, v.vartypmod) as \"%s\",\n" + " pg_catalog.pg_get_userbyid(v.varowner) as \"%s\",\n" + " pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\"", + gettext_noop("Schema"), + gettext_noop("Name"), + gettext_noop("Type"), + gettext_noop("Owner"), + gettext_noop("Default")); + + appendPQExpBufferStr(&buf, + "\nFROM pg_catalog.pg_variable v" + "\n LEFT JOIN pg_catalog.pg_namespace n ON n.oid = v.varnamespace"); + + appendPQExpBufferStr(&buf, "\nWHERE true\n"); + if (!pattern) + appendPQExpBufferStr(&buf, " AND n.nspname <> 'pg_catalog'\n" + " AND n.nspname <> 'information_schema'\n"); + + processSQLNamePattern(pset.db, &buf, pattern, true, false, + "n.nspname", "v.varname", NULL, + "pg_catalog.pg_variable_is_visible(v.oid)"); + + appendPQExpBufferStr(&buf, "ORDER BY 1,2;"); + + res = PSQLexec(buf.data); + termPQExpBuffer(&buf); + if (!res) + return false; + + /* + * Most functions in this file are content to print an empty table when + * there are no matching objects. We intentionally deviate from that + * here, but only in !quiet mode, for historical reasons. + */ + if (PQntuples(res) == 0 && !pset.quiet) + { + if (pattern) + psql_error("Did not find any schema variable named \"%s\".\n", + pattern); + else + psql_error("Did not find any schema variables.\n"); + } + else + { + myopt.nullPrint = NULL; + myopt.title = _("List of variables"); + myopt.translate_header = true; + myopt.translate_columns = translate_columns; + myopt.n_translate_columns = lengthof(translate_columns); + + printQuery(res, &myopt, pset.queryFout, false, pset.logfile); + } + + PQclear(res); + return true; +} /* * \dFp diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index a4cc5efae0..ecc4e3a531 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -63,6 +63,9 @@ extern bool listAllDbs(const char *pattern, bool verbose); /* \dt, \di, \ds, \dS, etc. */ extern bool listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem); +/* \dV */ +extern bool listVariables(const char *pattern, bool varbose); + /* \dD */ extern bool listDomains(const char *pattern, bool verbose, bool showSystem); diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 316030d358..adcc36cb6e 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -167,7 +167,7 @@ slashUsage(unsigned short int pager) * Use "psql --help=commands | wc" to count correctly. It's okay to count * the USE_READLINE line even in builds without that. */ - output = PageOutput(125, pager ? &(pset.popt.topt) : NULL); + output = PageOutput(126, pager ? &(pset.popt.topt) : NULL); fprintf(output, _("General\n")); fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n")); @@ -257,6 +257,7 @@ slashUsage(unsigned short int pager) fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n")); fprintf(output, _(" \\du[S+] [PATTERN] list roles\n")); fprintf(output, _(" \\dv[S+] [PATTERN] list views\n")); + fprintf(output, _(" \\dV [PATTERN] list variables\n")); fprintf(output, _(" \\dx[+] [PATTERN] list extensions\n")); fprintf(output, _(" \\dy [PATTERN] list event triggers\n")); fprintf(output, _(" \\l[+] [PATTERN] list databases\n")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index bb696f8ee9..a7583810e8 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -805,6 +805,22 @@ static const SchemaQuery Query_for_list_of_statistics = { NULL }; +static const SchemaQuery Query_for_list_of_variables = { + /* min_server_version */ + 0, + /* catname */ + "pg_catalog.pg_variable v", + /* selcondition */ + NULL, + /* viscondition */ + "pg_catalog.pg_variable_is_visible(v.oid)", + /* namespace */ + "v.varnamespace", + /* result */ + "pg_catalog.quote_ident(v.varname)", + /* qualresult */ + NULL +}; /* * Queries to get lists of names of various kinds of things, possibly @@ -1249,6 +1265,7 @@ static const pgsql_thing_t words_after_create[] = { * TABLE ... */ {"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"}, {"USER MAPPING FOR", NULL, NULL, NULL}, + {"VARIABLE", NULL, NULL, &Query_for_list_of_variables}, {"VIEW", NULL, NULL, &Query_for_list_of_views}, {NULL} /* end of list */ }; @@ -1604,7 +1621,7 @@ psql_completion(const char *text, int start, int end) "ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN", - "FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK", + "FETCH", "GRANT", "IMPORT", "INSERT", "LET", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "PREPARE", "REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", @@ -1621,9 +1638,9 @@ psql_completion(const char *text, int start, int end) "\\d", "\\da", "\\dA", "\\db", "\\dc", "\\dC", "\\dd", "\\ddp", "\\dD", "\\des", "\\det", "\\deu", "\\dew", "\\dE", "\\df", "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL", - "\\dm", "\\dn", "\\do", "\\dO", "\\dp", + "\\dm", "\\dn", "\\do", "\\dO", "\\dp" "\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS", - "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", + "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", "\\dV", "\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding", "\\endif", "\\errverbose", "\\ev", "\\f", @@ -1988,6 +2005,9 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars); else if (Matches4("ALTER", "SYSTEM", "SET", MatchAny)) COMPLETE_WITH_CONST("TO"); + /* ALTER VARIABLE */ + else if (Matches3("ALTER", "VARIABLE", MatchAny)) + COMPLETE_WITH_LIST3("OWNER TO", "RENAME TO", "SET SCHEMA"); /* ALTER VIEW */ else if (Matches3("ALTER", "VIEW", MatchAny)) COMPLETE_WITH_LIST4("ALTER COLUMN", "OWNER TO", "RENAME TO", @@ -2837,6 +2857,14 @@ psql_completion(const char *text, int start, int end) else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN")) COMPLETE_WITH_LIST2("GROUP", "ROLE"); +/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ + /* Complete CREATE VARIABLE with AS */ + else if (TailMatches3("CREATE", "VARIABLE", MatchAny)) + COMPLETE_WITH_CONST("AS"); + /* Complete CREATE VARIABLE with AS types*/ + else if (TailMatches4("CREATE", "VARIABLE", MatchAny, "AS")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete CREATE VIEW with AS */ else if (TailMatches3("CREATE", "VIEW", MatchAny)) @@ -2890,7 +2918,7 @@ psql_completion(const char *text, int start, int end) /* DISCARD */ else if (Matches1("DISCARD")) - COMPLETE_WITH_LIST4("ALL", "PLANS", "SEQUENCES", "TEMP"); + COMPLETE_WITH_LIST5("ALL", "PLANS", "SEQUENCES", "TEMP", "VARIABLES"); /* DO */ else if (Matches1("DO")) @@ -2992,6 +3020,12 @@ psql_completion(const char *text, int start, int end) else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny)) COMPLETE_WITH_LIST2("CASCADE", "RESTRICT"); + /* DROP VARIABLE */ + else if (Matches2("DROP", "VARIABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); + else if (Matches3("DROP", "VARIABLE", MatchAny)) + COMPLETE_WITH_LIST2("CASCADE", "RESTRICT"); + /* EXECUTE */ else if (Matches1("EXECUTE")) COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements); @@ -3002,14 +3036,14 @@ psql_completion(const char *text, int start, int end) * Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands */ else if (Matches1("EXPLAIN")) - COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", - "ANALYZE", "VERBOSE"); + COMPLETE_WITH_LIST8("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", + "ANALYZE", "VERBOSE", "LET"); else if (Matches2("EXPLAIN", "ANALYZE")) - COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", - "VERBOSE"); + COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", + "VERBOSE", "LET"); else if (Matches2("EXPLAIN", "VERBOSE") || Matches3("EXPLAIN", "ANALYZE", "VERBOSE")) - COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE"); + COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "LET"); /* FETCH && MOVE */ /* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */ @@ -3118,6 +3152,7 @@ psql_completion(const char *text, int start, int end) " UNION SELECT 'ALL ROUTINES IN SCHEMA'" " UNION SELECT 'ALL SEQUENCES IN SCHEMA'" " UNION SELECT 'ALL TABLES IN SCHEMA'" + " UNION SELECT 'ALL VARIABLES IN SCHEMA'" " UNION SELECT 'DATABASE'" " UNION SELECT 'DOMAIN'" " UNION SELECT 'FOREIGN DATA WRAPPER'" @@ -3131,14 +3166,16 @@ psql_completion(const char *text, int start, int end) " UNION SELECT 'SEQUENCE'" " UNION SELECT 'TABLE'" " UNION SELECT 'TABLESPACE'" - " UNION SELECT 'TYPE'"); + " UNION SELECT 'TYPE'" + " UNION SELECT 'VARIABLE'"); } else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL")) - COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA", + COMPLETE_WITH_LIST6("FUNCTIONS IN SCHEMA", "PROCEDURES IN SCHEMA", "ROUTINES IN SCHEMA", "SEQUENCES IN SCHEMA", - "TABLES IN SCHEMA"); + "TABLES IN SCHEMA", + "VARIABLES IN SCHEMA"); else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN")) COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER"); @@ -3172,6 +3209,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); else if (TailMatches1("TYPE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + else if (TailMatches1("VARIABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); else if (TailMatches4("GRANT", MatchAny, MatchAny, MatchAny)) COMPLETE_WITH_CONST("TO"); else @@ -3324,7 +3363,7 @@ psql_completion(const char *text, int start, int end) /* PREPARE xx AS */ else if (Matches3("PREPARE", MatchAny, "AS")) - COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM"); + COMPLETE_WITH_LIST5("SELECT", "UPDATE", "INSERT", "DELETE FROM", "LET"); /* * PREPARE TRANSACTION is missing on purpose. It's intended for transaction @@ -3547,6 +3586,14 @@ psql_completion(const char *text, int start, int end) else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny)) COMPLETE_WITH_CONST("="); +/* LET --- can be inside EXPLAIN, PREPARE etc */ + /* If prev. word is LET suggest a list of variables */ + else if (TailMatches1("LET")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); + /* Complete LET with "=" */ + else if (TailMatches2("LET", MatchAny)) + COMPLETE_WITH_CONST("="); + /* USER MAPPING */ else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING")) COMPLETE_WITH_CONST("FOR"); diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index 46c271a46c..3e38a05e55 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -180,7 +180,8 @@ typedef enum ObjectClass OCLASS_PUBLICATION, /* pg_publication */ OCLASS_PUBLICATION_REL, /* pg_publication_rel */ OCLASS_SUBSCRIPTION, /* pg_subscription */ - OCLASS_TRANSFORM /* pg_transform */ + OCLASS_TRANSFORM, /* pg_transform */ + OCLASS_VARIABLE /* pg_variable */ } ObjectClass; #define LAST_OCLASS OCLASS_TRANSFORM diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 24915824ca..dae80c20a8 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -360,4 +360,10 @@ DECLARE_UNIQUE_INDEX(pg_subscription_subname_index, 6115, on pg_subscription usi DECLARE_UNIQUE_INDEX(pg_subscription_rel_srrelid_srsubid_index, 6117, on pg_subscription_rel using btree(srrelid oid_ops, srsubid oid_ops)); #define SubscriptionRelSrrelidSrsubidIndexId 6117 +DECLARE_UNIQUE_INDEX(pg_variable_oid_index, 4288, on pg_variable using btree(oid oid_ops)); +#define VariableObjectIndexId 4288 + +DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 4289, on pg_variable using btree(varname name_ops, varnamespace oid_ops)); +#define VariableNameNspIndexId 4289 + #endif /* INDEXING_H */ diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index 7991de5e21..75068d7e92 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -75,10 +75,13 @@ extern Oid RangeVarGetAndCheckCreationNamespace(RangeVar *newRelation, extern void RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid); extern Oid RelnameGetRelid(const char *relname); extern bool RelationIsVisible(Oid relid); +extern bool VariableIsVisible(Oid relid); extern Oid TypenameGetTypid(const char *typname); extern bool TypeIsVisible(Oid typid); +extern bool VariableIsVisible(Oid varid); + extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs, List *argnames, bool expand_variadic, @@ -145,6 +148,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId, Oid tempToastNamespaceId); extern void ResetTempTableNamespace(void); +extern List *NamesFromList(List *names); +extern Oid lookup_variable(const char *nspname, const char *varname, bool missing_ok); +extern Oid identify_variable(List *names, char **attrname, bool *not_uniq); + extern OverrideSearchPath *GetOverrideSearchPath(MemoryContext context); extern OverrideSearchPath *CopyOverrideSearchPath(OverrideSearchPath *path); extern bool OverrideSearchPathMatchesCurrent(OverrideSearchPath *path); diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h index d0410f5586..56deef1a45 100644 --- a/src/include/catalog/pg_default_acl.h +++ b/src/include/catalog/pg_default_acl.h @@ -57,6 +57,7 @@ typedef FormData_pg_default_acl *Form_pg_default_acl; #define DEFACLOBJ_FUNCTION 'f' /* function */ #define DEFACLOBJ_TYPE 'T' /* type */ #define DEFACLOBJ_NAMESPACE 'n' /* namespace */ +#define DEFACLOBJ_VARIABLE 'V' /* variable */ #endif /* EXPOSE_TO_CLIENT_CODE */ diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index a14651010f..61cbe65805 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5961,6 +5961,9 @@ proname => 'pg_collation_is_visible', procost => '10', provolatile => 's', prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_collation_is_visible' }, +{ oid => '4187', descr => 'is schema variable visible in search path?', + proname => 'pg_variable_is_visible', procost => '10', provolatile => 's', + prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_variable_is_visible' }, { oid => '2854', descr => 'get OID of current session\'s temp schema, if any', proname => 'pg_my_temp_schema', provolatile => 's', proparallel => 'r', diff --git a/src/include/catalog/pg_variable.h b/src/include/catalog/pg_variable.h new file mode 100644 index 0000000000..34f4c34202 --- /dev/null +++ b/src/include/catalog/pg_variable.h @@ -0,0 +1,85 @@ +/*------------------------------------------------------------------------- + * + * pg_variable.h + * definition of schema variables system catalog (pg_variables) + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_variable.h + * + * NOTES + * The Catalog.pm module reads this file and derives schema + * information. + * + *------------------------------------------------------------------------- + */ +#ifndef PG_VARIABLE_H +#define PG_VARIABLE_H + +#include "catalog/genbki.h" +#include "catalog/objectaddress.h" +#include "catalog/pg_variable_d.h" +#include "utils/acl.h" + +/* ---------------- + * pg_variable definition. cpp turns this into + * typedef struct FormData_pg_variable + * ---------------- + */ +CATALOG(pg_variable,4287,VariableRelationId) +{ + NameData varname; /* variable name */ + Oid varnamespace; /* OID of namespace containing variable class */ + Oid vartype; /* OID of entry in pg_type for variable's type */ + int32 vartypmod; /* typmode for variable's type */ + Oid varowner; /* class owner */ + +#ifdef CATALOG_VARLEN /* variable-length fields start here */ + + /* list of expression trees for variable default (NULL if none) */ + pg_node_tree vardefexpr BKI_DEFAULT(_null_); + + aclitem varacl[1] BKI_DEFAULT(_null_); /* access permissions */ + +#endif +} FormData_pg_variable; + +/* ---------------- + * Form_pg_variable corresponds to a pointer to a tuple with + * the format of pg_variable relation. + * ---------------- + */ +typedef FormData_pg_variable *Form_pg_variable; + +typedef struct Variable +{ + Oid oid; + char *name; + Oid namespace; + Oid typid; + int32 typmod; + Oid owner; + Node *defexpr; + Acl *acl; +} Variable; + +/* returns fields from pg_variable table */ +extern char *get_schema_variable_name(Oid varid); +extern void get_schema_variable_type_typmod(Oid varid, Oid *typid, int32 *typmod); + +/* returns name of variable based on current search path */ +extern char *schema_variable_get_name(Oid varid); + +extern Variable *GetVariable(Oid varid, bool missing_ok); +extern ObjectAddress VariableCreate(const char *varName, + Oid varNamespace, + Oid varType, + int32 varTypmod, + Oid varOwner, + Node *varDefexpr, + bool if_not_exists); + + +#endif /* PG_VARIABLE_H */ diff --git a/src/include/commands/schemavariable.h b/src/include/commands/schemavariable.h new file mode 100644 index 0000000000..2823d35b7c --- /dev/null +++ b/src/include/commands/schemavariable.h @@ -0,0 +1,35 @@ +/*------------------------------------------------------------------------- + * + * schemavariable.h + * prototypes for schemavariable.c. + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/schemavariable.h + * + *------------------------------------------------------------------------- + */ + +#ifndef SCHEMAVARIABLE_H +#define SCHEMAVARIABLE_H + +#include "catalog/objectaddress.h" +#include "catalog/pg_variable.h" +#include "nodes/params.h" +#include "nodes/parsenodes.h" +#include "nodes/plannodes.h" +#include "utils/queryenvironment.h" + +extern void ResetSchemaVariableCache(void); + +extern void RemoveVariableById(Oid varid); +extern ObjectAddress DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *stmt); + +extern Datum GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy); +extern void SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod); + +extern void doLetStmt(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, const char *queryString); + +#endif diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index f7b1f77616..4fdceb6cee 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -138,6 +138,7 @@ typedef enum ExprEvalOp EEOP_PARAM_EXEC, EEOP_PARAM_EXTERN, EEOP_PARAM_CALLBACK, + EEOP_PARAM_VARIABLE, /* return CaseTestExpr value */ EEOP_CASE_TESTVAL, @@ -344,13 +345,22 @@ typedef struct ExprEvalStep TupleDesc argdesc; } nulltest_row; - /* for EEOP_PARAM_EXEC/EXTERN */ + /* for EEOP_PARAM_EXEC/EXTERN/VARIABLE */ struct { - int paramid; /* numeric ID for parameter */ - Oid paramtype; /* OID of parameter's datatype */ + int paramid; /* numeric ID for parameter */ + Oid paramtype; /* OID of parameter's datatype */ } param; + /* for EEOP_PARAM_VARIABLE */ + struct + { + int paramid; /* numeric ID for parameter */ + Oid varoid; /* OID of assigned variable */ + Oid paramtype; /* OID of parameter's datatype */ + } vparam; + + /* for EEOP_PARAM_CALLBACK */ struct { @@ -700,6 +710,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, extern void ExecEvalParamExecParams(Bitmapset *params, EState *estate); extern void ExecEvalParamExtern(ExprState *state, ExprEvalStep *op, ExprContext *econtext); +extern void ExecEvalParamVariable(ExprState *state, ExprEvalStep *op, + ExprContext *econtext); extern void ExecEvalSQLValueFunction(ExprState *state, ExprEvalStep *op); extern void ExecEvalCurrentOfExpr(ExprState *state, ExprEvalStep *op); extern void ExecEvalNextValueExpr(ExprState *state, ExprEvalStep *op); diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h new file mode 100644 index 0000000000..8c8117701f --- /dev/null +++ b/src/include/executor/svariableReceiver.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * svariableReceiver.h + * prototypes for svariableReceiver.c + * + * + * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/svariableReceiver.h + * + *------------------------------------------------------------------------- + */ + +#ifndef SVARIABLE_RECEIVER_H +#define SVARIABLE_RECEIVER_H + +#include "tcop/dest.h" + + +extern DestReceiver *CreateVariableDestReceiver(void); + +extern void SetVariableDestReceiverParams(DestReceiver *self, Oid varid); + +#endif /* SVARIABLE_RECEIVER_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 018f50bbb7..33cc8be55a 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -100,6 +100,8 @@ typedef struct ExprState int steps_len; /* number of steps currently */ int steps_alloc; /* allocated length of steps array */ + int nvariables; /* number of used variables */ + struct PlanState *parent; /* parent PlanState node, if any */ ParamListInfo ext_params; /* for compiling PARAM_EXTERN nodes */ @@ -472,6 +474,7 @@ typedef struct ResultRelInfo typedef struct EState { NodeTag type; + bool es_shared; /* plpgsql uses share estate */ /* Basic state for all query types: */ ScanDirection es_direction; /* current scan direction */ @@ -564,6 +567,14 @@ typedef struct EState /* The per-query shared memory area to use for parallel execution. */ struct dsa_area *es_query_dsa; + int es_result_variable; /* Oid of target variable */ + + /* query schema variable cache */ + int es_nvariables; + bool *es_varnulls; + Oid *es_vartypes; + Datum *es_varvalues; + /* * JIT information. es_jit_flags indicates whether JIT should be performed * and with which options. es_jit is created on-demand when JITing is diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 697d3d7a5f..dd7fd8ed42 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -348,6 +348,7 @@ typedef enum NodeTag T_CreateTableAsStmt, T_CreateSeqStmt, T_AlterSeqStmt, + T_CreateSchemaVarStmt, T_VariableSetStmt, T_VariableShowStmt, T_DiscardStmt, @@ -419,6 +420,7 @@ typedef enum NodeTag T_CreateStatsStmt, T_AlterCollationStmt, T_CallStmt, + T_LetStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) @@ -663,6 +665,7 @@ typedef enum CmdType CMD_DELETE, CMD_UTILITY, /* cmds like create, destroy, copy, vacuum, * etc. */ + CMD_PLAN_UTILITY, /* only let stmt now, requires planning */ CMD_NOTHING /* dummy command for instead nothing rules * with qual */ } CmdType; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 07ab1a3dde..2d4a3cb1b6 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -84,7 +84,9 @@ typedef uint32 AclMode; /* a bitmask of privilege bits */ #define ACL_CREATE (1<<9) /* for namespaces and databases */ #define ACL_CREATE_TEMP (1<<10) /* for databases */ #define ACL_CONNECT (1<<11) /* for databases */ -#define N_ACL_RIGHTS 12 /* 1 plus the last 1<es_shared = true; MemoryContextSwitchTo(oldcontext); } estate->simple_eval_estate = shared_simple_eval_estate; diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 7d3647a12d..7f183d4f1b 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -332,6 +332,7 @@ plpgsql_inline_handler(PG_FUNCTION_ARGS) /* Create a private EState for simple-expression execution */ simple_eval_estate = CreateExecutorState(); + simple_eval_estate->es_shared = true; /* And run the function */ PG_TRY(); diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out index 2d3522b500..48286f8e1a 100644 --- a/src/test/regress/expected/misc_sanity.out +++ b/src/test/regress/expected/misc_sanity.out @@ -105,5 +105,7 @@ ORDER BY 1, 2; pg_index | indpred | pg_node_tree pg_largeobject | data | bytea pg_largeobject_metadata | lomacl | aclitem[] -(11 rows) + pg_variable | varacl | aclitem[] + pg_variable | vardefexpr | pg_node_tree +(13 rows) diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 0aa5357917..848b041a4b 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -163,6 +163,7 @@ pg_ts_parser|t pg_ts_template|t pg_type|t pg_user_mapping|t +pg_variable|t point_tbl|t polygon_tbl|t quad_box_tbl|t diff --git a/src/test/regress/expected/schema_variables.out b/src/test/regress/expected/schema_variables.out new file mode 100644 index 0000000000..84fe30a2c0 --- /dev/null +++ b/src/test/regress/expected/schema_variables.out @@ -0,0 +1,351 @@ +CREATE VARIABLE var1 AS integer; +CREATE TEMP VARIABLE var2 AS text; +DROP VARIABLE var1, var2; +-- functional interface +CREATE VARIABLE var1 AS numeric; +CREATE ROLE var_test_role; +SET ROLE TO var_test_role; +-- should to fail +SELECT var1; +ERROR: permission denied for schema variable var1 +SET ROLE TO DEFAULT; +GRANT READ ON VARIABLE var1 TO var_test_role; +SET ROLE TO var_test_role; +-- should to fail +LET var1 = 10; +ERROR: permission denied for schema variable var1 +-- should to work +SELECT var1; + var1 +------ + +(1 row) + +SET ROLE TO DEFAULT; +GRANT WRITE ON VARIABLE var1 TO var_test_role; +SET ROLE TO var_test_role; +-- should to work +LET var1 = 333; +SET ROLE TO DEFAULT; +REVOKE ALL ON VARIABLE var1 FROM var_test_role; +CREATE OR REPLACE FUNCTION secure_var() +RETURNS int AS $$ + SELECT public.var1::int; +$$ LANGUAGE sql SECURITY DEFINER; +SELECT secure_var(); + secure_var +------------ + 333 +(1 row) + +SET ROLE TO var_test_role; +-- should to fail +SELECT public.var1; +ERROR: permission denied for schema variable var1 +-- should to work; +SELECT secure_var(); + secure_var +------------ + 333 +(1 row) + +SET ROLE TO DEFAULT; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1; + QUERY PLAN +----------------------------------------------- + Function Scan on pg_catalog.generate_series g + Output: v + Function Call: generate_series(1, 100) + Filter: ((g.v)::numeric = var1) +(4 rows) + +CREATE VIEW schema_var_view AS SELECT var1; +SELECT * FROM schema_var_view; + var1 +------ + 333 +(1 row) + +\c - +-- should to work still, but var will be empty +SELECT * FROM schema_var_view; + var1 +------ + +(1 row) + +LET var1 = pi(); +SELECT var1; + var1 +------------------ + 3.14159265358979 +(1 row) + +-- we can look on execution plan +EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi(); + QUERY PLAN +---------------------------- + Result + Output: 3.14159265358979 +(2 rows) + +-- LET can be prepared +PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2; +EXECUTE var_pp(100, 1.23456); +SELECT var1; + var1 +----------- + 101.23456 +(1 row) + +CREATE VARIABLE var3 AS int; +CREATE OR REPLACE FUNCTION inc(int) +RETURNS int AS $$ +BEGIN + LET public.var3 = COALESCE(public.var3 + $1, $1); + RETURN var3; +END; +$$ LANGUAGE plpgsql; +SELECT inc(1); + inc +----- + 1 +(1 row) + +SELECT inc(1); + inc +----- + 2 +(1 row) + +SELECT inc(1); + inc +----- + 3 +(1 row) + +SELECT inc(1) FROM generate_series(1,10); + inc +----- + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 +(10 rows) + +SET ROLE TO var_test_role; +-- should to fail +LET var3 = 0; +ERROR: permission denied for schema variable var3 +SET ROLE TO DEFAULT; +DROP VIEW schema_var_view; +DROP VARIABLE var1 CASCADE; +DROP VARIABLE var3 CASCADE; +-- composite variables +CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2)); +CREATE VARIABLE v1 AS sv_xyz; +CREATE VARIABLE v2 AS sv_xyz; +\d v1 +\d v2 +LET v1 = (1,2,3.14); +LET v2 = (10,20,3.14*10); +-- should to work too - there are prepared casts +LET v1 = (1,2,3.14); +SELECT v1; + v1 +------------ + (1,2,3.14) +(1 row) + +SELECT v2; + v2 +--------------- + (10,20,31.40) +(1 row) + +SELECT (v1).*; + x | y | z +---+---+------ + 1 | 2 | 3.14 +(1 row) + +SELECT (v2).*; + x | y | z +----+----+------- + 10 | 20 | 31.40 +(1 row) + +SELECT v1.x + v1.z; + ?column? +---------- + 4.14 +(1 row) + +SELECT v2.x + v2.z; + ?column? +---------- + 41.40 +(1 row) + +-- access to composite fields should be safe too +-- should to fail +SET ROLE TO var_test_role; +SELECT v2.x; +ERROR: permission denied for schema variable v2 +SET ROLE TO DEFAULT; +DROP VARIABLE v1; +DROP VARIABLE v2; +DROP ROLE var_test_role; +-- scalar variables should not be in conflict with qualified column +CREATE VARIABLE varx AS text; +SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class'; + relname +---------- + pg_class +(1 row) + +-- should to fail +SELECT varx.xxx; +ERROR: type text is not composite +-- variables can be updated under RO transaction +BEGIN; +SET TRANSACTION READ ONLY; +LET varx = 'hello'; +COMMIT; +SELECT varx; + varx +------- + hello +(1 row) + +DROP VARIABLE varx; +CREATE TYPE t1 AS (a int, b numeric, c text); +CREATE VARIABLE v1 AS t1; +LET v1 = (1, pi(), 'hello'); +SELECT v1; + v1 +---------------------------- + (1,3.14159265358979,hello) +(1 row) + +LET v1.b = 10.2222; +SELECT v1; + v1 +------------------- + (1,10.2222,hello) +(1 row) + +-- should to fail +LET v1.x = 10; +ERROR: cannot assign to field "x" of column "x" because there is no such column in data type t1 +LINE 1: LET v1.x = 10; + ^ +DROP VARIABLE v1; +DROP TYPE t1; +-- arrays are supported +CREATE VARIABLE va1 AS numeric[]; +LET va1 = ARRAY[1.1,2.1]; +LET va1[1] = 10.1; +SELECT va1; + va1 +------------ + {10.1,2.1} +(1 row) + +CREATE TYPE ta2 AS (a numeric, b numeric[]); +CREATE VARIABLE va2 AS ta2; +LET va2 = (10.1, ARRAY[0.0, 0.0]); +LET va2.a = 10.2; +SELECT va2; + va2 +-------------------- + (10.2,"{0.0,0.0}") +(1 row) + +LET va2.b[1] = 10.3; +SELECT va2; + va2 +--------------------- + (10.2,"{10.3,0.0}") +(1 row) + +DROP VARIABLE va1; +DROP VARIABLE va2; +DROP TYPE ta2; +-- default values +CREATE VARIABLE v1 AS numeric DEFAULT pi(); +LET v1 = v1 * 2; +SELECT v1; + v1 +------------------ + 6.28318530717958 +(1 row) + +CREATE TYPE t2 AS (a numeric, b text); +CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello'); +LET public.v2.a = pi(); +SELECT v2; + v2 +-------------------------- + (3.14159265358979,Hello) +(1 row) + +-- shoudl fail due dependency +DROP TYPE t2; +ERROR: cannot drop type t2 because other objects depend on it +DETAIL: schema variable v2 depends on type t2 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- should be ok +DROP VARIABLE v1; +DROP VARIABLE v2; +-- tests of alters +CREATE SCHEMA var_schema1; +CREATE SCHEMA var_schema2; +CREATE VARIABLE var_schema1.var1 AS integer; +LET var_schema1.var1 = 1000; +SELECT var_schema1.var1; + var1 +------ + 1000 +(1 row) + +ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2; +SELECT var_schema2.var1; + var1 +------ + 1000 +(1 row) + +CREATE ROLE var_test_role; +ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role; +SET ROLE TO var_test_role; +-- should fail, no access to schema var_schema2.var +SELECT var_schema2.var1; +ERROR: permission denied for schema var_schema2 +DROP VARIABLE var_schema2.var1; +ERROR: permission denied for schema var_schema2 +SET ROLE TO DEFAULT; +ALTER VARIABLE var_schema2.var1 SET SCHEMA public; +SET ROLE TO var_test_role; +SELECT public.var1; + var1 +------ + 1000 +(1 row) + +ALTER VARIABLE public.var1 RENAME TO var1_renamed; +SELECT public.var1_renamed; + var1_renamed +-------------- + 1000 +(1 row) + +DROP VARIABLE public.var1_renamed; +SET ROLE TO DEFAULt; +DROP ROLE var_test_role; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 16f979c8d9..9bf379b87b 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -111,7 +111,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 schema_variables # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 42632be675..42bf4ecb3f 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -191,3 +191,4 @@ test: partition_aggregate test: event_trigger test: fast_default test: stats +test: schema_variables diff --git a/src/test/regress/sql/schema_variables.sql b/src/test/regress/sql/schema_variables.sql new file mode 100644 index 0000000000..91b2bbb28b --- /dev/null +++ b/src/test/regress/sql/schema_variables.sql @@ -0,0 +1,247 @@ +CREATE VARIABLE var1 AS integer; +CREATE TEMP VARIABLE var2 AS text; + +DROP VARIABLE var1, var2; + +-- functional interface +CREATE VARIABLE var1 AS numeric; + +CREATE ROLE var_test_role; + +SET ROLE TO var_test_role; + +-- should to fail +SELECT var1; + +SET ROLE TO DEFAULT; + +GRANT READ ON VARIABLE var1 TO var_test_role; + +SET ROLE TO var_test_role; +-- should to fail +LET var1 = 10; +-- should to work +SELECT var1; + +SET ROLE TO DEFAULT; + +GRANT WRITE ON VARIABLE var1 TO var_test_role; + +SET ROLE TO var_test_role; + +-- should to work +LET var1 = 333; + +SET ROLE TO DEFAULT; + +REVOKE ALL ON VARIABLE var1 FROM var_test_role; + +CREATE OR REPLACE FUNCTION secure_var() +RETURNS int AS $$ + SELECT public.var1::int; +$$ LANGUAGE sql SECURITY DEFINER; + +SELECT secure_var(); + +SET ROLE TO var_test_role; + +-- should to fail +SELECT public.var1; + +-- should to work; +SELECT secure_var(); + +SET ROLE TO DEFAULT; + +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1; + +CREATE VIEW schema_var_view AS SELECT var1; + +SELECT * FROM schema_var_view; + +\c - + +-- should to work still, but var will be empty +SELECT * FROM schema_var_view; + +LET var1 = pi(); + +SELECT var1; + +-- we can look on execution plan +EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi(); + +-- LET can be prepared +PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2; + +EXECUTE var_pp(100, 1.23456); + +SELECT var1; + +CREATE VARIABLE var3 AS int; + +CREATE OR REPLACE FUNCTION inc(int) +RETURNS int AS $$ +BEGIN + LET public.var3 = COALESCE(public.var3 + $1, $1); + RETURN var3; +END; +$$ LANGUAGE plpgsql; + +SELECT inc(1); +SELECT inc(1); +SELECT inc(1); + +SELECT inc(1) FROM generate_series(1,10); + +SET ROLE TO var_test_role; + +-- should to fail +LET var3 = 0; + +SET ROLE TO DEFAULT; + +DROP VIEW schema_var_view; + +DROP VARIABLE var1 CASCADE; +DROP VARIABLE var3 CASCADE; + +-- composite variables + +CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2)); + +CREATE VARIABLE v1 AS sv_xyz; +CREATE VARIABLE v2 AS sv_xyz; + +\d v1 +\d v2 + +LET v1 = (1,2,3.14); +LET v2 = (10,20,3.14*10); + +-- should to work too - there are prepared casts +LET v1 = (1,2,3.14); + +SELECT v1; +SELECT v2; +SELECT (v1).*; +SELECT (v2).*; + +SELECT v1.x + v1.z; +SELECT v2.x + v2.z; + +-- access to composite fields should be safe too +-- should to fail +SET ROLE TO var_test_role; + +SELECT v2.x; + +SET ROLE TO DEFAULT; + +DROP VARIABLE v1; +DROP VARIABLE v2; + +DROP ROLE var_test_role; + +-- scalar variables should not be in conflict with qualified column +CREATE VARIABLE varx AS text; +SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class'; + +-- should to fail +SELECT varx.xxx; + +-- variables can be updated under RO transaction + +BEGIN; +SET TRANSACTION READ ONLY; +LET varx = 'hello'; +COMMIT; + +SELECT varx; + +DROP VARIABLE varx; + +CREATE TYPE t1 AS (a int, b numeric, c text); + +CREATE VARIABLE v1 AS t1; +LET v1 = (1, pi(), 'hello'); +SELECT v1; +LET v1.b = 10.2222; +SELECT v1; + +-- should to fail +LET v1.x = 10; + +DROP VARIABLE v1; +DROP TYPE t1; + +-- arrays are supported +CREATE VARIABLE va1 AS numeric[]; +LET va1 = ARRAY[1.1,2.1]; +LET va1[1] = 10.1; +SELECT va1; + +CREATE TYPE ta2 AS (a numeric, b numeric[]); +CREATE VARIABLE va2 AS ta2; +LET va2 = (10.1, ARRAY[0.0, 0.0]); +LET va2.a = 10.2; +SELECT va2; +LET va2.b[1] = 10.3; +SELECT va2; + +DROP VARIABLE va1; +DROP VARIABLE va2; +DROP TYPE ta2; + +-- default values +CREATE VARIABLE v1 AS numeric DEFAULT pi(); +LET v1 = v1 * 2; +SELECT v1; + +CREATE TYPE t2 AS (a numeric, b text); +CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello'); +LET public.v2.a = pi(); +SELECT v2; + +-- shoudl fail due dependency +DROP TYPE t2; + +-- should be ok +DROP VARIABLE v1; +DROP VARIABLE v2; + +-- tests of alters +CREATE SCHEMA var_schema1; +CREATE SCHEMA var_schema2; + +CREATE VARIABLE var_schema1.var1 AS integer; +LET var_schema1.var1 = 1000; +SELECT var_schema1.var1; +ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2; +SELECT var_schema2.var1; + +CREATE ROLE var_test_role; + +ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role; +SET ROLE TO var_test_role; + +-- should fail, no access to schema var_schema2.var +SELECT var_schema2.var1; +DROP VARIABLE var_schema2.var1; + +SET ROLE TO DEFAULT; + +ALTER VARIABLE var_schema2.var1 SET SCHEMA public; + +SET ROLE TO var_test_role; +SELECT public.var1; + +ALTER VARIABLE public.var1 RENAME TO var1_renamed; + +SELECT public.var1_renamed; + +DROP VARIABLE public.var1_renamed; + +SET ROLE TO DEFAULt; + +DROP ROLE var_test_role;