diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 0179deea2e..13043987ff 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 + @@ -11303,4 +11308,124 @@ 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 + + + + varcollation + oid + pg_collation.oid + + The defined collation of the variable, or zero if the variable is + not of a collatable data type. + + + + + vareoxaction + char + + + n = no action, d = drop variable, + r = reset 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..bcb6824711 --- /dev/null +++ b/doc/src/sgml/ref/create_variable.sgml @@ -0,0 +1,170 @@ + + + + + CREATE VARIABLE + + + + CREATE VARIABLE + 7 + SQL - Language Statements + + + + CREATE VARIABLE + define a new permissioned typed schema variable + + + + +CREATE { TEMPORARY | TEMP } VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ] [ COLLATE collation ] + [ DEFAULT default_expr ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ] + + + + 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. + + + + + + COLLATE collation + + + The COLLATE clause assigns a collation to + the variable (which must be of a collatable data type). + If not specified, the variable data type's default collation is used. + + + + + + DEFAULT default_expr + + + The DEFAULT clause assigns a default data for + schema variable. + + + + + + ON COMMIT DROP, ON TRANSACTION END RESET + + + The ON COMMIT DROP clause specify the bahaviour of + temporary schema variable at commit of transaction. It is allowed only + for temporal variables, and enforce drop variable at commit time. The + ON TRANSACTION END RESET enforce reset to default + value at transaction end (COMMIT, ROLLBACK). + + + + + + + + + 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..299cfdf413 --- /dev/null +++ b/doc/src/sgml/ref/let.sgml @@ -0,0 +1,104 @@ + + + + + LET + + + + LET + 7 + SQL - Language Statements + + + + LET + change a schema variable's value + + + + +LET schema_variable = sql_expression +LET schema_variable = DEFAULT + + + + + + 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. + + + + + + DEFAULT + + + Ensure reset schema variable to default value if it is defined. + When there are not assigned default value, then value of schema + variable will be null. + + + + + + + Example: + +CREATE VARIABLE myvar AS integer; +LET myvar = 10; +LET myvar = (SELECT sum(val) FROM tab); +LET myvar = DEFAULT; + + + + + + 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/access/transam/xact.c b/src/backend/access/transam/xact.c index 875be180fe..adc91a4238 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -33,6 +33,7 @@ #include "catalog/namespace.h" #include "catalog/storage.h" #include "commands/async.h" +#include "commands/schemavariable.h" #include "commands/tablecmds.h" #include "commands/trigger.h" #include "executor/spi.h" @@ -1978,6 +1979,9 @@ CommitTransaction(void) */ PreCommit_on_commit_actions(); + /* Let ON COMMIT DROP or ON TRANSACTION END */ + AtPreEOXact_SchemaVariable_on_commit_actions(true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2102,6 +2106,7 @@ CommitTransaction(void) AtEOXact_GUC(true, 1); AtEOXact_SPI(true); AtEOXact_on_commit_actions(true); + AtEOXact_SchemaVariable_on_commit_actions(true); AtEOXact_Namespace(true, is_parallel_worker); AtEOXact_SMgr(); AtEOXact_Files(true); @@ -2519,6 +2524,9 @@ AbortTransaction(void) AfterTriggerEndXact(false); /* 'false' means it's abort */ AtAbort_Portals(); AtEOXact_LargeObject(false); + + /* 'false' means it's abort */ + AtPreEOXact_SchemaVariable_on_commit_actions(false); AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); AtAbort_Twophase(); @@ -2582,6 +2590,7 @@ AbortTransaction(void) AtEOXact_GUC(false, 1); AtEOXact_SPI(false); AtEOXact_on_commit_actions(false); + AtEOXact_SchemaVariable_on_commit_actions(false); AtEOXact_Namespace(false, is_parallel_worker); AtEOXact_SMgr(); AtEOXact_Files(false); 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 5d13e6a3d7..e47caa356d 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,69 @@ 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 +2840,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 @@ -4490,3 +4750,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..5372f30f31 --- /dev/null +++ b/src/backend/catalog/pg_variable.c @@ -0,0 +1,352 @@ +/*------------------------------------------------------------------------- + * + * 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 "commands/schemavariable.h" +#include "nodes/makefuncs.h" +#include "nodes/primnodes.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" + +static VariableEOXActionCodes +to_eoxaction_code(VariableEOXAction action) +{ + switch (action) + { + case VARIABLE_EOX_NOOP: + return VARIABLE_EOX_CODE_NOOP; + + case VARIABLE_EOX_DROP: + return VARIABLE_EOX_CODE_DROP; + + case VARIABLE_EOX_RESET: + return VARIABLE_EOX_CODE_RESET; + + default: + elog(ERROR, "unexpected action"); + } + +} + +static VariableEOXAction +to_eoxaction(VariableEOXActionCodes code) +{ + switch (code) + { + case VARIABLE_EOX_CODE_NOOP: + return VARIABLE_EOX_NOOP; + + case VARIABLE_EOX_CODE_DROP: + return VARIABLE_EOX_DROP; + + case VARIABLE_EOX_CODE_RESET: + return VARIABLE_EOX_RESET; + + default: + elog(ERROR, "unexpected code"); + } +} + +/* + * 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_collid(Oid varid, Oid *typid, int32 *typmod, Oid *collid) +{ + 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; + *collid = varform->varcollation; + + 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; + var->collation = varform->varcollation; + var->eoxaction = to_eoxaction(varform->vareoxaction); + + /* 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, + Oid varCollation, + Node *varDefexpr, + VariableEOXAction eoxaction, + 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); + values[Anum_pg_variable_varcollation - 1] = ObjectIdGetDatum(varCollation); + values[Anum_pg_variable_vareoxaction - 1] = CharGetDatum((char) to_eoxaction_code(eoxaction)); + /* 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); + + /* register on commit action if it is necessary */ + register_variable_on_commit_action(myself.objectId, eoxaction); + + 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..3d21dddbf8 --- /dev/null +++ b/src/backend/commands/schemavariable.c @@ -0,0 +1,753 @@ +#include "postgres.h" +#include "miscadmin.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/pg_class.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_collate.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" + +/* + * ON COMMIT action list + */ +typedef struct OnCommitItem +{ + Oid varid; /* relid of relation */ + VariableEOXAction eoxaction; /* what to do at end of xact */ + TransactionId creating_xid; + + /* + * If this entry was created during the current transaction, + * creating_subid is the ID of the creating subxact; if created in a prior + * transaction, creating_subid is zero. If deleted during the current + * transaction, deleting_subid is the ID of the deleting subxact; if no + * deletion request is pending, deleting_subid is zero. + */ + SubTransactionId creating_subid; + SubTransactionId deleting_subid; +} OnCommitItem; + +static List *on_commits = NIL; + +/* + * 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); +static void remove_variable_on_commit_actions(Oid varid); + + +/* + * 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); + } +} + +/* + * Clean variable defined by varid + */ +static void +clean_cache_varid(Oid varid) +{ + SchemaVariable svar; + bool found; + + if (schemavarhashtab == NULL) + return; + + svar = (SchemaVariable) hash_search(schemavarhashtab, &varid, + HASH_FIND, &found); + if (found) + { + /* clean content, if it is necessary */ + if (svar->freeval) + pfree(DatumGetPointer(svar->value)); + + if (hash_search(schemavarhashtab, + (void *) &svar->varid, + HASH_REMOVE, + NULL) == NULL) + elog(DEBUG1, "hash table corrupted"); + + remove_variable_on_commit_actions(varid); + } +} + +/* + * 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); + + /* remove variable from on_commits list */ + remove_variable_on_commit_actions(varid); +} + +/* + * 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(); + Oid collation; + Oid typcollation; + ObjectAddress variable; + + Node *cooked_default = NULL; + + /* + * Check consistency of arguments + */ + if (stmt->eoxaction == VARIABLE_EOX_DROP + && stmt->variable->relpersistence != RELPERSISTENCE_TEMP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("ON COMMIT DROP can only be used on temporary variables"))); + + namespaceid = + RangeVarGetAndCheckCreationNamespace(stmt->variable, NoLock, NULL); + + typenameTypeIdAndMod(pstate, stmt->typeName, &typid, &typmod); + typcollation = get_typcollation(typid); + + aclresult = pg_type_aclcheck(typid, GetUserId(), ACL_USAGE); + if (aclresult != ACLCHECK_OK) + aclcheck_error_type(aclresult, typid); + + if (stmt->collClause) + collation = LookupCollation(pstate, + stmt->collClause->collname, + stmt->collClause->location); + else + collation = typcollation;; + + /* Complain if COLLATE is applied to an uncollatable type */ + if (OidIsValid(collation) && !OidIsValid(typcollation)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("collations are not supported by type %s", + format_type_be(typid)), + parser_errposition(pstate, stmt->collClause->location))); + + if (stmt->defexpr) + { + cooked_default = transformExpr(pstate, stmt->defexpr, + EXPR_KIND_VARIABLE_DEFAULT); + + cooked_default = coerce_to_specific_type(pstate, + cooked_default, typid, "DEFAULT"); + assign_expr_collations(pstate, cooked_default); + } + + variable = VariableCreate(stmt->variable->relname, + namespaceid, + typid, + typmod, + varowner, + collation, + cooked_default, + stmt->eoxaction, + stmt->if_not_exists); + + /* + * We must bump the command counter to make the newly-created variable + * tuple visible for any other operations. + */ + CommandCounterIncrement(); + + return variable; +} + +/* + * 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; + + if (var->eoxaction != VARIABLE_EOX_NOOP) + register_variable_on_commit_action(varid, var->eoxaction); + } + 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; +} + +/* + * Returns copy of value specified schema variable + */ +Datum +CopySchemaVariable(Oid varid, bool *isNull, Oid *typid) +{ + SchemaVariable svar; + + svar = PrepareSchemaVariableForReading(varid); + Assert(svar != NULL); + + *isNull = svar->isnull; + *typid = svar->typid; + + if (!svar->isnull) + return datumCopy(svar->value, svar->typbyval, svar->typlen); + + return (Datum) 0; +} + +/* + * 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; + Oid var_collid; + bool found; + + if (schemavarhashtab == NULL) + create_schemavar_hashtable(); + + svar = (SchemaVariable) hash_search(schemavarhashtab, &varid, + HASH_ENTER, &found); + + get_schema_variable_type_typmod_collid(varid, + &var_typid, + &var_typmod, + &var_collid); + + /* 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; + } + } + else + { + Variable *var = GetVariable(varid, false); + + register_variable_on_commit_action(varid, var->eoxaction); + } + + 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); +} + +/* + * Reset to default specified schema variable + */ +void +doLetStmtReset(PlannedStmt *pstmt) +{ + clean_cache_varid(pstmt->resultVariable); +} + +/* + * Assign result of evaluated expression to schema variable + */ +void +doLetStmtEval(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(); +} + +/* + * Register a newly-created relation's ON COMMIT action. + */ +void +register_variable_on_commit_action(Oid varid, VariableEOXAction action) +{ + OnCommitItem *oc; + MemoryContext oldcxt; + + /* + * We needn't bother registering the relation unless there is an ON COMMIT + * action we need to take. + */ + if (action == VARIABLE_EOX_NOOP) + return; + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + + oc = (OnCommitItem *) palloc(sizeof(OnCommitItem)); + oc->varid = varid; + oc->eoxaction = action; + oc->creating_subid = GetCurrentSubTransactionId(); + oc->deleting_subid = InvalidSubTransactionId; + + on_commits = lcons(oc, on_commits); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Remove variable from on_commits action + */ +static void +remove_variable_on_commit_actions(Oid varid) +{ + ListCell *l; + + foreach(l, on_commits) + { + OnCommitItem *oc = (OnCommitItem *) lfirst(l); + + if (oc->varid == varid) + { + oc->deleting_subid = GetCurrentSubTransactionId(); + } + } +} + +/* + * Perform ON TRANSACTION END RESET or ON COMMIT DROP + */ +void +AtPreEOXact_SchemaVariable_on_commit_actions(bool isCommit) +{ + ListCell *l; + + foreach(l, on_commits) + { + OnCommitItem *oc = (OnCommitItem *) lfirst(l); + + /* Ignore entry if already dropped in this xact */ + if (oc->deleting_subid != InvalidSubTransactionId) + continue; + + switch (oc->eoxaction) + { + case VARIABLE_EOX_NOOP: + /* Do nothing */ + break; + case VARIABLE_EOX_RESET: + clean_cache_varid(oc->varid); + break; + case VARIABLE_EOX_DROP: + { + /* + * ON COMMIT DROP is allowed only for temp schema variables. + * So we should explicit delete only when current transaction + * was committed. When is rollback, then schema variable is + * removed automatically. + */ + if (isCommit) + { + ObjectAddress object; + + object.classId = VariableRelationId; + object.objectId = oc->varid; + object.objectSubId = 0; + + /* + * Since this is an automatic drop, rather than one + * directly initiated by the user, we pass the + * PERFORM_DELETION_INTERNAL flag. + */ + performDeletion(&object, + DROP_CASCADE, PERFORM_DELETION_INTERNAL); + } + } + break; + } + } +} + +/* + * Post-commit or post-abort cleanup for ON COMMIT management. + * + * All we do here is remove no-longer-needed OnCommitItem entries. + * + * During commit, remove entries that were deleted during this transaction; + * during abort, remove those created during this transaction. + */ +void +AtEOXact_SchemaVariable_on_commit_actions(bool isCommit) +{ + ListCell *cur_item; + ListCell *prev_item; + + prev_item = NULL; + cur_item = list_head(on_commits); + + while (cur_item != NULL) + { + OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item); + + if (isCommit ? oc->deleting_subid != InvalidSubTransactionId : + oc->creating_subid != InvalidSubTransactionId) + { + /* cur_item must be removed */ + on_commits = list_delete_cell(on_commits, cur_item, prev_item); + pfree(oc); + if (prev_item) + cur_item = lnext(prev_item); + else + cur_item = list_head(on_commits); + } + else + { + oc->creating_subid = InvalidSubTransactionId; + oc->deleting_subid = InvalidSubTransactionId; + prev_item = cur_item; + cur_item = lnext(prev_item); + } + } +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e96512e051..0bd9b240b4 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9655,6 +9655,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..6ad95028cf 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" @@ -736,6 +737,56 @@ ExecInitExprRec(Expr *node, ExprState *state, scratch.d.param.paramtype = param->paramtype; ExprEvalPushStep(state, &scratch); break; + + case PARAM_VARIABLE: + { + int es_num_schema_variables = 0; + SchemaVariableValue *es_schema_variables = NULL; + + if (state->parent && state->parent->state) + { + es_schema_variables = state->parent->state->es_schema_variables; + es_num_schema_variables = state->parent->state->es_num_schema_variables; + } + + /* + * We should to use schema variable buffer, when it is + * available. + */ + if (es_schema_variables) + { + SchemaVariableValue *var; + + /* check params, unexpected */ + if (param->paramid >= es_num_schema_variables) + elog(ERROR, "paramid of PARAM_VARIABLE param is out of range"); + + var = &es_schema_variables[param->paramid]; + + /* unexpected */ + if (var->typid != param->paramtype) + elog(ERROR, "type of buffered value is different than PARAM_VARIABLE type"); + + /* In this case, the parameter is like a constant */ + scratch.opcode = EEOP_CONST; + scratch.d.constval.value = var->value; + scratch.d.constval.isnull = var->isnull; + ExprEvalPushStep(state, &scratch); + } + else + { + /* + * When we have not a full PlanState (plpgsql simple + * expr evaluation, then we should to use direct access. + */ + scratch.opcode = EEOP_PARAM_VARIABLE; + scratch.d.vparam.varid = param->paramvarid; + scratch.d.vparam.vartype = 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..5dc22ee1c7 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,16 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + EEO_CASE(EEOP_PARAM_VARIABLE) + { + /* direct access to schema variable (without buffering) */ + *op->resvalue = GetSchemaVariable(op->d.vparam.varid, + op->resnull, + op->d.vparam.vartype, + true); + EEO_NEXT(); + } + EEO_CASE(EEOP_CASE_TESTVAL) { /* diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index c583e020a0..70d1fcdaa7 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -43,9 +43,12 @@ #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 "commands/schemavariable.h" #include "executor/execdebug.h" +#include "executor/svariableReceiver.h" #include "foreign/fdwapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" @@ -199,17 +202,65 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) estate->es_sourceText = queryDesc->sourceText; + /* + * Prepare schema variables, if are not prepared in queryDesc + */ + if (queryDesc->num_schema_variables > 0) + { + /* When buffer of used schema variables loaded from shared memory */ + estate->es_schema_variables = queryDesc->schema_variables; + estate->es_num_schema_variables = queryDesc->num_schema_variables; + } + else if (queryDesc->plannedstmt->schemaVariables) + { + ListCell *lc; + int nSchemaVariables; + int i = 0; + + nSchemaVariables = list_length(queryDesc->plannedstmt->schemaVariables); + + /* Create buffer for used schema variables */ + estate->es_schema_variables = (SchemaVariableValue *) + palloc(nSchemaVariables * sizeof(SchemaVariableValue)); + + foreach(lc, queryDesc->plannedstmt->schemaVariables) + { + AclResult aclresult; + Oid varid = lfirst_oid(lc); + + aclresult = pg_variable_aclcheck(varid, GetUserId(), ACL_READ); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_VARIABLE, + schema_variable_get_name(varid)); + + estate->es_schema_variables[i].varid = varid; + estate->es_schema_variables[i].value = CopySchemaVariable(varid, + &estate->es_schema_variables[i].isnull, + &estate->es_schema_variables[i].typid); + + i++; + } + + estate->es_num_schema_variables = nSchemaVariables; + } + /* * Fill in the query environment, if any, from queryDesc. */ 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 +396,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 +976,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/execParallel.c b/src/backend/executor/execParallel.c index ee0f07a81e..be36fbb011 100644 --- a/src/backend/executor/execParallel.c +++ b/src/backend/executor/execParallel.c @@ -12,8 +12,9 @@ * workers and ensuring that their state generally matches that of the * leader; see src/backend/access/transam/README.parallel for details. * However, we must save and restore relevant executor state, such as - * any ParamListInfo associated with the query, buffer usage info, and - * the actual plan to be passed down to the worker. + * any ParamListInfo associated with the query, buffer usage info, used + * schema variables buffer, and the actual plan to be passed down to the + * worker. * * IDENTIFICATION * src/backend/executor/execParallel.c @@ -62,6 +63,7 @@ #define PARALLEL_KEY_INSTRUMENTATION UINT64CONST(0xE000000000000006) #define PARALLEL_KEY_DSA UINT64CONST(0xE000000000000007) #define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008) +#define PARALLEL_KEY_SCHEMA_VARIABLES UINT64CONST(0xE000000000000009) #define PARALLEL_TUPLE_QUEUE_SIZE 65536 @@ -136,6 +138,12 @@ static bool ExecParallelRetrieveInstrumentation(PlanState *planstate, /* Helper function that runs in the parallel worker. */ static DestReceiver *ExecParallelGetReceiver(dsm_segment *seg, shm_toc *toc); +/* Helper functions that can pass values of used schema variables */ +static Size EstimateSchemaVariables(EState *estate); +static void SerializeSchemaVariables(EState *estate, char **start_address); +static SchemaVariableValue *RestoreSchemaVariables(char **start_address, + int *num_schema_variables); + /* * Create a serialized representation of the plan to be sent to each worker. */ @@ -571,12 +579,14 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, char *pstmt_data; char *pstmt_space; char *paramlistinfo_space; + char *schema_variables_space; BufferUsage *bufusage_space; SharedExecutorInstrumentation *instrumentation = NULL; int pstmt_len; int paramlistinfo_len; int instrumentation_len = 0; int instrument_offset = 0; + int schema_variables_len = 0; Size dsa_minsize = dsa_minimum_size(); char *query_string; int query_len; @@ -622,6 +632,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, shm_toc_estimate_chunk(&pcxt->estimator, paramlistinfo_len); shm_toc_estimate_keys(&pcxt->estimator, 1); + /* Estimate space for serialized schema variables. */ + schema_variables_len = EstimateSchemaVariables(estate); + shm_toc_estimate_chunk(&pcxt->estimator, schema_variables_len); + shm_toc_estimate_keys(&pcxt->estimator, 1); + /* * Estimate space for BufferUsage. * @@ -699,6 +714,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, shm_toc_insert(pcxt->toc, PARALLEL_KEY_PARAMLISTINFO, paramlistinfo_space); SerializeParamList(estate->es_param_list_info, ¶mlistinfo_space); + /* Store serialized schema variables. */ + schema_variables_space = shm_toc_allocate(pcxt->toc, schema_variables_len); + shm_toc_insert(pcxt->toc, PARALLEL_KEY_SCHEMA_VARIABLES, schema_variables_space); + SerializeSchemaVariables(estate, &schema_variables_space); + /* Allocate space for each worker's BufferUsage; no need to initialize. */ bufusage_space = shm_toc_allocate(pcxt->toc, mul_size(sizeof(BufferUsage), pcxt->nworkers)); @@ -1099,6 +1119,7 @@ ExecParallelGetQueryDesc(shm_toc *toc, DestReceiver *receiver, { char *pstmtspace; char *paramspace; + char *schemavariablespace; PlannedStmt *pstmt; ParamListInfo paramLI; char *queryString; @@ -1262,6 +1283,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc) SharedExecutorInstrumentation *instrumentation; int instrument_options = 0; void *area_space; + char *schemavariable_space; dsa_area *area; ParallelWorkerContext pwcxt; @@ -1285,6 +1307,14 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc) area_space = shm_toc_lookup(toc, PARALLEL_KEY_DSA, false); area = dsa_attach_in_place(area_space, seg); + /* Reconstruct schema variables. */ + schemavariable_space = shm_toc_lookup(toc, + PARALLEL_KEY_SCHEMA_VARIABLES, + false); + queryDesc->schema_variables = + RestoreSchemaVariables(&schemavariable_space, + &queryDesc->num_schema_variables); + /* Start up the executor */ queryDesc->plannedstmt->jitFlags = fpes->jit_flags; ExecutorStart(queryDesc, fpes->eflags); @@ -1344,3 +1374,118 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc) FreeQueryDesc(queryDesc); receiver->rDestroy(receiver); } + +/* + * Estimate the amount of space required to serialize a used + * schema variables. + */ +static Size +EstimateSchemaVariables(EState *estate) +{ + int i; + Size sz = sizeof(int); + + if (estate->es_schema_variables == NULL) + return sz; + + for (i = 0; i < estate->es_num_schema_variables; i++) + { + SchemaVariableValue *svarval; + Oid typeOid; + int16 typLen; + bool typByVal; + + svarval = &estate->es_schema_variables[i]; + + typeOid = svarval->typid; + + sz = add_size(sz, sizeof(Oid)); /* space for type OID */ + + /* space for datum/isnull */ + Assert(OidIsValid(typeOid)); + get_typlenbyval(typeOid, &typLen, &typByVal); + + sz = add_size(sz, + datumEstimateSpace(svarval->value, svarval->isnull, typByVal, typLen)); + } + + return sz; +} + +/* + * Serialize a schema variables buffer into caller-provided storage. + * + * We write the number of parameters first, as a 4-byte integer, and then + * write details for each parameter in turn. The details for each parameter + * consist of a 4-byte type OID, and then the datum as serialized by + * datumSerialize(). The caller is responsible for ensuring that there is + * enough storage to store the number of bytes that will be written; use + * EstimateSchemaVariables to find out how many will be needed. + * *start_address is updated to point to the byte immediately following those + * written. + * + * RestoreSchemaVariables can be used to recreate a schema variable buffer + * based on the serialized representation; + */ +static void +SerializeSchemaVariables(EState *estate, char **start_address) +{ + int nparams; + int i; + + /* Write number of parameters. */ + nparams = estate->es_num_schema_variables; + memcpy(*start_address, &nparams, sizeof(int)); + *start_address += sizeof(int); + + /* Write each parameter in turn. */ + for (i = 0; i < nparams; i++) + { + SchemaVariableValue *svarval; + Oid typeOid; + int16 typLen; + bool typByVal; + + svarval = &estate->es_schema_variables[i]; + typeOid = svarval->typid; + + /* Write type OID. */ + memcpy(*start_address, &typeOid, sizeof(Oid)); + *start_address += sizeof(Oid); + + Assert(OidIsValid(typeOid)); + get_typlenbyval(typeOid, &typLen, &typByVal); + + datumSerialize(svarval->value, svarval->isnull, typByVal, typLen, + start_address); + } +} + +static SchemaVariableValue * +RestoreSchemaVariables(char **start_address, int *num_schema_variables) +{ + SchemaVariableValue *schema_variables; + int i; + int nparams; + + memcpy(&nparams, *start_address, sizeof(int)); + *start_address += sizeof(int); + + *num_schema_variables = nparams; + schema_variables = (SchemaVariableValue *) + palloc(nparams * sizeof(SchemaVariableValue)); + + for (i = 0; i < nparams; i++) + { + SchemaVariableValue *svarval = &schema_variables[i]; + + /* Read type OID. */ + memcpy(&svarval->typid, *start_address, sizeof(Oid)); + *start_address += sizeof(Oid); + + /* Read datum/isnull. */ + svarval->value = datumRestore(start_address, &svarval->isnull); + } + + return schema_variables; +} 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..523db538aa 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); @@ -100,6 +101,7 @@ _copyPlannedStmt(const PlannedStmt *from) COPY_NODE_FIELD(invalItems); COPY_NODE_FIELD(paramExecTypes); COPY_NODE_FIELD(utilityStmt); + COPY_NODE_FIELD(schemaVariables); COPY_LOCATION_FIELD(stmt_location); COPY_LOCATION_FIELD(stmt_len); @@ -1415,6 +1417,7 @@ _copyParam(const Param *from) COPY_SCALAR_FIELD(paramtype); COPY_SCALAR_FIELD(paramtypmod); COPY_SCALAR_FIELD(paramcollid); + COPY_SCALAR_FIELD(paramvarid); COPY_LOCATION_FIELD(location); return newnode; @@ -3000,6 +3003,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); @@ -3009,6 +3013,7 @@ _copyQuery(const Query *from) COPY_SCALAR_FIELD(hasModifyingCTE); COPY_SCALAR_FIELD(hasForUpdate); COPY_SCALAR_FIELD(hasRowSecurity); + COPY_SCALAR_FIELD(hasSchemaVariable); COPY_NODE_FIELD(cteList); COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(jointree); @@ -3118,6 +3123,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 +5183,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..2ea5e18b1a 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -202,6 +202,7 @@ _equalParam(const Param *a, const Param *b) COMPARE_SCALAR_FIELD(paramtype); COMPARE_SCALAR_FIELD(paramtypmod); COMPARE_SCALAR_FIELD(paramcollid); + COMPARE_SCALAR_FIELD(paramvarid); COMPARE_LOCATION_FIELD(location); return true; @@ -949,6 +950,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); @@ -958,6 +960,7 @@ _equalQuery(const Query *a, const Query *b) COMPARE_SCALAR_FIELD(hasModifyingCTE); COMPARE_SCALAR_FIELD(hasForUpdate); COMPARE_SCALAR_FIELD(hasRowSecurity); + COMPARE_SCALAR_FIELD(hasSchemaVariable); COMPARE_NODE_FIELD(cteList); COMPARE_NODE_FIELD(rtable); COMPARE_NODE_FIELD(jointree); @@ -1057,6 +1060,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 +3238,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 b5af904c18..9eb3e3f3bb 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); @@ -285,6 +286,7 @@ _outPlannedStmt(StringInfo str, const PlannedStmt *node) WRITE_NODE_FIELD(invalItems); WRITE_NODE_FIELD(paramExecTypes); WRITE_NODE_FIELD(utilityStmt); + WRITE_NODE_FIELD(schemaVariables); WRITE_LOCATION_FIELD(stmt_location); WRITE_LOCATION_FIELD(stmt_len); } @@ -1194,6 +1196,7 @@ _outParam(StringInfo str, const Param *node) WRITE_OID_FIELD(paramtype); WRITE_INT_FIELD(paramtypmod); WRITE_OID_FIELD(paramcollid); + WRITE_OID_FIELD(paramvarid); WRITE_LOCATION_FIELD(location); } @@ -2260,6 +2263,7 @@ _outPlannerGlobal(StringInfo str, const PlannerGlobal *node) WRITE_NODE_FIELD(relationOids); WRITE_NODE_FIELD(invalItems); WRITE_NODE_FIELD(paramExecTypes); + WRITE_NODE_FIELD(schemaVariables); WRITE_UINT_FIELD(lastPHId); WRITE_UINT_FIELD(lastRowMarkId); WRITE_INT_FIELD(lastPlanNodeId); @@ -2316,6 +2320,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BOOL_FIELD(hasHavingQual); WRITE_BOOL_FIELD(hasPseudoConstantQuals); WRITE_BOOL_FIELD(hasRecursion); + WRITE_BOOL_FIELD(hasSchemaVariable); WRITE_INT_FIELD(wt_param_id); WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); @@ -2794,6 +2799,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) { @@ -2972,6 +2987,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); @@ -2981,6 +2997,7 @@ _outQuery(StringInfo str, const Query *node) WRITE_BOOL_FIELD(hasModifyingCTE); WRITE_BOOL_FIELD(hasForUpdate); WRITE_BOOL_FIELD(hasRowSecurity); + WRITE_BOOL_FIELD(hasSchemaVariable); WRITE_NODE_FIELD(cteList); WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(jointree); @@ -4192,6 +4209,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..3ea7e47387 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); @@ -251,6 +252,7 @@ _readQuery(void) READ_BOOL_FIELD(hasModifyingCTE); READ_BOOL_FIELD(hasForUpdate); READ_BOOL_FIELD(hasRowSecurity); + READ_BOOL_FIELD(hasSchemaVariable); READ_NODE_FIELD(cteList); READ_NODE_FIELD(rtable); READ_NODE_FIELD(jointree); @@ -571,6 +573,7 @@ _readParam(void) READ_OID_FIELD(paramtype); READ_INT_FIELD(paramtypmod); READ_OID_FIELD(paramcollid); + READ_OID_FIELD(paramvarid); READ_LOCATION_FIELD(location); READ_DONE(); @@ -1485,6 +1488,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); @@ -1492,6 +1496,7 @@ _readPlannedStmt(void) READ_NODE_FIELD(invalItems); READ_NODE_FIELD(paramExecTypes); READ_NODE_FIELD(utilityStmt); + READ_NODE_FIELD(schemaVariables); READ_LOCATION_FIELD(stmt_location); READ_LOCATION_FIELD(stmt_len); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index e589471fee..c0eb222296 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -301,6 +301,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) glob->lastPlanNodeId = 0; glob->transientPlan = false; glob->dependsOnRole = false; + glob->schemaVariables = NIL; /* * Assess whether it's feasible to use parallel mode for this query. We @@ -334,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() && @@ -520,6 +522,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; @@ -528,6 +531,7 @@ standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams) result->paramExecTypes = glob->paramExecTypes; /* utilityStmt should be null, but we might as well copy it */ result->utilityStmt = parse->utilityStmt; + result->schemaVariables = glob->schemaVariables; result->stmt_location = parse->stmt_location; result->stmt_len = parse->stmt_len; @@ -659,6 +663,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, */ pull_up_subqueries(root); + /* + * Check if some subquery uses schema variable. Flag hasSchemaVariable + * should be true if query or some subquery uses any schema variable. + */ + pull_up_has_schema_variable(root); + /* * If this is a simple UNION ALL query, flatten it into an appendrel. We * do this now because it requires applying pull_up_subqueries to the leaf @@ -2172,7 +2182,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/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index f66f39d8c6..8db0ae0501 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -140,6 +140,7 @@ static List *set_returning_clause_references(PlannerInfo *root, int rtoffset); static bool extract_query_dependencies_walker(Node *node, PlannerInfo *context); +static bool pull_up_has_schema_variable_walker(Node *node, PlannerInfo *root); /***************************************************************************** * @@ -1004,6 +1005,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) return plan; } +/* + * Search usage of schema variables in subqueries + */ +void +pull_up_has_schema_variable(PlannerInfo *root) +{ + Query *query = root->parse; + + if (query->hasSchemaVariable) + { + root->hasSchemaVariable = true; + } + else + { + (void) query_tree_walker(query, + pull_up_has_schema_variable_walker, + (void *) root, 0); + } +} + +static bool +pull_up_has_schema_variable_walker(Node *node, PlannerInfo *root) +{ + if (node == NULL) + return false; + if (IsA(node, Query)) + { + Query *query = (Query *) node; + + if (query->hasSchemaVariable) + { + root->hasSchemaVariable = true; + return false; + } + + /* Recurse into subselects */ + return query_tree_walker((Query *) node, + pull_up_has_schema_variable_walker, + (void *) root, 0); + } + return expression_tree_walker(node, pull_up_has_schema_variable_walker, + (void *) root); +} + /* * set_indexonlyscan_references * Do set_plan_references processing on an IndexOnlyScan @@ -1439,10 +1484,14 @@ fix_expr_common(PlannerInfo *root, Node *node) /* * fix_param_node * Do set_plan_references processing on a Param + * Collect schema variables list and replace variable oid by + * index to collected list. * * If it's a PARAM_MULTIEXPR, replace it with the appropriate Param from * root->multiexpr_params; otherwise no change is needed. * Just for paranoia's sake, we make a copy of the node in either case. + * + * If it's a PARAM_VARIABLE, then we should to calculate paramid. */ static Node * fix_param_node(PlannerInfo *root, Param *p) @@ -1461,6 +1510,52 @@ fix_param_node(PlannerInfo *root, Param *p) elog(ERROR, "unexpected PARAM_MULTIEXPR ID: %d", p->paramid); return copyObject(list_nth(params, colno - 1)); } + + if (p->paramkind == PARAM_VARIABLE) + { + ListCell *lc; + int n = 0; + bool found = false; + + /* We will modify object */ + p = (Param *) copyObject(p); + + /* + * Now, we can actualize list of schema variables, and we can + * complete paramid parameter. + */ + foreach(lc, root->glob->schemaVariables) + { + if (lfirst_oid(lc) == p->paramvarid) + { + p->paramid = n; + found = true; + break; + } + n += 1; + } + + if (!found) + { + PlanInvalItem *inval_item = makeNode(PlanInvalItem); + + /* paramid is still schema variable id */ + inval_item->cacheId = VARIABLEOID; + inval_item->hashValue = GetSysCacheHashValue1(VARIABLEOID, + ObjectIdGetDatum(p->paramvarid)); + + /* Append this variable to global, register dependency */ + root->glob->invalItems = lappend(root->glob->invalItems, + inval_item); + root->glob->schemaVariables = lappend_oid(root->glob->schemaVariables, + p->paramvarid); + + p->paramid = n; + } + + return (Node *) p; + } + return (Node *) copyObject(p); } @@ -1472,7 +1567,9 @@ fix_param_node(PlannerInfo *root, Param *p) * replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars, * replacing Aggref nodes that should be replaced by initplan output Params, * looking up operator opcode info for OpExpr and related nodes, - * and adding OIDs from regclass Const nodes into root->glob->relationOids. + * adding OIDs from regclass Const nodes into root->glob->relationOids, + * and replacing PARAM_VARIABLE paramid, that is oid of schema variable + * to offset to array of by query used schema variables. */ static Node * fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) @@ -1485,7 +1582,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset) if (rtoffset != 0 || root->multiexpr_params != NIL || root->glob->lastPHId != 0 || - root->minmax_aggs != NIL) + root->minmax_aggs != NIL || + root->hasSchemaVariable) { return fix_scan_expr_mutator(node, &context); } 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 ee6f4cdf4d..f232c6cfd1 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -1268,7 +1268,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 || @@ -4813,7 +4814,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..86e21d519b 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; @@ -454,6 +468,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) if (pstate->p_hasAggs) parseCheckAggregates(pstate, qry); + qry->hasSchemaVariable = pstate->p_hasSchemaVariable; + assign_query_collations(pstate, qry); return qry; @@ -880,6 +896,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSchemaVariable = pstate->p_hasSchemaVariable; assign_query_collations(pstate, qry); @@ -1327,6 +1344,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) (LockingClause *) lfirst(l), false); } + qry->hasSchemaVariable = pstate->p_hasSchemaVariable; + assign_query_collations(pstate, qry); return qry; @@ -1561,12 +1580,229 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSchemaVariable = pstate->p_hasSchemaVariable; assign_query_collations(pstate, qry); 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; + Oid collid; + + 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); + + 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; + + /* Simple behave, when LET xx = DEFAULT was used */ + if (stmt->selectStmt == NULL) + { + if (attrname != NULL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("only complete variable can be set to default"), + parser_errposition(pstate, stmt->location))); + + qry->commandType = CMD_UTILITY; + qry->utilityStmt = (Node *) stmt; + + return qry; + } + + /* Exec this command as utility */ + qry->commandType = CMD_PLAN_UTILITY; + qry->utilityStmt = (Node *) stmt; + + get_schema_variable_type_typmod_collid(varid, &typid, &typmod, &collid); + + 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->paramvarid = 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)); + + pstate->p_hasSchemaVariable = true; + + 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; + qry->hasSchemaVariable = pstate->p_hasSchemaVariable; + + assign_query_collations(pstate, qry); + + pstate->p_expr_kind = sv_expr_kind; + + return qry; +} + /* * transformSetOperationStmt - * transforms a set-operations tree @@ -1799,6 +2035,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) (LockingClause *) lfirst(l), false); } + qry->hasSchemaVariable = pstate->p_hasSchemaVariable; + assign_query_collations(pstate, qry); return qry; @@ -2278,6 +2516,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSchemaVariable = pstate->p_hasSchemaVariable; assign_query_collations(pstate, qry); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 4bd2223f26..9f5229914b 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -211,6 +211,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JoinType jtype; DropBehavior dbehavior; OnCommitAction oncommit; + VariableEOXAction oneoxaction; List *list; Node *node; Value *value; @@ -257,8 +258,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 +269,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 +401,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 @@ -422,6 +424,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type OptTemp %type OptNoLog %type OnCommitOption +%type OnEOXActionOption %type for_locking_strength %type for_locking_item @@ -584,6 +587,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 +653,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 +691,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 +882,7 @@ stmt : | CreatePolicyStmt | CreatePLangStmt | CreateSchemaStmt + | CreateSchemaVarStmt | CreateSeqStmt | CreateStmt | CreateSubscriptionStmt @@ -917,6 +922,7 @@ stmt : | ImportForeignSchemaStmt | IndexStmt | InsertStmt + | LetStmt | ListenStmt | RefreshMatViewStmt | LoadStmt @@ -1808,7 +1814,12 @@ DiscardStmt: n->target = DISCARD_SEQUENCES; $$ = (Node *) n; } - + | DISCARD VARIABLES + { + DiscardStmt *n = makeNode(DiscardStmt); + n->target = DISCARD_VARIABLES; + $$ = (Node *) n; + } ; @@ -4479,6 +4490,48 @@ create_extension_opt_item: } ; +/***************************************************************************** + * + * QUERY : + * CREATE VARIABLE varname [AS] type + * + *****************************************************************************/ + +CreateSchemaVarStmt: + CREATE OptTemp VARIABLE qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr OnEOXActionOption + { + CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt); + $4->relpersistence = $2; + n->variable = $4; + n->typeName = $6; + n->collClause = (CollateClause *) $7; + n->defexpr = $8; + n->eoxaction = $9; + n->if_not_exists = false; + $$ = (Node *) n; + } + | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause optSchemaVarDefExpr OnEOXActionOption + { + CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt); + $7->relpersistence = $2; + n->variable = $7; + n->typeName = $9; + n->collClause = (CollateClause *) $10; + n->defexpr = $11; + n->eoxaction = $12; + n->if_not_exists = true; + $$ = (Node *) n; + } + ; + +optSchemaVarDefExpr: DEFAULT b_expr { $$ = $2; } + | /* EMPTY */ { $$ = NULL; } + ; + +OnEOXActionOption: ON COMMIT DROP { $$ = VARIABLE_EOX_DROP; } + | ON TRANSACTION END_P RESET { $$ = VARIABLE_EOX_RESET; } + | /*EMPTY*/ { $$ = VARIABLE_EOX_NOOP; } + /***************************************************************************** * * ALTER EXTENSION name UPDATE [ TO version ] @@ -6340,6 +6393,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 */ @@ -6609,6 +6663,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 */ @@ -6747,6 +6802,7 @@ security_label_type_any_name: | TABLE { $$ = OBJECT_TABLE; } | VIEW { $$ = OBJECT_VIEW; } | MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; /* object types taking name */ @@ -7168,6 +7224,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)); @@ -7208,6 +7272,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; + } ; @@ -7368,6 +7440,7 @@ defacl_privilege_target: | SEQUENCES { $$ = OBJECT_SEQUENCE; } | TYPES_P { $$ = OBJECT_TYPE; } | SCHEMAS { $$ = OBJECT_SCHEMA; } + | VARIABLES { $$ = OBJECT_VARIABLE; } ; @@ -8964,6 +9037,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; } @@ -9282,6 +9374,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; + } + ; /***************************************************************************** @@ -9517,6 +9628,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; + } ; @@ -10698,6 +10817,7 @@ ExplainableStmt: | CreateMatViewStmt | RefreshMatViewStmt | ExecuteStmt /* by default all are $$=$1 */ + | LetStmt ; explain_option_list: @@ -10755,6 +10875,7 @@ PreparableStmt: | InsertStmt | UpdateStmt | DeleteStmt /* by default all are $$=$1 */ + | LetStmt ; /***************************************************************************** @@ -11153,6 +11274,50 @@ opt_hold: /* EMPTY */ { $$ = 0; } | WITHOUT HOLD { $$ = 0; } ; +/***************************************************************************** + * + * QUERY: + * LET STATEMENTS + * + *****************************************************************************/ +LetStmt: LET let_target '=' a_expr + { + LetStmt *n = makeNode(LetStmt); + + n->target = $2; + + if (!IsA((Node *) $4, SetToDefault)) + { + SelectStmt *select = makeNode(SelectStmt); + ResTarget *res = makeNode(ResTarget); + + /* 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; + } + else + n->selectStmt = NULL; + + n->location = @2; + + $$ = (Node *) n; + } + ; + +let_target: + ColId opt_indirection + { + $$ = list_make1(makeString($1)); + if ($2) + $$ = list_concat($$, + check_indirection($2, yyscanner)); + } + /***************************************************************************** * * QUERY: @@ -15132,6 +15297,7 @@ unreserved_keyword: | LARGE_P | LAST_P | LEAKPROOF + | LET | LEVEL | LISTEN | LOAD @@ -15280,6 +15446,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..54565834fd 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, Oid collid, + char *attrname, int location); static Node *transformWholeRowRef(ParseState *pstate, RangeTblEntry *rte, int location); static Node *transformIndirection(ParseState *pstate, A_Indirection *ind); @@ -512,6 +517,9 @@ 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 +757,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 +790,72 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) parser_errposition(pstate, cref->location))); } + if (OidIsValid(varid)) + { + Oid typid; + int32 typmod; + Oid collid; + + get_schema_variable_type_typmod_collid(varid, &typid, &typmod, &collid); + + 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, collid, + attrname, cref->location); + } + /* * Throw error if no translation found. */ @@ -807,6 +890,74 @@ 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, Oid collid, + char *attrname, int location) +{ + Param *param; + + param = makeNode(Param); + + param->paramkind = PARAM_VARIABLE; + param->paramvarid = varid; + param->paramtype = typid; + param->paramtypmod = typmod; + param->paramcollid = collid; + + /* + * There are two access to schema variables - direct, used by simple + * plpgsql expressions, where there are not necessary to emulate stability. + * Buffered access is used elsewhere. We should to ensure stable values, + * and because schema variables are global, then we should to work with + * copied values instead direct access to variables. For direct access + * the varid is best for access. For buffered access we need to assign + * index to buffer - later, when we will know what variables are used. + * Now, we just remember, so we use schema variables. + */ + pstate->p_hasSchemaVariable = true; + + 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 +1969,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 +1978,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 +3613,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 +3629,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 d830569641..c27aecedb5 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -3359,7 +3359,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/postgres.c b/src/backend/tcop/postgres.c index 7a9ada2c71..e711cd3366 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -869,6 +869,7 @@ pg_plan_queries(List *querytrees, int cursorOptions, ParamListInfo boundParams) stmt->utilityStmt = query->utilityStmt; stmt->stmt_location = query->stmt_location; stmt->stmt_len = query->stmt_len; + stmt->resultVariable = query->resultVariable; } else { diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 66cc5c35c6..cd07fcb6ee 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -86,6 +86,9 @@ CreateQueryDesc(PlannedStmt *plannedstmt, qd->queryEnv = queryEnv; qd->instrument_options = instrument_options; /* instrumentation wanted? */ + qd->num_schema_variables = 0; + qd->schema_variables = NULL; + /* null these fields until set by ExecutorStart */ qd->tupDesc = NULL; qd->estate = NULL; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index b5804f64ad..da09893103 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,21 @@ standard_ProcessUtility(PlannedStmt *pstmt, break; } + case T_LetStmt: + { + if (pstmt->commandType == CMD_UTILITY) + doLetStmtReset(pstmt); + else + { + Assert(pstmt->commandType == CMD_PLAN_UTILITY); + doLetStmtEval(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 +1237,10 @@ ProcessUtilitySlow(ParseState *pstate, } break; + case T_CreateSchemaVarStmt: + address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree); + break; + /* * ************* object creation / destruction ************** */ @@ -2055,6 +2075,9 @@ AlterObjectTypeCommandTag(ObjectType objtype) case OBJECT_STATISTIC_EXT: tag = "ALTER STATISTICS"; break; + case OBJECT_VARIABLE: + tag = "ALTER VARIABLE"; + break; default: tag = "???"; break; @@ -2104,6 +2127,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 +2385,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_STATISTIC_EXT: tag = "DROP STATISTICS"; break; + case OBJECT_VARIABLE: + tag = "DROP VARIABLE"; + break; default: tag = "???"; } @@ -2639,6 +2669,9 @@ CreateCommandTag(Node *parsetree) case DISCARD_SEQUENCES: tag = "DISCARD SEQUENCES"; break; + case DISCARD_VARIABLES: + tag = "DISCARD VARIABLES"; + break; default: tag = "???"; } @@ -2844,6 +2877,7 @@ CreateCommandTag(Node *parsetree) tag = "DELETE"; break; case CMD_UTILITY: + case CMD_PLAN_UTILITY: tag = CreateCommandTag(stmt->utilityStmt); break; default: @@ -2915,6 +2949,10 @@ CreateCommandTag(Node *parsetree) } break; + case T_CreateSchemaVarStmt: + tag = "CREATE VARIABLE"; + break; + default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); @@ -2961,6 +2999,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 4c2408d655..ec10cb4468 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" @@ -7395,6 +7396,14 @@ get_parameter(Param *param, deparse_context *context) return; } + /* translate paramvarid to schema variable name */ + if (param->paramkind == PARAM_VARIABLE) + { + appendStringInfo(context->buf, "%s", + schema_variable_get_name(param->paramvarid)); + 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 9b5869add8..c4e4d10c6a 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 36e3383b85..58d15af7b1 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -3465,6 +3465,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 || @@ -3664,7 +3665,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 f0ea83e6a9..c365d9fcf8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -258,6 +258,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); @@ -4224,6 +4225,220 @@ 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_vareoxaction; + 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.vareoxaction, " + "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_vareoxaction = PQfnumber(res, "vareoxaction"); + 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].vareoxaction = pg_strdup(PQgetvalue(res, i, i_vareoxaction)); + + 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; + const char *vareoxaction; + + /* 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; + vareoxaction = varinfo->vareoxaction; + + appendPQExpBuffer(delq, "DROP VARIABLE %s;\n", + varname); + + appendPQExpBuffer(query, "CREATE VARIABLE %s AS %s", + varname, vartypname); + + if (vardefexpr) + appendPQExpBuffer(query, " DEFAULT %s", + vardefexpr); + + if (strcmp(vareoxaction, "d") == 0) + appendPQExpBuffer(query, " ON COMMIT DROP"); + else if (strcmp(vareoxaction, "r") == 0) + appendPQExpBuffer(query, " ON TRANSACTION END RESET"); + + 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, @@ -9791,6 +10006,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 */ @@ -17877,6 +18095,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..5471e667fc 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,23 @@ 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 *vareoxaction; + 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 +743,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 4ca0db1d0c..fd4e1285d9 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -4198,6 +4198,84 @@ 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\",\n" + " CASE v.vareoxaction\n" + " WHEN 'd' THEN 'ON COMMIT DROP'\n" + " WHEN 'r' THEN 'ON TRANSACTION END RESET' END as \"%s\"\n", + gettext_noop("Schema"), + gettext_noop("Name"), + gettext_noop("Type"), + gettext_noop("Owner"), + gettext_noop("Default"), + gettext_noop("Transaction end action")); + + 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 586aebddd3..5e0175b384 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 7549b40192..6ce9a3d6ec 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -610,6 +610,22 @@ static const SchemaQuery Query_for_list_of_statistics = { .result = "pg_catalog.quote_ident(s.stxname)", }; +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 @@ -1054,6 +1070,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 */ }; @@ -1409,7 +1426,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", @@ -1426,9 +1443,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", @@ -1793,6 +1810,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", @@ -2643,6 +2663,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)) @@ -2696,7 +2724,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")) @@ -2798,6 +2826,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); @@ -2808,14 +2842,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 */ @@ -2924,6 +2958,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'" @@ -2937,14 +2972,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"); @@ -2978,6 +3015,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 @@ -3130,7 +3169,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 @@ -3353,6 +3392,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 254fbef1f7..67ed04f351 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 0e202372d5..8812075b2e 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, @@ -146,6 +149,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 aee49fdb6d..f84ea21c68 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 860571440a..7f3a0884d2 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..62355edf4c --- /dev/null +++ b/src/include/catalog/pg_variable.h @@ -0,0 +1,101 @@ +/*------------------------------------------------------------------------- + * + * 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 */ + Oid varcollation; /* variable collation */ + char vareoxaction; /* action on transaction end */ + +#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; + +typedef enum VariableEOXActionCodes +{ + VARIABLE_EOX_CODE_NOOP = 'n', /* NOOP */ + VARIABLE_EOX_CODE_DROP = 'd', /* ON COMMIT DROP */ + VARIABLE_EOX_CODE_RESET = 'r', /* ON COMMIT RESET */ +} VariableEOXActionCodes; + +/* ---------------- + * 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; + Oid collation; + VariableEOXAction eoxaction; + 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_collid(Oid varid, + Oid *typid, + int32 *typmod, + Oid *collid); + +/* 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, + Oid varCollation, + Node *varDefexpr, + VariableEOXAction eoxaction, + 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..d1577fcec3 --- /dev/null +++ b/src/include/commands/schemavariable.h @@ -0,0 +1,42 @@ +/*------------------------------------------------------------------------- + * + * 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 Datum CopySchemaVariable(Oid varid, bool *isNull, Oid *typid); + +extern void SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod); + +extern void doLetStmtReset(PlannedStmt *pstmt); +extern void doLetStmtEval(PlannedStmt *pstmt, ParamListInfo params, QueryEnvironment *queryEnv, const char *queryString); + +extern void register_variable_on_commit_action(Oid varid, VariableEOXAction action); +extern void AtPreEOXact_SchemaVariable_on_commit_actions(bool isCommit); +extern void AtEOXact_SchemaVariable_on_commit_actions(bool isCommit); + +#endif diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index f7b1f77616..1e92e8e1be 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, @@ -351,6 +352,13 @@ typedef struct ExprEvalStep Oid paramtype; /* OID of parameter's datatype */ } param; + /* for EEOP_PARAM_VARIABLE */ + struct + { + Oid varid; /* OID of assigned variable */ + Oid vartype; /* OID of parameter's datatype */ + } vparam; + /* for EEOP_PARAM_CALLBACK */ struct { @@ -700,6 +708,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/execdesc.h b/src/include/executor/execdesc.h index 10e9ded246..1ba9b9f4c6 100644 --- a/src/include/executor/execdesc.h +++ b/src/include/executor/execdesc.h @@ -48,6 +48,10 @@ typedef struct QueryDesc EState *estate; /* executor's query-wide state */ PlanState *planstate; /* tree of per-plan-node state */ + /* reference to schema variables buffer */ + int num_schema_variables; + SchemaVariableValue *schema_variables; + /* This field is set by ExecutorRun */ bool already_executed; /* true if previously executed */ 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 c830f141b1..0d9cdd551c 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -464,6 +464,18 @@ typedef struct ResultRelInfo bool ri_PartitionReadyForRouting; } ResultRelInfo; +/* ---------------- + * SchemaVariableValue + * ---------------- + */ +typedef struct SchemaVariableValue +{ + Oid varid; + Oid typid; + bool isnull; + Datum value; +} SchemaVariableValue; + /* ---------------- * EState information * @@ -518,6 +530,13 @@ typedef struct EState ParamListInfo es_param_list_info; /* values of external params */ ParamExecData *es_param_exec_vals; /* values of internal params */ + /* Variables info: */ + /* number of used schema variables */ + int es_num_schema_variables; + + /* array of copied values of schema variables */ + SchemaVariableValue *es_schema_variables; + QueryEnvironment *es_queryEnv; /* query environment */ /* Other working state: */ @@ -565,6 +584,8 @@ 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 */ + /* * 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..539f248851 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< Gather + Workers Planned: 2 + -> Partial Aggregate + -> Parallel Seq Scan on svar_test + Filter: ((a % 10) = zero) +(6 rows) + +-- result should be 100000 +select count(*) from svar_test where a%10 = zero; + count +-------- + 100000 +(1 row) + +drop table svar_test; +drop variable zero; 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..4be31e1bb0 --- /dev/null +++ b/src/test/regress/sql/schema_variables.sql @@ -0,0 +1,328 @@ +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; + +CREATE VARIABLE xx AS text DEFAULT 'hello'; + +SELECT xx, upper(xx); + +LET xx = 'Hi'; + +SELECT xx; + +DROP VARIABLE xx; + +-- ON TRANSACTION END RESET tests +CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET; + +BEGIN; + SELECT t1; + LET t1 = 100; + SELECT t1; +COMMIT; + +SELECT t1; + +BEGIN; + SELECT t1; + LET t1 = 100; + SELECT t1; +ROLLBACK; + +SELECT t1; + +DROP VARIABLE t1; + +CREATE VARIABLE v1 AS int DEFAULT 0; +CREATE VARIABLE v2 AS text DEFAULT 'none'; + +LET v1 = 100; +LET v2 = 'Hello'; +SELECT v1, v2; +LET v1 = DEFAULT; +LET v2 = DEFAULT; +SELECT v1, v2; + +DROP VARIABLE v1; +DROP VARIABLE v2; + +-- ON COMMIT DROP tests +-- should be 0 always +SELECT count(*) FROM pg_variable; + +CREATE TEMP VARIABLE g AS int ON COMMIT DROP; + +SELECT count(*) FROM pg_variable; + +BEGIN; + CREATE TEMP VARIABLE g AS int ON COMMIT DROP; +COMMIT; + +SELECT count(*) FROM pg_variable; + +BEGIN; + CREATE TEMP VARIABLE g AS int ON COMMIT DROP; +ROLLBACK; + +SELECT count(*) FROM pg_variable; + +-- test on query with workers +create table svar_test(a int); +insert into svar_test select * from generate_series(1,1000000); +analyze svar_test; +create variable zero int; +let zero = 0; + +-- parallel workers should be used +explain (costs off) select count(*) from svar_test where a%10 = zero; + +-- result should be 100000 +select count(*) from svar_test where a%10 = zero; + +drop table svar_test; +drop variable zero; +