diff --git a/doc/src/sgml/advanced.sgml b/doc/src/sgml/advanced.sgml index 2d4ab85d45..5fbc6f73ab 100644 --- a/doc/src/sgml/advanced.sgml +++ b/doc/src/sgml/advanced.sgml @@ -700,6 +700,56 @@ SELECT name, elevation + + Schema Variables + + + Schema variables + + + + schema variable + introduction + + + + Schema variables are database objects that can hold a value. + Schema variables, like relations, exist within a schema and their access is + controlled via GRANT and REVOKE commands. + The schema variable can be created by the CREATE VARIABLE command. + + + + The value of a schema variable is local to the current session. Retrieving + a variable's value returns either a NULL or a default value, unless its value + is set to something else in the current session with a LET command. The content + of a variable is not transactional. This is the same as in regular variables + in PL languages. + + + + Schema variables are retrieved by the SELECT SQL command. + Their value is set with the LET SQL command. + While schema variables share properties with tables, their value cannot be updated + with an UPDATE command. + +CREATE VARIABLE var1 AS date; +LET var1 = current_date; +SELECT var1; + + + or + + +CREATE VARIABLE public.current_user_id AS integer; +GRANT READ ON VARIABLE public.current_user_id TO PUBLIC; +LET current_user_id = (SELECT id FROM users WHERE usename = session_user); +SELECT current_user_id; + + + + + Conclusion diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 2656786d1e..0edd6ba91c 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 + @@ -13886,4 +13891,143 @@ 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_variable</structname> Columns + + + + + Name + Type + References + Description + + + + + oid + oid + + Row identifier + + + + 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 variable's data type. + + + + + vartypmod + int4 + + + vartypmod records type-specific data + supplied at variable 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. + + + + + varisnotnull + boolean + + + True if the schema variable doesn't allow null value. The default value is false. + + + + + varisimmutable + boolean + + + True if the variable is immutable (cannot be modified). The default value is false. + + + + + vareoxaction + char + + + n = no action, d = drop the variable, + r = reset the variable to its default value. + + + + + 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/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 52f60c827c..ac72f3c71d 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -5926,6 +5926,19 @@ $$ LANGUAGE plpgsql STRICT IMMUTABLE; + + + <command>Global variables and constants</command> + + + The PL/pgSQL language has no packages, + and therefore no package variables or package constants. + PostgreSQL has schema variables and + immutable schema variables. Schema variables can be created + by CREATE VARIABLE described in . + + diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index d67270ccc3..8458cad752 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_default_privileges.sgml b/doc/src/sgml/ref/alter_default_privileges.sgml index f1d54f5aa3..95e8352167 100644 --- a/doc/src/sgml/ref/alter_default_privileges.sgml +++ b/doc/src/sgml/ref/alter_default_privileges.sgml @@ -50,6 +50,10 @@ GRANT { USAGE | CREATE | ALL [ PRIVILEGES ] } ON SCHEMAS TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] +GRANT { READ | WRITE | ALL [ PRIVILEGES ] } + ON VARIABLES + TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ] + REVOKE [ GRANT OPTION FOR ] { { SELECT | INSERT | UPDATE | DELETE | TRUNCATE | REFERENCES | TRIGGER } [, ...] | ALL [ PRIVILEGES ] } @@ -81,6 +85,12 @@ REVOKE [ GRANT OPTION FOR ] ON SCHEMAS FROM { [ GROUP ] role_name | PUBLIC } [, ...] [ CASCADE | RESTRICT ] + +REVOKE [ GRANT OPTION FOR ] + { { READ | WRITE } [, ...] | ALL [ PRIVILEGES ] } + ON VARIABLES + FROM { [ GROUP ] role_name | PUBLIC } [, ...] + [ CASCADE | RESTRICT ] @@ -92,8 +102,8 @@ REVOKE [ GRANT OPTION FOR ] that will be applied to objects created in the future. (It does not affect privileges assigned to already-existing objects.) Currently, only the privileges for schemas, tables (including views and foreign - tables), sequences, functions, and types (including domains) can be - altered. For this command, functions include aggregates and procedures. + tables), sequences, functions, types (including domains) and schema variables + can be altered. For this command, functions include aggregates and procedures. The words FUNCTIONS and ROUTINES are equivalent in this command. (ROUTINES is preferred going forward as the standard term for functions and procedures taken diff --git a/doc/src/sgml/ref/alter_variable.sgml b/doc/src/sgml/ref/alter_variable.sgml new file mode 100644 index 0000000000..85a38fb9d5 --- /dev/null +++ b/doc/src/sgml/ref/alter_variable.sgml @@ -0,0 +1,179 @@ + + + + + ALTER VARIABLE + + + + schema variable + altering + + + + 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 + + + The ALTER VARIABLE command 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. + + + + + + + + + Only the owner or a superuser is allowed to alter a schema variable. + In order to move a schema variable from one schema to another, the user must + also have the CREATE privilege on the new schema (or be + a superuser). + + In order to move the schema variable ownership from one role to another, + the user must also be a direct or indirect member of the new + owning role, and that role must have the CREATE privilege + on the variable's schema (or be a superuser). These restrictions enforce that + altering the owner doesn't do anything you couldn't do by dropping and + recreating the variable. + + + + + Parameters + + + + + name + + + The name (possibly schema-qualified) of the 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 + + + Schema variables and this command in particular are 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..859c0bc2f1 --- /dev/null +++ b/doc/src/sgml/ref/create_variable.sgml @@ -0,0 +1,190 @@ + + + + + CREATE VARIABLE + + + + schema variable + defining + + + + CREATE VARIABLE + 7 + SQL - Language Statements + + + + CREATE VARIABLE + define a schema variable + + + + +CREATE [ { TEMPORARY | TEMP } ] [ IMMUTABLE ] VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ] [ COLLATE collation ] + [ NOT NULL ] [ DEFAULT default_expr ] [ { ON COMMIT DROP | ON TRANSACTION END RESET } ] + + + + Description + + + The CREATE VARIABLE command creates a schema variable. + Schema variables, like relations, exist within a schema and their access is + controlled via GRANT and REVOKE commands. + Changing a schema variable is non-transactional. + + + + The value of a schema variable is local to the current session. Retrieving + a variable's value returns either a NULL or a default value, unless its value + is set to something else in the current session with a LET command. The content + of a variable is not transactional. This is the same as in regular variables in PL languages. + + + + Schema variables are retrieved by the SELECT SQL command. + Their value is set with the LET SQL command. + While schema variables share properties with tables, their value cannot be updated + with an UPDATE command. + + + + + Parameters + + + + IMMUTABLE + + + The value of the variable cannot be changed. + + + + + + IF NOT EXISTS + + + Do not throw an error if the name already exists. A notice is issued in this case. + + + + + + name + + + The name, optionally schema-qualified, of the variable. + + + + + + data_type + + + The name, optionally schema-qualified, of the data type of the variable. + + + + + + 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. + + + + + + NOT NULL + + + The NOT NULL clause forbids to set the variable to + a null value. A variable created as NOT NULL and without an explicitly + declared default value cannot be read until it is initialized by a LET + command. This obliges the user to explicitly initialize the variable + content before reading it. + + + + + + DEFAULT default_expr + + + The DEFAULT clause can be used to assign a default value to + a schema variable. + + + + + + ON COMMIT DROP, ON TRANSACTION END RESET + + + The ON COMMIT DROP clause specifies the behaviour + of a temporary schema variable at transaction commit. With this clause the +      variable is dropped at commit time. The clause is only allowed +      for temporary variables. The ON TRANSACTION END RESET + clause enforces the variable to be reset to its default value when + the transaction is committed or rolled back. + + + + + + + + + Notes + + + Use the DROP VARIABLE command to remove a variable. + + + + + Examples + + + Create an integer variable var1: + +CREATE VARIABLE var1 AS date; +LET var1 = current_date; +SELECT var1; + + + + + + + Compatibility + + + The CREATE VARIABLE command is a PostgreSQL extension. + + + + + + See Also + + + + + + + + + diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml index bf44c523ca..698b2c07fd 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. If a variable +      is later reused, it is re-initialized to either +      NULL or its default value. + + + + ALL @@ -93,6 +104,7 @@ SELECT pg_advisory_unlock_all(); DISCARD PLANS; DISCARD TEMP; DISCARD SEQUENCES; +DISCARD VARIABLES; diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml new file mode 100644 index 0000000000..da3632e780 --- /dev/null +++ b/doc/src/sgml/ref/drop_variable.sgml @@ -0,0 +1,120 @@ + + + + + DROP VARIABLE + + + + schema variable + removing + + + + 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. + + + + + + CASCADE + + + Automatically drop objects that depend on the variable (such as + views), + and in turn all objects that depend on those objects + (see ). + + + + + + RESTRICT + + + Refuse to drop the variable if any objects depend on it. This is + the default. + + + + + + + + Examples + + + To remove the schema variable var1: + + +DROP VARIABLE var1; + + + + + Compatibility + + + The DROP VARIABLE command is a PostgreSQL extension. + + + + + + See Also + + + + + + + + + diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml index a897712de2..6586e412c1 100644 --- a/doc/src/sgml/ref/grant.sgml +++ b/doc/src/sgml/ref/grant.sgml @@ -103,6 +103,11 @@ GRANT role_name [, ...] TO variable_name [, ...] + TO role_specification [, ...] [ WITH GRANT OPTION ] + @@ -201,6 +206,24 @@ GRANT role_name [, ...] TO + + READ + + + Allows to read a schema variable. + + + + + + WRITE + + + Allows to set a schema variable's content. + + + + ALL PRIVILEGES diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml new file mode 100644 index 0000000000..d4396cad3c --- /dev/null +++ b/doc/src/sgml/ref/let.sgml @@ -0,0 +1,107 @@ + + + + + LET + + + + schema variable + changing + + + + 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 the schema variable. + + + + + + sql_expression + + + An SQL expression. The result is cast into the schema variable's type. + + + + + + DEFAULT + + + Reset the schema variable to its default value, if that is defined. + If no explicit default value has been assigned, the schema variable + is set to NULL. + + + + + + + Example: + +CREATE VARIABLE myvar AS integer; +LET myvar = 10; +LET myvar = (SELECT sum(val) FROM tab); +LET myvar = DEFAULT; + + + + + + Compatibility + + + LET extends the syntax defined in the SQL + standard. The SET command from the SQL standard + is used for different purposes in PostgreSQL. + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml index 93ea937ac8..0d86239eb4 100644 --- a/doc/src/sgml/ref/pg_restore.sgml +++ b/doc/src/sgml/ref/pg_restore.sgml @@ -106,6 +106,17 @@ PostgreSQL documentation + + + + + + Restore a named schema variable only. Multiple schema variables may be specified with + multiple switches. + + + + diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml index 3014c864ea..3e9f9804be 100644 --- a/doc/src/sgml/ref/revoke.sgml +++ b/doc/src/sgml/ref/revoke.sgml @@ -130,6 +130,12 @@ REVOKE [ ADMIN OPTION FOR ] | CURRENT_ROLE | CURRENT_USER | SESSION_USER + +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 da421ff24e..f9e42443b6 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 441445927e..a702a9760f 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -35,6 +35,7 @@ #include "catalog/pg_enum.h" #include "catalog/storage.h" #include "commands/async.h" +#include "commands/schema_variable.h" #include "commands/tablecmds.h" #include "commands/trigger.h" #include "executor/spi.h" @@ -2145,6 +2146,9 @@ CommitTransaction(void) */ smgrDoPendingSyncs(true, is_parallel_worker); + /* Let ON COMMIT DROP or ON TRANSACTION END */ + AtPreEOXact_SchemaVariable_on_commit_actions(true); + /* close large objects before lower-level cleanup */ AtEOXact_LargeObject(true); @@ -2274,6 +2278,7 @@ CommitTransaction(void) AtEOXact_SPI(true); AtEOXact_Enum(); AtEOXact_on_commit_actions(true); + AtEOXact_SchemaVariable_on_commit_actions(true); AtEOXact_Namespace(true, is_parallel_worker); AtEOXact_SMgr(); AtEOXact_Files(true); @@ -2702,6 +2707,9 @@ AbortTransaction(void) AtAbort_Portals(); smgrDoPendingSyncs(false, is_parallel_worker); AtEOXact_LargeObject(false); + + /* 'false' means it's abort */ + AtPreEOXact_SchemaVariable_on_commit_actions(false); AtAbort_Notify(); AtEOXact_RelationMap(false, is_parallel_worker); AtAbort_Twophase(); @@ -2766,6 +2774,7 @@ AbortTransaction(void) AtEOXact_SPI(false); AtEOXact_Enum(); 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 e36a9602c1..cc52bf7cfe 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -42,6 +42,7 @@ OBJS = \ pg_shdepend.o \ pg_subscription.o \ pg_type.o \ + pg_variable.o \ storage.o \ toasting.o @@ -68,7 +69,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 system_fk_info.h diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index e1573eb398..c7f5796bcc 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -58,6 +58,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 "commands/event_trigger.h" #include "commands/extension.h" @@ -111,6 +112,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); @@ -258,6 +260,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 */ @@ -497,6 +502,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); @@ -599,6 +608,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); @@ -758,6 +770,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 = LookupVariable(varvar->schemaname, varvar->relname, false); + objects = lappend_oid(objects, relOid); + } + break; default: elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) objtype); @@ -847,6 +869,33 @@ objectsInSchemaToOids(ObjectType objtype, List *nspnames) table_close(rel, AccessShareLock); } break; + case OBJECT_VARIABLE: + { + ScanKeyData key; + Relation rel; + TableScanDesc scan; + HeapTuple tuple; + + ScanKeyInit(&key, + Anum_pg_variable_varnamespace, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(namespaceId)); + + rel = table_open(VariableRelationId, AccessShareLock); + scan = table_beginscan_catalog(rel, 1, &key); + + while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) + { + Oid oid = ((Form_pg_proc) GETSTRUCT(tuple))->oid; + + objects = lappend_oid(objects, oid); + } + + table_endscan(scan); + table_close(rel, AccessShareLock); + } + break; + default: /* should not happen */ elog(ERROR, "unrecognized GrantStmt.objtype: %d", @@ -1012,6 +1061,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); @@ -1209,6 +1262,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); @@ -1442,6 +1501,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", @@ -1499,6 +1561,9 @@ RemoveRoleFromObjectACL(Oid roleid, Oid classid, Oid objid) case ForeignDataWrapperRelationId: istmt.objtype = OBJECT_FDW; break; + case VariableRelationId: + istmt.objtype = OBJECT_VARIABLE; + break; default: elog(ERROR, "unexpected object class %u", classid); break; @@ -3230,6 +3295,129 @@ ExecGrant_Type(InternalGrant *istmt) table_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 = table_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(); + } + + table_close(relation, RowExclusiveLock); +} + static AclMode string_to_privilege(const char *privname) @@ -3262,6 +3450,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))); @@ -3297,6 +3489,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); } @@ -3420,6 +3616,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; @@ -3530,6 +3729,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; @@ -3674,6 +3876,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); @@ -4542,6 +4746,66 @@ 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 * @@ -4816,6 +5080,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). */ @@ -5433,6 +5709,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) * @@ -5561,6 +5864,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 8d8e926c21..7ce7c38532 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -61,12 +61,15 @@ #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" #include "commands/extension.h" #include "commands/policy.h" #include "commands/publicationcmds.h" +#include "commands/schemacmds.h" +#include "commands/schema_variable.h" #include "commands/seclabel.h" #include "commands/sequence.h" #include "commands/trigger.h" @@ -181,7 +184,8 @@ static const Oid object_classes[] = { PublicationRelationId, /* OCLASS_PUBLICATION */ PublicationRelRelationId, /* OCLASS_PUBLICATION_REL */ SubscriptionRelationId, /* OCLASS_SUBSCRIPTION */ - TransformRelationId /* OCLASS_TRANSFORM */ + TransformRelationId, /* OCLASS_TRANSFORM */ + VariableRelationId /* OCLASS_VARIABLE */ }; @@ -1572,6 +1576,10 @@ doDeletion(const ObjectAddress *object, int flags) DropObjectById(object); break; + case OCLASS_VARIABLE: + RemoveSchemaVariable(object->objectId); + break; + /* * These global object types are not supported here. */ @@ -2010,6 +2018,11 @@ find_expr_references_walker(Node *node, { Param *param = (Param *) node; + if (param->paramkind == PARAM_VARIABLE) + /* A variable parameter depends on the schema variable */ + add_object_address(OCLASS_VARIABLE, param->paramvarid, 0, + context->addrs); + /* A parameter must depend on the parameter's datatype */ add_object_address(OCLASS_TYPE, param->paramtype, 0, context->addrs); @@ -3002,6 +3015,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 005e029c38..1f2b207687 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" @@ -762,6 +763,70 @@ 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 @@ -2796,6 +2861,209 @@ TSConfigIsVisible(Oid cfgid) return visible; } +/* + * When we know a variable name, then we can find variable simply + */ +Oid +LookupVariable(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, Anum_pg_variable_oid, + PointerGetDatum(varname), + ObjectIdGetDatum(namespaceId)); + } + else + { + /* search for it in search path */ + recomputeNamespacePath(); + + foreach(l, activeSearchPath) + { + namespaceId = lfirst_oid(l); + + varoid = GetSysCacheOid2(VARIABLENAMENSP, Anum_pg_variable_oid, + 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; +} + +/* + * The source list holds names with indirection expressions used + * as left part of LET statement. Following routine makes new + * list with only initial strings (names) - without indirection + * expressions. + */ +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; +} + +/* + * IdentifyVariable + * + * 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 +IdentifyVariable(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 LookupVariable(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 = LookupVariable(a, b, true); + varoid_with_attr = LookupVariable(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 = LookupVariable(b, c, true); + varoid_with_attr = LookupVariable(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 LookupVariable(b, c, true); + + default: + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("improper qualified name (too many dotted names): %s", + NameListToString(names)))); + break; + } +} /* * DeconstructQualifiedName @@ -4611,3 +4879,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 6d88b690d8..e4df1834dc 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -61,6 +61,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" @@ -615,6 +616,20 @@ static const ObjectPropertyType ObjectProperty[] = OBJECT_USER_MAPPING, false }, + { + "schema variable", + VariableRelationId, + VariableObjectIndexId, + VARIABLEOID, + VARIABLENAMENSP, + Anum_pg_variable_oid, + Anum_pg_variable_varname, + Anum_pg_variable_varnamespace, + Anum_pg_variable_varowner, + Anum_pg_variable_varacl, + OBJECT_VARIABLE, + true + } }; /* @@ -839,6 +854,10 @@ static const struct object_type_map /* OCLASS_STATISTIC_EXT */ { "statistics object", OBJECT_STATISTIC_EXT + }, + /* OCLASS_VARIABLE */ + { + "schema variable", OBJECT_VARIABLE } }; @@ -864,6 +883,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, @@ -1127,6 +1147,9 @@ 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 */ @@ -1983,16 +2006,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))); } /* @@ -2077,6 +2104,24 @@ textarray_to_strvaluelist(ArrayType *arr) return list; } +/* + * Find the ObjectAddress for a schema variable + */ +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 = LookupVariable(nspname, varname, missing_ok); + + return address; +} + /* * SQL-callable version of get_object_address */ @@ -2266,6 +2311,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: @@ -2572,6 +2618,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); @@ -3448,6 +3499,32 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) 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; @@ -3758,6 +3835,16 @@ getObjectDescription(const ObjectAddress *object, bool missing_ok) _("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) @@ -4478,6 +4565,10 @@ getObjectTypeDescription(const ObjectAddress *object, bool missing_ok) 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. @@ -5570,6 +5661,10 @@ getObjectIdentityParts(const ObjectAddress *object, appendStringInfoString(&buffer, " on schemas"); break; + case DEFACLOBJ_VARIABLE: + appendStringInfoString(&buffer, + " on schema variables"); + break; } if (objname) @@ -5762,6 +5857,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..451b57a0b7 --- /dev/null +++ b/src/backend/catalog/pg_variable.c @@ -0,0 +1,388 @@ +/*------------------------------------------------------------------------- + * + * pg_variable.c + * schema variables + * + * Portions Copyright (c) 1996-2020, 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 "access/heapam.h" +#include "access/htup_details.h" + +#include "catalog/catalog.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/schema_variable.h" + +#include "storage/lmgr.h" + +#include "utils/builtins.h" +#include "utils/lsyscache.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. + */ +void +initVariable(Variable *var, Oid varid, bool missing_ok, bool fast_only) +{ + HeapTuple tup; + Form_pg_variable varform; + Datum defexpr_datum; + bool defexpr_isnull; + + tup = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(varid)); + + if (!HeapTupleIsValid(tup)) + { + if (missing_ok) + { + var->oid = InvalidOid; + return; + } + + elog(ERROR, "cache lookup failed for variable %u", varid); + } + + varform = (Form_pg_variable) GETSTRUCT(tup); + + 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); + var->is_not_null = varform->varisnotnull; + var->is_immutable = varform->varisimmutable; + + /* Get defexpr */ + defexpr_datum = SysCacheGetAttr(VARIABLEOID, + tup, + Anum_pg_variable_vardefexpr, + &defexpr_isnull); + + var->has_defexpr = !defexpr_isnull; + + /* + * Sometimes we don't need deserialized defexpr, but we need info about + * existence of defexpr. + */ + + if (!fast_only) + { + Datum aclDatum; + bool isnull; + + /* name */ + var->name = pstrdup(NameStr(varform->varname)); + + if (!defexpr_isnull) + var->defexpr = stringToNode(TextDatumGetCString(defexpr_datum)); + 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; + } + else + { + var->name = NULL; + var->defexpr = NULL; + var->acl = NULL; + } + + ReleaseSysCache(tup); + + return; +} + +/* + * Create entry in pg_variable table + */ +ObjectAddress +VariableCreate(const char *varName, + Oid varNamespace, + Oid varType, + int32 varTypmod, + Oid varOwner, + Oid varCollation, + Node *varDefexpr, + VariableEOXAction eoxaction, + bool is_not_null, + bool if_not_exists, + bool is_immutable) +{ + 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 varid; + int i; + + for (i = 0; i < Natts_pg_variable; i++) + { + nulls[i] = false; + values[i] = (Datum) 0; + } + + namestrcpy(&varname, varName); + + rel = table_open(VariableRelationId, RowExclusiveLock); + + varid = GetNewOidWithIndex(rel, VariableObjectIndexId, Anum_pg_variable_oid); + values[Anum_pg_variable_oid - 1] = ObjectIdGetDatum(varid); + 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)); + values[Anum_pg_variable_varisnotnull - 1] = BoolGetDatum(is_not_null); + values[Anum_pg_variable_varisimmutable - 1] = BoolGetDatum(is_immutable); + /* proacl will be determined later */ + + if (varDefexpr) + values[Anum_pg_variable_vardefexpr - 1] = CStringGetTextDatum(nodeToString(varDefexpr)); + else + nulls[Anum_pg_variable_vardefexpr - 1] = true; + + 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))); + + ReleaseSysCache(oldtup); + table_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); + + myself.classId = VariableRelationId; + myself.objectId = varid; + 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 default expr */ + if (varDefexpr) + recordDependencyOnExpr(&myself, (Node *) varDefexpr, + NIL, DEPENDENCY_NORMAL); + + /* dependency on any roles mentioned in ACL */ + if (varacl != NULL) + { + int nnewmembers; + Oid *newmembers; + + nnewmembers = aclmembers(varacl, &newmembers); + updateAclDependencies(VariableRelationId, varid, 0, + varOwner, + 0, NULL, + nnewmembers, newmembers); + } + + /* dependency on extension */ + recordDependencyOnCurrentExtension(&myself, false); + + /* + * register on commit action if it is necessary. This moment, the variable + * has not any value, so we don't need to solve content transactionality. + */ + register_variable_on_commit_action(myself.objectId, eoxaction); + + heap_freetuple(tup); + + /* Post creation hook for new function */ + InvokeObjectPostCreateHook(VariableRelationId, varid, 0); + + table_close(rel, RowExclusiveLock); + + return myself; +} diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index e8504f0ae4..0b785be2ce 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -48,6 +48,7 @@ OBJS = \ proclang.o \ publicationcmds.o \ schemacmds.o \ + schemavariable.o \ seclabel.o \ sequence.o \ statscmds.o \ diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 29249498a9..225b971d90 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -40,6 +40,7 @@ #include "catalog/pg_ts_dict.h" #include "catalog/pg_ts_parser.h" #include "catalog/pg_ts_template.h" +#include "catalog/pg_variable.h" #include "commands/alter.h" #include "commands/collationcmds.h" #include "commands/conversioncmds.h" @@ -141,6 +142,10 @@ report_namespace_conflict(Oid classId, const char *name, Oid nspOid) Assert(OidIsValid(nspOid)); msgfmt = gettext_noop("text search configuration \"%s\" already exists in schema \"%s\""); break; + case VariableRelationId: + Assert(OidIsValid(nspOid)); + msgfmt = gettext_noop("schema variable \"%s\" already exists in schema \"%s\""); + break; default: elog(ERROR, "unsupported object class %u", classId); break; @@ -393,6 +398,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_TSTEMPLATE: case OBJECT_PUBLICATION: case OBJECT_SUBSCRIPTION: + case OBJECT_VARIABLE: { ObjectAddress address; Relation catalog; @@ -536,6 +542,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt, case OBJECT_TSDICTIONARY: case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: + case OBJECT_VARIABLE: { Relation catalog; Relation relation; @@ -626,6 +633,7 @@ AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid, case OCLASS_TSDICT: case OCLASS_TSTEMPLATE: case OCLASS_TSCONFIG: + case OCLASS_VARIABLE: { Relation catalog; @@ -883,6 +891,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 57d3d7dd9b..a712bf44b6 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/schema_variable.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/dropcmds.c b/src/backend/commands/dropcmds.c index 97e5e9a765..b925e0dbdc 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -481,6 +481,10 @@ does_not_exist_skipping(ObjectType objtype, Node *object) msg = gettext_noop("publication \"%s\" does not exist, skipping"); name = strVal((Value *) object); break; + case OBJECT_VARIABLE: + msg = gettext_noop("schema variable \"%s\" does not exist, skipping"); + name = NameListToString(castNode(List, object)); + break; default: elog(ERROR, "unrecognized object type: %d", (int) objtype); break; diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 5bde507c75..aa330b20b8 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -990,6 +990,7 @@ EventTriggerSupportsObjectType(ObjectType obtype) case OBJECT_TSTEMPLATE: case OBJECT_TYPE: case OBJECT_USER_MAPPING: + case OBJECT_VARIABLE: case OBJECT_VIEW: return true; @@ -1053,6 +1054,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass) case OCLASS_PUBLICATION_REL: case OCLASS_SUBSCRIPTION: case OCLASS_TRANSFORM: + case OCLASS_VARIABLE: return true; /* @@ -2097,6 +2099,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: @@ -2179,6 +2183,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/explain.c b/src/backend/commands/explain.c index b62a76e7e5..1cbe810435 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -1191,6 +1191,9 @@ ExplainNode(PlanState *planstate, List *ancestors, break; } break; + case T_SetVariable: + pname = sname = "Set Variable"; + break; case T_Append: pname = sname = "Append"; break; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index f767751c71..e7277c9e08 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -144,6 +144,7 @@ PrepareQuery(ParseState *pstate, PrepareStmt *stmt, case CMD_INSERT: case CMD_UPDATE: case CMD_DELETE: + case CMD_LET: /* OK */ break; default: diff --git a/src/backend/commands/schemavariable.c b/src/backend/commands/schemavariable.c new file mode 100644 index 0000000000..9f50fe62c9 --- /dev/null +++ b/src/backend/commands/schemavariable.c @@ -0,0 +1,775 @@ +/*------------------------------------------------------------------------- + * + * schemavariable.c + * schema variable creation/manipulation commands + * + * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/schemavariable.c + * + *------------------------------------------------------------------------- + */ +#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_variable.h" +#include "commands/schema_variable.h" +#include "executor/executor.h" +#include "executor/svariableReceiver.h" +#include "optimizer/optimizer.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/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 */ + + /* + * 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 */ + 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 */ + bool reset_auto; /* next read fix invalidate value by self */ + + bool is_not_null; /* don't allow null values */ + bool has_defexpr; /* true when there are default value */ + bool is_immutable; /* true when variable is immutable */ + + SubTransactionId creating_subid; +} SchemaVariableData; + +typedef SchemaVariableData * SchemaVariable; + +static HTAB *schemavarhashtab = NULL; /* hash table for session variables */ +static MemoryContext SchemaVariableMemoryContext = NULL; + +static bool first_time = true; +static bool clean_cache_req = false; + +static void create_schema_variable_hashtable(void); +static void clean_cache_callback(XactEvent event, void *arg); +static void free_schema_variable(SchemaVariable svar, bool force); +static void set_schema_variable(SchemaVariable svar, Datum value, bool isnull, Oid typid); +static void init_schema_variable(SchemaVariable svar, Variable *var); +static SchemaVariable prepare_variable_for_reading(Oid varid, bool reset); +static void remove_variable_on_commit_actions(Oid varid, VariableEOXAction eoxaction); +static void clean_cache_variable(Oid varid); + +/* + * Save info about necessity to clean hash table, because some + * schema variable was dropped. Don't do here more, recheck + * needs to be in transaction state. + */ +static void +InvalidateSchemaVariableCacheCallback(Datum arg, int cacheid, uint32 hashvalue) +{ + if (cacheid != VARIABLEOID) + return; + + clean_cache_req = true; +} + +/* + * Recheck values of all schema variables stored in local memory. + * Remove the value from local memory, when related schema variable + * was dropped (and removed from system catalogue). + */ +static void +clean_cache_callback(XactEvent event, void *arg) +{ + /* + * should continue only in transaction time, when syscache is available. + */ + if (clean_cache_req && IsTransactionState()) + { + HASH_SEQ_STATUS status; + SchemaVariable svar; + + if (!schemavarhashtab) + return; + + hash_seq_init(&status, schemavarhashtab); + + while ((svar = (SchemaVariable) hash_seq_search(&status)) != NULL) + { + HeapTuple tp; + + tp = SearchSysCache1(VARIABLEOID, ObjectIdGetDatum(svar->varid)); + if (!HeapTupleIsValid(tp)) + { + elog(DEBUG1, "variable %d is removed from cache", svar->varid); + + free_schema_variable(svar, true); + + remove_variable_on_commit_actions(svar->varid, VARIABLE_EOX_DROP); + + (void) hash_search(schemavarhashtab, (void *) &svar->varid, + HASH_REMOVE, NULL); + } + else + ReleaseSysCache(tp); + } + + clean_cache_req = false; + } +} + +/* + * Create the hash table for storing schema variables + */ +static void +create_schema_variable_hashtable(void) +{ + HASHCTL ctl; + + /* set callbacks */ + if (first_time) + { + CacheRegisterSyscacheCallback(VARIABLEOID, + InvalidateSchemaVariableCacheCallback, + (Datum) 0); + + RegisterXactCallback(clean_cache_callback, 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) +{ + /* Remove temporal schema variables */ + AtPreEOXact_SchemaVariable_on_commit_actions(true); + + /* Destroy hash table and reset related memory context */ + if (schemavarhashtab) + { + hash_destroy(schemavarhashtab); + schemavarhashtab = NULL; + } + + /* Release memory allocated by schema variables */ + if (SchemaVariableMemoryContext != NULL) + MemoryContextReset(SchemaVariableMemoryContext); +} + +/* + * Release data stored inside svar. When a variable is transactional, + * and was not modified already in this transaction, then it archives + * current value for possible future usage on rollback. + * + * When force is true, then release current and possibly archived value. + */ +static void +free_schema_variable(SchemaVariable svar, bool force) +{ + if (svar->freeval) + pfree(DatumGetPointer(svar->value)); + + /* Clean current value, and mark it as invalid */ + svar->value = (Datum) 0; + svar->isnull = true; + svar->freeval = false; + + svar->is_valid = false; +} + +/* + * Assign some content to the schema variable. It does copy to + * SchemaVariableMemoryContext if it is necessary. + */ +static void +set_schema_variable(SchemaVariable svar, Datum value, bool isnull, Oid typid) +{ + MemoryContext oldcxt; + Datum newval = value; + + /* Don't allow assign null to NOT NULL variable */ + if (isnull && svar->is_not_null) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value is not allowed for NOT NULL schema variable \"%s\"", + schema_variable_get_name(svar->varid)))); + + if (!isnull && svar->typid != typid) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type \"%s\" of assigned value is different than type \"%s\" of schema variable \"%s\"", + format_type_be(typid), + format_type_be(svar->typid), + schema_variable_get_name(svar->varid)))); + + /* copy value to session persistent context */ + oldcxt = MemoryContextSwitchTo(SchemaVariableMemoryContext); + if (!isnull) + newval = datumCopy(value, svar->typbyval, svar->typlen); + MemoryContextSwitchTo(oldcxt); + + free_schema_variable(svar, false); + + svar->value = newval; + svar->isnull = isnull; + svar->freeval = newval != value; + svar->is_valid = true; + svar->reset_auto = false; +} + +/* + * Initialize svar from var + * svar - SchemaVariable - holds data + * var - Variable - holds metadata + */ +static void +init_schema_variable(SchemaVariable svar, Variable *var) +{ + Assert(OidIsValid(var->oid)); + + svar->varid = var->oid; + svar->typid = var->typid; + + get_typlenbyval(var->typid, + &svar->typlen, + &svar->typbyval); + + svar->isnull = true; + svar->freeval = false; + svar->value = (Datum) 0; + + svar->is_rowtype = type_is_rowtype(var->typid); + svar->is_not_null = var->is_not_null; + svar->is_immutable = var->is_immutable; + svar->has_defexpr = var->has_defexpr; + + /* the variable initialization was not complete here */ + svar->is_valid = false; + svar->reset_auto = false; + + svar->creating_subid = InvalidSubTransactionId; +} + +/* + * Try to search value in hash table. If doesn't + * exists insert it (and calculate defexpr if exists. + * When reset is true, we would to enforce calculate + * defexpr. When some is wrong, then this function try + * don't break previous value. + */ +static SchemaVariable +prepare_variable_for_reading(Oid varid, bool reset) +{ + SchemaVariable svar; + Variable var; + bool found; + + var.oid = InvalidOid; + + if (schemavarhashtab == NULL) + create_schema_variable_hashtable(); + + svar = (SchemaVariable) hash_search(schemavarhashtab, &varid, + HASH_ENTER, &found); + + /* Return content if it is available, and reset is not required */ + if (found && !reset && !svar->reset_auto) + { + /* raise exception when content is not valid */ + if (!svar->is_valid) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("variable \"%s\" has not valid content", + schema_variable_get_name(varid)), + errhint("Overwrite the content of variable or assign DEFAULT again."))); + + return svar; + } + + /* We need to load defexpr. */ + initVariable(&var, varid, false, false); + + if (!found) + init_schema_variable(svar, &var); + + /* Raise a error when we cannot to initialize variable correctly */ + if (var.is_not_null && !var.defexpr) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null value is not allowed for NOT NULL schema variable \"%s\"", + schema_variable_get_name(varid)), + errdetail("The schema variable was not initialized yet."))); + + if (!found) + register_variable_on_commit_action(varid, var.eoxaction); + + if (svar->has_defexpr) + { + Datum value = (Datum) 0; + bool isnull; + EState *estate = NULL; + Expr *defexpr; + ExprState *defexprs; + MemoryContext oldcxt; + + /* Prepare default expr */ + estate = CreateExecutorState(); + + oldcxt = MemoryContextSwitchTo(estate->es_query_cxt); + + defexpr = expression_planner((Expr *) var.defexpr); + defexprs = ExecInitExpr(defexpr, NULL); + value = ExecEvalExprSwitchContext(defexprs, + GetPerTupleExprContext(estate), + &isnull); + + + /* Store result before releasing Executor memory */ + set_schema_variable(svar, value, isnull, svar->typid); + + MemoryContextSwitchTo(oldcxt); + + FreeExecutorState(estate); + } + else + set_schema_variable(svar, (Datum) 0, true, svar->typid); + + return svar; +} + +/* + * Write value to variable. We expect secured access in this moment. + * We try to don't break previous value, if some is wrong. + */ +void +SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid) +{ + SchemaVariable svar; + bool found; + + if (schemavarhashtab == NULL) + create_schema_variable_hashtable(); + + svar = (SchemaVariable) hash_search(schemavarhashtab, &varid, + HASH_ENTER, &found); + + /* Initialize svar when was not initialized or when stored value is null */ + if (!found) + { + Variable var; + + /* don't need defexpr and acl here */ + initVariable(&var, varid, false, true); + init_schema_variable(svar, &var); + register_variable_on_commit_action(varid, var.eoxaction); + } + + /* don't allow a update on immutable variable */ + if (svar->is_immutable) + ereport(ERROR, + (errcode(ERRCODE_ERROR_IN_ASSIGNMENT), + errmsg("schema variable \"%s\" is declared IMMUTABLE", + schema_variable_get_name(svar->varid)))); + + set_schema_variable(svar, value, isNull, typid); +} + +/* + * Returns copy of value of schema variable spcified by varid + */ +Datum +CopySchemaVariable(Oid varid, bool *isNull, Oid *typid) +{ + SchemaVariable svar; + + svar = prepare_variable_for_reading(varid, false); + Assert(svar != NULL && svar->is_valid); + + *isNull = svar->isnull; + *typid = svar->typid; + + if (!svar->isnull) + return datumCopy(svar->value, svar->typbyval, svar->typlen); + + return (Datum) 0; +} + +/* + * Returns value of schema variable specified by varid. Check correct + * result type. Optionaly the result can be copy. + */ +Datum +GetSchemaVariable(Oid varid, bool *isNull, Oid expected_typid, bool copy) +{ + SchemaVariable svar; + Datum value; + bool isnull; + + svar = prepare_variable_for_reading(varid, false); + 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; +} + +/* + * Clean variable defined by varid + */ +static void +clean_cache_variable(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 */ + free_schema_variable(svar, true); + + if (hash_search(schemavarhashtab, + (void *) &svar->varid, + HASH_REMOVE, + NULL) == NULL) + elog(DEBUG1, "hash table corrupted"); + } +} + +/* + * Drop variable by OID + */ +void +RemoveSchemaVariable(Oid varid) +{ + Relation rel; + HeapTuple tup; + + rel = table_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); + + table_close(rel, RowExclusiveLock); + + clean_cache_variable(varid); + + /* remove variable from on_commits list */ + remove_variable_on_commit_actions(varid, VARIABLE_EOX_DROP); +} + +/* + * Creates new variable - entry in pg_catalog.pg_variable table + * + * Used by CREATE VARIABLE command + */ +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->is_not_null, + stmt->if_not_exists, + stmt->is_immutable); + + /* + * We must bump the command counter to make the newly-created variable + * tuple visible for any other operations. + */ + CommandCounterIncrement(); + + return variable; +} + +/* + * Register a newly-created variable 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, + VariableEOXAction eoxaction) +{ + ListCell *l; + + foreach(l, on_commits) + { + OnCommitItem *oc = (OnCommitItem *) lfirst(l); + + if (oc->varid == varid) + { + if (eoxaction == VARIABLE_EOX_DROP || + (oc->eoxaction == VARIABLE_EOX_RESET && + eoxaction == VARIABLE_EOX_RESET)) + oc->deleting_subid = GetCurrentSubTransactionId(); + } + } +} + +/* + * Perform ON TRANSACTION END RESET, ON COMMIT DROP + * and COMMIT/ROLLBACK of transaction schema variables. + */ +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_variable(oc->varid); + remove_variable_on_commit_actions(oc->varid, + VARIABLE_EOX_RESET); + 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. + * + */ +void +AtEOXact_SchemaVariable_on_commit_actions(bool isCommit) +{ + ListCell *cur_item; + + foreach(cur_item, on_commits) + { + OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item); + + /* + * During commit, remove entries that were deleted during this + * transaction; during abort, remove those created during this + * transaction. + * + * The transact variable event is removed every time. + */ + + if ((isCommit ? oc->deleting_subid != InvalidSubTransactionId : + oc->creating_subid != InvalidSubTransactionId)) + { + on_commits = foreach_delete_current(on_commits, cur_item); + pfree(oc); + } + else + { + oc->creating_subid = InvalidSubTransactionId; + oc->deleting_subid = InvalidSubTransactionId; + } + } +} diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index 6906714298..0f2df23116 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -90,6 +90,7 @@ SecLabelSupportsObjectType(ObjectType objtype) case OBJECT_TSPARSER: case OBJECT_TSTEMPLATE: case OBJECT_USER_MAPPING: + case OBJECT_VARIABLE: return false; /* diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 096a6f2891..0d4f2d5e2c 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -12067,6 +12067,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 f08b282a5e..aa4037f9eb 100644 --- a/src/backend/executor/Makefile +++ b/src/backend/executor/Makefile @@ -65,6 +65,7 @@ OBJS = \ nodeSamplescan.o \ nodeSeqscan.o \ nodeSetOp.o \ + nodeSetVariable.o \ nodeSort.o \ nodeSubplan.o \ nodeSubqueryscan.o \ @@ -76,6 +77,7 @@ OBJS = \ nodeWindowAgg.o \ nodeWorktablescan.o \ spi.o \ + svariableReceiver.o \ tqueue.o \ tstoreReceiver.o diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index 77c9d785d9..58232e5168 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/schema_variable.h" #include "executor/execExpr.h" #include "executor/nodeSubplan.h" #include "funcapi.h" @@ -940,6 +941,60 @@ 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 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 094e22d392..5eae836a98 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -59,6 +59,7 @@ #include "access/heaptoast.h" #include "catalog/pg_type.h" #include "commands/sequence.h" +#include "commands/schema_variable.h" #include "executor/execExpr.h" #include "executor/nodeSubplan.h" #include "funcapi.h" @@ -444,6 +445,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, @@ -1073,6 +1075,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 b2e2df8773..46cda5fd2a 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -45,8 +45,10 @@ #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/schema_variable.h" #include "executor/execdebug.h" #include "executor/nodeSubplan.h" #include "foreign/fdwapi.h" @@ -197,6 +199,52 @@ 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) + { + /* + * link shared memory with working copy of schema variable's values + * used in this query. This access is used by parallel query executor's + * workers. + */ + 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. */ @@ -208,6 +256,7 @@ standard_ExecutorStart(QueryDesc *queryDesc, int eflags) switch (queryDesc->operation) { case CMD_SELECT: + case CMD_LET: /* * SELECT FOR [KEY] UPDATE/SHARE and modifying CTEs need to mark diff --git a/src/backend/executor/execParallel.c b/src/backend/executor/execParallel.c index 4fca8782b2..1ec11a9ea3 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/WAL usage info, and - * the actual plan to be passed down to the worker. + * any ParamListInfo associated with the query, buffer/WAL usage info, + * used schema variables buffer, and the actual plan to be passed down + * to the worker. * * IDENTIFICATION * src/backend/executor/execParallel.c @@ -66,6 +67,7 @@ #define PARALLEL_KEY_QUERY_TEXT UINT64CONST(0xE000000000000008) #define PARALLEL_KEY_JIT_INSTRUMENTATION UINT64CONST(0xE000000000000009) #define PARALLEL_KEY_WAL_USAGE UINT64CONST(0xE00000000000000A) +#define PARALLEL_KEY_SCHEMA_VARIABLES UINT64CONST(0xE00000000000000B) #define PARALLEL_TUPLE_QUEUE_SIZE 65536 @@ -140,6 +142,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. */ @@ -597,6 +605,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, char *pstmt_data; char *pstmt_space; char *paramlistinfo_space; + char *schema_variables_space; BufferUsage *bufusage_space; WalUsage *walusage_space; SharedExecutorInstrumentation *instrumentation = NULL; @@ -606,6 +615,7 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate, int instrumentation_len = 0; int jit_instrumentation_len = 0; int instrument_offset = 0; + int schema_variables_len = 0; Size dsa_minsize = dsa_minimum_size(); char *query_string; int query_len; @@ -661,6 +671,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. * @@ -758,6 +773,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)); @@ -1406,6 +1426,7 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc) SharedJitInstrumentation *jit_instrumentation; int instrument_options = 0; void *area_space; + char *schemavariable_space; dsa_area *area; ParallelWorkerContext pwcxt; @@ -1431,6 +1452,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); @@ -1500,3 +1529,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/execProcnode.c b/src/backend/executor/execProcnode.c index 9f8c7582e0..d42cbe4099 100644 --- a/src/backend/executor/execProcnode.c +++ b/src/backend/executor/execProcnode.c @@ -106,6 +106,7 @@ #include "executor/nodeSamplescan.h" #include "executor/nodeSeqscan.h" #include "executor/nodeSetOp.h" +#include "executor/nodeSetVariable.h" #include "executor/nodeSort.h" #include "executor/nodeSubplan.h" #include "executor/nodeSubqueryscan.h" @@ -177,6 +178,11 @@ ExecInitNode(Plan *node, EState *estate, int eflags) estate, eflags); break; + case T_SetVariable: + result = (PlanState *) ExecInitSetVariable((SetVariable *) node, + estate, eflags); + break; + case T_Append: result = (PlanState *) ExecInitAppend((Append *) node, estate, eflags); @@ -755,6 +761,10 @@ ExecEndNode(PlanState *node) ExecEndLimit((LimitState *) node); break; + case T_SetVariableState: + ExecEndSetVariable((SetVariableState *) node); + break; + default: elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); break; diff --git a/src/backend/executor/nodeSetVariable.c b/src/backend/executor/nodeSetVariable.c new file mode 100644 index 0000000000..7e992c0550 --- /dev/null +++ b/src/backend/executor/nodeSetVariable.c @@ -0,0 +1,142 @@ +/*------------------------------------------------------------------------- + * + * nodeSetVariable.c + * routines to handle SetVariable node + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/executor/nodeModifyTable.c + * + *------------------------------------------------------------------------- + */ +/* INTERFACE ROUTINES + * ExecInitnodeSetVariable - initialize the SetVariable node + * ExecnodeSetVariable - execute SetVariable node + * ExecEndnodeSetVariable - shut down the SetVariable node + * ExecReScannodeSetVariable - rescan the SetVariable node + * + * NOTES + * The nodeSetVariable node receives input from its outerPlan and + * store this value in target schema variable. + */ + +#include "postgres.h" + +#include "commands/schema_variable.h" +#include "executor/executor.h" +#include "executor/nodeSetVariable.h" +#include "executor/svariableReceiver.h" +#include "miscadmin.h" +#include "nodes/nodeFuncs.h" + +/* ---------------------------------------------------------------- + * ExecnodeSetVariable + * + * Execute an plan - and set an result to schema variable + * + * It returns NULL every time, but we can implement RETURNING + * clase in future. + * ---------------------------------------------------------------- + */ +static TupleTableSlot * /* return NULL allways */ +ExecSetVariable(PlanState *pstate) +{ + SetVariableState *mstate = castNode(SetVariableState, pstate); + TupleTableSlot *slot; + PlanState *outerPlan = outerPlanState(mstate); + EState *estate = mstate->ps.state; + + CHECK_FOR_INTERRUPTS(); + + /* + * outerPlan should to produce just one tuple. If there are + * more tuples, then receiver raises an exception. + */ + for (;;) + { + slot = ExecProcNode(outerPlan); + + if (TupIsNull(slot)) + { + return NULL; + } + + (void) mstate->dest->receiveSlot(slot, mstate->dest); + estate->es_processed++; + } + + return NULL; +} + +/* ---------------------------------------------------------------- + * ExecInitnodeSetVariable + * + * initialize the node - initialize outer plan and schema + * variable receiver + * ---------------------------------------------------------------- + */ +SetVariableState * +ExecInitSetVariable(SetVariable *node, EState *estate, int eflags) +{ + SetVariableState *mstate; + Plan *outerPlan; + PlanState *outerNode; + TupleDesc tupdesc; + AclResult aclresult; + + /* check for unsupported flags */ + Assert(!(eflags & EXEC_FLAG_MARK)); + + /* + * Is possible to write to schema variable? + */ + aclresult = pg_variable_aclcheck(node->varid, GetUserId(), ACL_WRITE); + if (aclresult != ACLCHECK_OK) + aclcheck_error(aclresult, OBJECT_VARIABLE, + schema_variable_get_name(node->varid)); + + mstate = makeNode(SetVariableState); + mstate->ps.plan = (Plan *) node; + mstate->ps.state = estate; + mstate->ps.ExecProcNode = ExecSetVariable; + + outerPlan = outerPlan(node); + outerNode = ExecInitNode(outerPlan, estate, eflags); + outerPlanState(mstate) = outerNode; + + tupdesc = ExecGetResultType(outerNode); + mstate->dest = CreateVariableDestReceiver(); + SetVariableDestReceiverParams(mstate->dest, node->varid); + + mstate->dest->rStartup(mstate->dest, 0, tupdesc); + + return mstate; +} + +/* ---------------------------------------------------------------- + * ExecEndnodeSetVariable + * + * Shuts down the plan. + * + * Returns nothing of interest. + * ---------------------------------------------------------------- + */ +void +ExecEndSetVariable(SetVariableState *node) +{ + node->dest->rShutdown(node->dest); + ExecEndNode(outerPlanState(node)); +} + +void +ExecReScanSetVariable(SetVariableState *node) +{ + /* + * Currently, we don't need to support rescan on SetVariable nodes. The + * semantics of that would be a bit debatable anyway. + */ + elog(ERROR, "ExecReScanSetVariable is not implemented"); +} diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 00aa78ea53..80cc393c16 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -1909,6 +1909,8 @@ SPI_result_code_string(int code) return "SPI_OK_UPDATE"; case SPI_OK_CURSOR: return "SPI_OK_CURSOR"; + case SPI_OK_LET: + return "SPI_OK_LET"; case SPI_OK_INSERT_RETURNING: return "SPI_OK_INSERT_RETURNING"; case SPI_OK_DELETE_RETURNING: @@ -2709,6 +2711,10 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount) else res = SPI_OK_UPDATE; break; + case CMD_LET: + res = SPI_OK_LET; + break; + default: return SPI_ERROR_OPUNKNOWN; } diff --git a/src/backend/executor/svariableReceiver.c b/src/backend/executor/svariableReceiver.c new file mode 100644 index 0000000000..298330c1cf --- /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-2020, 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/detoast.h" +#include "executor/svariableReceiver.h" +#include "commands/schema_variable.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(detoast_external_attr((struct varlena *) + DatumGetPointer(value))); + freeval = true; + } + + SetSchemaVariable(myState->varid, value, isnull, myState->typid); + + 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/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 0f9cc790c7..3c1e30a15b 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -1073,6 +1073,12 @@ llvm_compile_expr(ExprState *state) LLVMBuildBr(b, opblocks[opno + 1]); break; + case EEOP_PARAM_VARIABLE: + build_EvalXFunc(b, mod, "ExecEvalParamVariable", + v_state, op, v_econtext); + LLVMBuildBr(b, opblocks[opno + 1]); + break; + case EEOP_PARAM_CALLBACK: { LLVMTypeRef v_functype; diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 632cc31a04..b5c2067aab 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -99,6 +99,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_SCALAR_FIELD(stmt_len); @@ -224,6 +225,27 @@ _copyModifyTable(const ModifyTable *from) return newnode; } +/* + * _copySetVariable + */ +static SetVariable * +_copySetVariable(const SetVariable *from) +{ + SetVariable *newnode = makeNode(SetVariable); + + /* + * copy node superclass fields + */ + CopyPlanFields((const Plan *) from, (Plan *) newnode); + + /* + * copy remainder of node + */ + COPY_SCALAR_FIELD(varid); + + return newnode; +} + /* * _copyAppend */ @@ -1513,6 +1535,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; @@ -3163,6 +3186,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); @@ -3173,6 +3197,7 @@ _copyQuery(const Query *from) COPY_SCALAR_FIELD(hasForUpdate); COPY_SCALAR_FIELD(hasRowSecurity); COPY_SCALAR_FIELD(isReturn); + COPY_SCALAR_FIELD(hasSchemaVariables); COPY_NODE_FIELD(cteList); COPY_NODE_FIELD(rtable); COPY_NODE_FIELD(jointree); @@ -3286,6 +3311,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) { @@ -4865,6 +4902,23 @@ _copyDropSubscriptionStmt(const DropSubscriptionStmt *from) return newnode; } +static CreateSchemaVarStmt * +_copyCreateSchemaVarStmt(const CreateSchemaVarStmt *from) +{ + CreateSchemaVarStmt *newnode = makeNode(CreateSchemaVarStmt); + + COPY_NODE_FIELD(variable); + COPY_NODE_FIELD(typeName); + COPY_NODE_FIELD(collClause); + COPY_NODE_FIELD(defexpr); + COPY_SCALAR_FIELD(eoxaction); + COPY_SCALAR_FIELD(if_not_exists); + COPY_SCALAR_FIELD(is_not_null); + COPY_SCALAR_FIELD(is_immutable); + + return newnode; +} + /* **************************************************************** * extensible.h copy functions * **************************************************************** @@ -4975,6 +5029,9 @@ copyObjectImpl(const void *from) case T_ModifyTable: retval = _copyModifyTable(from); break; + case T_SetVariable: + retval = _copySetVariable(from); + break; case T_Append: retval = _copyAppend(from); break; @@ -5354,6 +5411,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; @@ -5696,6 +5756,9 @@ copyObjectImpl(const void *from) case T_DropSubscriptionStmt: retval = _copyDropSubscriptionStmt(from); break; + case T_CreateSchemaVarStmt: + retval = _copyCreateSchemaVarStmt(from); + break; case T_A_Expr: retval = _copyAExpr(from); break; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index a410a29a17..28bf78befe 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -208,6 +208,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; @@ -967,6 +968,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); @@ -977,6 +979,7 @@ _equalQuery(const Query *a, const Query *b) COMPARE_SCALAR_FIELD(hasForUpdate); COMPARE_SCALAR_FIELD(hasRowSecurity); COMPARE_SCALAR_FIELD(isReturn); + COMPARE_SCALAR_FIELD(hasSchemaVariables); COMPARE_NODE_FIELD(cteList); COMPARE_NODE_FIELD(rtable); COMPARE_NODE_FIELD(jointree); @@ -1080,6 +1083,15 @@ _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) { @@ -2333,6 +2345,22 @@ _equalDropSubscriptionStmt(const DropSubscriptionStmt *a, return true; } +static bool +_equalCreateSchemaVarStmt(const CreateSchemaVarStmt *a, + const CreateSchemaVarStmt *b) +{ + COMPARE_NODE_FIELD(variable); + COMPARE_NODE_FIELD(typeName); + COMPARE_NODE_FIELD(collClause); + COMPARE_NODE_FIELD(defexpr); + COMPARE_SCALAR_FIELD(eoxaction); + COMPARE_SCALAR_FIELD(if_not_exists); + COMPARE_SCALAR_FIELD(is_not_null); + COMPARE_SCALAR_FIELD(is_immutable); + + return true; +} + static bool _equalCreatePolicyStmt(const CreatePolicyStmt *a, const CreatePolicyStmt *b) { @@ -3347,6 +3375,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; @@ -3689,6 +3720,9 @@ equal(const void *a, const void *b) case T_DropSubscriptionStmt: retval = _equalDropSubscriptionStmt(a, b); break; + case T_CreateSchemaVarStmt: + retval = _equalCreateSchemaVarStmt(a, b); + break; case T_A_Expr: retval = _equalAExpr(a, b); break; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index c723f6d635..7c9668d0ea 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -317,6 +317,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_INT_FIELD(stmt_len); } @@ -423,6 +424,16 @@ _outModifyTable(StringInfo str, const ModifyTable *node) WRITE_NODE_FIELD(exclRelTlist); } +static void +_outSetVariable(StringInfo str, const SetVariable *node) +{ + WRITE_NODE_TYPE("SETVARIABLE"); + + _outPlanInfo(str, (const Plan *) node); + + WRITE_OID_FIELD(varid); +} + static void _outAppend(StringInfo str, const Append *node) { @@ -1156,6 +1167,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); } @@ -2184,6 +2196,17 @@ _outModifyTablePath(StringInfo str, const ModifyTablePath *node) WRITE_INT_FIELD(epqParam); } +static void +_outSetVariablePath(StringInfo str, const SetVariablePath *node) +{ + WRITE_NODE_TYPE("SetVariablePATH"); + + _outPathInfo(str, (const Path *) node); + + WRITE_NODE_FIELD(subpath); + WRITE_OID_FIELD(varid); +} + static void _outLimitPath(StringInfo str, const LimitPath *node) { @@ -2257,6 +2280,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); @@ -2317,6 +2341,7 @@ _outPlannerInfo(StringInfo str, const PlannerInfo *node) WRITE_BOOL_FIELD(hasPseudoConstantQuals); WRITE_BOOL_FIELD(hasAlternativeSubPlans); WRITE_BOOL_FIELD(hasRecursion); + WRITE_BOOL_FIELD(hasSchemaVariables); WRITE_INT_FIELD(wt_param_id); WRITE_BITMAPSET_FIELD(curOuterRels); WRITE_NODE_FIELD(curOuterParams); @@ -2856,6 +2881,16 @@ _outPLAssignStmt(StringInfo str, const PLAssignStmt *node) WRITE_LOCATION_FIELD(location); } +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) { @@ -3047,6 +3082,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); @@ -3057,6 +3093,7 @@ _outQuery(StringInfo str, const Query *node) WRITE_BOOL_FIELD(hasForUpdate); WRITE_BOOL_FIELD(hasRowSecurity); WRITE_BOOL_FIELD(isReturn); + WRITE_BOOL_FIELD(hasSchemaVariables); WRITE_NODE_FIELD(cteList); WRITE_NODE_FIELD(rtable); WRITE_NODE_FIELD(jointree); @@ -3849,6 +3886,9 @@ outNode(StringInfo str, const void *obj) case T_ModifyTable: _outModifyTable(str, obj); break; + case T_SetVariable: + _outSetVariable(str, obj); + break; case T_Append: _outAppend(str, obj); break; @@ -4236,6 +4276,9 @@ outNode(StringInfo str, const void *obj) case T_ModifyTablePath: _outModifyTablePath(str, obj); break; + case T_SetVariablePath: + _outSetVariablePath(str, obj); + break; case T_LimitPath: _outLimitPath(str, obj); break; @@ -4353,6 +4396,9 @@ outNode(StringInfo str, const void *obj) case T_PLAssignStmt: _outPLAssignStmt(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 3746668f52..6cb9e732ae 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -254,6 +254,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); @@ -264,6 +265,7 @@ _readQuery(void) READ_BOOL_FIELD(hasForUpdate); READ_BOOL_FIELD(hasRowSecurity); READ_BOOL_FIELD(isReturn); + READ_BOOL_FIELD(hasSchemaVariables); READ_NODE_FIELD(cteList); READ_NODE_FIELD(rtable); READ_NODE_FIELD(jointree); @@ -628,6 +630,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(); @@ -1598,6 +1601,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_INT_FIELD(stmt_len); @@ -1704,6 +1708,20 @@ _readModifyTable(void) READ_DONE(); } +/* + * _readSetVariable + */ +static SetVariable * +_readSetVariable(void) +{ + READ_LOCALS(SetVariable); + + ReadCommonPlan(&local_node->plan); + READ_OID_FIELD(varid); + + READ_DONE(); +} + /* * _readAppend */ @@ -2865,6 +2883,8 @@ parseNodeString(void) return_value = _readProjectSet(); else if (MATCH("MODIFYTABLE", 11)) return_value = _readModifyTable(); + else if (MATCH("SETVARIABLE", 11)) + return_value = _readSetVariable(); else if (MATCH("APPEND", 6)) return_value = _readAppend(); else if (MATCH("MERGEAPPEND", 11)) diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index 22f10fa339..fba2169a9a 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -117,6 +117,8 @@ static RecursiveUnion *create_recursiveunion_plan(PlannerInfo *root, RecursiveUn static LockRows *create_lockrows_plan(PlannerInfo *root, LockRowsPath *best_path, int flags); static ModifyTable *create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path); +static SetVariable *create_SetVariable_plan(PlannerInfo *root, + SetVariablePath *best_path); static Limit *create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags); static SeqScan *create_seqscan_plan(PlannerInfo *root, Path *best_path, @@ -314,6 +316,7 @@ static ModifyTable *make_modifytable(PlannerInfo *root, Plan *subplan, List *updateColnosLists, List *withCheckOptionLists, List *returningLists, List *rowMarks, OnConflictExpr *onconflict, int epqParam); +static SetVariable *make_SetVariable(Plan *lefttree, Oid varid); static GatherMerge *create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path); @@ -532,6 +535,10 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags) plan = (Plan *) create_modifytable_plan(root, (ModifyTablePath *) best_path); break; + case T_SetVariable: + plan = (Plan *) create_SetVariable_plan(root, + (SetVariablePath *) best_path); + break; case T_Limit: plan = (Plan *) create_limit_plan(root, (LimitPath *) best_path, @@ -2764,6 +2771,31 @@ create_modifytable_plan(PlannerInfo *root, ModifyTablePath *best_path) return plan; } +/* + * create_SetVariable_plan + * + * Returns a Plan node. + */ +static SetVariable * +create_SetVariable_plan(PlannerInfo *root, SetVariablePath *best_path) +{ + SetVariable *plan; + Path *subpath = best_path->subpath; + Plan *subplan; + + /* Subplan must produce exactly the specified tlist */ + subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST); + + /* Transfer resname/resjunk labeling, too, to keep executor happy */ + apply_tlist_labeling(subplan->targetlist, root->processed_tlist); + + plan = make_SetVariable(subplan, best_path->varid); + + copy_generic_path_info(&plan->plan, (Path *) best_path); + + return plan; +} + /* * create_limit_plan * @@ -7111,6 +7143,25 @@ make_modifytable(PlannerInfo *root, Plan *subplan, return node; } +/* + * make_SetVariable + */ +static SetVariable * +make_SetVariable(Plan *lefttree, Oid varid) +{ + SetVariable *node = makeNode(SetVariable); + Plan *plan = &node->plan; + + plan->targetlist = lefttree->targetlist; + plan->qual = NIL; + plan->lefttree = lefttree; + plan->righttree = NULL; + + node->varid = varid; + + return node; +} + /* * is_projection_capable_path * Check whether a given Path node is able to do projection. diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 1868c4eff4..52099f5082 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -310,6 +310,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, 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 @@ -523,6 +524,7 @@ standard_planner(Query *parse, const char *query_string, int cursorOptions, 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; @@ -672,6 +674,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse, */ pull_up_subqueries(root); + /* + * Check if some subquery uses schema variable. Flag hasSchemaVariables + * should be true if query or some subquery uses any schema variable. + */ + pull_up_has_schema_variables(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 @@ -1705,10 +1713,20 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) offset_est, count_est); } + /* + * If this is LET statement, add SetVariable node. + */ + if (parse->commandType == CMD_LET) + { + path = (Path *) + create_setvariable_path(root, final_rel, path, + parse->resultVariable); + } + /* * If this is an INSERT/UPDATE/DELETE, add the ModifyTable node. */ - if (parse->commandType != CMD_SELECT) + else if (parse->commandType != CMD_SELECT) { Index rootRelation; List *resultRelations = NIL; diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c index 9f40ed77e6..115fb9d1a5 100644 --- a/src/backend/optimizer/plan/setrefs.c +++ b/src/backend/optimizer/plan/setrefs.c @@ -172,7 +172,8 @@ static List *set_returning_clause_references(PlannerInfo *root, Plan *topplan, Index resultRelation, int rtoffset); - +static bool pull_up_has_schema_variables_walker(Node *node, + PlannerInfo *root); /***************************************************************************** * @@ -767,6 +768,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) case T_IncrementalSort: case T_Unique: case T_SetOp: + case T_SetVariable: /* * These plan types don't actually bother to evaluate their @@ -1094,6 +1096,50 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset) return plan; } +/* + * Search usage of schema variables in subqueries + */ +void +pull_up_has_schema_variables(PlannerInfo *root) +{ + Query *query = root->parse; + + if (query->hasSchemaVariables) + { + root->hasSchemaVariables = true; + } + else + { + (void) query_tree_walker(query, + pull_up_has_schema_variables_walker, + (void *) root, 0); + } +} + +static bool +pull_up_has_schema_variables_walker(Node *node, PlannerInfo *root) +{ + if (node == NULL) + return false; + if (IsA(node, Query)) + { + Query *query = (Query *) node; + + if (query->hasSchemaVariables) + { + root->hasSchemaVariables = true; + return false; + } + + /* Recurse into subselects */ + return query_tree_walker((Query *) node, + pull_up_has_schema_variables_walker, + (void *) root, 0); + } + return expression_tree_walker(node, pull_up_has_schema_variables_walker, + (void *) root); +} + /* * set_indexonlyscan_references * Do set_plan_references processing on an IndexOnlyScan @@ -1722,10 +1768,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) @@ -1744,6 +1794,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); } @@ -1819,7 +1915,9 @@ fix_alternative_subplan(PlannerInfo *root, AlternativeSubPlan *asplan, * replacing Aggref nodes that should be replaced by initplan output Params, * choosing the best implementation for AlternativeSubPlans, * looking up operator opcode info for OpExpr and related nodes, - * and adding OIDs from regclass Const nodes into root->glob->relationOids. + * and 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. * * 'node': the expression to be modified * 'rtoffset': how much to increment varnos by @@ -1841,7 +1939,8 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset, double num_exec) root->multiexpr_params != NIL || root->glob->lastPHId != 0 || root->minmax_aggs != NIL || - root->hasAlternativeSubPlans) + root->hasAlternativeSubPlans || + root->hasSchemaVariables) { return fix_scan_expr_mutator(node, &context); } diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c index 0881a208ac..4ba9757c33 100644 --- a/src/backend/optimizer/plan/subselect.c +++ b/src/backend/optimizer/plan/subselect.c @@ -2758,6 +2758,7 @@ finalize_plan(PlannerInfo *root, Plan *plan, case T_Unique: case T_SetOp: case T_Group: + case T_SetVariable: /* no node-type-specific fields need fixing */ break; diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c index 363132185d..59e583bffa 100644 --- a/src/backend/optimizer/prep/preptlist.c +++ b/src/backend/optimizer/prep/preptlist.c @@ -94,7 +94,8 @@ preprocess_targetlist(PlannerInfo *root) target_relation = table_open(target_rte->relid, NoLock); } else - Assert(command_type == CMD_SELECT); + Assert(command_type == CMD_SELECT || + command_type == CMD_LET); /* * In an INSERT, the executor expects the targetlist to match the exact diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 526997327c..c008343c37 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -26,6 +26,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" +#include "commands/schema_variable.h" #include "executor/executor.h" #include "executor/functions.h" #include "funcapi.h" @@ -814,7 +815,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 || @@ -2180,6 +2182,7 @@ convert_saop_to_hashed_saop_walker(Node *node, void *context) * value of the Param. * 2. Fold stable, as well as immutable, functions to constants. * 3. Reduce PlaceHolderVar nodes to their contained expressions. + * 4. Current value of schema variable can be used for estimation too. *-------------------- */ Node * @@ -2299,6 +2302,30 @@ eval_const_expressions_mutator(Node *node, } } } + else if (param->paramkind == PARAM_VARIABLE && + context->estimate) + { + int16 typLen; + bool typByVal; + Datum pval; + bool isnull; + + get_typlenbyval(param->paramtype, + &typLen, &typByVal); + + pval = GetSchemaVariable(param->paramvarid, + &isnull, + param->paramtype, + true); + + return (Node *) makeConst(param->paramtype, + param->paramtypmod, + param->paramcollid, + (int) typLen, + pval, + isnull, + typByVal); + } /* * Not replaceable, so just copy the Param (no need to @@ -4628,7 +4655,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/pathnode.c b/src/backend/optimizer/util/pathnode.c index b248b038e0..e539287da2 100644 --- a/src/backend/optimizer/util/pathnode.c +++ b/src/backend/optimizer/util/pathnode.c @@ -3686,6 +3686,41 @@ create_modifytable_path(PlannerInfo *root, RelOptInfo *rel, return pathnode; } +/* + * create_SetVariable_path + * Create a pathnode that represents LET statement + * + * 'rel' is the parent relation associated with the result + * 'subpath' is a Path producing source data + * 'varid' is an oid of target schema variable + */ +SetVariablePath * +create_setvariable_path(PlannerInfo *root, RelOptInfo *rel, + Path *subpath, + Oid varid) +{ + SetVariablePath *pathnode = makeNode(SetVariablePath); + + pathnode->path.pathtype = T_SetVariable; + pathnode->path.parent = rel; + /* Limit doesn't project, so use source path's pathtarget */ + pathnode->path.pathtarget = subpath->pathtarget; + /* For now, assume we are above any joins, so no parameterization */ + pathnode->path.param_info = NULL; + pathnode->path.parallel_aware = false; + pathnode->path.parallel_safe = false; + pathnode->path.parallel_workers = 0; + pathnode->path.rows = 1; + pathnode->path.startup_cost = subpath->startup_cost; + pathnode->path.total_cost = subpath->total_cost; + pathnode->path.pathkeys = NIL; + pathnode->subpath = subpath; + pathnode->varid = varid; + + return pathnode; +} + + /* * create_limit_path * Creates a pathnode that represents performing LIMIT/OFFSET diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 862f18a92f..04eb99dee7 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/schema_variable.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -49,6 +52,7 @@ #include "utils/builtins.h" #include "utils/guc.h" #include "utils/queryjumble.h" +#include "utils/lsyscache.h" #include "utils/rel.h" @@ -86,6 +90,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 @@ -287,6 +293,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: @@ -356,6 +363,11 @@ transformStmt(ParseState *pstate, Node *parseTree) (CallStmt *) parseTree); break; + case T_LetStmt: + result = transformLetStmt(pstate, + (LetStmt *) parseTree); + break; + default: /* @@ -397,6 +409,7 @@ analyze_requires_snapshot(RawStmt *parseTree) case T_UpdateStmt: case T_SelectStmt: case T_PLAssignStmt: + case T_LetStmt: result = true; break; @@ -480,6 +493,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt) qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasAggs = pstate->p_hasAggs; + qry->hasSchemaVariables = pstate->p_hasSchemaVariables; + assign_query_collations(pstate, qry); /* this must be done after collations, for reliable comparison of exprs */ @@ -897,6 +912,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt) qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSchemaVariables = pstate->p_hasSchemaVariables; assign_query_collations(pstate, qry); @@ -1239,6 +1255,7 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) Query *qry = makeNode(Query); Node *qual; ListCell *l; + ParseExprKind target_exprkind; qry->commandType = CMD_SELECT; @@ -1267,9 +1284,19 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) /* process the FROM clause */ transformFromClause(pstate, stmt->fromClause); - /* transform targetlist */ - qry->targetList = transformTargetList(pstate, stmt->targetList, - EXPR_KIND_SELECT_TARGET); + /* + * Transform targetlist. Second usage of this transformation + * is for Let statement. In this case we would to allow DEFAULT + * keyword - we specify EXPR_KIND_LET_TARGET. + */ + target_exprkind = + (pstate->p_expr_kind != EXPR_KIND_LET_TARGET || + pstate->parentParseState != NULL) ? + EXPR_KIND_SELECT_TARGET : EXPR_KIND_LET_TARGET; + + qry->targetList = transformTargetList(pstate, + stmt->targetList, + target_exprkind); /* mark column origins */ markTargetListOrigins(pstate, qry->targetList); @@ -1359,6 +1386,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt) (LockingClause *) lfirst(l), false); } + qry->hasSchemaVariables = pstate->p_hasSchemaVariables; + assign_query_collations(pstate, qry); /* this must be done after collations, for reliable comparison of exprs */ @@ -1583,12 +1612,243 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt) qry->jointree = makeFromExpr(pstate->p_joinlist, NULL); qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSchemaVariables = pstate->p_hasSchemaVariables; 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; + bool to_default = false; + Oid typid; + int32 typmod; + Oid collid; + AclResult aclresult; + List *names = NULL; + int indirection_start; + + qry->commandType = CMD_LET; + + /* utilityStmt should be defined, let is executed like utility stmts */ + qry->utilityStmt = NULL; //(Node *) stmt; + + sv_expr_kind = pstate->p_expr_kind; + pstate->p_expr_kind = EXPR_KIND_LET_TARGET; + + /* There can't be any outer WITH to worry about */ + Assert(pstate->p_ctenamespace == NIL); + + names = NamesFromList(stmt->target); + + varid = IdentifyVariable(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; + + indirection_start = list_length(names) - (attrname ? 1 : 0); + indirection = list_copy_tail(stmt->target, indirection_start); + + 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 command"); + + /*---------- + * 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; + + if (IsA(tle->expr, SetToDefault)) + to_default = true; + + 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)))); + + if (to_default && attrname != NULL) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("only complete variable can be set to default"), + parser_errposition(pstate, stmt->location))); + + exprListCoer = NIL; + + foreach(lc, exprList) + { + Expr *expr = (Expr *) lfirst(lc); + Expr *coerced_expr; + Param *param; + Oid exprtypid; + + if (IsA(expr, SetToDefault)) + { + SetToDefault *def = (SetToDefault *) expr; + + def->typeId = typid; + def->typeMod = typmod; + def->collation = collid; + } + else if (IsA(expr, Const) && ((Const *) expr)->constisnull) + { + /* use known type for NULL value */ + expr = (Expr *) makeNullConst(typid, typmod, collid); + } + + /* now we can read type of expression */ + exprtypid = exprType((Node *) expr); + + param = makeNode(Param); + 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_hasSchemaVariables = true; + + coerced_expr = (Expr *) + transformAssignmentIndirection(pstate, + (Node *) param, + targetName, + targetIsArray, + typid, + typmod, + InvalidOid, + indirection, + list_head(indirection), + (Node *) expr, + COERCION_PLPGSQL, + stmt->location); + } + else + coerced_expr = (Expr *) + coerce_to_target_type(pstate, + (Node *) expr, + exprtypid, + typid, typmod, + COERCION_ASSIGNMENT, + COERCE_IMPLICIT_CAST, + stmt->location); + + if (coerced_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 *) expr)))); + + exprListCoer = lappend(exprListCoer, coerced_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->hasSchemaVariables = pstate->p_hasSchemaVariables; + + assign_query_collations(pstate, qry); + + pstate->p_expr_kind = sv_expr_kind; + + return qry; +} + /* * transformSetOperationStmt - * transforms a set-operations tree @@ -1839,6 +2099,8 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt) (LockingClause *) lfirst(l), false); } + qry->hasSchemaVariables = pstate->p_hasSchemaVariables; + assign_query_collations(pstate, qry); /* this must be done after collations, for reliable comparison of exprs */ @@ -2354,6 +2616,7 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt) qry->hasTargetSRFs = pstate->p_hasTargetSRFs; qry->hasSubLinks = pstate->p_hasSubLinks; + qry->hasSchemaVariables = pstate->p_hasSchemaVariables; assign_query_collations(pstate, qry); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 73494002ad..8b89edf00a 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -226,6 +226,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); JoinType jtype; DropBehavior dbehavior; OnCommitAction oncommit; + VariableEOXAction oneoxaction; List *list; Node *node; Value *value; @@ -276,8 +277,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 CreateAssertionStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt @@ -287,7 +288,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 ReturnStmt RevokeStmt RevokeRoleStmt RuleActionStmt RuleActionStmtOrEmpty RuleStmt @@ -427,6 +428,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); TriggerTransitions TriggerReferencing vacuum_relation_list opt_vacuum_relation_list drop_option_list + let_target %type opt_routine_body %type group_clause @@ -450,6 +452,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 @@ -608,6 +611,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type PartitionBoundSpec %type hash_partbound %type hash_partbound_elem +%type OptSchemaVarDefExpr +%type OptNotNull OptImmutable %type optColumnCompression @@ -678,7 +683,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 @@ -717,8 +722,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); UESCAPE 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 @@ -960,6 +965,7 @@ stmt: | CreatePolicyStmt | CreatePLangStmt | CreateSchemaStmt + | CreateSchemaVarStmt | CreateSeqStmt | CreateStmt | CreateSubscriptionStmt @@ -997,6 +1003,7 @@ stmt: | ImportForeignSchemaStmt | IndexStmt | InsertStmt + | LetStmt | ListenStmt | RefreshMatViewStmt | LoadStmt @@ -1889,7 +1896,12 @@ DiscardStmt: n->target = DISCARD_SEQUENCES; $$ = (Node *) n; } - + | DISCARD VARIABLES + { + DiscardStmt *n = makeNode(DiscardStmt); + n->target = DISCARD_VARIABLES; + $$ = (Node *) n; + } ; @@ -4645,6 +4657,61 @@ create_extension_opt_item: } ; +/***************************************************************************** + * + * QUERY : + * CREATE VARIABLE varname [AS] type + * + *****************************************************************************/ + +CreateSchemaVarStmt: + CREATE OptTemp OptImmutable VARIABLE qualified_name opt_as Typename opt_collate_clause OptNotNull OptSchemaVarDefExpr OnEOXActionOption + { + CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt); + $5->relpersistence = $2; + n->is_immutable = $3; + n->variable = $5; + n->typeName = $7; + n->collClause = (CollateClause *) $8; + n->is_not_null = $9; + n->defexpr = $10; + n->eoxaction = $11; + n->if_not_exists = false; + $$ = (Node *) n; + } + | CREATE OptTemp OptImmutable VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename opt_collate_clause OptNotNull OptSchemaVarDefExpr OnEOXActionOption + { + CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt); + $8->relpersistence = $2; + n->is_immutable = $3; + n->variable = $8; + n->typeName = $10; + n->collClause = (CollateClause *) $11; + n->is_not_null = $12; + n->defexpr = $13; + n->eoxaction = $14; + 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; } + ; + +OptNotNull: NOT NULL_P { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + +OptImmutable: IMMUTABLE { $$ = true; } + | /* EMPTY */ { $$ = false; } + ; + /***************************************************************************** * * ALTER EXTENSION name UPDATE [ TO version ] @@ -6332,6 +6399,7 @@ object_type_any_name: | TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; } | TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; } | TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; } + | VARIABLE { $$ = OBJECT_VARIABLE; } ; /* @@ -7100,6 +7168,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)); @@ -7140,6 +7216,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; + } ; @@ -7300,6 +7384,7 @@ defacl_privilege_target: | SEQUENCES { $$ = OBJECT_SEQUENCE; } | TYPES_P { $$ = OBJECT_TYPE; } | SCHEMAS { $$ = OBJECT_SCHEMA; } + | VARIABLES { $$ = OBJECT_VARIABLE; } ; @@ -9026,6 +9111,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 @@ -9354,6 +9458,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; + } + ; /***************************************************************************** @@ -9607,6 +9730,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; + } ; @@ -10848,6 +10979,7 @@ ExplainableStmt: | CreateMatViewStmt | RefreshMatViewStmt | ExecuteStmt /* by default all are $$=$1 */ + | LetStmt ; /***************************************************************************** @@ -10876,6 +11008,7 @@ PreparableStmt: | InsertStmt | UpdateStmt | DeleteStmt /* by default all are $$=$1 */ + | LetStmt ; /***************************************************************************** @@ -11293,6 +11426,47 @@ opt_hold: /* EMPTY */ { $$ = 0; } | WITHOUT HOLD { $$ = 0; } ; +/***************************************************************************** + * + * QUERY: + * LET STATEMENTS + * + *****************************************************************************/ +LetStmt: LET let_target '=' a_expr + { + LetStmt *n = makeNode(LetStmt); + SelectStmt *select; + ResTarget *res; + + n->target = $2; + + select = makeNode(SelectStmt); + 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; + + n->location = @2; + $$ = (Node *) n; + } + ; + +let_target: + ColId opt_indirection + { + $$ = list_make1(makeString($1)); + if ($2) + $$ = list_concat($$, + check_indirection($2, yyscanner)); + } + ; + /***************************************************************************** * * QUERY: @@ -15615,6 +15789,7 @@ unreserved_keyword: | LARGE_P | LAST_P | LEAKPROOF + | LET | LEVEL | LISTEN | LOAD @@ -15772,6 +15947,8 @@ unreserved_keyword: | VALIDATE | VALIDATOR | VALUE_P + | VARIABLE + | VARIABLES | VARYING | VERSION_P | VIEW @@ -16179,6 +16356,7 @@ bare_label_keyword: | LEAKPROOF | LEAST | LEFT + | LET | LEVEL | LIKE | LISTEN @@ -16378,6 +16556,8 @@ bare_label_keyword: | VALUE_P | VALUES | VARCHAR + | VARIABLE + | VARIABLES | VARIADIC | VERBOSE | VERSION_P diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index ceb0bf597d..2c48f3a08b 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -348,6 +348,7 @@ check_agglevels_and_constraints(ParseState *pstate, Node *expr) Assert(false); /* can't happen */ break; case EXPR_KIND_OTHER: + case EXPR_KIND_LET_TARGET: /* * Accept aggregate/grouping here; caller must throw error if @@ -464,6 +465,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"); @@ -912,6 +914,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: @@ -950,6 +953,9 @@ transformWindowFuncCall(ParseState *pstate, WindowFunc *wfunc, case EXPR_KIND_CYCLE_MARK: errkind = true; break; + case EXPR_KIND_LET_TARGET: + err = _("window functions are not allowed in LET statement"); + break; /* * 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 f928c32311..e662b335b9 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" @@ -36,6 +37,7 @@ #include "utils/date.h" #include "utils/lsyscache.h" #include "utils/timestamp.h" +#include "utils/typcache.h" #include "utils/xml.h" /* GUC parameters */ @@ -82,7 +84,9 @@ static Expr *make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, int location); static Node *make_nulltest_from_distinct(ParseState *pstate, A_Expr *distincta, Node *arg); - +static Node *makeParamSchemaVariable(ParseState *pstate, + Oid varid, Oid typid, int32 typmod, Oid collid, + char *attrname, int location); /* * transformExpr - @@ -282,6 +286,7 @@ transformExprRecurse(ParseState *pstate, Node *expr) * processed it rather than passing it to transformExpr(). */ case T_SetToDefault: + Assert(false); ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("DEFAULT is not allowed in this context"), @@ -448,6 +453,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) char *relname = NULL; char *colname = NULL; ParseNamespaceItem *nsitem; + Oid varid = InvalidOid; + char *attrname = NULL; + bool not_unique; int levels_up; enum { @@ -509,6 +517,9 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) case EXPR_KIND_COPY_WHERE: case EXPR_KIND_GENERATED_COLUMN: case EXPR_KIND_CYCLE_MARK: + case EXPR_KIND_VARIABLE_DEFAULT: + case EXPR_KIND_LET_TARGET: + /* okay */ break; @@ -766,6 +777,15 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) break; } + varid = IdentifyVariable(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 @@ -790,6 +810,78 @@ 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 cases with ambiguous references can be solved without + * raising an error. When there is an collision between column + * name (or label) and some schema variable name, and when we know + * attribute name, then we can ignore the collision when: + * + * a) variable is of scalar type (then indirection cannot be + * applied on this schema variable. + * + * b) when related variable has no field of attrname, then + * indirection cannot be applied on this schema variable. + */ + 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. */ @@ -824,6 +916,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_hasSchemaVariables = 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) { @@ -1726,6 +1886,7 @@ transformSubLink(ParseState *pstate, SubLink *sublink) case EXPR_KIND_VALUES: case EXPR_KIND_VALUES_SINGLE: case EXPR_KIND_CYCLE_MARK: + case EXPR_KIND_LET_TARGET: /* okay */ break; case EXPR_KIND_CHECK_CONSTRAINT: @@ -1734,6 +1895,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: @@ -3064,6 +3226,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"; @@ -3089,6 +3252,8 @@ ParseExprKindName(ParseExprKind exprKind) return "GENERATED AS"; case EXPR_KIND_CYCLE_MARK: return "CYCLE"; + case EXPR_KIND_LET_TARGET: + 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 baac089d68..99f7993ea7 100644 --- a/src/backend/parser/parse_func.c +++ b/src/backend/parser/parse_func.c @@ -2501,6 +2501,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: @@ -2539,6 +2540,9 @@ check_srf_call_placement(ParseState *pstate, Node *last_srf, int location) case EXPR_KIND_CYCLE_MARK: errkind = true; break; + case EXPR_KIND_LET_TARGET: + 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 6e8fbc4780..26acb72a53 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -89,7 +89,9 @@ transformTargetEntry(ParseState *pstate, * through unmodified. (transformExpr will throw the appropriate * error if we're disallowing it.) */ - if (exprKind == EXPR_KIND_UPDATE_SOURCE && IsA(node, SetToDefault)) + if ((exprKind == EXPR_KIND_UPDATE_SOURCE || + exprKind == EXPR_KIND_LET_TARGET) + && IsA(node, SetToDefault)) expr = node; else expr = transformExpr(pstate, node, exprKind); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index da78f02775..8f9cabc30a 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -25,6 +25,7 @@ #include "access/table.h" #include "catalog/dependency.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "commands/trigger.h" #include "foreign/fdwapi.h" #include "miscadmin.h" @@ -3665,6 +3666,41 @@ RewriteQuery(Query *parsetree, List *rewrite_events) } } + /* + * Rewrite SetToDefault by default expression of Let statement. + */ + if (event == CMD_LET) + { + Oid resultVariable = parsetree->resultVariable; + + Assert(OidIsValid(resultVariable)); + + if (list_length(parsetree->targetList) == 1 && + IsA(((TargetEntry *) linitial(parsetree->targetList))->expr, SetToDefault)) + { + Variable var; + TargetEntry *tle; + TargetEntry *newtle; + Expr *defexpr; + + /* Read schema variable metadata with defexpr */ + initVariable(&var, resultVariable, false, false); + + if (var.has_defexpr) + defexpr = (Expr *) var.defexpr; + else + defexpr = (Expr *) makeNullConst(var.typid, var.typmod, var.collation); + + tle = (TargetEntry *) linitial(parsetree->targetList); + newtle = makeTargetEntry(defexpr, + tle->resno, + pstrdup(tle->resname), + false); + + parsetree->targetList = list_make1(newtle); + } + } + /* * If the statement is an insert, update, or delete, adjust its targetlist * as needed, and then fire INSERT/UPDATE/DELETE rules on it. @@ -3673,7 +3709,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) + else if (event != CMD_SELECT && event != CMD_UTILITY) { int result_relation; RangeTblEntry *rt_entry; diff --git a/src/backend/rewrite/rowsecurity.c b/src/backend/rewrite/rowsecurity.c index fc26cb23a2..df9b0e39ee 100644 --- a/src/backend/rewrite/rowsecurity.c +++ b/src/backend/rewrite/rowsecurity.c @@ -213,10 +213,10 @@ get_row_security_policies(Query *root, RangeTblEntry *rte, int rt_index, } /* - * For SELECT, 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. + * 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. */ get_policies_for_relation(rel, commandType, user_id, &permissive_policies, diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c index 1dfadfa8e1..c676cb19fc 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" @@ -152,6 +153,9 @@ CreateDestReceiver(CommandDest dest) case DestTupleQueue: return CreateTupleQueueDestReceiver(NULL); + + case DestVariable: + return CreateVariableDestReceiver(); } /* should never get here */ @@ -207,6 +211,7 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o case DestSQLFunction: case DestTransientRel: case DestTupleQueue: + case DestVariable: break; } } @@ -252,6 +257,7 @@ NullCommand(CommandDest dest) case DestSQLFunction: case DestTransientRel: case DestTupleQueue: + case DestVariable: break; } } @@ -295,6 +301,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 825fd55107..78ba782b91 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -2604,7 +2604,6 @@ exec_describe_statement_message(const char *stmt_name) } else pq_putemptymessage('n'); /* NoData */ - } /* diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index 44f5fe8fc9..5b70fc782a 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; @@ -178,6 +181,9 @@ ProcessQuery(PlannedStmt *plan, case CMD_DELETE: SetQueryCompletion(qc, CMDTAG_DELETE, queryDesc->estate->es_processed); break; + case CMD_LET: + SetQueryCompletion(qc, CMDTAG_LET, queryDesc->estate->es_processed); + break; default: SetQueryCompletion(qc, CMDTAG_UNKNOWN, queryDesc->estate->es_processed); break; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 16c6f17e23..9440933486 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -48,6 +48,7 @@ #include "commands/proclang.h" #include "commands/publicationcmds.h" #include "commands/schemacmds.h" +#include "commands/schema_variable.h" #include "commands/seclabel.h" #include "commands/sequence.h" #include "commands/subscriptioncmds.h" @@ -186,6 +187,7 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree) case T_CreateRangeStmt: case T_CreateRoleStmt: case T_CreateSchemaStmt: + case T_CreateSchemaVarStmt: case T_CreateSeqStmt: case T_CreateStatsStmt: case T_CreateStmt: @@ -1352,6 +1354,10 @@ ProcessUtilitySlow(ParseState *pstate, } break; + case T_CreateSchemaVarStmt: + address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree); + break; + /* * ************* object creation / destruction ************** */ @@ -2086,6 +2092,8 @@ QueryReturnsTuples(Query *parsetree) break; case CMD_UTILITY: return UtilityReturnsTuples(parsetree->utilityStmt); + case CMD_LET: + break; case CMD_UNKNOWN: case CMD_NOTHING: /* probably shouldn't get here */ @@ -2276,6 +2284,9 @@ AlterObjectTypeCommandTag(ObjectType objtype) case OBJECT_STATISTIC_EXT: tag = CMDTAG_ALTER_STATISTICS; break; + case OBJECT_VARIABLE: + tag = CMDTAG_ALTER_VARIABLE; + break; default: tag = CMDTAG_UNKNOWN; break; @@ -2326,6 +2337,10 @@ CreateCommandTag(Node *parsetree) tag = CMDTAG_SELECT; break; + case T_LetStmt: + tag = CMDTAG_LET; + break; + /* utility statements --- same whether raw or cooked */ case T_TransactionStmt: { @@ -2580,6 +2595,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_STATISTIC_EXT: tag = CMDTAG_DROP_STATISTICS; break; + case OBJECT_VARIABLE: + tag = CMDTAG_DROP_VARIABLE; + break; default: tag = CMDTAG_UNKNOWN; } @@ -2868,6 +2886,9 @@ CreateCommandTag(Node *parsetree) case DISCARD_SEQUENCES: tag = CMDTAG_DISCARD_SEQUENCES; break; + case DISCARD_VARIABLES: + tag = CMDTAG_DISCARD_VARIABLES; + break; default: tag = CMDTAG_UNKNOWN; } @@ -3076,6 +3097,9 @@ CreateCommandTag(Node *parsetree) case CMD_DELETE: tag = CMDTAG_DELETE; break; + case CMD_LET: + tag = CMDTAG_LET; + break; case CMD_UTILITY: tag = CreateCommandTag(stmt->utilityStmt); break; @@ -3136,6 +3160,9 @@ CreateCommandTag(Node *parsetree) case CMD_DELETE: tag = CMDTAG_DELETE; break; + case CMD_LET: + tag = CMDTAG_LET; + break; case CMD_UTILITY: tag = CreateCommandTag(stmt->utilityStmt); break; @@ -3148,6 +3175,10 @@ CreateCommandTag(Node *parsetree) } break; + case T_CreateSchemaVarStmt: + tag = CMDTAG_CREATE_VARIABLE; + break; + default: elog(WARNING, "unrecognized node type: %d", (int) nodeTag(parsetree)); @@ -3195,6 +3226,7 @@ GetCommandLogLevel(Node *parsetree) break; case T_PLAssignStmt: + case T_LetStmt: lev = LOGSTMT_ALL; break; @@ -3631,6 +3663,7 @@ GetCommandLogLevel(Node *parsetree) case CMD_UPDATE: case CMD_INSERT: case CMD_DELETE: + case CMD_LET: lev = LOGSTMT_MOD; break; @@ -3661,6 +3694,7 @@ GetCommandLogLevel(Node *parsetree) case CMD_UPDATE: case CMD_INSERT: case CMD_DELETE: + case CMD_LET: lev = LOGSTMT_MOD; break; diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c index 7861a0a613..a1152a4fa7 100644 --- a/src/backend/utils/adt/acl.c +++ b/src/backend/utils/adt/acl.c @@ -306,6 +306,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; @@ -794,6 +800,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 */ @@ -888,6 +898,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); } @@ -1604,6 +1617,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), @@ -1698,6 +1715,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 0a4fa93d01..60150d9af8 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" @@ -7864,6 +7865,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; + } + /* * If it's an external parameter, see if the outermost namespace provides * function argument names. diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 6bba5f8ec4..d8c8ad7ff4 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -35,6 +35,7 @@ #include "catalog/pg_statistic.h" #include "catalog/pg_transform.h" #include "catalog/pg_type.h" +#include "catalog/pg_variable.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "utils/array.h" @@ -1860,6 +1861,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, Anum_pg_variable_oid, + 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 e4dc4ee34e..2cb9a74ca8 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 "lib/qunique.h" #include "utils/catcache.h" #include "utils/rel.h" @@ -991,6 +992,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, + { + Anum_pg_variable_oid, + 0, + 0, + 0 + }, + 8 } }; diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index b6835c2c4c..e8f3f709f4 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -1913,15 +1913,19 @@ get_call_expr_arg_stable(Node *expr, int argnum) arg = (Node *) list_nth(args, argnum); /* - * Either a true Const or an external Param will have a value that doesn't - * change during the execution of the query. In future we might want to - * consider other cases too, e.g. now(). + * Either a true Const or an external Param or variable will have a value + * that doesn't change during the execution of the query. In future we + * might want to consider other cases too, e.g. now(). */ if (IsA(arg, Const)) return true; - if (IsA(arg, Param) && - ((Param *) arg)->paramkind == PARAM_EXTERN) - return true; + if (IsA(arg, Param)) + { + Param *p = (Param *) arg; + + if (p->paramkind == PARAM_EXTERN || p->paramkind == PARAM_VARIABLE) + return true; + } return false; } diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index 1a261a5545..191b606077 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -260,6 +260,9 @@ getSchemaData(Archive *fout, int *numTablesPtr) pg_log_info("reading subscriptions"); getSubscriptions(fout); + pg_log_info("reading variables"); + getVariables(fout); + *numTablesPtr = numTables; return tblinfo; } diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 3bc86635f7..6ec33c185e 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -113,12 +113,14 @@ typedef struct _restoreOptions int selFunction; int selTrigger; int selTable; + int selVariable; SimpleStringList indexNames; SimpleStringList functionNames; SimpleStringList schemaNames; SimpleStringList schemaExcludeNames; SimpleStringList triggerNames; SimpleStringList tableNames; + SimpleStringList variableNames; int useDB; ConnParams cparams; /* parameters to use if useDB */ diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 86de26a4bf..a72e04978f 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -2948,6 +2948,14 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) !simple_string_list_member(&ropt->triggerNames, te->tag)) return 0; } + else if (strcmp(te->desc, "VARIABLE") == 0) + { + if (!ropt->selVariable) + return 0; + if (ropt->variableNames.head != NULL && + !simple_string_list_member(&ropt->variableNames, te->tag)) + return 0; + } else return 0; } @@ -3434,6 +3442,7 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te) 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 || @@ -3619,7 +3628,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 d0ea489614..2f1a0cd75d 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -271,6 +271,7 @@ static void dumpPolicy(Archive *fout, const PolicyInfo *polinfo); static void dumpPublication(Archive *fout, const PublicationInfo *pubinfo); static void dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo); static void dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo); +static void dumpVariable(Archive *fout, const VariableInfo * varinfo); static void dumpDatabase(Archive *AH); static void dumpDatabaseConfig(Archive *AH, PQExpBuffer outbuf, const char *dbname, Oid dboid); @@ -4639,6 +4640,232 @@ get_next_possible_free_pg_type_oid(Archive *fout, PQExpBuffer upgrade_query) return next_possible_free_oid; } +/* + * 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_varisnotnull; + int i_varisimmutable; + int i, + ntups; + + if (fout->remoteVersion < 130000) + 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, " + "v.varisnotnull, " + "v.varisimmutable, " + "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"); + i_varisnotnull = PQfnumber(res, "varisnotnull"); + i_varisimmutable = PQfnumber(res, "varisimmutable"); + + 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(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)); + + varinfo[i].varisnotnull = *(PQgetvalue(res, i, i_varisnotnull)) == 't'; + varinfo[i].varisimmutable = *(PQgetvalue(res, i, i_varisimmutable)) == 't'; + + /* 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) + pg_log_warning("owner of variable \"%s\" appears to be invalid", + 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, const VariableInfo * varinfo) +{ + DumpOptions *dopt = fout->dopt; + + PQExpBuffer delq; + PQExpBuffer query; + const char *varname; + const char *vartypname; + const char *vardefexpr; + const char *vareoxaction; + const char *varisnotnull; + const char *varisimmutable; + + /* 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; + varisnotnull = varinfo->varisnotnull ? " NOT NULL" : ""; + varisimmutable = varinfo->varisimmutable ? "IMMUTABLE " : ""; + + appendPQExpBuffer(delq, "DROP VARIABLE %s;\n", + varname); + + appendPQExpBuffer(query, "CREATE %sVARIABLE %s AS %s%s", + varisimmutable, varname, vartypname, varisnotnull); + + 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"); + + if (varinfo->dobj.dump & DUMP_COMPONENT_DEFINITION) + ArchiveEntry(fout, varinfo->dobj.catId, varinfo->dobj.dumpId, + ARCHIVE_OPTS(.tag = varinfo->dobj.name, + .namespace = varinfo->dobj.namespace->dobj.name, + .owner = varinfo->rolname, + .description = "VARIABLE", + .section = SECTION_PRE_DATA, + .createStmt = query->data, + .dropStmt = delq->data)); + + 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, @@ -10472,6 +10699,9 @@ dumpDumpableObject(Archive *fout, const DumpableObject *dobj) case DO_SUBSCRIPTION: dumpSubscription(fout, (const 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 */ @@ -18625,6 +18855,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs, case DO_CONVERSION: case DO_TABLE: case DO_TABLE_ATTACH: + case DO_VARIABLE: case DO_ATTRDEF: case DO_PROCLANG: case DO_CAST: diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 5340843081..9eced17344 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -52,6 +52,7 @@ typedef enum DO_TABLE, DO_TABLE_ATTACH, DO_ATTRDEF, + DO_VARIABLE, DO_INDEX, DO_INDEX_ATTACH, DO_STATSEXT, @@ -81,7 +82,7 @@ typedef enum DO_POLICY, DO_PUBLICATION, DO_PUBLICATION_REL, - DO_SUBSCRIPTION + DO_SUBSCRIPTION, } DumpableObjectType; /* component types of an object which can be selected for dumping */ @@ -643,6 +644,25 @@ 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; + bool varisnotnull; + bool varisimmutable; +} VariableInfo; + /* * We build an array of these with an entry for each object that is an * extension member according to pg_depend. @@ -734,5 +754,6 @@ extern PublicationInfo *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 46461fb6a1..cb76d70f2d 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -66,6 +66,7 @@ enum dbObjectTypePriorities PRIO_TABLE_ATTACH, PRIO_DUMMY_TYPE, PRIO_ATTRDEF, + PRIO_VARIABLE, PRIO_BLOB, PRIO_PRE_DATA_BOUNDARY, /* boundary! */ PRIO_TABLE_DATA, @@ -106,6 +107,7 @@ static const int dbObjectTypePriority[] = PRIO_TABLE, /* DO_TABLE */ PRIO_TABLE_ATTACH, /* DO_TABLE_ATTACH */ PRIO_ATTRDEF, /* DO_ATTRDEF */ + PRIO_VARIABLE, /* DO_VARIABLE */ PRIO_INDEX, /* DO_INDEX */ PRIO_INDEX_ATTACH, /* DO_INDEX_ATTACH */ PRIO_STATSEXT, /* DO_STATSEXT */ @@ -1492,6 +1494,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/pg_restore.c b/src/bin/pg_dump/pg_restore.c index 589b4aed53..b844aceb5c 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -101,6 +101,7 @@ main(int argc, char **argv) {"trigger", 1, NULL, 'T'}, {"use-list", 1, NULL, 'L'}, {"username", 1, NULL, 'U'}, + {"variable", 1, NULL, 'A'}, {"verbose", 0, NULL, 'v'}, {"single-transaction", 0, NULL, '1'}, @@ -148,7 +149,7 @@ main(int argc, char **argv) } } - while ((c = getopt_long(argc, argv, "acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1", + while ((c = getopt_long(argc, argv, "A:acCd:ef:F:h:I:j:lL:n:N:Op:P:RsS:t:T:U:vwWx1", cmdopts, NULL)) != -1) { switch (c) @@ -156,6 +157,11 @@ main(int argc, char **argv) case 'a': /* Dump data only */ opts->dataOnly = 1; break; + case 'A': /* vAriable */ + opts->selTypes = 1; + opts->selVariable = 1; + simple_string_list_append(&opts->variableNames, optarg); + break; case 'c': /* clean (i.e., drop) schema prior to create */ opts->dropSchema = 1; break; @@ -473,6 +479,7 @@ usage(const char *progname) printf(_("\nOptions controlling the restore:\n")); printf(_(" -a, --data-only restore only the data, no schema\n")); + printf(_(" -A, --variable=NAME restore named schema variable\n")); printf(_(" -c, --clean clean (drop) database objects before recreating\n")); printf(_(" -C, --create create the target database\n")); printf(_(" -e, --exit-on-error exit on error, default is to continue\n")); diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 86113df29c..fa4d7efc2b 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -2774,6 +2774,30 @@ my %tests = ( }, }, + 'CREATE VARIABLE test_variable' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE VARIABLE dump_test.variable1 AS integer DEFAULT 0;', + regexp => qr/^ + \QCREATE VARIABLE dump_test.variable1 AS integer DEFAULT 0;\E/xm, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { exclude_dump_test_schema => 1, }, + }, + + 'CREATE IMMUTABLE VARIABLE test_variable' => { + all_runs => 1, + catch_all => 'CREATE ... commands', + create_order => 61, + create_sql => 'CREATE IMMUTABLE VARIABLE dump_test.variable2 AS integer DEFAULT 0;', + regexp => qr/^ + \QCREATE IMMUTABLE VARIABLE dump_test.variable2 AS integer DEFAULT 0;\E/xm, + like => + { %full_runs, %dump_test_schema_runs, section_pre_data => 1, }, + unlike => { exclude_dump_test_schema => 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 543401c6d6..6662f298dd 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -928,6 +928,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/common.c b/src/bin/psql/common.c index 028a357991..e9c8123d20 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -361,6 +361,7 @@ AcceptResult(const PGresult *result, bool show_error) if (!result) OK = false; else + { switch (PQresultStatus(result)) { case PGRES_COMMAND_OK: @@ -384,6 +385,7 @@ AcceptResult(const PGresult *result, bool show_error) PQresultStatus(result)); break; } + } if (!OK && show_error) { diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index fdc2a89085..41140a8b24 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -5067,6 +5067,88 @@ 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, 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" + " NOT v.varisnotnull as \"%s\",\n" + " NOT v.varisimmutable as \"%s\",\n" + " pg_catalog.pg_get_expr(v.vardefexpr, 0) as \"%s\",\n" + " pg_catalog.pg_get_userbyid(v.varowner) 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("Is nullable"), + gettext_noop("Is mutable"), + gettext_noop("Default"), + gettext_noop("Owner"), + gettext_noop("Transactional 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) + pg_log_error("Did not find any schema variable named \"%s\".", + pattern); + else + pg_log_error("Did not find any schema variables."); + } + 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 71b320f1fc..a86fccc0d7 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -139,5 +139,7 @@ extern bool listOpFamilyOperators(const char *accessMethod_pattern, extern bool listOpFamilyFunctions(const char *access_method_pattern, const char *family_pattern, bool verbose); +/* \dV */ +extern bool listVariables(const char *pattern, bool varbose); #endif /* DESCRIBE_H */ diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 36501d5e2b..8812a58333 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -264,6 +264,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, _(" \\dX [PATTERN] list extended statistics\n")); fprintf(output, _(" \\dy [PATTERN] list event triggers\n")); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index d34271e3b8..a624944086 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -629,6 +629,13 @@ static const SchemaQuery Query_for_list_of_collations = { .result = "pg_catalog.quote_ident(c.collname)", }; +static const SchemaQuery Query_for_list_of_variables = { + .min_server_version = 120000, + .catname = "pg_catalog.pg_variable v", + .viscondition = "pg_catalog.pg_variable_is_visible(v.oid)", + .namespace = "v.varnamespace", + .result = "pg_catalog.quote_ident(v.varname)", +}; /* * Queries to get lists of names of various kinds of things, possibly @@ -1107,6 +1114,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 */ }; @@ -1505,8 +1513,8 @@ 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 FOREIGN SCHEMA", "INSERT", "LISTEN", "LOAD", "LOCK", - "MOVE", "NOTIFY", "PREPARE", + "FETCH", "GRANT", "IMPORT FOREIGN SCHEMA", "INSERT", "LET", "LISTEN", "LOAD", + "LOCK", "MOVE", "NOTIFY", "PREPARE", "REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", "SAVEPOINT", "SECURITY LABEL", "SELECT", "SET", "SHOW", "START", @@ -1525,7 +1533,7 @@ psql_completion(const char *text, int start, int end) "\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL", "\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\dP", "\\dPi", "\\dPt", "\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS", - "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy", + "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dX", "\\dy", "\\dV", "\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding", "\\endif", "\\errverbose", "\\ev", "\\f", @@ -1930,6 +1938,9 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_alter_system_set_vars); else if (Matches("ALTER", "SYSTEM", "SET", MatchAny)) COMPLETE_WITH("TO"); + /* ALTER VARIABLE */ + else if (Matches("ALTER", "VARIABLE", MatchAny)) + COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA"); /* ALTER VIEW */ else if (Matches("ALTER", "VIEW", MatchAny)) COMPLETE_WITH("ALTER COLUMN", "OWNER TO", "RENAME", @@ -2709,7 +2720,7 @@ psql_completion(const char *text, int start, int end) /* CREATE TABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ else if (TailMatches("CREATE", "TEMP|TEMPORARY")) - COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW"); + COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW", "VARIABLE"); /* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */ else if (TailMatches("CREATE", "UNLOGGED")) COMPLETE_WITH("TABLE", "MATERIALIZED VIEW"); @@ -2997,6 +3008,14 @@ psql_completion(const char *text, int start, int end) else if (TailMatches("=", MatchAnyExcept("*)"))) COMPLETE_WITH(",", ")"); } +/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */ + /* Complete CREATE VARIABLE with AS */ + else if (TailMatches("CREATE", "VARIABLE", MatchAny) || + TailMatches("TEMP|TEMPORARY", "VARIABLE", MatchAny)) + COMPLETE_WITH("AS"); + else if (TailMatches("VARIABLE", MatchAny, "AS")) + /* Complete CREATE VARIABLE with AS types */ + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); /* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* Complete CREATE [ OR REPLACE ] VIEW with AS */ @@ -3111,7 +3130,7 @@ psql_completion(const char *text, int start, int end) /* DISCARD */ else if (Matches("DISCARD")) - COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP"); + COMPLETE_WITH("ALL", "PLANS", "SEQUENCES", "TEMP", "VARIABLES"); /* DO */ else if (Matches("DO")) @@ -3217,6 +3236,12 @@ psql_completion(const char *text, int start, int end) else if (Matches("DROP", "RULE", MatchAny, "ON", MatchAny)) COMPLETE_WITH("CASCADE", "RESTRICT"); + /* DROP VARIABLE */ + else if (Matches("DROP", "VARIABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); + else if (Matches("DROP", "VARIABLE", MatchAny)) + COMPLETE_WITH("CASCADE", "RESTRICT"); + /* EXECUTE */ else if (Matches("EXECUTE")) COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements); @@ -3388,6 +3413,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'" @@ -3401,14 +3427,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 (TailMatches("GRANT|REVOKE", MatchAny, "ON", "ALL")) COMPLETE_WITH("FUNCTIONS IN SCHEMA", "PROCEDURES IN SCHEMA", "ROUTINES IN SCHEMA", "SEQUENCES IN SCHEMA", - "TABLES IN SCHEMA"); + "TABLES IN SCHEMA", + "VARIABLES IN SCHEMA"); else if (TailMatches("GRANT|REVOKE", MatchAny, "ON", "FOREIGN")) COMPLETE_WITH("DATA WRAPPER", "SERVER"); @@ -3442,6 +3470,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces); else if (TailMatches("TYPE")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL); + else if (TailMatches("VARIABLE")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); else if (TailMatches("GRANT", MatchAny, MatchAny, MatchAny)) COMPLETE_WITH("TO"); else @@ -3605,7 +3635,7 @@ psql_completion(const char *text, int start, int end) /* PREPARE xx AS */ else if (Matches("PREPARE", MatchAny, "AS")) - COMPLETE_WITH("SELECT", "UPDATE", "INSERT", "DELETE FROM"); + COMPLETE_WITH("SELECT", "UPDATE", "INSERT", "DELETE FROM", "LET"); /* * PREPARE TRANSACTION is missing on purpose. It's intended for transaction @@ -3879,6 +3909,14 @@ psql_completion(const char *text, int start, int end) else if (TailMatches("UPDATE", MatchAny, "SET", MatchAnyExcept("*="))) COMPLETE_WITH("="); +/* LET --- can be inside EXPLAIN, PREPARE etc */ + /* If prev. word is LET suggest a list of variables */ + else if (TailMatches("LET")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); + /* Complete LET with "=" */ + else if (TailMatches("LET", MatchAny)) + COMPLETE_WITH("="); + /* USER MAPPING */ else if (Matches("ALTER|CREATE|DROP", "USER", "MAPPING")) COMPLETE_WITH("FOR"); @@ -4040,6 +4078,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH_QUERY(Query_for_list_of_roles); else if (TailMatchesCS("\\dv*")) COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_views, NULL); + else if (TailMatchesCS("\\dV*")) + COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL); else if (TailMatchesCS("\\dx*")) COMPLETE_WITH_QUERY(Query_for_list_of_extensions); else if (TailMatchesCS("\\dX*")) diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index f272e2c99f..e7ce899f0e 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -132,10 +132,11 @@ 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 +#define LAST_OCLASS OCLASS_VARIABLE /* flag bits for performDeletion/performMultipleDeletions: */ #define PERFORM_DELETION_INTERNAL 0x0001 /* internal action */ diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index aa2774e2d4..e3b22a1537 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -95,6 +95,8 @@ extern Oid TypenameGetTypid(const char *typname); extern Oid TypenameGetTypidExtended(const char *typname, bool temp_ok); extern bool TypeIsVisible(Oid typid); +extern bool VariableIsVisible(Oid varid); + extern FuncCandidateList FuncnameGetCandidates(List *names, int nargs, List *argnames, bool expand_variadic, @@ -162,6 +164,10 @@ extern void SetTempNamespaceState(Oid tempNamespaceId, Oid tempToastNamespaceId); extern void ResetTempTableNamespace(void); +extern List *NamesFromList(List *names); +extern Oid LookupVariable(const char *nspname, const char *varname, bool missing_ok); +extern Oid IdentifyVariable(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 156fc0712d..22839aa356 100644 --- a/src/include/catalog/pg_default_acl.h +++ b/src/include/catalog/pg_default_acl.h @@ -68,6 +68,7 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_default_acl_oid_index, 828, on pg_default_acl using #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 f4957653ae..b556c7e99e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6178,6 +6178,9 @@ proname => 'pg_collation_is_visible', procost => '10', provolatile => 's', prorettype => 'bool', proargtypes => 'oid', prosrc => 'pg_collation_is_visible' }, +{ oid => '9221', 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..30f84f6b6d --- /dev/null +++ b/src/include/catalog/pg_variable.h @@ -0,0 +1,117 @@ +/*------------------------------------------------------------------------- + * + * pg_variable.h + * definition of schema variables system catalog (pg_variables) + * + * + * Portions Copyright (c) 1996-2020, 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,9222,VariableRelationId) +{ + Oid oid; /* oid */ + 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 */ + bool varisnotnull; /* Don't allow NULL */ + bool varisimmutable; /* Don't allow changes */ + 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; + +DECLARE_UNIQUE_INDEX_PKEY(pg_variable_oid_index, 9223, on pg_variable using btree(oid oid_ops)); +#define VariableObjectIndexId 9223 + +DECLARE_UNIQUE_INDEX(pg_variable_varname_nsp_index, 9224, on pg_variable using btree(varname name_ops, varnamespace oid_ops)); +#define VariableNameNspIndexId 9224 + +typedef struct Variable +{ + Oid oid; + char *name; + Oid namespace; + Oid typid; + int32 typmod; + Oid owner; + Oid collation; + bool is_not_null; + bool is_immutable; + VariableEOXAction eoxaction; + bool has_defexpr; + 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 void initVariable(Variable *var, + Oid varid, + bool missing_ok, + bool fast_only); +extern ObjectAddress VariableCreate(const char *varName, + Oid varNamespace, + Oid varType, + int32 varTypmod, + Oid varOwner, + Oid varCollation, + Node *varDefexpr, + VariableEOXAction eoxaction, + bool is_not_null, + bool if_not_exists, + bool is_immutable); + +#endif /* PG_VARIABLE_H */ diff --git a/src/include/commands/schema_variable.h b/src/include/commands/schema_variable.h new file mode 100644 index 0000000000..0398b9d00a --- /dev/null +++ b/src/include/commands/schema_variable.h @@ -0,0 +1,39 @@ +/*------------------------------------------------------------------------- + * + * schemavariable.h + * prototypes for schemavariable.c. + * + * + * Portions Copyright (c) 1996-2020, 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 RemoveSchemaVariable(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); + +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 785600d04d..13b7902633 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -158,6 +158,7 @@ typedef enum ExprEvalOp EEOP_PARAM_EXEC, EEOP_PARAM_EXTERN, EEOP_PARAM_CALLBACK, + EEOP_PARAM_VARIABLE, /* return CaseTestExpr value */ EEOP_CASE_TESTVAL, @@ -380,6 +381,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 { @@ -735,6 +743,8 @@ extern void ExecEvalParamExec(ExprState *state, ExprEvalStep *op, ExprContext *econtext); 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 017ad87117..c85e7ee89d 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/nodeSetVariable.h b/src/include/executor/nodeSetVariable.h new file mode 100644 index 0000000000..0d2140fb2c --- /dev/null +++ b/src/include/executor/nodeSetVariable.h @@ -0,0 +1,23 @@ +/*------------------------------------------------------------------------- + * + * nodeSetVariable.h + * + * + * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/executor/nodeSetVariable.h + * + *------------------------------------------------------------------------- + */ +#ifndef NODESETVARIABLE_H +#define NODESETVARIABLE_H + +#include "nodes/execnodes.h" + +extern SetVariableState *ExecInitSetVariable(SetVariable *node, + EState *estate, int eflags); +extern void ExecEndSetVariable(SetVariableState *node); +extern void ExecReScanSetVariable(SetVariableState *node); + +#endif /* NODESETVARIABLE_H */ diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index 6455d100f5..0fa13b8da6 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -95,6 +95,7 @@ typedef struct _SPI_plan *SPIPlanPtr; #define SPI_OK_REL_REGISTER 15 #define SPI_OK_REL_UNREGISTER 16 #define SPI_OK_TD_REGISTER 17 +#define SPI_OK_LET 18 #define SPI_OPT_NONATOMIC (1 << 0) diff --git a/src/include/executor/svariableReceiver.h b/src/include/executor/svariableReceiver.h new file mode 100644 index 0000000000..5305936547 --- /dev/null +++ b/src/include/executor/svariableReceiver.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * svariableReceiver.h + * prototypes for svariableReceiver.c + * + * + * Portions Copyright (c) 1996-2020, 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 e7ae21c023..b886c5ae40 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -24,6 +24,7 @@ #include "nodes/tidbitmap.h" #include "partitioning/partdefs.h" #include "storage/condition_variable.h" +#include "tcop/dest.h" #include "utils/hsearch.h" #include "utils/queryenvironment.h" #include "utils/reltrigger.h" @@ -541,6 +542,18 @@ typedef struct AsyncRequest TupleTableSlot *result; /* Result (NULL if no more tuples) */ } AsyncRequest; +/* ---------------- + * SchemaVariableValue + * ---------------- + */ +typedef struct SchemaVariableValue +{ + Oid varid; + Oid typid; + bool isnull; + Datum value; +} SchemaVariableValue; + /* ---------------- * EState information * @@ -592,6 +605,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: */ @@ -1225,6 +1245,17 @@ typedef struct ModifyTableState struct TransitionCaptureState *mt_oc_transition_capture; } ModifyTableState; +/* ---------------- + * SetVariableState information + * ---------------- + */ +typedef struct SetVariableState +{ + PlanState ps; + DestReceiver *dest; + Oid varid; +} SetVariableState; + /* ---------------- * AppendState information * diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index d9e417bcd7..1ab5e0f4fb 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -46,6 +46,7 @@ typedef enum NodeTag T_Result, T_ProjectSet, T_ModifyTable, + T_SetVariable, T_Append, T_MergeAppend, T_RecursiveUnion, @@ -105,6 +106,7 @@ typedef enum NodeTag T_ResultState, T_ProjectSetState, T_ModifyTableState, + T_SetVariableState, T_AppendState, T_MergeAppendState, T_RecursiveUnionState, @@ -262,6 +264,7 @@ typedef enum NodeTag T_RecursiveUnionPath, T_LockRowsPath, T_ModifyTablePath, + T_SetVariablePath, T_LimitPath, /* these aren't subclasses of Path: */ T_EquivalenceClass, @@ -359,6 +362,7 @@ typedef enum NodeTag T_CreateTableAsStmt, T_CreateSeqStmt, T_AlterSeqStmt, + T_CreateSchemaVarStmt, T_VariableSetStmt, T_VariableShowStmt, T_DiscardStmt, @@ -432,6 +436,7 @@ typedef enum NodeTag T_AlterCollationStmt, T_CallStmt, T_AlterStatsStmt, + T_LetStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) @@ -685,7 +690,8 @@ typedef enum CmdType CMD_SELECT, /* select stmt */ CMD_UPDATE, /* update stmt */ CMD_INSERT, /* insert stmt */ - CMD_DELETE, + CMD_DELETE, /* delete stmt */ + CMD_LET, /* let stmt */ CMD_UTILITY, /* cmds like create, destroy, copy, vacuum, * etc. */ CMD_NOTHING /* dummy command for instead nothing rules diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 7a44bccdd3..bd3a1bef88 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -92,7 +92,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<mod_stmt); exec_set_found(estate, (SPI_processed != 0)); break; diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index e11837559d..24a5e6e598 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -2436,6 +2436,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp, case SPI_OK_INSERT: case SPI_OK_DELETE: case SPI_OK_UPDATE: + case SPI_OK_LET: Tcl_SetObjResult(interp, Tcl_NewWideIntObj(ntuples)); break; diff --git a/src/test/regress/expected/misc_sanity.out b/src/test/regress/expected/misc_sanity.out index 9ebe28a78d..dc8a14ffc6 100644 --- a/src/test/regress/expected/misc_sanity.out +++ b/src/test/regress/expected/misc_sanity.out @@ -109,7 +109,9 @@ ORDER BY 1, 2; pg_index | indpred | pg_node_tree pg_largeobject | data | bytea pg_largeobject_metadata | lomacl | aclitem[] -(11 rows) + pg_variable | varacl | aclitem[] + pg_variable | vardefexpr | pg_node_tree +(13 rows) -- system catalogs without primary keys -- diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index d9ce961be2..947851f3eb 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -163,6 +163,7 @@ pg_ts_parser|t pg_ts_template|t pg_type|t pg_user_mapping|t +pg_variable|t point_tbl|t polygon_tbl|t quad_box_tbl|t diff --git a/src/test/regress/expected/schema_variables.out b/src/test/regress/expected/schema_variables.out new file mode 100644 index 0000000000..c81f6014da --- /dev/null +++ b/src/test/regress/expected/schema_variables.out @@ -0,0 +1,634 @@ +CREATE SCHEMA svartest; +SET search_path = svartest; +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; +GRANT USAGE ON SCHEMA svartest TO var_test_role; +SET ROLE TO var_test_role; +-- should fail +SELECT var1; +ERROR: permission denied for schema variable var1 +SET ROLE TO DEFAULT; +GRANT READ ON VARIABLE var1 TO var_test_role; +SET ROLE TO var_test_role; +-- should fail +LET var1 = 10; +ERROR: permission denied for schema variable var1 +-- should work +SELECT var1; + var1 +------ + +(1 row) + +SET ROLE TO DEFAULT; +GRANT WRITE ON VARIABLE var1 TO var_test_role; +SET ROLE TO var_test_role; +-- should 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 svartest.var1::int; +$$ LANGUAGE sql SECURITY DEFINER; +SELECT secure_var(); + secure_var +------------ + 333 +(1 row) + +SET ROLE TO var_test_role; +-- should fail +SELECT svartest.var1; +ERROR: permission denied for schema variable var1 +-- should work; +SELECT secure_var(); + secure_var +------------ + 333 +(1 row) + +SET ROLE TO DEFAULT; +EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var1; + QUERY PLAN +----------------------------------------------- + Function Scan on pg_catalog.generate_series g + Output: v + Function Call: generate_series(1, 100) + Filter: ((g.v)::numeric = var1) +(4 rows) + +CREATE VIEW schema_var_view AS SELECT var1; +SELECT * FROM schema_var_view; + var1 +------ + 333 +(1 row) + +\c - +SET search_path = svartest; +-- should work still, but var will be empty +SELECT * FROM schema_var_view; + var1 +------ + +(1 row) + +LET var1 = pi(); +SELECT var1; + var1 +------------------ + 3.14159265358979 +(1 row) + +-- we can see execution plan of LET statement +EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi(); + QUERY PLAN +---------------------------------- + Set Variable + Output: 3.14159265358979 + -> Result + Output: 3.14159265358979 +(4 rows) + +-- LET can be prepared +PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2; +EXECUTE var_pp(100, 1.23456); +SELECT var1; + var1 +----------- + 101.23456 +(1 row) + +CREATE VARIABLE var3 AS int; +CREATE OR REPLACE FUNCTION inc(int) +RETURNS int AS $$ +BEGIN + LET svartest.var3 = COALESCE(svartest.var3 + $1, $1); + RETURN var3; +END; +$$ LANGUAGE plpgsql; +SELECT inc(1); + inc +----- + 1 +(1 row) + +SELECT inc(1); + inc +----- + 2 +(1 row) + +SELECT inc(1); + inc +----- + 3 +(1 row) + +SELECT inc(1) FROM generate_series(1,10); + inc +----- + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 +(10 rows) + +SET ROLE TO var_test_role; +-- should fail +LET var3 = 0; +ERROR: permission denied for schema variable var3 +SET ROLE TO DEFAULT; +DROP VIEW schema_var_view; +DROP VARIABLE var1 CASCADE; +DROP VARIABLE var3 CASCADE; +-- composite variables +CREATE TYPE sv_xyz AS (x int, y int, z numeric(10,2)); +CREATE VARIABLE v1 AS sv_xyz; +CREATE VARIABLE v2 AS sv_xyz; +\d v1 +\d v2 +LET v1 = (1,2,3.14); +LET v2 = (10,20,3.14*10); +-- should work too - there are prepared casts +LET v1 = (1,2,3.14); +SELECT v1; + v1 +------------ + (1,2,3.14) +(1 row) + +SELECT v2; + v2 +--------------- + (10,20,31.40) +(1 row) + +SELECT (v1).*; + x | y | z +---+---+------ + 1 | 2 | 3.14 +(1 row) + +SELECT (v2).*; + x | y | z +----+----+------- + 10 | 20 | 31.40 +(1 row) + +SELECT v1.x + v1.z; + ?column? +---------- + 4.14 +(1 row) + +SELECT v2.x + v2.z; + ?column? +---------- + 41.40 +(1 row) + +-- access to composite fields should be safe too +-- should fail +SET ROLE TO var_test_role; +SELECT v2.x; +ERROR: permission denied for schema variable v2 +SET ROLE TO DEFAULT; +DROP VARIABLE v1; +DROP VARIABLE v2; +REVOKE USAGE ON SCHEMA svartest FROM var_test_role; +DROP ROLE var_test_role; +-- scalar variables should not be in conflict with qualified column +CREATE VARIABLE varx AS text; +SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class'; + relname +---------- + pg_class +(1 row) + +-- should fail +SELECT varx.xxx; +ERROR: type text is not composite +-- variables can be updated under RO transaction +BEGIN; +SET TRANSACTION READ ONLY; +LET varx = 'hello'; +COMMIT; +SELECT varx; + varx +------- + hello +(1 row) + +DROP VARIABLE varx; +CREATE TYPE t1 AS (a int, b numeric, c text); +CREATE VARIABLE v1 AS t1; +LET v1 = (1, pi(), 'hello'); +SELECT v1; + v1 +---------------------------- + (1,3.14159265358979,hello) +(1 row) + +LET v1.b = 10.2222; +SELECT v1; + v1 +------------------- + (1,10.2222,hello) +(1 row) + +-- should fail +LET v1.x = 10; +ERROR: cannot assign to field "x" of column "x" because there is no such column in data type t1 +LINE 1: LET v1.x = 10; + ^ +DROP VARIABLE v1; +DROP TYPE t1; +-- arrays are supported +CREATE VARIABLE va1 AS numeric[]; +LET va1 = ARRAY[1.1,2.1]; +LET va1[1] = 10.1; +SELECT va1; + va1 +------------ + {10.1,2.1} +(1 row) + +CREATE TYPE ta2 AS (a numeric, b numeric[]); +CREATE VARIABLE va2 AS ta2; +LET va2 = (10.1, ARRAY[0.0, 0.0]); +LET va2.a = 10.2; +SELECT va2; + va2 +-------------------- + (10.2,"{0.0,0.0}") +(1 row) + +LET va2.b[1] = 10.3; +SELECT va2; + va2 +--------------------- + (10.2,"{10.3,0.0}") +(1 row) + +DROP VARIABLE va1; +DROP VARIABLE va2; +DROP TYPE ta2; +-- default values +CREATE VARIABLE v1 AS numeric DEFAULT pi(); +LET v1 = v1 * 2; +SELECT v1; + v1 +------------------ + 6.28318530717958 +(1 row) + +CREATE TYPE t2 AS (a numeric, b text); +CREATE VARIABLE v2 AS t2 DEFAULT (NULL, 'Hello'); +LET svartest.v2.a = pi(); +SELECT v2; + v2 +-------------------------- + (3.14159265358979,Hello) +(1 row) + +-- shoudl fail due dependency +DROP TYPE t2; +ERROR: cannot drop type t2 because other objects depend on it +DETAIL: schema variable v2 depends on type t2 +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- should be ok +DROP VARIABLE v1; +DROP VARIABLE v2; +-- tests of alters +CREATE SCHEMA var_schema1; +CREATE SCHEMA var_schema2; +CREATE VARIABLE var_schema1.var1 AS integer; +LET var_schema1.var1 = 1000; +SELECT var_schema1.var1; + var1 +------ + 1000 +(1 row) + +ALTER VARIABLE var_schema1.var1 SET SCHEMA var_schema2; +SELECT var_schema2.var1; + var1 +------ + 1000 +(1 row) + +CREATE ROLE var_test_role; +ALTER VARIABLE var_schema2.var1 OWNER TO var_test_role; +SET ROLE TO var_test_role; +-- should fail, no access to schema var_schema2.var +SELECT var_schema2.var1; +ERROR: permission denied for schema var_schema2 +DROP VARIABLE var_schema2.var1; +ERROR: permission denied for schema var_schema2 +SET ROLE TO DEFAULT; +ALTER VARIABLE var_schema2.var1 SET SCHEMA public; +SET ROLE TO var_test_role; +SELECT public.var1; + var1 +------ + 1000 +(1 row) + +ALTER VARIABLE public.var1 RENAME TO var1_renamed; +SELECT public.var1_renamed; + var1_renamed +-------------- + 1000 +(1 row) + +DROP VARIABLE public.var1_renamed; +SET ROLE TO DEFAULt; +DROP ROLE var_test_role; +CREATE VARIABLE xx AS text DEFAULT 'hello'; +SELECT xx, upper(xx); + xx | upper +-------+------- + hello | HELLO +(1 row) + +LET xx = 'Hi'; +SELECT xx; + xx +---- + Hi +(1 row) + +DROP VARIABLE xx; +-- ON TRANSACTION END RESET tests +CREATE VARIABLE t1 AS int DEFAULT -1 ON TRANSACTION END RESET; +BEGIN; + SELECT t1; + t1 +---- + -1 +(1 row) + + LET t1 = 100; + SELECT t1; + t1 +----- + 100 +(1 row) + +COMMIT; +SELECT t1; + t1 +---- + -1 +(1 row) + +BEGIN; + SELECT t1; + t1 +---- + -1 +(1 row) + + LET t1 = 100; + SELECT t1; + t1 +----- + 100 +(1 row) + +ROLLBACK; +SELECT t1; + t1 +---- + -1 +(1 row) + +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; + v1 | v2 +-----+------- + 100 | Hello +(1 row) + +LET v1 = DEFAULT; +LET v2 = DEFAULT; +SELECT v1, v2; + v1 | v2 +----+------ + 0 | none +(1 row) + +DROP VARIABLE v1; +DROP VARIABLE v2; +-- ON COMMIT DROP tests +-- should be 0 always +SELECT count(*) FROM pg_variable; + count +------- + 0 +(1 row) + +CREATE TEMP VARIABLE g AS int ON COMMIT DROP; +SELECT count(*) FROM pg_variable; + count +------- + 0 +(1 row) + +BEGIN; + CREATE TEMP VARIABLE g AS int ON COMMIT DROP; +COMMIT; +SELECT count(*) FROM pg_variable; + count +------- + 0 +(1 row) + +BEGIN; + CREATE TEMP VARIABLE g AS int ON COMMIT DROP; +ROLLBACK; +SELECT count(*) FROM pg_variable; + count +------- + 0 +(1 row) + +-- 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; + QUERY PLAN +-------------------------------------------------- + Finalize Aggregate + -> 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) + +LET zero = (SELECT count(*) FROM svar_test); +-- result should be 1000000 +SELECT zero; + zero +--------- + 1000000 +(1 row) + +-- parallel workers should be used (!!! DOESNT WORK YET !!!) +EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test); + QUERY PLAN +------------------------------------- + Set Variable + InitPlan 1 (returns $0) + -> Aggregate + -> Seq Scan on svar_test + -> Result +(5 rows) + +DROP TABLE svar_test; +DROP VARIABLE zero; +-- use variables in prepared statements +CREATE VARIABLE v AS numeric; +LET v = 3.14; +PREPARE pv1(int) AS LET v = v + $1; +PREPARE pv2 AS SELECT v; +EXECUTE pv1(1000); +EXECUTE pv2; + v +--------- + 1003.14 +(1 row) + +-- use variables in views +CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result; +SELECT * FROM vv; + result +--------- + 2003.14 +(1 row) + +-- start a new session +\c +SET search_path to svartest; +SELECT * FROM vv; + result +-------- + 1000 +(1 row) + +LET v = 3.14; +SELECT * FROM vv; + result +--------- + 1003.14 +(1 row) + +-- should fail, dependency +DROP VARIABLE v; +ERROR: cannot drop schema variable v because other objects depend on it +DETAIL: view vv depends on schema variable v +HINT: Use DROP ... CASCADE to drop the dependent objects too. +-- should be ok +DROP VARIABLE v CASCADE; +NOTICE: drop cascades to view vv +-- other features +CREATE VARIABLE dt AS integer DEFAULT 0; +LET dt = 100; +SELECT dt; + dt +----- + 100 +(1 row) + +DISCARD VARIABLES; +SELECT dt; + dt +---- + 0 +(1 row) + +DROP VARIABLE dt; +-- NOT NULL +CREATE VARIABLE v1 AS int NOT NULL; +CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL; +-- should fail +SELECT v1; +ERROR: null value is not allowed for NOT NULL schema variable "v1" +DETAIL: The schema variable was not initialized yet. +SELECT v2; +ERROR: null value is not allowed for NOT NULL schema variable "v2" +LET v1 = NULL; +ERROR: null value is not allowed for NOT NULL schema variable "v1" +LET v2 = NULL; +ERROR: null value is not allowed for NOT NULL schema variable "v2" +LET v1 = DEFAULT; +ERROR: null value is not allowed for NOT NULL schema variable "v1" +LET v2 = DEFAULT; +ERROR: null value is not allowed for NOT NULL schema variable "v2" +-- should be ok +LET v1 = 100; +LET v2 = 1000; +SELECT v1, v2; + v1 | v2 +-----+------ + 100 | 1000 +(1 row) + +DROP VARIABLE v1; +DROP VARIABLE v2; +CREATE VARIABLE tv AS int; +CREATE VARIABLE IF NOT EXISTS tv AS int; +NOTICE: schema variable "tv" already exists, skipping +DROP VARIABLE tv; +CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100; +SELECT iv; + iv +----- + 100 +(1 row) + +-- should fail; +LET iv = 10000; +ERROR: schema variable "iv" is declared IMMUTABLE +DROP VARIABLE iv; +-- create variable inside plpgsql block +DO $$ +BEGIN + CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01'; +END; +$$; +SELECT do_test_svar; + do_test_svar +-------------- + 01-01-2000 +(1 row) + +DROP VARIABLE do_test_svar; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index a091300857..059e698635 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -114,7 +114,7 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath # 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 conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 temp domain rangefuncs prepare 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 5644847601..67f6e31e90 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -209,3 +209,4 @@ test: event_trigger test: oidjoins 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..5f638302c7 --- /dev/null +++ b/src/test/regress/sql/schema_variables.sql @@ -0,0 +1,427 @@ +CREATE SCHEMA svartest; + +SET search_path = svartest; + +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; +GRANT USAGE ON SCHEMA svartest TO var_test_role; + +SET ROLE TO var_test_role; + +-- should fail +SELECT var1; + +SET ROLE TO DEFAULT; + +GRANT READ ON VARIABLE var1 TO var_test_role; + +SET ROLE TO var_test_role; +-- should fail +LET var1 = 10; +-- should work +SELECT var1; + +SET ROLE TO DEFAULT; + +GRANT WRITE ON VARIABLE var1 TO var_test_role; + +SET ROLE TO var_test_role; + +-- should 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 svartest.var1::int; +$$ LANGUAGE sql SECURITY DEFINER; + +SELECT secure_var(); + +SET ROLE TO var_test_role; + +-- should fail +SELECT svartest.var1; + +-- should 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 - + +SET search_path = svartest; + +-- should work still, but var will be empty +SELECT * FROM schema_var_view; + +LET var1 = pi(); + +SELECT var1; + +-- we can see execution plan of LET statement +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 svartest.var3 = COALESCE(svartest.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 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 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 fail +SET ROLE TO var_test_role; + +SELECT v2.x; + +SET ROLE TO DEFAULT; + +DROP VARIABLE v1; +DROP VARIABLE v2; + +REVOKE USAGE ON SCHEMA svartest FROM var_test_role; +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 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 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 svartest.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; + +LET zero = (SELECT count(*) FROM svar_test); + +-- result should be 1000000 +SELECT zero; + +-- parallel workers should be used (!!! DOESNT WORK YET !!!) +EXPLAIN (costs off) LET zero = (SELECT count(*) FROM svar_test); + +DROP TABLE svar_test; +DROP VARIABLE zero; + +-- use variables in prepared statements +CREATE VARIABLE v AS numeric; +LET v = 3.14; + +PREPARE pv1(int) AS LET v = v + $1; +PREPARE pv2 AS SELECT v; + +EXECUTE pv1(1000); +EXECUTE pv2; + +-- use variables in views +CREATE VIEW vv AS SELECT COALESCE(v, 0) + 1000 AS result; +SELECT * FROM vv; + +-- start a new session +\c + +SET search_path to svartest; + +SELECT * FROM vv; +LET v = 3.14; +SELECT * FROM vv; + +-- should fail, dependency +DROP VARIABLE v; + +-- should be ok +DROP VARIABLE v CASCADE; + +-- other features +CREATE VARIABLE dt AS integer DEFAULT 0; + +LET dt = 100; +SELECT dt; + +DISCARD VARIABLES; + +SELECT dt; + +DROP VARIABLE dt; + +-- NOT NULL +CREATE VARIABLE v1 AS int NOT NULL; +CREATE VARIABLE v2 AS int NOT NULL DEFAULT NULL; + +-- should fail +SELECT v1; +SELECT v2; +LET v1 = NULL; +LET v2 = NULL; +LET v1 = DEFAULT; +LET v2 = DEFAULT; + +-- should be ok +LET v1 = 100; +LET v2 = 1000; +SELECT v1, v2; + +DROP VARIABLE v1; +DROP VARIABLE v2; + +CREATE VARIABLE tv AS int; +CREATE VARIABLE IF NOT EXISTS tv AS int; +DROP VARIABLE tv; + +CREATE IMMUTABLE VARIABLE iv AS int DEFAULT 100; +SELECT iv; + +-- should fail; +LET iv = 10000; + +DROP VARIABLE iv; + +-- create variable inside plpgsql block +DO $$ +BEGIN + CREATE VARIABLE do_test_svar AS date DEFAULT '2000-01-01'; +END; +$$; + +SELECT do_test_svar; + +DROP VARIABLE do_test_svar;