diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 26984b6cba..e3473a119b 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1840,7 +1840,8 @@ SCRAM-SHA-256$<iteration count>:&l
m = materialized view,
c = composite type,
f = foreign table,
- p = partitioned table
+ p = partitioned table,
+ V = schema variable
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 53a40ddeec..92848dead2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -15883,6 +15883,83 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
+
+ Schema Variable Functions
+
+
+ Schema Variable Functions
+ functions
+
+
+
+ get_schema_variable
+
+
+
+ set_schema_variable
+
+
+
+ These functions allow reading and writing schema variables values.
+ If the schema variable referenced does not exist (created using )
+ these functions will (do something...).
+
+
+
+ Functions for access to schema variables
+
+
+
+ Function
+ Argument Type
+ Return Type
+ Description
+
+
+
+
+ get_schema_variable(variable, expected type)
+ regclass, anyelement
+ anyelement
+
+ Returns value of schema variable converted to expected type.
+
+
+
+
+ set_schema_variable(variable, value)
+ regclass, anyelement
+ void
+
+ Sets the value of schema variable to value, after converting the input to the correct type.
+
+
+
+
+
+
+
+ An example:
+
+
+CREATE TEMP VARIABLE foo AS numeric;
+SELECT set_schema_variable('foo', 345.445);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT get_schema_variable('foo', null::numeric);
+
+ get_schema_variable
+---------------------
+ 345.445
+(1 row)
+
+
+
+
+
System Information Functions
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index c81c87ef41..a73ad01d41 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -99,6 +99,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -147,6 +148,7 @@ Complete list of usable sgml source files in this directory.
+
@@ -155,6 +157,7 @@ Complete list of usable sgml source files in this directory.
+
diff --git a/doc/src/sgml/ref/create_variable.sgml b/doc/src/sgml/ref/create_variable.sgml
new file mode 100644
index 0000000000..0205e4365a
--- /dev/null
+++ b/doc/src/sgml/ref/create_variable.sgml
@@ -0,0 +1,149 @@
+
+
+
+
+ CREATE VARIABLE
+
+
+
+ CREATE VARIABLE
+ 7
+ SQL - Language Statements
+
+
+
+ CREATE VARIABLE
+ define a new permissioned typed schema variable
+
+
+
+
+CREATE VARIABLE [ IF NOT EXISTS ] name [ AS ] data_type ]
+
+
+
+ Description
+
+
+ CREATE VARIABLE creates a new schema variable.
+ These variables are scalar typed, non-transactional, and, like relations,
+ exist within a schema with access controlled via
+ GRANT and REVOKE.
+
+
+
+ The value of a schema variable is session-local. Retrieving
+ a variable's value will return NULL unless its value has been set
+ to something else in the current session.
+
+
+
+ Retrieval is done via the get_schema_variabledunxrion or the SQL
+ command SELECT. Setting of values is done via the
+ set_schema_variable function or the SQL command
+ LET.
+ Notably, while schema variables are in many ways a kind of table you cannot use
+ UPDATE on them.
+
+
+
+ For purposes of name uniqueness relation-like objects (e.g., tables, indexes)
+ within the same schema are considered. i.e., you cannot give a table and a
+ schema variable the same name. This is a consequence of them being treated
+ like relations for purposes of SELECT.
+
+
+
+
+ Parameters
+
+
+
+ IF NOT EXISTS
+
+
+ Do not throw an error if the name already exists. A notice is issued in this case.
+ Note that type of the variable is not considered, nor could it be since the namespace
+ searched contains non-variable objects.
+
+
+
+
+
+ name
+
+
+ The name (optionally schema-qualified) of the variable to be created.
+
+
+
+
+
+ data_type
+
+
+ The name (optionally schema-qualified) of the data type of the variable to be created.
+
+
+
+
+
+
+
+ Notes
+
+
+ Use DROP VARIABLE to remove a variable.
+
+
+
+
+ Examples
+
+
+ Create an integer variable var1:
+
+CREATE VARIABLE var1 AS integer;
+
+
+
+
+ Set this variable's value; then retrieve it converted to numeric.
+
+CREATE VARIABLE
+postgres=# select set_schema_variable('var1', 10);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+postgres=# select get_schema_variable('var1', null::numeric);
+ get_schema_variable
+---------------------
+ 10
+(1 row)
+
+
+
+
+
+ Compatibility
+
+
+ CREATE VARIABLE is a PostgreSQL feature.
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/discard.sgml b/doc/src/sgml/ref/discard.sgml
index 6b909b7232..395453bba0 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,16 @@ DISCARD { ALL | PLANS | SEQUENCES | TEMPORARY | TEMP }
+
+ VARIABLES
+
+
+ Sets the value of all schema variables to NULL.
+
+
+
+
+
ALL
diff --git a/doc/src/sgml/ref/drop_variable.sgml b/doc/src/sgml/ref/drop_variable.sgml
new file mode 100644
index 0000000000..06130fd510
--- /dev/null
+++ b/doc/src/sgml/ref/drop_variable.sgml
@@ -0,0 +1,92 @@
+
+
+
+
+ DROP VARIABLE
+
+
+
+ DROP VARIABLE
+ 7
+ SQL - Language Statements
+
+
+
+ DROP VARIABLE
+ remove a schema variable
+
+
+
+
+DROP VARIABLE [ IF EXISTS ] name [, ...] [ CASCADE | RESTRICT ]
+
+
+
+
+ Description
+
+
+ DROP VARIABLE removes a schema variable.
+ A variable can only be dropped by its owner or a superuser.
+
+
+
+
+
+ Parameters
+
+
+
+ IF EXISTS
+
+
+ Do not throw an error if the variable does not exist. A notice is issued
+ in this case.
+
+
+
+
+
+ name
+
+
+ The name (optionally schema-qualified) of a schema variable.
+
+
+
+
+
+
+
+ Examples
+
+
+ To remove the schema variable var1:
+
+
+DROP VARIABLE var1;
+
+
+
+
+ Compatibility
+
+
+ DROP VARIABLE is proprietary PostgreSQL command.
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/grant.sgml b/doc/src/sgml/ref/grant.sgml
index ff64c7a3ba..006364ebe5 100644
--- a/doc/src/sgml/ref/grant.sgml
+++ b/doc/src/sgml/ref/grant.sgml
@@ -79,6 +79,12 @@ GRANT { USAGE | ALL [ PRIVILEGES ] }
ON TYPE type_name [, ...]
TO role_specification [, ...] [ WITH GRANT OPTION ]
+GRANT { { SELECT | UPDATE }
+ [, ...] | ALL [ PRIVILEGES ] }
+ ON { VARIABLE variable_name [, ...]
+ | ALL VARIABLES IN SCHEMA schema_name [, ...] }
+ TO role_specification [, ...] [ WITH GRANT OPTION ]
+
where role_specification can be:
[ GROUP ] role_name
@@ -167,6 +173,7 @@ GRANT role_name [, ...] TO PUBLIC are as follows:
@@ -204,6 +211,8 @@ GRANT role_name [, ...] TO currval function.
For large objects, this privilege allows the object to be read.
+ For schema variables, this privilege allows the get_schema_variable
+ to read the variable's value.
@@ -239,6 +248,9 @@ GRANT role_name [, ...] TO setval functions.
For large objects, this privilege allows writing or truncating the
object.
+ For schema variables, this privilege allows LET
+ and set_schema_variable to modify the schema variable's
+ value.
diff --git a/doc/src/sgml/ref/let.sgml b/doc/src/sgml/ref/let.sgml
new file mode 100644
index 0000000000..e8bf3f6dd4
--- /dev/null
+++ b/doc/src/sgml/ref/let.sgml
@@ -0,0 +1,90 @@
+
+
+
+
+ LET
+
+
+
+ LET
+ 7
+ SQL - Language Statements
+
+
+
+ LET
+ change a schema variable's value
+
+
+
+
+LET schema_variable = sql_expression
+
+
+
+
+ Description
+
+
+ The LET command updates the specified schema variable' value.
+
+
+
+
+
+ Parameters
+
+
+
+ schema_variable
+
+
+ The name of schema variable.
+
+
+
+
+
+ sql expression
+
+
+ An SQL expression, the result is cast to the schema variable's type.
+
+
+
+
+
+
+ Example:
+
+CREATE VARIABLE myvar AS integer;
+LET myvar = 10;
+LET myvar = (SELECT sum(val) FROM tab);
+
+
+
+
+
+ Compatibility
+
+
+
+ LET extends syntax defined in the SQL
+ standard. The standard knows SET command,
+ that is used for different purpouse in PostgreSQL.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
diff --git a/doc/src/sgml/ref/revoke.sgml b/doc/src/sgml/ref/revoke.sgml
index 7018202f14..73778f01f9 100644
--- a/doc/src/sgml/ref/revoke.sgml
+++ b/doc/src/sgml/ref/revoke.sgml
@@ -108,6 +108,14 @@ REVOKE [ GRANT OPTION FOR ]
REVOKE [ ADMIN OPTION FOR ]
role_name [, ...] FROM role_name [, ...]
[ CASCADE | RESTRICT ]
+
+REVOKE [ GRANT OPTION FOR ]
+ { { SELECT | UPDATE }
+ [, ...] | ALL [ PRIVILEGES ] }
+ ON { VARIABLE variable_name [, ...]
+ | ALL VARIABLES IN SCHEMA schema_name [, ...] }
+ FROM { [ GROUP ] role_name | PUBLIC } [, ...]
+ [ CASCADE | RESTRICT ]
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 73ef212c08..c3c5d9d106 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -127,6 +127,7 @@
&createType;
&createUser;
&createUserMapping;
+ &createVariable;
&createView;
&deallocate;
&declare;
@@ -175,6 +176,7 @@
&dropType;
&dropUser;
&dropUserMapping;
+ &dropVariable;
&dropView;
&end;
&execute;
@@ -183,6 +185,7 @@
&grant;
&importForeignSchema;
&insert;
+ &let;
&listen;
&load;
&lock;
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 578e4c6592..69ea16e6a7 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -284,6 +284,9 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs,
case OBJECT_TYPE:
whole_mask = ACL_ALL_RIGHTS_TYPE;
break;
+ case OBJECT_VARIABLE:
+ whole_mask = ACL_ALL_RIGHTS_VARIABLE;
+ break;
default:
elog(ERROR, "unrecognized object type: %d", objtype);
/* not reached, but keep compiler quiet */
@@ -507,6 +510,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);
@@ -577,6 +584,7 @@ ExecGrantStmt_oids(InternalGrant *istmt)
{
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
+ case OBJECT_VARIABLE:
ExecGrant_Relation(istmt);
break;
case OBJECT_DATABASE:
@@ -646,6 +654,7 @@ objectNamesToOids(ObjectType objtype, List *objnames)
{
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
+ case OBJECT_VARIABLE:
foreach(cell, objnames)
{
RangeVar *relvar = (RangeVar *) lfirst(cell);
@@ -1018,6 +1027,10 @@ ExecAlterDefaultPrivilegesStmt(ParseState *pstate, AlterDefaultPrivilegesStmt *s
all_privileges = ACL_ALL_RIGHTS_SCHEMA;
errormsg = gettext_noop("invalid privilege type %s for schema");
break;
+ case OBJECT_VARIABLE:
+ all_privileges = ACL_ALL_RIGHTS_VARIABLE;
+ errormsg = gettext_noop("invalid privilege type %s for schema variable");
+ break;
default:
elog(ERROR, "unrecognized GrantStmt.objtype: %d",
(int) action->objtype);
@@ -1215,6 +1228,12 @@ SetDefaultACL(InternalDefaultACL *iacls)
this_privileges = ACL_ALL_RIGHTS_SCHEMA;
break;
+ case OBJECT_VARIABLE:
+ objtype = DEFACLOBJ_VARIABLE;
+ if (iacls->all_privs && this_privileges == ACL_NO_RIGHTS)
+ this_privileges = ACL_ALL_RIGHTS_VARIABLE;
+ break;
+
default:
elog(ERROR, "unrecognized objtype: %d",
(int) iacls->objtype);
@@ -1441,6 +1460,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",
@@ -1612,8 +1634,9 @@ expand_all_col_privileges(Oid table_oid, Form_pg_class classForm,
if (curr_att == ObjectIdAttributeNumber && !classForm->relhasoids)
continue;
- /* Views don't have any system columns at all */
- if (classForm->relkind == RELKIND_VIEW && curr_att < 0)
+ /* Views and variables don't have any system columns at all */
+ if ((classForm->relkind == RELKIND_VIEW || classForm->relkind == RELKIND_VARIABLE) &&
+ curr_att < 0)
continue;
attTuple = SearchSysCache2(ATTNUM,
@@ -3456,6 +3479,9 @@ aclcheck_error(AclResult aclerr, ObjectType objtype,
case OBJECT_TYPE:
msg = gettext_noop("permission denied for type %s");
break;
+ case OBJECT_VARIABLE:
+ msg = gettext_noop("permission denied for schema variable %s");
+ break;
case OBJECT_VIEW:
msg = gettext_noop("permission denied for view %s");
break;
@@ -3566,6 +3592,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;
@@ -3682,6 +3711,7 @@ pg_aclmask(ObjectType objtype, Oid table_oid, AttrNumber attnum, Oid roleid,
pg_attribute_aclmask(table_oid, attnum, roleid, mask, how);
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
+ case OBJECT_VARIABLE:
return pg_class_aclmask(table_oid, roleid, mask, how);
case OBJECT_DATABASE:
return pg_database_aclmask(table_oid, roleid, mask, how);
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 39813de991..f1acc0aa70 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -294,6 +294,7 @@ heap_create(const char *relname,
switch (relkind)
{
case RELKIND_VIEW:
+ case RELKIND_VARIABLE:
case RELKIND_COMPOSITE_TYPE:
case RELKIND_FOREIGN_TABLE:
case RELKIND_PARTITIONED_TABLE:
@@ -431,7 +432,8 @@ CheckAttributeNamesTypes(TupleDesc tupdesc, char relkind,
* Skip this for a view or type relation, since those don't have system
* attributes.
*/
- if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
+ if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE &&
+ relkind != RELKIND_VARIABLE)
{
for (i = 0; i < natts; i++)
{
@@ -720,7 +722,8 @@ AddNewAttributeTuples(Oid new_rel_oid,
* all for a view or type relation. We don't bother with making datatype
* dependencies here, since presumably all these types are pinned.
*/
- if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE)
+ if (relkind != RELKIND_VIEW && relkind != RELKIND_COMPOSITE_TYPE &&
+ relkind != RELKIND_VARIABLE)
{
for (i = 0; i < (int) lengthof(SysAtt); i++)
{
@@ -1073,7 +1076,9 @@ heap_create_with_catalog(const char *relname,
if (existing_relid != InvalidOid)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_TABLE),
- errmsg("relation \"%s\" already exists", relname)));
+ errmsg("%s \"%s\" already exists",
+ relkind == RELKIND_VARIABLE ? "variable" : "relation",
+ relname)));
/*
* Since we are going to create a rowtype as well, also check for
@@ -1114,7 +1119,7 @@ heap_create_with_catalog(const char *relname,
(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE ||
relkind == RELKIND_VIEW || relkind == RELKIND_MATVIEW ||
relkind == RELKIND_COMPOSITE_TYPE || relkind == RELKIND_FOREIGN_TABLE ||
- relkind == RELKIND_PARTITIONED_TABLE))
+ relkind == RELKIND_PARTITIONED_TABLE || relkind == RELKIND_VARIABLE))
{
if (!OidIsValid(binary_upgrade_next_heap_pg_class_oid))
ereport(ERROR,
@@ -1156,6 +1161,10 @@ heap_create_with_catalog(const char *relname,
relacl = get_user_default_acl(OBJECT_SEQUENCE, ownerid,
relnamespace);
break;
+ case RELKIND_VARIABLE:
+ relacl = get_user_default_acl(OBJECT_VARIABLE, ownerid,
+ relnamespace);
+ break;
default:
relacl = NULL;
break;
@@ -1189,14 +1198,16 @@ heap_create_with_catalog(const char *relname,
* Decide whether to create an array type over the relation's rowtype. We
* do not create any array types for system catalogs (ie, those made
* during initdb). We do not create them where the use of a relation as
- * such is an implementation detail: toast tables, sequences and indexes.
+ * such is an implementation detail: toast tables, sequences, indexes and
+ * variables.
*/
if (IsUnderPostmaster && (relkind == RELKIND_RELATION ||
relkind == RELKIND_VIEW ||
relkind == RELKIND_MATVIEW ||
relkind == RELKIND_FOREIGN_TABLE ||
relkind == RELKIND_COMPOSITE_TYPE ||
- relkind == RELKIND_PARTITIONED_TABLE))
+ relkind == RELKIND_PARTITIONED_TABLE ||
+ relkind == RELKIND_VARIABLE))
new_array_oid = AssignTypeArrayOid();
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index ef3ea64bd0..f74f27c8e3 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -519,6 +519,9 @@ static const struct object_type_map
{
"sequence", OBJECT_SEQUENCE
},
+ {
+ "variable", OBJECT_VARIABLE
+ },
{
"toast table", -1
}, /* unmapped */
@@ -823,6 +826,7 @@ get_object_address(ObjectType objtype, Node *object,
case OBJECT_VIEW:
case OBJECT_MATVIEW:
case OBJECT_FOREIGN_TABLE:
+ case OBJECT_VARIABLE:
address =
get_relation_by_qualified_name(objtype, castNode(List, object),
&relation, lockmode,
@@ -1259,6 +1263,14 @@ get_relation_by_qualified_name(ObjectType objtype, List *object,
errmsg("\"%s\" is not a foreign table",
RelationGetRelationName(relation))));
break;
+ case OBJECT_VARIABLE:
+ if (relation->rd_rel->relkind != RELKIND_VARIABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not a schema variable",
+ RelationGetRelationName(relation))));
+ break;
+
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
break;
@@ -1848,6 +1860,8 @@ get_object_address_defacl(List *object, bool missing_ok)
case DEFACLOBJ_NAMESPACE:
objtype_str = "schemas";
break;
+ case DEFACLOBJ_VARIABLE:
+ objtype_str = "variables";
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -2110,6 +2124,7 @@ pg_get_object_address(PG_FUNCTION_ARGS)
{
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
+ case OBJECT_VARIABLE:
case OBJECT_VIEW:
case OBJECT_MATVIEW:
case OBJECT_INDEX:
@@ -2234,6 +2249,7 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
case OBJECT_INDEX:
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
+ case OBJECT_VARIABLE:
case OBJECT_VIEW:
case OBJECT_MATVIEW:
case OBJECT_FOREIGN_TABLE:
@@ -3301,6 +3317,11 @@ getObjectDescription(const ObjectAddress *object)
_("default privileges on new schemas belonging to role %s"),
GetUserNameFromId(defacl->defaclrole, false));
break;
+ case DEFACLOBJ_VARIABLE:
+ appendStringInfo(&buffer,
+ _("default privileges on new schema variables belonging to role %s"),
+ GetUserNameFromId(defacl->defaclrole, false));
+ break;
default:
/* shouldn't get here */
appendStringInfo(&buffer,
@@ -3504,6 +3525,10 @@ getRelationDescription(StringInfo buffer, Oid relid)
appendStringInfo(buffer, _("sequence %s"),
relname);
break;
+ case RELKIND_VARIABLE:
+ appendStringInfo(buffer, _("variable %s"),
+ relname);
+ break;
case RELKIND_TOASTVALUE:
appendStringInfo(buffer, _("toast table %s"),
relname);
@@ -4832,6 +4857,10 @@ getObjectIdentityParts(const ObjectAddress *object,
appendStringInfoString(&buffer,
" on schemas");
break;
+ case DEFACLOBJ_VARIABLE:
+ appendStringInfoString(&buffer,
+ " on schema variables");
+ break;
}
if (objname)
@@ -5124,6 +5153,8 @@ get_relkind_objtype(char relkind)
return OBJECT_INDEX;
case RELKIND_SEQUENCE:
return OBJECT_SEQUENCE;
+ case RELKIND_VARIABLE:
+ return OBJECT_VARIABLE;
case RELKIND_VIEW:
return OBJECT_VIEW;
case RELKIND_MATVIEW:
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile
index 4a6c99e090..5747272c9a 100644
--- a/src/backend/commands/Makefile
+++ b/src/backend/commands/Makefile
@@ -18,7 +18,7 @@ OBJS = amcmds.o aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
policy.o portalcmds.o prepare.o proclang.o publicationcmds.o \
- schemacmds.o seclabel.o sequence.o statscmds.o subscriptioncmds.o \
+ schemacmds.o schemavar.o seclabel.o sequence.o statscmds.o subscriptioncmds.o \
tablecmds.o tablespace.o trigger.o tsearchcmds.o typecmds.o user.o \
vacuum.o vacuumlazy.o variable.o view.o
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index eff325cc7d..8a4071c927 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -348,6 +348,7 @@ ExecRenameStmt(RenameStmt *stmt)
case OBJECT_MATVIEW:
case OBJECT_INDEX:
case OBJECT_FOREIGN_TABLE:
+ case OBJECT_VARIABLE:
return RenameRelation(stmt);
case OBJECT_COLUMN:
@@ -478,6 +479,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt,
case OBJECT_TABLE:
case OBJECT_VIEW:
case OBJECT_MATVIEW:
+ case OBJECT_VARIABLE:
address = AlterTableNamespace(stmt,
oldSchemaAddr ? &oldNspOid : NULL);
break;
diff --git a/src/backend/commands/comment.c b/src/backend/commands/comment.c
index 2f2e69b4a8..f4a3cae6b4 100644
--- a/src/backend/commands/comment.c
+++ b/src/backend/commands/comment.c
@@ -83,7 +83,7 @@ CommentObject(CommentStmt *stmt)
/*
* Allow comments only on columns of tables, views, materialized
- * views, composite types, and foreign tables (which are the only
+ * views, composite types, variables and foreign tables (which are the only
* relkinds for which pg_dump will dump per-column comments). In
* particular we wish to disallow comments on index columns,
* because the naming of an index's columns may change across PG
@@ -95,10 +95,11 @@ CommentObject(CommentStmt *stmt)
relation->rd_rel->relkind != RELKIND_MATVIEW &&
relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE &&
relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
- relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+ relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE &&
+ relation->rd_rel->relkind != RELKIND_VARIABLE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("\"%s\" is not a table, view, materialized view, composite type, or foreign table",
+ errmsg("\"%s\" is not a table, view, materialized view, composite type, schema variable or foreign table",
RelationGetRelationName(relation))));
break;
default:
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 770c75fe2c..5cd8a8e42c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -1503,6 +1503,9 @@ BeginCopy(ParseState *pstate,
Assert(query->utilityStmt == NULL);
+ /* Don't expect LET stmt here, is not possible to do write it */
+ Assert(query->commandType != CMD_LET);
+
/*
* Similarly the grammar doesn't enforce the presence of a RETURNING
* clause, but this is required here.
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 01a999c2ac..7aa7d835f8 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -18,6 +18,7 @@
#include "commands/async.h"
#include "commands/discard.h"
#include "commands/prepare.h"
+#include "commands/schemavar.h"
#include "commands/sequence.h"
#include "utils/guc.h"
#include "utils/portal.h"
@@ -25,7 +26,7 @@
static void DiscardAll(bool isTopLevel);
/*
- * DISCARD { ALL | SEQUENCES | TEMP | PLANS }
+ * DISCARD { ALL | SEQUENCES | TEMP | PLANS | VARIABLES}
*/
void
DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
@@ -48,6 +49,10 @@ DiscardCommand(DiscardStmt *stmt, bool isTopLevel)
ResetTempTableNamespace();
break;
+ case DISCARD_VARIABLES:
+ ResetSchemaVariablesCache();
+ break;
+
default:
elog(ERROR, "unrecognized DISCARD target: %d", stmt->target);
}
@@ -75,4 +80,5 @@ DiscardAll(bool isTopLevel)
ResetPlanCache();
ResetTempTableNamespace();
ResetSequenceCaches();
+ ResetSchemaVariablesCache();
}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index eecc85d14e..62799ed284 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -126,6 +126,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"TEXT SEARCH TEMPLATE", true},
{"TYPE", true},
{"USER MAPPING", true},
+ {"VARIABLE", true},
{"VIEW", true},
{NULL, false}
};
@@ -1146,6 +1147,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_TSTEMPLATE:
case OBJECT_TYPE:
case OBJECT_USER_MAPPING:
+ case OBJECT_VARIABLE:
case OBJECT_VIEW:
return true;
@@ -2244,6 +2246,8 @@ stringify_grant_objtype(ObjectType objtype)
return "TABLESPACE";
case OBJECT_TYPE:
return "TYPE";
+ case OBJECT_VARIABLE:
+ return "VARIABLE";
/* these currently aren't used */
case OBJECT_ACCESS_METHOD:
case OBJECT_AGGREGATE:
@@ -2326,6 +2330,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 e1a62a1bce..fc4469cb6c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -946,6 +946,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
case CMD_DELETE:
pname = operation = "Delete";
break;
+ case CMD_LET:
+ pname = operation = "Let";
+ break;
default:
pname = "???";
break;
diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c
index b945b1556a..a69471e926 100644
--- a/src/backend/commands/prepare.c
+++ b/src/backend/commands/prepare.c
@@ -151,6 +151,7 @@ PrepareQuery(PrepareStmt *stmt, const char *queryString,
case CMD_INSERT:
case CMD_UPDATE:
case CMD_DELETE:
+ case CMD_LET:
/* OK */
break;
default:
diff --git a/src/backend/commands/schemavar.c b/src/backend/commands/schemavar.c
new file mode 100644
index 0000000000..7ddf91708f
--- /dev/null
+++ b/src/backend/commands/schemavar.c
@@ -0,0 +1,1279 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavar.c
+ * PostgreSQL session variable support code.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/schemavar.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "miscadmin.h"
+#include "funcapi.h"
+
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/objectaddress.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_cast.h"
+#include "catalog/pg_class.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "commands/tablecmds.h"
+#include "commands/schemavar.h"
+#include "nodes/makefuncs.h"
+#include "optimizer/planner.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_type.h"
+#include "rewrite/rewriteHandler.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+/*
+ * This schema variable cache mixes the cache and storages behave. That is not
+ * correct and it is problematic, when variable is removed. The own storage
+ * based on storage manager can be implemented, RelFileNode can be defined and
+ * mechanism based on PendingRelDelete struct can be used. This is a argument
+ * for implementation schema variables based on pg_class.
+ * Alternative solution can be detection of schema changes and recheck at and
+ * of transaction.
+ */
+typedef struct SchemaVarData
+{
+ Oid varid; /* pg_class OID of this sequence (hash key) */
+ Oid typid; /* OID of the data type */
+ int32 typmod;
+ int16 typlen;
+ bool typbyval;
+ bool isnull;
+ bool freeval;
+ Datum value;
+ bool is_rowtype; /* true when variable is composite */
+} SchemaVarData;
+
+typedef SchemaVarData *SchemaVar;
+
+static HTAB *schemavarhashtab = NULL; /* hash table for session variables */
+static MemoryContext SchemaVarMemoryContext = NULL;
+
+static Datum datumCast(Datum value,
+ Oid target_typid, int target_typmod,
+ Oid source_typid, int source_typmod);
+
+static bool first_time = true;
+static bool cache_is_valid = true;
+
+static void InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue);
+static void create_schemavar_hashtable(void);
+
+/* just mark cache to recheck */
+static void
+InvalidateSchemaVarCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ /*
+ * because this cache holds values of schema variables, then
+ * the content cannot be removed in this momemt. We should to
+ * wait on transaction end.
+ */
+ cache_is_valid = false;
+}
+
+/*
+ * Cast datum
+ */
+static Datum
+datumCast(Datum value,
+ Oid target_typid, int target_typmod,
+ Oid source_typid, int source_typmod)
+{
+ CoercionPathType cpathtype;
+ Oid cfuncid;
+ Datum result = (Datum) 0;
+ bool is_binary_cast = false;
+
+ if (target_typid != source_typid)
+ {
+ cpathtype = find_coercion_pathway(target_typid, source_typid,
+ COERCION_EXPLICIT,
+ &cfuncid);
+
+ if (cpathtype == COERCION_PATH_NONE)
+ elog(ERROR, "could not find cast from %s to %s",
+ format_type_be(source_typid),
+ format_type_be(target_typid));
+
+ if (cpathtype == COERCION_PATH_RELABELTYPE)
+ {
+ result = value;
+ is_binary_cast = true;
+ }
+ else if (cpathtype == COERCION_PATH_COERCEVIAIO)
+ {
+ Oid outfunc;
+ Oid infunc;
+ Oid ioparam;
+ bool isVarlena;
+ char *str;
+
+ getTypeOutputInfo(source_typid, &outfunc, &isVarlena);
+ str = OidOutputFunctionCall(outfunc, value);
+
+ getTypeInputInfo(target_typid, &infunc, &ioparam);
+ result = OidInputFunctionCall(infunc, str, ioparam, -1);
+ }
+ else if (cpathtype == COERCION_PATH_FUNC)
+ {
+ result = OidFunctionCall3(cfuncid,
+ value,
+ Int32GetDatum(target_typmod),
+ BoolGetDatum(false));
+ }
+ }
+ else
+ {
+ result = value;
+ is_binary_cast = true;
+ }
+
+ if (target_typmod < 1 || (target_typmod == source_typmod && is_binary_cast))
+ return result;
+
+ cpathtype = find_typmod_coercion_function(target_typid, &cfuncid);
+ if (cpathtype == COERCION_PATH_FUNC)
+ {
+ result = OidFunctionCall3(cfuncid,
+ result,
+ Int32GetDatum(target_typmod),
+ BoolGetDatum(false));
+ }
+
+ return result;
+}
+
+static bool
+sv_get_typ_typmod(Form_pg_class classform, Oid *typ, int32 *typmod)
+{
+ if (classform->relkind != RELKIND_VARIABLE)
+ return false;
+
+ if (classform->reloftype != InvalidOid)
+ {
+ Oid typoid = classform->reloftype;
+
+ if (get_typtype(typoid) == TYPTYPE_COMPOSITE)
+ {
+ *typ = classform->reloftype;
+ *typmod = -1;
+ }
+ else
+ {
+ TupleDesc tupdesc;
+ Relation rel;
+ Form_pg_attribute attrStruct;
+
+ /* find first field */
+ rel = relation_open(typeidTypeRelid(classform->reltype), AccessShareLock);
+ tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+
+ attrStruct = TupleDescAttr(tupdesc, 0);
+
+ *typ = attrStruct->atttypid;
+ *typmod = attrStruct->atttypmod;
+
+ FreeTupleDesc(tupdesc);
+ relation_close(rel, AccessShareLock);
+ }
+ }
+ else
+ {
+ *typ = classform->reltype;
+ *typmod = -1;
+ }
+
+ return true;
+}
+
+void
+schemavar_get_typ_typmod(Oid varid, Oid *typ, int32 *typmod)
+{
+ HeapTuple tp;
+ Form_pg_class classform;
+
+ /* now, type info for schema variable is collected */
+ tp = SearchSysCache1(RELOID, ObjectIdGetDatum(varid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup faild for variable %u", varid);
+
+ classform = (Form_pg_class) GETSTRUCT(tp);
+
+ if (!sv_get_typ_typmod(classform, typ, typmod))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("object %u is not a schema variable", varid)));
+
+ ReleaseSysCache(tp);
+}
+
+bool
+is_schemavar(Oid varid, bool *is_rowtype)
+{
+ HeapTuple tp;
+ Form_pg_class classform;
+ bool result;
+ Oid typ;
+ int32 typmod;
+
+ if (!OidIsValid(varid))
+ return false;
+
+ /* now, type info for schema variable is collected */
+ tp = SearchSysCache1(RELOID, ObjectIdGetDatum(varid));
+ if (!HeapTupleIsValid(tp))
+ elog(ERROR, "cache lookup faild for variable %u", varid);
+
+ classform = (Form_pg_class) GETSTRUCT(tp);
+
+ result = sv_get_typ_typmod(classform, &typ, &typmod);
+
+ if (result && is_rowtype)
+ *is_rowtype = type_is_rowtype(typ);
+
+ ReleaseSysCache(tp);
+
+ return result;
+}
+
+
+static SchemaVar
+PrepareSchemaVar(Oid varid)
+{
+ SchemaVar var;
+ bool found;
+
+ if (schemavarhashtab == NULL)
+ create_schemavar_hashtable();
+
+ var = (SchemaVar) hash_search(schemavarhashtab, &varid,
+ HASH_ENTER, &found);
+ if (!found)
+ {
+ Relation rel;
+ TupleDesc desc;
+ int natts;
+ Datum *values;
+ bool *nulls;
+ EState *estate = NULL;
+ MemoryContext oldcontext = NULL;
+ int nnotnulls = 0;
+ int i;
+
+ var->varid = varid;
+
+ schemavar_get_typ_typmod(varid, &var->typid, &var->typmod);
+ get_typlenbyval(var->typid, &var->typlen, &var->typbyval);
+
+ var->is_rowtype = type_is_rowtype(var->typid);
+
+ /* prepare default value */
+ rel = relation_open(varid, AccessShareLock);
+ desc = RelationGetDescr(rel);
+ natts = desc->natts;
+
+ estate = CreateExecutorState();
+ oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+ values = (Datum *) palloc0(natts * sizeof(Datum));
+ nulls = (bool *) palloc0(natts * sizeof(bool));
+
+ for (i = 0; i < natts; i++)
+ {
+ Expr *defexpr;
+ ExprState *defexprs;
+
+ if (TupleDescAttr(desc, i)->attisdropped)
+ {
+ nulls[i] = true;
+ continue;
+ }
+
+ defexpr = (Expr *) build_column_default(rel, i + 1);
+ if (defexpr != NULL)
+ {
+ defexpr = expression_planner(defexpr);
+ defexprs = ExecInitExpr(defexpr, NULL);
+
+ values[i] = ExecEvalExprSwitchContext(defexprs, GetPerTupleExprContext(estate), &nulls[i]);
+ if (!nulls[i])
+ nnotnulls++;
+ }
+ else
+ nulls[i] = true;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (nnotnulls > 0)
+ {
+ Datum value;
+
+ if (var->is_rowtype)
+ {
+ HeapTuple tuple;
+
+ tuple = heap_form_tuple(desc, values, nulls);
+ value = HeapTupleHeaderGetDatum((HeapTupleHeader) tuple->t_data);
+ }
+ else
+ value = values[0];
+
+ MemoryContextSwitchTo(SchemaVarMemoryContext);
+ var->value = datumCopy(value, var->typbyval, var->typlen);
+ var->isnull = false;
+ var->freeval = var->value != values[0];
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+ else
+ {
+ var->value = (Datum) 0;
+ var->isnull = true;
+ var->freeval = false;
+ }
+
+ FreeExecutorState(estate);
+ relation_close(rel, AccessShareLock);
+ }
+
+ return var;
+}
+
+/*
+ * Wait on commit or rollback and clean values that miss entry in system
+ * catalog. It is temporary solution (although it is working). Storage manager
+ * based solution will be better, but it is not necessary for this PoC.
+ *
+ * removes uncommitted or dropped schema variables, so event can be ignored.
+ */
+static void
+recheck_schema_variables(XactEvent event, void *arg)
+{
+ HASH_SEQ_STATUS status;
+ SchemaVar var;
+
+ if (cache_is_valid || schemavarhashtab == NULL || !IsTransactionState())
+ return;
+
+ hash_seq_init(&status, schemavarhashtab);
+
+ while ((var = (SchemaVar) hash_seq_search(&status)) != NULL)
+ {
+ HeapTuple tp = InvalidOid;
+
+ tp = SearchSysCache1(RELOID, ObjectIdGetDatum(var->varid));
+ if (!HeapTupleIsValid(tp))
+ {
+ elog(DEBUG1, "variable %d is removed from cache", var->varid);
+
+ if (var->freeval)
+ {
+ pfree(DatumGetPointer(var->value));
+ var->freeval = false;
+ }
+
+ if (hash_search(schemavarhashtab,
+ (void *) &var->varid,
+ HASH_REMOVE,
+ NULL) == NULL)
+ elog(ERROR, "hash table corrupted");
+ }
+ else
+ ReleaseSysCache(tp);
+ }
+ cache_is_valid = true;
+}
+
+/*
+ * DefineSessionVariable
+ * Creates a new variable related relation
+ */
+ObjectAddress
+DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *var)
+{
+ CreateStmt *stmt = makeNode(CreateStmt);
+ Oid typoid;
+ Oid varoid;
+ ObjectAddress address;
+ bool create_cast;
+
+ /*
+ * If if_not_exists was given and a relation with the same name already
+ * exists, bail out. (Note: we needn't check this when not if_not_exists,
+ * because DefineRelation will complain anyway.)
+ */
+ if (var->if_not_exists)
+ {
+ RangeVarGetAndCheckCreationNamespace(var->variable, NoLock, &varoid);
+ if (OidIsValid(varoid))
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_TABLE),
+ errmsg("variable \"%s\" already exists, skipping",
+ var->variable->relname)));
+ return InvalidObjectAddress;
+ }
+ }
+
+ /*
+ * Variable can be defined by type (scalar, composite) or by
+ * field list. When is defined by field list, we should to create
+ * new type related to this variable.
+ */
+ if (var->typeName != NULL)
+ {
+ Oid typrelid;
+
+ Assert(var->fields == NIL);
+
+ typoid = LookupTypeNameOid(pstate, var->typeName, false);
+
+ if (get_typtype(typoid) == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable cannot be %s",
+ format_type_be(varoid))));
+
+ typrelid = typeidTypeRelid(typoid);
+
+ if (!OidIsValid(typrelid))
+ {
+ /*
+ * when variable type is not composite type, we should to create
+ * aux composite type.
+ */
+ ColumnDef *coldef = makeNode(ColumnDef);
+
+ coldef->inhcount = 0;
+ coldef->is_local = true;
+ coldef->is_not_null = false;
+ coldef->is_from_type = false;
+ coldef->storage = 0;
+ coldef->raw_default = var->defexpr;
+ coldef->cooked_default = NULL;
+ coldef->collClause = NULL;
+ coldef->collOid = InvalidOid;
+ coldef->constraints = NIL;
+ coldef->location = -1;
+
+ /* Use implit name same like variable name */
+ coldef->colname = var->variable->relname;
+ coldef->typeName = var->typeName;
+
+ stmt->tableElts = lappend(stmt->tableElts, coldef);
+
+ create_cast = false;
+ }
+ else
+ {
+ TupleDesc tupdesc;
+ Relation rel;
+ int i;
+
+ rel = relation_open(typrelid, AccessShareLock);
+ tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attrStruct = TupleDescAttr(tupdesc, i);
+
+ if (!attrStruct->attisdropped)
+ {
+ ColumnDef *coldef = makeNode(ColumnDef);
+
+ coldef->inhcount = 0;
+ coldef->is_local = true;
+ coldef->is_not_null = false;
+ coldef->is_from_type = false;
+ coldef->storage = 0;
+ coldef->raw_default = NULL;
+ coldef->cooked_default = NULL;
+ coldef->collClause = NULL;
+ coldef->collOid = InvalidOid;
+ coldef->constraints = NIL;
+ coldef->location = -1;
+
+ coldef->colname = pstrdup(NameStr(attrStruct->attname));
+ coldef->typeName = makeTypeNameFromOid(attrStruct->atttypid,
+ attrStruct->atttypmod);
+
+ stmt->tableElts = lappend(stmt->tableElts, coldef);
+ }
+ }
+
+ FreeTupleDesc(tupdesc);
+ relation_close(rel, AccessShareLock);
+
+ create_cast = true;
+ }
+ }
+ else
+ {
+ Assert(var->typeName == NULL);
+ Assert(var->fields != NIL);
+
+ stmt->tableElts = var->fields;
+
+ create_cast = false;
+ }
+
+ stmt->relation = var->variable;
+ stmt->inhRelations = NIL;
+ stmt->constraints = NIL;
+ stmt->options = NIL;
+ stmt->oncommit = ONCOMMIT_NOOP;
+ stmt->tablespacename = NULL;
+ stmt->if_not_exists = var->if_not_exists;
+
+ /*
+ * Schema variables uses reltype and reloftype fields. The reltype is
+ * composite always for scalar and non scalar variables. It is used when
+ * we need a variable's tuple descriprion. When variable type is composite,
+ * then is referenced without change. Scalar variable V is translated to
+ * composite type (v_name v_type).
+ * The reltype is always composite - see catalog/heap.c AddNewRelationType,
+ * but for schema variables based on some type is this behave unwanted.
+ * variable type is not compatible with base type. So in this case is better
+ * define reltype as domain of base type. But this change break consistency
+ * of reltype - so I changed nothing and implicitly generate necessary casts.
+ */
+ stmt->ofTypename = var->typeName;
+
+ address = DefineRelation(stmt, RELKIND_VARIABLE, InvalidOid, NULL, NULL);
+ Assert(address.objectId != InvalidOid);
+
+ if (create_cast)
+ {
+ ObjectAddress c1, c2, referenced_t1, referenced_t2;
+ TypeName *t1 = var->typeName;
+ TypeName *t2 = makeTypeNameFromOid(get_rel_type_id(address.objectId), -1);
+
+ /*
+ * This is ugly part - but I should to teach Postgres, so t1 and t2 types
+ * are castable. Unfortunatelly, CreateCast cannot to change dependency,
+ * so fresh dependency should be removed and recreated with DEPENDENCY_AUTO
+ * flag. In this case, we are sure so both types are composite, so maybe less
+ * ugly can be use reltype as domain type of base type instead copy.
+ */
+ CreateCastStmt *n = makeNode(CreateCastStmt);
+
+ n->sourcetype = t1;
+ n->targettype = t2;
+ n->func = NULL;
+ n->context = COERCION_IMPLICIT;
+ n->inout = true;
+
+ c1 = CreateCast(n);
+
+ n->sourcetype = t2;
+ n->targettype = t1;
+
+ c2 = CreateCast(n);
+
+ deleteDependencyRecordsFor(CastRelationId, c1.objectId, false);
+ deleteDependencyRecordsFor(CastRelationId, c2.objectId, false);
+
+ referenced_t1.classId = TypeRelationId;
+ referenced_t1.objectId = get_rel_type_id(address.objectId);
+ referenced_t1.objectSubId = 0;
+
+ referenced_t2.classId = TypeRelationId;
+ referenced_t2.objectId = LookupTypeNameOid(pstate, var->typeName, false);
+ referenced_t2.objectSubId = 0;
+
+ recordDependencyOn(&c1, &referenced_t1, DEPENDENCY_AUTO);
+ recordDependencyOn(&c1, &referenced_t2, DEPENDENCY_AUTO);
+ recordDependencyOn(&c2, &referenced_t1, DEPENDENCY_AUTO);
+ recordDependencyOn(&c2, &referenced_t2, DEPENDENCY_AUTO);
+ }
+
+ return address;
+}
+
+/*
+ * Implementation of schemavar cache. It is question if it should be in this place, or
+ * it should be storage related or cache related place? But for this moment (PoC) it
+ * can be here. Cache is implemented as hash table with own memory context.
+ */
+
+/*
+ * Create the hash table for storing schema variables
+ */
+static void
+create_schemavar_hashtable(void)
+{
+ HASHCTL ctl;
+
+ /* set callbacks */
+ if (first_time)
+ {
+
+ CacheRegisterSyscacheCallback(RELOID,
+ InvalidateSchemaVarCacheCallback,
+ (Datum) 0);
+ RegisterXactCallback(recheck_schema_variables, NULL);
+
+ first_time = false;
+ }
+
+ /* needs own long life memory context */
+ if (SchemaVarMemoryContext == NULL)
+ {
+ SchemaVarMemoryContext = AllocSetContextCreate(TopMemoryContext,
+ "schema variables",
+ ALLOCSET_START_SMALL_SIZES);
+ }
+
+ memset(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(SchemaVarData);
+ ctl.hcxt = SchemaVarMemoryContext;
+
+ schemavarhashtab = hash_create("Schema variables", 64, &ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ cache_is_valid = true;
+}
+
+/*
+ * Fast drop complete content of schema variables
+ */
+void
+ResetSchemaVariablesCache(void)
+{
+ if (schemavarhashtab)
+ {
+ hash_destroy(schemavarhashtab);
+ schemavarhashtab = NULL;
+ }
+
+ if (SchemaVarMemoryContext != NULL)
+ {
+ MemoryContextReset(SchemaVarMemoryContext);
+ }
+}
+
+/*
+ * Copy datum value to schema variables cache place
+ */
+static void
+SetValue(SchemaVar var,
+ Datum value, bool isNull,
+ Oid typid, int32 typmod)
+{
+ /* release previously stored value */
+ if (var->freeval)
+ {
+ pfree(DatumGetPointer(var->value));
+ var->freeval = false;
+ }
+
+ if (!isNull)
+ {
+ MemoryContext oldcxt;
+
+ /*
+ * cast the value if conversion is necessary.
+ * Expecting: current context is short context.
+ *
+ * QUESTION: how much should be this cast tolerant/strict?
+ */
+ if (var->typid != typid || var->typmod != typmod)
+ {
+ value = datumCast(value,
+ var->typid, var->typmod,
+ typid, typmod);
+ }
+
+ var->isnull = false;
+
+ oldcxt = MemoryContextSwitchTo(SchemaVarMemoryContext);
+
+ var->value = datumCopy(value, var->typbyval, var->typlen);
+ if (var->value != value)
+ var->freeval = true;
+
+ MemoryContextSwitchTo(oldcxt);
+ }
+ else
+ {
+ var->value = (Datum) 0;
+ var->isnull = true;
+ }
+}
+
+/*
+ * Returns variable name
+ */
+char *
+get_schemavar_name(Oid varid)
+{
+ HeapTuple relTup;
+ Form_pg_class relForm;
+ char *nspname;
+ char *relname;
+
+ relTup = SearchSysCache1(RELOID,
+ ObjectIdGetDatum(varid));
+ if (!HeapTupleIsValid(relTup))
+ elog(ERROR, "cache lookup failed for schema variable %u", varid);
+ relForm = (Form_pg_class) GETSTRUCT(relTup);
+
+ /* Qualify the name if not visible in search path */
+ if (RelationIsVisible(varid))
+ nspname = NULL;
+ else
+ nspname = get_namespace_name(relForm->relnamespace);
+
+ relname = quote_qualified_identifier(nspname, NameStr(relForm->relname));
+
+ ReleaseSysCache(relTup);
+
+ return relname;
+}
+
+/*
+ * Access functions to schema variables.
+ */
+void
+SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod)
+{
+ SchemaVar var;
+
+ var = PrepareSchemaVar(varid);
+ SetValue(var, value, isNull, typid, typmod);
+}
+
+/*
+ * Securized versions SetSchemaVariable
+ */
+void
+SetSchemaVariableSecure(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod)
+{
+ AclResult aclresult;
+
+ /* Check permissions */
+ aclresult = pg_class_aclcheck(varid, GetUserId(), ACL_UPDATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(varid));
+
+ SetSchemaVariable(varid, value, isNull, typid, typmod);
+}
+
+Datum
+GetSchemaVariable(Oid varid, bool *isNull,
+ Oid typid, int32 typmod,
+ int16 typlen, bool typbyval)
+{
+ SchemaVar var;
+ Datum result = (Datum) 0;
+
+ var = PrepareSchemaVar(varid);
+
+ if (!var->isnull)
+ {
+ result = datumCast(var->value, typid, typmod,
+ var->typid, var->typmod);
+ *isNull = false;
+
+ if (result == var->value)
+ result = datumCopy(result, typbyval, typlen);
+ }
+ else
+ *isNull = true;
+
+ return result;
+}
+
+/*
+ * Securized version of GetSchemaVariable
+ */
+Datum
+GetSchemaVariableSecure(Oid varid, bool *isNull,
+ Oid typid, int32 typmod,
+ int16 typlen, bool typbyval)
+{
+ AclResult aclresult;
+
+ /* Check permissions */
+ aclresult = pg_class_aclcheck(varid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(varid));
+
+ return GetSchemaVariable(varid, isNull, typid, typmod, typlen, typbyval);
+}
+
+/*
+ * V1 function API
+ *
+ * void set_schema_variable(var regclass, value anyelement);
+ * anyelement get_schema_variable(var regclass, expected_type anyelement)
+ *
+ */
+Datum
+set_schema_variable(PG_FUNCTION_ARGS)
+{
+ Oid varid;
+ Datum value;
+ bool isNull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for variable identity")));
+
+ varid = PG_GETARG_OID(0);
+
+ if (!PG_ARGISNULL(1))
+ {
+ value = PG_GETARG_DATUM(1);
+ isNull = false;
+ }
+ else
+ {
+ value = (Datum) 0;
+ isNull = true;
+ }
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (typid == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine input data type")));
+
+ get_typlenbyval(typid, &typlen, &typbyval);
+ SetSchemaVariableSecure(varid, value, isNull, typid, -1);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+get_schema_variable(PG_FUNCTION_ARGS)
+{
+ Oid varid;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ bool isNull;
+ Datum result;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for variable identity")));
+
+ varid = PG_GETARG_OID(0);
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (typid == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine input data type")));
+
+ get_typlenbyval(typid, &typlen, &typbyval);
+ result = GetSchemaVariableSecure(varid, &isNull, typid, -1, typlen, typbyval);
+
+ if (isNull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(result);
+}
+
+/*
+ * Access functions to fields of composite variables
+ */
+void
+SetSchemaVariableField(Oid varid, const char *fieldname,
+ Datum value, bool isNull,
+ Oid typid, int32 typmod)
+{
+
+ SchemaVar var;
+ TupleDesc tupDesc;
+ Form_pg_attribute sattr = NULL;
+ Datum *values;
+ bool *nulls;
+ int natts;
+ HeapTuple tuple;
+ int i;
+
+ var = PrepareSchemaVar(varid);
+
+ if (!var->is_rowtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("the variable \"%s\" is not of row type",
+ get_schemavar_name(varid))));
+
+ tupDesc = lookup_rowtype_tupdesc(var->typid, var->typmod);
+ natts = tupDesc->natts;
+
+ /*
+ * We should to check correct field name, although result can be NULL,
+ * and this information will not be used.
+ */
+ for (i = 0; i < natts; i++)
+ {
+ if (namestrcmp(&(TupleDescAttr(tupDesc, i)->attname), fieldname) == 0 &&
+ !TupleDescAttr(tupDesc, i)->attisdropped)
+ {
+ sattr = TupleDescAttr(tupDesc, i);
+ break;
+ }
+ }
+
+ if (sattr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("the variable \"%s\" has not field \"%s\"",
+ get_schemavar_name(varid),
+ fieldname)));
+
+ /* leave, when we don't need to change composite */
+ if (var->isnull && isNull)
+ {
+ FreeTupleDesc(tupDesc);
+ return;
+ }
+
+ values = (Datum *) palloc0(natts * sizeof(Datum));
+ nulls = (bool *) palloc0(natts * sizeof(bool));
+
+ if (!var->isnull)
+ {
+ HeapTupleHeader rec = DatumGetHeapTupleHeader(var->value);
+ HeapTupleData tup;
+
+ /* Build a temporary HeapTuple control structure */
+ tup.t_len = HeapTupleHeaderGetDatumLength(rec);
+ ItemPointerSetInvalid(&(tup.t_self));
+ tup.t_tableOid = InvalidOid;
+ tup.t_data = rec;
+
+ heap_deform_tuple(&tup, tupDesc, values, nulls);
+ }
+ else
+ {
+ for (i = 0; i < natts; i++)
+ nulls[i] = true;
+ }
+
+ if (!isNull)
+ {
+ values[sattr->attnum - 1] = datumCast(value,
+ sattr->atttypid, sattr->atttypmod,
+ typid, typmod);
+ nulls[sattr->attnum - 1] = false;
+ }
+ else
+ {
+ values[sattr->attnum - 1] = (Datum) 0;
+ nulls[sattr->attnum - 1] = true;
+ }
+
+ tuple = heap_form_tuple(tupDesc, values, nulls);
+ SetValue(var,
+ HeapTupleHeaderGetDatum((HeapTupleHeader) tuple->t_data),
+ false,
+ var->typid, var->typmod);
+
+ heap_freetuple(tuple);
+ ReleaseTupleDesc(tupDesc);
+}
+
+Datum
+GetSchemaVariableField(Oid varid, const char *fieldname,
+ bool *isNull,
+ Oid typid, int32 typmod,
+ int16 typlen, bool typbyval)
+{
+ Datum result = (Datum) 0;
+ Datum value = (Datum) 0;
+ TupleDesc tupDesc;
+ Form_pg_attribute sattr = NULL;
+ SchemaVar var;
+ int i;
+
+ Assert(varid != InvalidOid);
+
+ var = PrepareSchemaVar(varid);
+
+ if (!var->is_rowtype)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("the variable \"%s\" is not of row type",
+ get_schemavar_name(varid))));
+
+ tupDesc = lookup_rowtype_tupdesc(var->typid, var->typmod);
+
+ /*
+ * We should to check correct field name, although result can be NULL,
+ * and this information will not be used.
+ */
+ for (i = 0; i < tupDesc->natts; i++)
+ {
+ if (namestrcmp(&(TupleDescAttr(tupDesc, i)->attname), fieldname) == 0 &&
+ !TupleDescAttr(tupDesc, i)->attisdropped)
+ {
+ sattr = TupleDescAttr(tupDesc, i);
+ break;
+ }
+ }
+
+ if (sattr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("the variable \"%s\" has not field \"%s\"",
+ get_schemavar_name(varid),
+ fieldname)));
+
+ /*
+ * Theoretically, we check a possibility to cast to target type everytime,
+ * but can be ignored, when result will be null.
+ */
+ if (!var->isnull)
+ {
+ HeapTupleHeader tuple;
+ HeapTupleData tmptup;
+
+ tuple = DatumGetHeapTupleHeader(var->value);
+
+ /*
+ * heap_getattr needs a HeapTuple not a bare HeapTupleHeader. We set all
+ * the fields in the struct just in case user tries to inspect system
+ * columns.
+ */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
+ ItemPointerSetInvalid(&(tmptup.t_self));
+ tmptup.t_tableOid = InvalidOid;
+ tmptup.t_data = tuple;
+
+ value = heap_getattr(&tmptup,
+ sattr->attnum,
+ tupDesc,
+ isNull);
+
+ if (!*isNull)
+ {
+ result = datumCast(value, typid, typmod,
+ sattr->atttypid, sattr->atttypmod);
+
+ if (result == value)
+ result = datumCopy(result, typbyval, typlen);
+ }
+ }
+
+ ReleaseTupleDesc(tupDesc);
+
+ return result;
+}
+
+void
+SetSchemaVariableFieldSecure(Oid varid, const char *fieldname,
+ Datum value, bool isNull,
+ Oid typid, int32 typmod)
+{
+ AclResult aclresult;
+
+ /* Check permissions */
+ aclresult = pg_class_aclcheck(varid, GetUserId(), ACL_UPDATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(varid));
+
+ SetSchemaVariableField(varid, fieldname, value, isNull, typid, typmod);
+}
+
+Datum
+GetSchemaVariableFieldSecure(Oid varid, const char *fieldname,
+ bool *isNull,
+ Oid typid, int32 typmod,
+ int16 typlen, bool typbyval)
+{
+ AclResult aclresult;
+
+ /*
+ * There are not any special access to fields. Just check a
+ * access to variable.
+ */
+ aclresult = pg_class_aclcheck(varid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(varid));
+
+ return GetSchemaVariableField(varid, fieldname, isNull, typid, typmod, typlen, typbyval);
+}
+
+Datum
+set_schema_variable_field(PG_FUNCTION_ARGS)
+{
+ Oid varid;
+ Datum value;
+ bool isNull;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ const char *fieldname;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for variable identity")));
+
+ varid = PG_GETARG_OID(0);
+
+ if (PG_ARGISNULL(1))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for field name")));
+
+ fieldname = NameStr(*PG_GETARG_NAME(1));
+
+ if (!PG_ARGISNULL(2))
+ {
+ value = PG_GETARG_DATUM(2);
+ isNull = false;
+ }
+ else
+ {
+ value = (Datum) 0;
+ isNull = true;
+ }
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 2);
+ if (typid == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine input data type")));
+
+ get_typlenbyval(typid, &typlen, &typbyval);
+ SetSchemaVariableFieldSecure(varid, fieldname, value, isNull, typid, -1);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+get_schema_variable_field(PG_FUNCTION_ARGS)
+{
+ Oid varid;
+ Oid typid;
+ int16 typlen;
+ bool typbyval;
+ bool isNull;
+ Datum result;
+ const char *fieldname;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for variable identity")));
+
+ varid = PG_GETARG_OID(0);
+
+ if (PG_ARGISNULL(1))
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value not allowed for field name")));
+
+ fieldname = NameStr(*PG_GETARG_NAME(1));
+
+ typid = get_fn_expr_argtype(fcinfo->flinfo, 2);
+ if (typid == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine input data type")));
+
+ get_typlenbyval(typid, &typlen, &typbyval);
+ result = GetSchemaVariableFieldSecure(varid, fieldname, &isNull, typid, -1, typlen, typbyval);
+
+ if (isNull)
+ PG_RETURN_NULL();
+ else
+ PG_RETURN_DATUM(result);
+
+ PG_RETURN_NULL();
+}
+
+/*
+ * Results:
+ *=
+
+1. The schema variables are similar to temporary tables - but the data are not saved
+ in 8KB blocks, so new storage and some simple storage manager should be created. We
+ would not to use local buffer due allocation 8KB as minimun, creating temp file.
+
+2. We should to work with typmod, so pg_attribute entry should be created anytime.
+
+3. A risk of collisions of variable and table name will be reduced, when variables
+ and tables cannot to have same name.
+
+4. If schema variables are pg_class based, then some current syntax has sense
+
+ We can support UPDATE and SELECT commands on variables.
+
+ possible syntaxes:
+
+ -- there can be a analogy with record functions
+ SELECT varname;
+ SELECT varname.field;
+ SELECT varname FROM varname; -- when var is scalar, then varname in target list is scalar
+
+ UPDATE varname SET varname = 10; -- scalar version
+ UPDATE varname SET varname = (x,y,z) -- composite version
+ UPDATE varname SET varname.field1 = x, varname.field2 = y, ..
+
+ LET varname = expr;
+ LET varname.field1 = expr;
+ LET varname = (x, y, z); -- composite version
+
+ Possible
+
+ LET varname(a,b) = (select (10, 20) FROM ... )
+
+
+5. LET cmd can be implemented as CMD (like INSERT, UPDATE, DELETE) or Utility (like
+ CreateTableAsSelect). Prefer first option, because there can be prepared, can be
+ used together with EXPLAIN, etc.
+
+ Expected form:
+ LET foo = (SELECT id FROM boo WHERE some = 'hello');
+
+ so possibility to run EXPLAIN LET .. has good enough benefit
+
+6. What should be mechanism of compatibility between base type and variable type?
+
+ CREATE VARIABLE xx AS BT
+
+ Example: CREATE TYPE ct AS (x int, int); CREATE VARIABLE var AS ct;
+ var type (implicitly created) should be compatible with ct type, because we want
+
+ LET var = (10,20)::var; LET var = (10,20)::ct;
+
+7. When LET command allows to specify target field, then there is possible collision
+ I1.I2 can be schema.variable, but should be variable.field too. It should be tested
+ and if both variants are possible, then exception should be raised. LET var.field = expr
+ is a analogy to UPDATE var SET field = expr.
+
+
+*/
\ No newline at end of file
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 0e95037dcf..0af558c3cd 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -272,6 +272,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = {
gettext_noop("index \"%s\" does not exist, skipping"),
gettext_noop("\"%s\" is not an index"),
gettext_noop("Use DROP INDEX to remove an index.")},
+ {RELKIND_VARIABLE,
+ ERRCODE_UNDEFINED_TABLE,
+ gettext_noop("schema variable \"%s\" does not exist"),
+ gettext_noop("schema variable \"%s\" does not exist, skipping"),
+ gettext_noop("\"%s\" is not a schema variable"),
+ gettext_noop("Use DROP VARIABLE to remove a variable.")},
{'\0', 0, NULL, NULL, NULL, NULL}
};
@@ -10268,6 +10274,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock
case RELKIND_MATVIEW:
case RELKIND_FOREIGN_TABLE:
case RELKIND_PARTITIONED_TABLE:
+ case RELKIND_VARIABLE:
/* ok to change owner */
break;
case RELKIND_INDEX:
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index e284fd71d7..6f5aa3993e 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/schemavar.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
@@ -727,6 +728,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
{
Param *param = (Param *) node;
ParamListInfo params;
+ AclResult aclresult;
switch (param->paramkind)
{
@@ -734,6 +736,23 @@ ExecInitExprRec(Expr *node, ExprState *state,
scratch.opcode = EEOP_PARAM_EXEC;
scratch.d.param.paramid = param->paramid;
scratch.d.param.paramtype = param->paramtype;
+ ExprEvalPushStep(state, &scratch);
+ break;
+ case PARAM_SCHEMA_VARIABLE:
+ /* Check permission to read schema variable */
+ aclresult = pg_class_aclcheck(param->paramid, GetUserId(), ACL_SELECT);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_VARIABLE, get_schemavar_name(param->paramid));
+
+ scratch.opcode = EEOP_PARAM_SCHEMA_VARIABLE;
+ scratch.d.param.paramid = param->paramid;
+ scratch.d.param.paramtype = param->paramtype;
+ scratch.d.param.paramtypmod = param->paramtypmod;
+
+ get_typlenbyval(param->paramtype,
+ &scratch.d.param.paramtyplen,
+ &scratch.d.param.paramtypbyval);
+
ExprEvalPushStep(state, &scratch);
break;
case PARAM_EXTERN:
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 9d6e25aae5..d6d1867dfa 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -59,6 +59,7 @@
#include "access/tuptoaster.h"
#include "catalog/pg_type.h"
#include "commands/sequence.h"
+#include "commands/schemavar.h"
#include "executor/execExpr.h"
#include "executor/nodeSubplan.h"
#include "funcapi.h"
@@ -351,6 +352,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
&&CASE_EEOP_PARAM_EXEC,
&&CASE_EEOP_PARAM_EXTERN,
&&CASE_EEOP_PARAM_CALLBACK,
+ &&CASE_EEOP_PARAM_SCHEMA_VARIABLE,
&&CASE_EEOP_CASE_TESTVAL,
&&CASE_EEOP_MAKE_READONLY,
&&CASE_EEOP_IOCOERCE,
@@ -1000,6 +1002,23 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_NEXT();
}
+ EEO_CASE(EEOP_PARAM_SCHEMA_VARIABLE)
+ {
+ Datum d;
+ bool isnull;
+
+ d = GetSchemaVariable(op->d.param.paramid, &isnull,
+ op->d.param.paramtype,
+ -1,
+ op->d.param.paramtyplen,
+ op->d.param.paramtypbyval);
+
+ *op->resvalue = d;
+ *op->resnull = isnull;
+
+ EEO_NEXT();
+ }
+
EEO_CASE(EEOP_PARAM_CALLBACK)
{
/* allow an extension module to supply a PARAM_EXTERN value */
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 51d5bd01d3..8fbc38e4bb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -211,6 +211,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
@@ -775,7 +776,8 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
/*
* Fail if write permissions are requested in parallel mode for table
- * (temp or non-temp), otherwise fail for any non-temp table.
+ * (temp or non-temp), otherwise fail for any non-temp table. Allow to
+ * modify content of schema variables.
*/
foreach(l, plannedstmt->rtable)
{
@@ -790,6 +792,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (rte->relkind == RELKIND_VARIABLE)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandTag((Node *) plannedstmt));
}
@@ -1125,6 +1130,14 @@ CheckValidResultRel(ResultRelInfo *resultRelInfo, CmdType operation)
errmsg("cannot change TOAST relation \"%s\"",
RelationGetRelationName(resultRel))));
break;
+ case RELKIND_VARIABLE:
+ /* only LET command can change a variable */
+ if (operation != CMD_LET)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot change schema variable \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
case RELKIND_VIEW:
/*
@@ -1285,6 +1298,13 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
errmsg("cannot lock rows in foreign table \"%s\"",
RelationGetRelationName(rel))));
break;
+ case RELKIND_VARIABLE:
+ /* Must disallow this because we there are not rows */
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot lock rows in schema variable \"%s\"",
+ RelationGetRelationName(rel))));
+ break;
default:
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index f6482f8411..c44ee540b9 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -39,6 +39,7 @@
#include "access/htup_details.h"
#include "access/xact.h"
+#include "commands/schemavar.h"
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
@@ -73,6 +74,7 @@ static void ExecSetupChildParentMapForSubplan(ModifyTableState *mtstate);
static TupleConversionMap *tupconv_map_for_subplan(ModifyTableState *node,
int whichplan);
+
/*
* Verify that the tuples to be produced by INSERT or UPDATE match the
* target relation's rowtype
@@ -1516,6 +1518,72 @@ ExecOnConflictUpdate(ModifyTableState *mtstate,
return true;
}
+/* ----------------------------------------------------------------
+ * ExecLet
+ *
+ * For LET, we have to update target variable,
+ * Returns NULL, there are not RETURNING clause.
+ * ----------------------------------------------------------------
+ */
+static TupleTableSlot *
+ExecLet(ModifyTableState *mtstate,
+ TupleTableSlot *slot,
+ EState *estate,
+ bool canSetTag)
+{
+ ResultRelInfo *resultRelInfo;
+ Relation resultRelationDesc;
+ TupleDesc tupdesc;
+ bool isnull = true;
+ Datum value;
+ Form_pg_attribute attr = NULL;
+ Oid varid;
+
+ if (slot != NULL && !slot->tts_isempty)
+ {
+ tupdesc = slot->tts_tupleDescriptor;
+
+ Assert(tupdesc != NULL);
+
+ /* should be checked before */
+ if (tupdesc->natts != 1)
+ elog(ERROR, "unexpected number of attributes");
+
+ attr = TupleDescAttr(tupdesc, 0);
+
+ if (!slot->tts_isnull[0])
+ {
+ isnull = false;
+ value = slot->tts_values[0];
+ }
+ }
+
+ /*
+ * Now, es_result_relation_info is empty, but can be initialized
+ * to structure of used schema variable.
+ */
+ resultRelInfo = estate->es_result_relation_info;
+ resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ varid = resultRelationDesc->rd_id;
+
+ if (!isnull)
+ {
+ /* expecting so variable and expression are equal */
+ SetSchemaVariable(varid, value, isnull, attr->atttypid, -1);
+ }
+ else
+ {
+ SetSchemaVariable(varid, (Datum) 0, true, InvalidOid, -1);
+ }
+
+ if (canSetTag)
+ {
+ Assert(estate->es_processed == 0);
+ (estate->es_processed)++;
+ }
+
+ return NULL;
+}
/*
* Process BEFORE EACH STATEMENT triggers
@@ -1548,6 +1616,9 @@ fireBSTriggers(ModifyTableState *node)
case CMD_DELETE:
ExecBSDeleteTriggers(node->ps.state, resultRelInfo);
break;
+ case CMD_LET:
+ /* there are no trigger */
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -1603,6 +1674,9 @@ fireASTriggers(ModifyTableState *node)
ExecASDeleteTriggers(node->ps.state, resultRelInfo,
node->mt_transition_capture);
break;
+ case CMD_LET:
+ /* variables has not triggers */
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -2142,6 +2216,9 @@ ExecModifyTable(PlanState *pstate)
NULL, true, node->canSetTag,
false /* changingPart */ );
break;
+ case CMD_LET:
+ slot = ExecLet(node, slot, estate, node->canSetTag);
+ break;
default:
elog(ERROR, "unknown operation");
break;
@@ -2552,6 +2629,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
break;
case CMD_UPDATE:
case CMD_DELETE:
+ case CMD_LET:
junk_filter_needed = true;
break;
default:
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 08f6f67a15..e107abf6af 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -2420,6 +2420,9 @@ _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, uint64 tcount)
else
res = SPI_OK_UPDATE;
break;
+ case CMD_LET:
+ res = SPI_OK_UTILITY;
+ break;
default:
return SPI_ERROR_OPUNKNOWN;
}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 7c045a7afe..cfa6523c9d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3103,6 +3103,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)
{
@@ -5148,6 +5160,9 @@ copyObjectImpl(const void *from)
case T_SelectStmt:
retval = _copySelectStmt(from);
break;
+ case T_LetStmt:
+ retval = _copyLetStmt(from);
+ break;
case T_SetOperationStmt:
retval = _copySetOperationStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 6a971d0141..c701aeddf1 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1057,6 +1057,16 @@ _equalSelectStmt(const SelectStmt *a, const SelectStmt *b)
return true;
}
+static bool
+_equalLetStmt(const LetStmt *a, const LetStmt *b)
+{
+ COMPARE_NODE_FIELD(target);
+ COMPARE_NODE_FIELD(selectStmt);
+ COMPARE_LOCATION_FIELD(location);
+
+ return true;
+}
+
static bool
_equalSetOperationStmt(const SetOperationStmt *a, const SetOperationStmt *b)
{
@@ -3225,6 +3235,9 @@ equal(const void *a, const void *b)
case T_SelectStmt:
retval = _equalSelectStmt(a, b);
break;
+ case T_LetStmt:
+ retval = _equalLetStmt(a, b);
+ break;
case T_SetOperationStmt:
retval = _equalSetOperationStmt(a, b);
break;
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index a10014f755..a931237c1b 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -3469,6 +3469,16 @@ raw_expression_tree_walker(Node *node,
return true;
}
break;
+ case T_LetStmt:
+ {
+ LetStmt *stmt = (LetStmt *) node;
+
+ if (walker(stmt->target, context))
+ return true;
+ if (walker(stmt->selectStmt, context))
+ return true;
+ }
+ break;
case T_A_Expr:
{
A_Expr *expr = (A_Expr *) node;
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 505ae0af85..aee16f4676 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -1249,12 +1249,15 @@ max_parallel_hazard_walker(Node *node, max_parallel_hazard_context *context)
* PARAM_EXEC Params listed in safe_param_ids, meaning they could be
* either generated within the worker or can be computed in master and
* then their value can be passed to the worker.
+ * PARAM_SCHEMA_VARIABLE params are newer changed by workers, so they can be
+ * safe.
*/
else if (IsA(node, Param))
{
Param *param = (Param *) node;
- if (param->paramkind == PARAM_EXTERN)
+ if (param->paramkind == PARAM_EXTERN ||
+ param->paramkind == PARAM_SCHEMA_VARIABLE)
return false;
if (param->paramkind != PARAM_EXEC ||
@@ -4793,7 +4796,7 @@ substitute_actual_parameters_mutator(Node *node,
{
if (node == NULL)
return NULL;
- if (IsA(node, Param))
+ if (IsA(node, Param) && ((Param *) node)->paramkind != PARAM_SCHEMA_VARIABLE)
{
Param *param = (Param *) node;
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 05f57591e4..4ff6c443b4 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -24,6 +24,7 @@
#include "postgres.h"
+#include "access/heapam.h"
#include "access/sysattr.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
@@ -42,8 +43,12 @@
#include "parser/parse_param.h"
#include "parser/parse_relation.h"
#include "parser/parse_target.h"
+#include "parser/parse_type.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/typcache.h"
#include "utils/rel.h"
@@ -56,6 +61,7 @@ static Query *transformInsertStmt(ParseState *pstate, InsertStmt *stmt);
static List *transformInsertRow(ParseState *pstate, List *exprlist,
List *stmtcols, List *icolumns, List *attrnos,
bool strip_indirection);
+static Query *transformLetStmt(ParseState *pstate, LetStmt *stmt);
static OnConflictExpr *transformOnConflictClause(ParseState *pstate,
OnConflictClause *onConflictClause);
static int count_rowexpr_columns(ParseState *pstate, Node *expr);
@@ -267,6 +273,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:
@@ -304,6 +311,10 @@ transformStmt(ParseState *pstate, Node *parseTree)
}
break;
+ case T_LetStmt:
+ result = transformLetStmt(pstate, (LetStmt *) parseTree);
+ break;
+
/*
* Special cases
*/
@@ -367,6 +378,7 @@ analyze_requires_snapshot(RawStmt *parseTree)
case T_DeleteStmt:
case T_UpdateStmt:
case T_SelectStmt:
+ case T_LetStmt:
result = true;
break;
@@ -1541,6 +1553,354 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
return qry;
}
+/*
+ * transformLetStmt -
+ * transform an Let Statement
+ */
+static Query *
+transformLetStmt(ParseState *pstate, LetStmt *stmt)
+{
+ Query *qry = makeNode(Query);
+ List *exprList = NIL;
+ List *exprListCoer = NIL;
+ List *sub_rtable;
+ List *sub_namespace;
+ RangeTblEntry *rte;
+ RangeTblRef *rtr;
+ ListCell *lc;
+ AclMode targetPerms;
+ ParseState *sub_pstate;
+ Query *selectQuery;
+ int i = 0;
+
+ RangeVar *rvar;
+ char *str1 = NULL;
+ char *str2 = NULL;
+ char *str3 = NULL;
+ char *fieldname;
+ int fieldname_pos;
+ List *indirection = NIL;
+ int indirection_pos = -1;
+
+ Relation rd;
+ Oid vartypid = InvalidOid;
+ int32 vartypmod = -1;
+ Oid varid;
+
+ ParseExprKind sv_expr_kind;
+
+ sv_expr_kind = pstate->p_expr_kind;
+ pstate->p_expr_kind = EXPR_KIND_UPDATE_TARGET;
+
+ /* There can't be any outer WITH to worry about */
+ Assert(pstate->p_ctenamespace == NIL);
+
+ qry->commandType = CMD_LET;
+ pstate->p_is_let = true;
+
+ switch (list_length(stmt->target))
+ {
+ case 1:
+ {
+ Node *field1 = (Node *) linitial(stmt->target);
+
+ Assert(IsA(field1, String));
+ str2 = strVal(field1);
+ }
+ break;
+ case 2:
+ {
+ Node *field1 = (Node *) linitial(stmt->target);
+ Node *field2 = (Node *) lsecond(stmt->target);
+
+ Assert(IsA(field1, String));
+
+ if (IsA(field2, String))
+ {
+ str1 = strVal(field1);
+ str2 = strVal(field2);
+ }
+ else
+ {
+ str2 = strVal(field1);
+ indirection_pos = 2;
+ }
+ }
+ break;
+ case 3:
+ {
+ Node *field1 = (Node *) linitial(stmt->target);
+ Node *field2 = (Node *) lsecond(stmt->target);
+ Node *field3 = (Node *) lthird(stmt->target);
+
+ Assert(IsA(field1, String));
+
+ if (!IsA(field2, String))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("broken schema variable identifier"),
+ parser_errposition(pstate, stmt->location)));
+
+ if (IsA(field2, String) && IsA(field3, String))
+ {
+ str1 = strVal(field1);
+ str2 = strVal(field2);
+ str3 = strVal(field3);
+ }
+ else
+ {
+ str1 = strVal(field1);
+ str2 = strVal(field2);
+ indirection_pos = 3;
+ }
+ }
+ break;
+ case 4:
+ {
+ Node *field1 = (Node *) linitial(stmt->target);
+ Node *field2 = (Node *) lsecond(stmt->target);
+ Node *field3 = (Node *) lthird(stmt->target);
+ Node *field4 = (Node *) lfourth(stmt->target);
+
+ Assert(IsA(field1, String));
+
+ if (IsA(field2, String) && IsA(field3, String) && !IsA(field4, String))
+ {
+ str1 = strVal(field1);
+ str2 = strVal(field2);
+ str3 = strVal(field3);
+ indirection_pos = 4;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("broken schema variable identifier"),
+ parser_errposition(pstate, stmt->location)));
+ }
+ break;
+
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name"),
+ parser_errposition(pstate, stmt->location)));
+ }
+
+ rvar = makeRangeVarForTargetOfSchemaVariable(pstate, str1, str2, str3,
+ stmt->location, &fieldname,
+ &fieldname_pos, false);
+
+ /*
+ * If a non-nil rangetable/namespace was passed in, and we are doing
+ * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
+ * SELECT. This can only happen if we are inside a CREATE RULE, and in
+ * that case we want the rule's OLD and NEW rtable entries to appear as
+ * part of the SELECT's rtable, not as outer references for it. (Kluge!)
+ * The SELECT's joinlist is not affected however. We must do this before
+ * adding the target table to the INSERT's rtable.
+ */
+ sub_rtable = pstate->p_rtable;
+ pstate->p_rtable = NIL;
+ sub_namespace = pstate->p_namespace;
+ pstate->p_namespace = NIL;
+
+ targetPerms = ACL_UPDATE;
+ qry->resultRelation = setTargetTable(pstate, rvar,
+ false, false, targetPerms);
+
+ rd = pstate->p_target_relation;
+
+ vartypid = rd->rd_rel->reloftype;
+ varid = rd->rd_id;
+
+ if (vartypid != InvalidOid)
+ {
+ if (get_typtype(vartypid) != TYPTYPE_COMPOSITE)
+ {
+ TupleDesc tupdesc;
+ Relation rel;
+ Form_pg_attribute attrStruct;
+
+ /* find first field */
+ rel = relation_open(typeidTypeRelid(rd->rd_rel->reltype), AccessShareLock);
+ tupdesc = CreateTupleDescCopy(RelationGetDescr(rel));
+
+ attrStruct = TupleDescAttr(tupdesc, 0);
+
+ vartypid = attrStruct->atttypid;
+ vartypmod = attrStruct->atttypmod;
+
+ FreeTupleDesc(tupdesc);
+ relation_close(rel, AccessShareLock);
+ }
+ else
+ {
+ vartypid = rd->rd_rel->reloftype;
+ vartypmod = -1;
+ }
+ }
+ else
+ {
+ vartypid = rd->rd_rel->reltype;
+ vartypmod = -1;
+ }
+
+ if (fieldname != NULL)
+ indirection = list_copy_tail(stmt->target, fieldname_pos - 1);
+ else if (indirection_pos >= 2)
+ indirection = list_copy_tail(stmt->target, indirection_pos - 1);
+
+ /*
+ * We make the sub-pstate a child of the outer pstate so that it can
+ * see any Param definitions supplied from above. Since the outer
+ * pstate's rtable and namespace are presently empty, there are no
+ * side-effects of exposing names the sub-SELECT shouldn't be able to
+ * see.
+ */
+ sub_pstate = make_parsestate(pstate);
+
+ /*
+ * Process the source SELECT.
+ *
+ * It is important that this be handled just like a standalone SELECT;
+ * otherwise the behavior of SELECT within INSERT might be different
+ * from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
+ * bugs of just that nature...)
+ *
+ * The sole exception is that we prevent resolving unknown-type
+ * outputs as TEXT. This does not change the semantics since if the
+ * column type matters semantically, it would have been resolved to
+ * something else anyway. Doing this lets us resolve such outputs as
+ * the target column's type, which we handle below.
+ */
+ sub_pstate->p_rtable = sub_rtable;
+ sub_pstate->p_joinexprs = NIL; /* sub_rtable has no joins */
+ sub_pstate->p_namespace = sub_namespace;
+ sub_pstate->p_resolve_unknowns = false;
+
+ selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
+
+ free_parsestate(sub_pstate);
+
+ /* The grammar should have produced a SELECT */
+ if (!IsA(selectQuery, Query) ||
+ selectQuery->commandType != CMD_SELECT)
+ elog(ERROR, "unexpected non-SELECT command in LET ... SELECT");
+
+ /*
+ * Make the source be a subquery in the LET's rangetable, and add
+ * it to the LET's joinlist.
+ */
+ rte = addRangeTableEntryForSubquery(pstate,
+ selectQuery,
+ makeAlias("*SELECT*", NIL),
+ false,
+ false);
+ rtr = makeNode(RangeTblRef);
+ /* assume new rte is at end */
+ rtr->rtindex = list_length(pstate->p_rtable);
+ Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+ pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+
+ /*----------
+ * Generate an expression list for the LET that selects all the
+ * non-resjunk columns from the subquery.
+ *----------
+ */
+
+ exprList = NIL;
+ foreach(lc, selectQuery->targetList)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+
+ if (tle->resjunk)
+ continue;
+
+ exprList = lappend(exprList, tle->expr);
+ }
+
+ /*
+ * Because doesn't support pattern matching, don't allow multicolumn result
+ */
+ if (list_length(exprList) != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("expression is not scalar value"),
+ parser_errposition(pstate,
+ exprLocation((Node *) exprList))));
+
+ exprListCoer = NIL;
+ foreach(lc, exprList)
+ {
+ Node *orig_expr = (Node*) lfirst(lc);
+ Oid exprtypid = exprType((Node *) orig_expr);
+ Expr *expr = NULL;
+ Param *param = makeNode(Param);
+
+ param->paramkind = PARAM_SCHEMA_VARIABLE;
+ param->paramid = varid;
+ param->paramtype = vartypid;
+ param->paramtypmod = vartypmod;
+
+ expr = (Expr *)
+ transformAssignmentIndirection(pstate,
+ (Node *) param,
+ fieldname,
+ false,
+ vartypid,
+ vartypmod,
+ InvalidOid,
+ list_head(indirection),
+ (Node *) orig_expr,
+ -1);
+
+ if (expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s\" is of type %s"
+ " but expression is of type %s",
+ RelationGetRelationName(rd),
+ format_type_be(vartypid),
+ format_type_be(exprtypid)),
+ errhint("You will need to rewrite or cast the expression."),
+ parser_errposition(pstate, exprLocation((Node *) orig_expr))));
+
+ exprListCoer = lappend(exprListCoer, expr);
+ }
+
+ /*
+ * Generate query's target list using the computed list of expressions.
+ * Also, mark all the target columns as needing insert permissions.
+ */
+ rte = pstate->p_target_rangetblentry;
+ qry->targetList = NIL;
+ foreach(lc, exprListCoer)
+ {
+ Expr *expr = (Expr *) lfirst(lc);
+ TargetEntry *tle;
+
+ tle = makeTargetEntry(expr,
+ i + 1,
+ FigureColname((Node *)expr),
+ false);
+ qry->targetList = lappend(qry->targetList, tle);
+ }
+
+ /* done building the range table and jointree */
+ qry->rtable = pstate->p_rtable;
+ qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
+
+ qry->hasTargetSRFs = pstate->p_hasTargetSRFs;
+ qry->hasSubLinks = pstate->p_hasSubLinks;
+
+ assign_query_collations(pstate, qry);
+
+ pstate->p_expr_kind = sv_expr_kind;
+
+ return qry;
+}
+
/*
* transformSetOperationStmt -
* transforms a set-operations tree
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5a36367446..95d26a73b2 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -157,6 +157,8 @@ static RoleSpec *makeRoleSpec(RoleSpecType type, int location);
static void check_qualified_name(List *names, core_yyscan_t yyscanner);
static List *check_func_name(List *names, core_yyscan_t yyscanner);
static List *check_indirection(List *indirection, core_yyscan_t yyscanner);
+static List *check_indirection_let(List *indirection, core_yyscan_t yyscanner);
+
static List *extractArgTypes(List *parameters);
static List *extractAggrArgTypes(List *aggrargs);
static List *makeOrderedSetArgs(List *directargs, List *orderedargs,
@@ -257,8 +259,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
- CreateSchemaStmt CreateSeqStmt CreateStmt CreateStatsStmt CreateTableSpaceStmt
- CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
+ CreateSchemaStmt CreateSchemaVarStmt CreateSeqStmt CreateStmt CreateStatsStmt
+ CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
@@ -268,7 +270,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
DropTransformStmt
DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
- ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
+ LetStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
CreateFunctionStmt AlterFunctionStmt ReindexStmt RemoveAggrStmt
RemoveFuncStmt RemoveOperStmt RenameStmt RevokeStmt RevokeRoleStmt
RuleActionStmt RuleActionStmtOrEmpty RuleStmt
@@ -584,6 +586,9 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type partbound_datum PartitionRangeDatum
%type hash_partbound partbound_datum_list range_datum_list
%type hash_partbound_elem
+%type fieldList
+%type fieldDef optSchemaVarDefExpr
+%type schemavar_name
/*
* Non-keyword token types. These are hard-wired into the "flex" lexer.
@@ -649,7 +654,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
KEY
LABEL LANGUAGE LARGE_P LAST_P LATERAL_P
- LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
+ LEADING LEAKPROOF LEAST LEFT LET LEVEL LIKE LIMIT LISTEN LOAD LOCAL
LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P LOCKED LOGGED
MAPPING MATCH MATERIALIZED MAXVALUE METHOD MINUTE_P MINVALUE MODE MONTH_P MOVE
@@ -687,8 +692,8 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
UNTIL UPDATE USER USING
- VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIADIC VARYING
- VERBOSE VERSION_P VIEW VIEWS VOLATILE
+ VACUUM VALID VALIDATE VALIDATOR VALUE_P VALUES VARCHAR VARIABLE VARIABLES
+ VARIADIC VARYING VERBOSE VERSION_P VIEW VIEWS VOLATILE
WHEN WHERE WHITESPACE_P WINDOW WITH WITHIN WITHOUT WORK WRAPPER WRITE
@@ -879,6 +884,7 @@ stmt :
| CreatePLangStmt
| CreateSchemaStmt
| CreateSeqStmt
+ | CreateSchemaVarStmt
| CreateStmt
| CreateSubscriptionStmt
| CreateStatsStmt
@@ -920,6 +926,7 @@ stmt :
| ListenStmt
| RefreshMatViewStmt
| LoadStmt
+ | LetStmt
| LockStmt
| NotifyStmt
| PrepareStmt
@@ -1380,6 +1387,7 @@ schema_stmt:
CreateStmt
| IndexStmt
| CreateSeqStmt
+ | CreateSchemaVarStmt
| CreateTrigStmt
| GrantStmt
| ViewStmt
@@ -1808,7 +1816,12 @@ DiscardStmt:
n->target = DISCARD_SEQUENCES;
$$ = (Node *) n;
}
-
+ | DISCARD VARIABLES
+ {
+ DiscardStmt *n = makeNode(DiscardStmt);
+ n->target = DISCARD_VARIABLES;
+ $$ = (Node *) n;
+ }
;
@@ -2004,6 +2017,24 @@ AlterTableStmt:
n->nowait = $14;
$$ = (Node *)n;
}
+ | ALTER VARIABLE qualified_name alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $3;
+ n->cmds = $4;
+ n->relkind = OBJECT_VARIABLE;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER VARIABLE IF_P EXISTS qualified_name alter_table_cmds
+ {
+ AlterTableStmt *n = makeNode(AlterTableStmt);
+ n->relation = $5;
+ n->cmds = $6;
+ n->relkind = OBJECT_VARIABLE;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
;
alter_table_cmds:
@@ -4287,6 +4318,86 @@ NumericOnly_list: NumericOnly { $$ = list_make1($1); }
| NumericOnly_list ',' NumericOnly { $$ = lappend($1, $3); }
;
+/*****************************************************************************
+ *
+ * QUERY :
+ * CREATE VARIABLE seqname [AS] type
+ *
+ *****************************************************************************/
+
+CreateSchemaVarStmt:
+ CREATE OptTemp VARIABLE qualified_name opt_as Typename optSchemaVarDefExpr
+ {
+ CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+ $4->relpersistence = $2;
+ n->variable = $4;
+ n->typeName = $6;
+ n->defexpr = $7;
+ n->fields = NULL;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as Typename optSchemaVarDefExpr
+ {
+ CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+ $7->relpersistence = $2;
+ n->variable = $7;
+ n->typeName = $9;
+ n->defexpr = $10;
+ n->fields = NULL;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp VARIABLE qualified_name opt_as '(' fieldList ')'
+ {
+ CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+ $4->relpersistence = $2;
+ n->variable = $4;
+ n->typeName = NULL;
+ n->fields = $7;
+ n->if_not_exists = false;
+ $$ = (Node *)n;
+ }
+ | CREATE OptTemp VARIABLE IF_P NOT EXISTS qualified_name opt_as '(' fieldList ')'
+ {
+ CreateSchemaVarStmt *n = makeNode(CreateSchemaVarStmt);
+ $7->relpersistence = $2;
+ n->variable = $7;
+ n->typeName = NULL;
+ n->fields = $10;
+ n->if_not_exists = true;
+ $$ = (Node *)n;
+ }
+ ;
+
+optSchemaVarDefExpr: DEFAULT b_expr { $$ = $2; }
+ | /* EMPTY */ { $$ = NULL; }
+ ;
+
+fieldList:
+ fieldDef { $$ = list_make1($1); }
+ | fieldList ',' fieldDef { $$ = lappend($1, $3); }
+ ;
+
+fieldDef: ColId Typename optSchemaVarDefExpr
+ {
+ ColumnDef *n = makeNode(ColumnDef);
+ n->colname = $1;
+ n->typeName = $2;
+ n->inhcount = 0;
+ n->is_local = true;
+ n->is_not_null = false;
+ n->is_from_type = false;
+ n->is_from_parent = false;
+ n->storage = 0;
+ n->raw_default = $3;
+ n->cooked_default = NULL;
+ n->collOid = InvalidOid;
+ n->location = @1;
+ $$ = (Node *)n;
+ }
+ ;
+
/*****************************************************************************
*
* QUERIES :
@@ -6335,6 +6446,7 @@ drop_type_any_name:
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
| TEXT_P SEARCH CONFIGURATION { $$ = OBJECT_TSCONFIGURATION; }
+ | VARIABLE { $$ = OBJECT_VARIABLE; }
;
/* object types taking name_list */
@@ -6604,6 +6716,7 @@ comment_type_any_name:
| TEXT_P SEARCH DICTIONARY { $$ = OBJECT_TSDICTIONARY; }
| TEXT_P SEARCH PARSER { $$ = OBJECT_TSPARSER; }
| TEXT_P SEARCH TEMPLATE { $$ = OBJECT_TSTEMPLATE; }
+ | VARIABLE { $$ = OBJECT_VARIABLE; }
;
/* object types taking name */
@@ -6742,6 +6855,7 @@ security_label_type_any_name:
| TABLE { $$ = OBJECT_TABLE; }
| VIEW { $$ = OBJECT_VIEW; }
| MATERIALIZED VIEW { $$ = OBJECT_MATVIEW; }
+ | VARIABLE { $$ = OBJECT_VARIABLE; }
;
/* object types taking name */
@@ -7067,6 +7181,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;
+ }
| FOREIGN DATA_P WRAPPER name_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7179,6 +7301,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;
+ }
| ALL FUNCTIONS IN_P SCHEMA name_list
{
PrivTarget *n = (PrivTarget *) palloc(sizeof(PrivTarget));
@@ -7361,6 +7491,7 @@ defacl_privilege_target:
| FUNCTIONS { $$ = OBJECT_FUNCTION; }
| ROUTINES { $$ = OBJECT_FUNCTION; }
| SEQUENCES { $$ = OBJECT_SEQUENCE; }
+ | VARIABLES { $$ = OBJECT_VARIABLE; }
| TYPES_P { $$ = OBJECT_TYPE; }
| SCHEMAS { $$ = OBJECT_SCHEMA; }
;
@@ -8959,6 +9090,26 @@ RenameStmt: ALTER AGGREGATE aggregate_with_argtypes RENAME TO name
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER VARIABLE qualified_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_VARIABLE;
+ n->relation = $3;
+ n->subname = NULL;
+ n->newname = $6;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
+ | ALTER VARIABLE IF_P EXISTS qualified_name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_VARIABLE;
+ n->relation = $5;
+ n->subname = NULL;
+ n->newname = $8;
+ n->missing_ok = true;
+ $$ = (Node *)n;
+ }
;
opt_column: COLUMN { $$ = COLUMN; }
@@ -10686,7 +10837,8 @@ ExplainableStmt:
| CreateAsStmt
| CreateMatViewStmt
| RefreshMatViewStmt
- | ExecuteStmt /* by default all are $$=$1 */
+ | ExecuteStmt
+ | LetStmt /* by default all are $$=$1 */
;
explain_option_list:
@@ -10743,7 +10895,8 @@ PreparableStmt:
SelectStmt
| InsertStmt
| UpdateStmt
- | DeleteStmt /* by default all are $$=$1 */
+ | DeleteStmt
+ | LetStmt /* by default all are $$=$1 */
;
/*****************************************************************************
@@ -11142,6 +11295,50 @@ opt_hold: /* EMPTY */ { $$ = 0; }
| WITHOUT HOLD { $$ = 0; }
;
+/*****************************************************************************
+ *
+ * QUERY:
+ * LET STATEMENTS
+ *
+ *****************************************************************************/
+LetStmt: LET schemavar_name '=' a_expr
+ {
+ LetStmt *n = makeNode(LetStmt);
+ SelectStmt *select = makeNode(SelectStmt);
+ ResTarget *res = makeNode(ResTarget);
+
+ res->name = NULL;
+ res->indirection = NIL;
+ res->val = (Node *) $4;
+ res->location = @4;
+ select->targetList = list_make1(res);
+ n->target = $2;
+ n->selectStmt = (Node *) select;
+ n->location = @2;
+
+ $$ = (Node *) n;
+ }
+ ;
+
+schemavar_name:
+ ColId
+ {
+ $$ = list_make1(makeString($1));
+ }
+ | ColId indirection
+ {
+ check_indirection_let($2, yyscanner);
+ if (list_length($2) > 3)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("improper qualified name"),
+ parser_errposition(@1)));
+
+ $$ = lcons(makeString($1), $2);
+ $$ = list_union($$, $2);
+ }
+ ;
+
/*****************************************************************************
*
* QUERY:
@@ -15101,6 +15298,7 @@ unreserved_keyword:
| LARGE_P
| LAST_P
| LEAKPROOF
+ | LET
| LEVEL
| LISTEN
| LOAD
@@ -15249,6 +15447,8 @@ unreserved_keyword:
| VALIDATE
| VALIDATOR
| VALUE_P
+ | VARIABLE
+ | VARIABLES
| VARYING
| VERSION_P
| VIEW
@@ -15713,6 +15913,25 @@ check_func_name(List *names, core_yyscan_t yyscanner)
* We only allow '*' at the end of the list, but it's hard to enforce that
* in the grammar, so do it here.
*/
+static List *
+check_indirection_let(List *indirection, core_yyscan_t yyscanner)
+{
+ ListCell *l;
+
+ foreach(l, indirection)
+ {
+ if (IsA(lfirst(l), A_Star))
+ parser_yyerror("syntax error");
+
+ if (IsA(lfirst(l), A_Indices))
+ {
+ if (lnext(l) != NULL)
+ parser_yyerror("improper use of []");
+ }
+ }
+ return indirection;
+}
+
static List *
check_indirection(List *indirection, core_yyscan_t yyscanner)
{
@@ -15729,6 +15948,7 @@ check_indirection(List *indirection, core_yyscan_t yyscanner)
return indirection;
}
+
/* extractArgTypes()
* Given a list of FunctionParameter nodes, extract a list of just the
* argument types (TypeNames) for input parameters only. This is what
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 385e54a9b6..8f6e28da8f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -371,7 +371,7 @@ transformExprRecurse(ParseState *pstate, Node *expr)
default:
/* should not reach here */
- elog(ERROR, "unrecognized node type: %d", (int) nodeTag(expr));
+ elog(ERROR, "unrecognized node type: %d %d", (int) nodeTag(expr), T_Const);
result = NULL; /* keep compiler quiet */
break;
}
@@ -509,6 +509,7 @@ static Node *
transformColumnRef(ParseState *pstate, ColumnRef *cref)
{
Node *node = NULL;
+ Node *variable = NULL;
char *nspname = NULL;
char *relname = NULL;
char *colname = NULL;
@@ -749,6 +750,133 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
break;
}
+ /*
+ * Try to identify column ref as variable. Possible variants are
+ *
+ * A .. variable name
+ * A.B .. qualified variable name
+ * A.B .. variable.field
+ * A.B.C .. schema.variable.field
+ * A.B.C.D .. catalogue.schema.variable.field
+ */
+ switch (list_length(cref->fields))
+ {
+ case 1:
+ {
+ Node *field1 = (Node *) linitial(cref->fields);
+
+ if (IsA(field1, String))
+ {
+ char *varname = strVal(field1);
+
+ /* Try to identify as an unqualified column */
+ variable = toSchemaVariable(pstate,
+ NULL, varname, NULL,
+ cref->location);
+ }
+ break;
+ }
+ case 2:
+ {
+ Node *field1 = (Node *) linitial(cref->fields);
+ Node *field2 = (Node *) lsecond(cref->fields);
+
+ if (IsA(field1, String) && IsA(field2, String))
+ {
+ char *nspname = strVal(field1);
+ char *varname = strVal(field2);
+
+ /* Try to identify as an unqualified column */
+ variable = toSchemaVariable(pstate,
+ nspname, varname, NULL,
+ cref->location);
+ }
+ break;
+ }
+ case 3:
+ {
+ Node *field1 = (Node *) linitial(cref->fields);
+ Node *field2 = (Node *) lsecond(cref->fields);
+ Node *field3 = (Node *) lthird(cref->fields);
+
+ if (IsA(field1, String) && IsA(field2, String) && IsA(field2, String))
+ {
+ char *nspname = strVal(field1);
+ char *varname = strVal(field2);
+ char *fieldname = strVal(field3);
+
+ /* Try to identify as an unqualified column */
+ variable = toSchemaVariable(pstate,
+ nspname, varname, fieldname,
+ cref->location);
+ }
+ break;
+ }
+ case 4:
+ {
+ Node *field1 = (Node *) linitial(cref->fields);
+ Node *field2 = (Node *) lsecond(cref->fields);
+ Node *field3 = (Node *) lthird(cref->fields);
+ Node *field4 = (Node *) lfourth(cref->fields);
+ char *catname;
+
+ Assert(IsA(field1, String));
+ catname = strVal(field1);
+
+ /*
+ * We check the catalog name and then ignore it.
+ */
+ if (strcmp(catname, get_database_name(MyDatabaseId)) != 0)
+ {
+ crerr = CRERR_WRONG_DB;
+ break;
+ }
+
+ if (IsA(field1, String) && IsA(field2, String) && IsA(field2, String))
+ {
+ char *nspname = strVal(field2);
+ char *varname = strVal(field3);
+ char *fieldname = strVal(field4);
+
+ /* Try to identify as an unqualified column */
+ variable = toSchemaVariable(pstate,
+ nspname, varname, fieldname,
+ cref->location);
+ }
+ break;
+ }
+ default:
+
+ /*
+ * There can be another variants, more when composite variables
+ * will be supported. Currently only scalars are supported, so
+ * there are not necessary to solve other questions.
+ *
+ * do nothing
+ */
+ crerr = CRERR_TOO_MANY; /* too many dotted names */
+ break;
+ }
+
+ if (variable != NULL)
+ {
+ /*
+ * When we allow variables in FROM clause, then here can be check,
+ * if variable and node are same. Or better, when node will be
+ * referenced to variable, then toSchemaVariable will not be called.
+ * But now, we don't allow schema variable in FROM clause, so the
+ * conflict is not possible.
+ */
+ if (node != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_AMBIGUOUS_COLUMN),
+ errmsg("column reference \"%s\" is ambiguous",
+ NameListToString(cref->fields)),
+ parser_errposition(pstate, cref->location)));
+
+ node = variable;
+ }
+
/*
* 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
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index bf5df26009..512e364fa2 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -21,6 +21,7 @@
#include "catalog/heap.h"
#include "catalog/namespace.h"
#include "catalog/pg_type.h"
+#include "commands/schemavar.h"
#include "funcapi.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
@@ -32,6 +33,7 @@
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
+#include "utils/typcache.h"
#include "utils/varlena.h"
@@ -1150,6 +1152,7 @@ parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockmode)
setup_parser_errposition_callback(&pcbstate, pstate, relation->location);
rel = heap_openrv_extended(relation, lockmode, true);
+
if (rel == NULL)
{
if (relation->schemaname)
@@ -1180,6 +1183,24 @@ parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockmode)
relation->relname)));
}
}
+
+ /*
+ * RELKIND_VARIABLE can be used only in LET command.
+ * Probably this check can be done elsewhere, but here I
+ * have a used relation and parse state together first time.
+ */
+ if (rel->rd_rel->relkind == RELKIND_VARIABLE && !pstate->p_is_let)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is an schema variable",
+ RelationGetRelationName(rel))));
+
+ if (pstate->p_is_let && rel->rd_rel->relkind != RELKIND_VARIABLE)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("\"%s\" is not an schema variable",
+ RelationGetRelationName(rel))));
+
cancel_parser_errposition_callback(&pcbstate);
return rel;
}
@@ -3360,3 +3381,160 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+RangeVar *
+makeRangeVarForTargetOfSchemaVariable(ParseState *pstate,
+ char *field1, char *field2, char *field3,
+ int location, char **fieldname, int *fieldname_pos, bool noerror)
+{
+ RangeVar *rv1;
+ Oid varid1;
+ bool is_rowtype1;
+
+ rv1 = makeRangeVar(field1, field2, location);
+ varid1 = RangeVarGetRelid(rv1, NoLock, true);
+ varid1 = is_schemavar(varid1, &is_rowtype1) ? varid1 : InvalidOid;
+
+ if (field1 != NULL && field3 == NULL)
+ {
+ RangeVar *rv2;
+ bool is_rowtype2;
+ Oid varid2;
+
+ rv2 = makeRangeVar(NULL, field1, location);
+ varid2 = RangeVarGetRelid(rv2, NoLock, true);
+
+ /* varid2 has sense only as row type variable */
+ varid2 = is_schemavar(varid2, &is_rowtype2) ? varid2 : InvalidOid;
+ varid2 = is_rowtype2 ? varid2 : InvalidOid;
+
+ if (OidIsValid(varid1) && OidIsValid(varid2))
+ ereport(ERROR,
+ (errcode(ERRCODE_AMBIGUOUS_COLUMN),
+ errmsg("variable reference \"%s\".\"%s\" is ambiguous", field1, field2),
+ parser_errposition(pstate, location)));
+
+ if (OidIsValid(varid2))
+ {
+ *fieldname = field2;
+ *fieldname_pos = 2;
+ return rv2;
+ }
+ }
+
+ if (!OidIsValid(varid1))
+ {
+ if (!noerror)
+ {
+ if (field1 != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("schema variable \"%s.%s\" doesn't exists", field1, field2),
+ parser_errposition(pstate, location)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("schema variable \"%s\" doesn't exists", field2),
+ parser_errposition(pstate, location)));
+ }
+
+ return NULL;
+ }
+
+ *fieldname = field3;
+ *fieldname_pos = 3;
+
+ if (field3 != NULL && !is_rowtype1)
+ {
+ if (!noerror)
+ {
+ if (field1 != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("variable \"%s.%s\" is not row type", field1, field2),
+ parser_errposition(pstate, location)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("variable \"%s\" is not row type", field2),
+ parser_errposition(pstate, location)));
+ }
+
+ return NULL;
+ }
+
+ return rv1;
+}
+
+/*
+ * Try to replace ColumnRef by Param related to variable
+ *
+ */
+Node *
+toSchemaVariable(ParseState *pstate, char *nspname, char *varname, char *fieldname, int location)
+{
+ char *used_fieldname;
+ RangeVar *rv;
+ Oid varid;
+ int fieldname_pos;
+ Oid vartyp;
+ int32 vartypmod;
+ Param *param;
+
+ rv = makeRangeVarForTargetOfSchemaVariable(pstate,
+ nspname, varname, fieldname, location,
+ &used_fieldname, &fieldname_pos, true);
+
+ if (rv == NULL)
+ return NULL;
+
+ varid = RangeVarGetRelid(rv, NoLock, true);
+ if (!OidIsValid(varid))
+ return NULL;
+
+ param = makeNode(Param);
+
+ schemavar_get_typ_typmod(varid, &vartyp, &vartypmod);
+
+ param->paramkind = PARAM_SCHEMA_VARIABLE;
+ param->paramid = varid;
+ param->paramtype = vartyp;
+ param->paramtypmod = vartypmod;
+
+ if (used_fieldname != NULL)
+ {
+ TupleDesc tupdesc;
+ int i;
+
+ tupdesc = lookup_rowtype_tupdesc(param->paramtype, param->paramtypmod);
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+ if (strcmp(used_fieldname, 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", used_fieldname),
+ parser_errposition(pstate, location)));
+ }
+
+ return (Node *) param;
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 4932e58022..8549908095 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -35,16 +35,6 @@
static void markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
Var *var, int levelsup);
-static Node *transformAssignmentIndirection(ParseState *pstate,
- Node *basenode,
- const char *targetName,
- bool targetIsArray,
- Oid targetTypeId,
- int32 targetTypMod,
- Oid targetCollation,
- ListCell *indirection,
- Node *rhs,
- int location);
static Node *transformAssignmentSubscripts(ParseState *pstate,
Node *basenode,
const char *targetName,
@@ -596,7 +586,6 @@ transformAssignedExpr(ParseState *pstate,
return expr;
}
-
/*
* updateTargetListEntry()
* This is used in UPDATE statements (and ON CONFLICT DO UPDATE)
@@ -672,7 +661,7 @@ updateTargetListEntry(ParseState *pstate,
* might want to decorate indirection cells with their own location info,
* in which case the location argument could probably be dropped.)
*/
-static Node *
+Node *
transformAssignmentIndirection(ParseState *pstate,
Node *basenode,
const char *targetName,
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index da5ede866c..00af37a4a9 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -108,6 +108,7 @@ typedef struct
List *views; /* CREATE VIEW items */
List *indexes; /* CREATE INDEX items */
List *triggers; /* CREATE TRIGGER items */
+ List *variables; /* CREATE VARIABLE items */
List *grants; /* GRANT items */
} CreateSchemaStmtContext;
@@ -3438,6 +3439,7 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
cxt.views = NIL;
cxt.indexes = NIL;
cxt.triggers = NIL;
+ cxt.variables = NIL;
cxt.grants = NIL;
/*
@@ -3503,6 +3505,14 @@ transformCreateSchemaStmt(CreateSchemaStmt *stmt)
}
break;
+ case T_CreateSchemaVarStmt:
+ {
+ CreateSchemaVarStmt *elp = (CreateSchemaVarStmt *) element;
+
+ setSchemaName(cxt.schemaname, &elp->variable->schemaname);
+ cxt.variables = lappend(cxt.variables, element);
+ }
+
case T_GrantStmt:
cxt.grants = lappend(cxt.grants, element);
break;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 5b87c554f5..2d634c7dd3 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3342,7 +3342,7 @@ RewriteQuery(Query *parsetree, List *rewrite_events)
rt_entry_relation,
parsetree->resultRelation, NULL);
}
- else if (event == CMD_DELETE)
+ else if (event == CMD_DELETE || event == CMD_LET)
{
/* Nothing to do here */
}
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 66cc5c35c6..34ddb79a3d 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -193,6 +193,10 @@ ProcessQuery(PlannedStmt *plan,
"DELETE " UINT64_FORMAT,
queryDesc->estate->es_processed);
break;
+ case CMD_LET:
+ snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
+ "LET ");
+ break;
default:
strcpy(completionTag, "???");
break;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 287addf429..00965ca506 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -47,6 +47,7 @@
#include "commands/proclang.h"
#include "commands/publicationcmds.h"
#include "commands/schemacmds.h"
+#include "commands/schemavar.h"
#include "commands/seclabel.h"
#include "commands/sequence.h"
#include "commands/subscriptioncmds.h"
@@ -113,6 +114,7 @@ CommandIsReadOnly(PlannedStmt *pstmt)
case CMD_DELETE:
return false;
case CMD_UTILITY:
+ case CMD_LET:
/* For now, treat all utility commands as read/write */
return false;
default:
@@ -178,6 +180,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateSchemaStmt:
case T_CreateSeqStmt:
case T_CreateStmt:
+ case T_CreateSchemaVarStmt:
case T_CreateTableAsStmt:
case T_RefreshMatViewStmt:
case T_CreateTableSpaceStmt:
@@ -1459,6 +1462,10 @@ ProcessUtilitySlow(ParseState *pstate,
address = AlterSequence(pstate, (AlterSeqStmt *) parsetree);
break;
+ case T_CreateSchemaVarStmt:
+ address = DefineSchemaVariable(pstate, (CreateSchemaVarStmt *) parsetree);
+ break;
+
case T_CreateTableAsStmt:
address = ExecCreateTableAs((CreateTableAsStmt *) parsetree,
queryString, params, queryEnv,
@@ -2032,6 +2039,9 @@ AlterObjectTypeCommandTag(ObjectType objtype)
case OBJECT_STATISTIC_EXT:
tag = "ALTER STATISTICS";
break;
+ case OBJECT_VARIABLE:
+ tag = "ALTER VARIABLE";
+ break;
default:
tag = "???";
break;
@@ -2081,6 +2091,10 @@ CreateCommandTag(Node *parsetree)
tag = "SELECT";
break;
+ case T_LetStmt:
+ tag = "LET";
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
{
@@ -2245,6 +2259,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_INDEX:
tag = "DROP INDEX";
break;
+ case OBJECT_VARIABLE:
+ tag = "DROP VARIABLE";
+ break;
case OBJECT_TYPE:
tag = "DROP TYPE";
break;
@@ -2499,6 +2516,10 @@ CreateCommandTag(Node *parsetree)
tag = "ALTER SEQUENCE";
break;
+ case T_CreateSchemaVarStmt:
+ tag = "CREATE VARIABLE";
+ break;
+
case T_DoStmt:
tag = "DO";
break;
@@ -2616,6 +2637,9 @@ CreateCommandTag(Node *parsetree)
case DISCARD_SEQUENCES:
tag = "DISCARD SEQUENCES";
break;
+ case DISCARD_VARIABLES:
+ tag = "DISCARD VARIABLES";
+ break;
default:
tag = "???";
}
@@ -2820,6 +2844,9 @@ CreateCommandTag(Node *parsetree)
case CMD_DELETE:
tag = "DELETE";
break;
+ case CMD_LET:
+ tag = "LET";
+ break;
case CMD_UTILITY:
tag = CreateCommandTag(stmt->utilityStmt);
break;
@@ -2938,6 +2965,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_ALL;
break;
+ case T_LetStmt:
+ lev = LOGSTMT_ALL;
+ break;
+
/* utility statements --- same whether raw or cooked */
case T_TransactionStmt:
lev = LOGSTMT_ALL;
@@ -3391,6 +3422,7 @@ GetCommandLogLevel(Node *parsetree)
switch (stmt->commandType)
{
case CMD_SELECT:
+ case CMD_LET:
lev = LOGSTMT_ALL;
break;
diff --git a/src/backend/utils/adt/acl.c b/src/backend/utils/adt/acl.c
index 0cfc297b65..fcd695836a 100644
--- a/src/backend/utils/adt/acl.c
+++ b/src/backend/utils/adt/acl.c
@@ -808,6 +808,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 */
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 74e1cd8afb..5d3d7451b8 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -40,6 +40,7 @@
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "commands/tablespace.h"
+#include "commands/schemavar.h"
#include "common/keywords.h"
#include "executor/spi.h"
#include "funcapi.h"
@@ -382,6 +383,7 @@ static void get_update_query_targetlist_def(Query *query, List *targetList,
deparse_context *context,
RangeTblEntry *rte);
static void get_delete_query_def(Query *query, deparse_context *context);
+static void get_let_query_def(Query *query, deparse_context *context);
static void get_utility_query_def(Query *query, deparse_context *context);
static void get_basic_select_query(Query *query, deparse_context *context,
TupleDesc resultDesc);
@@ -4989,6 +4991,10 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
get_delete_query_def(query, &context);
break;
+ case CMD_LET:
+ get_let_query_def(query, &context);
+ break;
+
case CMD_NOTHING:
appendStringInfoString(buf, "NOTHING");
break;
@@ -6206,6 +6212,58 @@ get_insert_query_def(Query *query, deparse_context *context)
}
}
+/* ----------
+ * get_let_query_def - Parse back an LET parsetree
+ * ----------
+ */
+static void
+get_let_query_def(Query *query, deparse_context *context)
+{
+ StringInfo buf = context->buf;
+ RangeTblEntry *select_rte = NULL;
+ RangeTblEntry *rte;
+ ListCell *l;
+
+ /*
+ * If it's an INSERT ... SELECT or multi-row VALUES, there will be a
+ * single RTE for the SELECT or VALUES. Plain VALUES has neither.
+ */
+ foreach(l, query->rtable)
+ {
+ rte = (RangeTblEntry *) lfirst(l);
+
+ if (rte->rtekind == RTE_SUBQUERY)
+ {
+ if (select_rte)
+ elog(ERROR, "too many subquery RTEs in INSERT");
+ select_rte = rte;
+ }
+ }
+
+ /*
+ * Start the query with INSERT INTO relname
+ */
+ rte = rt_fetch(query->resultRelation, query->rtable);
+ Assert(rte->rtekind == RTE_RELATION);
+
+ if (PRETTY_INDENT(context))
+ {
+ context->indentLevel += PRETTYINDENT_STD;
+ appendStringInfoChar(buf, ' ');
+ }
+ appendStringInfo(buf, "LET %s ",
+ generate_relation_name(rte->relid, NIL));
+
+ appendStringInfo(buf, " = ");
+
+ if (select_rte)
+ {
+ /* Add the SELECT */
+ get_query_def(select_rte->subquery, buf, NIL, NULL,
+ context->prettyFlags, context->wrapColumn,
+ context->indentLevel);
+ }
+}
/* ----------
* get_update_query_def - Parse back an UPDATE parsetree
@@ -7281,6 +7339,13 @@ get_parameter(Param *param, deparse_context *context)
deparse_namespace *dpns;
ListCell *ancestor_cell;
+ if (param->paramkind == PARAM_SCHEMA_VARIABLE)
+ {
+ appendStringInfo(context->buf, "%s", get_schemavar_name(param->paramid));
+
+ return;
+ }
+
/*
* If it's a PARAM_EXEC parameter, try to locate the expression from which
* the parameter was computed. Note that failing to find a referent isn't
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 0d147cb08d..97d6607239 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -326,7 +326,8 @@ flagInhTables(Archive *fout, TableInfo *tblinfo, int numTables,
/* Some kinds never have parents */
if (tblinfo[i].relkind == RELKIND_SEQUENCE ||
tblinfo[i].relkind == RELKIND_VIEW ||
- tblinfo[i].relkind == RELKIND_MATVIEW)
+ tblinfo[i].relkind == RELKIND_MATVIEW ||
+ tblinfo[i].relkind == RELKIND_VARIABLE)
continue;
/*
@@ -475,7 +476,8 @@ flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables)
/* Some kinds never have parents */
if (tbinfo->relkind == RELKIND_SEQUENCE ||
tbinfo->relkind == RELKIND_VIEW ||
- tbinfo->relkind == RELKIND_MATVIEW)
+ tbinfo->relkind == RELKIND_MATVIEW ||
+ tbinfo->relkind == RELKIND_VARIABLE)
continue;
/* Don't bother computing anything for non-target tables, either */
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 83c976eaf7..70b3da9c93 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3480,7 +3480,8 @@ _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
strcmp(type, "SERVER") == 0 ||
strcmp(type, "PUBLICATION") == 0 ||
strcmp(type, "SUBSCRIPTION") == 0 ||
- strcmp(type, "USER MAPPING") == 0)
+ strcmp(type, "USER MAPPING") == 0 ||
+ strcmp(type, "VARIABLE") == 0)
{
appendPQExpBuffer(buf, "%s ", type);
if (te->namespace && *te->namespace)
@@ -3661,6 +3662,7 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData)
strcmp(te->desc, "TABLE") == 0 ||
strcmp(te->desc, "TYPE") == 0 ||
strcmp(te->desc, "VIEW") == 0 ||
+ strcmp(te->desc, "VARIABLE") == 0 ||
strcmp(te->desc, "MATERIALIZED VIEW") == 0 ||
strcmp(te->desc, "SEQUENCE") == 0 ||
strcmp(te->desc, "FOREIGN TABLE") == 0 ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c5b49459cc..046f2c7a96 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1325,10 +1325,11 @@ expand_table_name_patterns(Archive *fout,
"\n LEFT JOIN pg_catalog.pg_namespace n"
"\n ON n.oid OPERATOR(pg_catalog.=) c.relnamespace"
"\nWHERE c.relkind OPERATOR(pg_catalog.=) ANY"
- "\n (array['%c', '%c', '%c', '%c', '%c', '%c'])\n",
+ "\n (array['%c', '%c', '%c', '%c', '%c', '%c, %c'])\n",
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
- RELKIND_PARTITIONED_TABLE);
+ RELKIND_PARTITIONED_TABLE,
+ RELKIND_VARIABLE);
processSQLNamePattern(GetConnection(fout), query, cell->val, true,
false, "n.nspname", "c.relname", NULL,
"pg_catalog.pg_table_is_visible(c.oid)");
@@ -2256,6 +2257,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo, bool oids)
dopt->no_unlogged_table_data)
return;
+ /* Don't dump data of variables */
+ if (tbinfo->relkind == RELKIND_VARIABLE)
+ return;
+
/* Check that the data is not explicitly excluded */
if (simple_oid_list_member(&tabledata_exclude_oids,
tbinfo->dobj.catId.oid))
@@ -2477,9 +2482,10 @@ guessConstraintInheritance(TableInfo *tblinfo, int numTables)
TableInfo **parents;
TableInfo *parent;
- /* Sequences and views never have parents */
+ /* Sequences, variables and views never have parents */
if (tbinfo->relkind == RELKIND_SEQUENCE ||
- tbinfo->relkind == RELKIND_VIEW)
+ tbinfo->relkind == RELKIND_VIEW ||
+ tbinfo->relkind == RELKIND_VARIABLE)
continue;
/* Don't bother computing anything for non-target tables, either */
@@ -5989,7 +5995,7 @@ getTables(Archive *fout, int *numTables)
"(c.oid = pip.objoid "
"AND pip.classoid = 'pg_class'::regclass "
"AND pip.objsubid = 0) "
- "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c') "
+ "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid",
acl_subquery->data,
racl_subquery->data,
@@ -6008,7 +6014,8 @@ getTables(Archive *fout, int *numTables)
RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
RELKIND_MATVIEW, RELKIND_FOREIGN_TABLE,
- RELKIND_PARTITIONED_TABLE);
+ RELKIND_PARTITIONED_TABLE,
+ RELKIND_VARIABLE);
destroyPQExpBuffer(acl_subquery);
destroyPQExpBuffer(racl_subquery);
@@ -15416,6 +15423,11 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
srvname = NULL;
ftoptions = NULL;
break;
+ case RELKIND_VARIABLE:
+ reltypename = "VARIABLE";
+ srvname = NULL;
+ ftoptions = NULL;
+ break;
default:
reltypename = "TABLE";
srvname = NULL;
@@ -15437,12 +15449,28 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
reltypename,
qualrelname);
+ /*
+ * Binary upgrade of variables should to be fixed with support
+ * composite types, and should be tested ToDo
+ */
+ if (tbinfo->relkind == RELKIND_VARIABLE)
+ {
+ appendPQExpBuffer(q, " AS");
+ if (tbinfo->reloftype != NULL)
+ appendPQExpBuffer(q, " %s",
+ tbinfo->atttypnames[0]);
+ if (tbinfo->attrdefs[0] != NULL && tbinfo->attrdefs[0]->adef_expr != NULL)
+ appendPQExpBuffer(q, " DEFAULT %s",
+ tbinfo->attrdefs[0]->adef_expr);
+ }
+
/*
* Attach to type, if reloftype; except in case of a binary upgrade,
* we dump the table normally and attach it to the type afterward.
*/
- if (tbinfo->reloftype && !dopt->binary_upgrade)
- appendPQExpBuffer(q, " OF %s", tbinfo->reloftype);
+ else if (tbinfo->reloftype && !dopt->binary_upgrade)
+ appendPQExpBuffer(q, " OF %s",
+ tbinfo->reloftype);
/*
* If the table is a partition, dump it as such; except in the case of
@@ -15465,7 +15493,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
fmtQualifiedDumpable(parentRel));
}
- if (tbinfo->relkind != RELKIND_MATVIEW)
+ if (tbinfo->relkind != RELKIND_MATVIEW &&
+ !(tbinfo->relkind == RELKIND_VARIABLE && tbinfo->reloftype != NULL))
{
/* Dump the attributes */
actual_atts = 0;
@@ -15591,7 +15620,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
* We must have a parenthesized attribute list, even though
* empty, when not using the OF TYPE or PARTITION OF syntax.
*/
- appendPQExpBufferStr(q, " (\n)");
+ if (tbinfo->relkind != RELKIND_VARIABLE)
+ appendPQExpBufferStr(q, " (\n)");
}
if (tbinfo->ispartition && !dopt->binary_upgrade)
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index e96c662b1e..78eb878cfc 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -84,7 +84,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 */
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 81cd65ee8d..ddf78cc9d2 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -2287,6 +2287,75 @@ my %tests = (
{ %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
unlike => { exclude_dump_test_schema => 1, }, },
+ 'CREATE VARIABLE test_variable' => {
+ all_runs => 1,
+ catch_all => 'CREATE ... commands',
+ create_order => 61,
+ create_sql => 'CREATE VARIABLE dump_test.variable AS integer;',
+ regexp => qr/^
+ \QCREATE VARIABLE dump_test.variable AS integer;\E/xm,
+ like => {
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ only_dump_test_schema => 1,
+ pg_dumpall_dbprivs => 1,
+ schema_only => 1,
+ section_pre_data => 1,
+ test_schema_plus_blobs => 1,
+ with_oids => 1, },
+ unlike => {
+ exclude_dump_test_schema => 1,
+ only_dump_test_table => 1,
+ pg_dumpall_globals => 1,
+ pg_dumpall_globals_clean => 1,
+ role => 1,
+ section_post_data => 1, }, },
+
+ 'CREATE VARIABLE test_variable with field list' => {
+ all_runs => 1,
+ catch_all => 'CREATE ... commands',
+ create_order => 61,
+ create_sql => 'CREATE VARIABLE dump_test.variable1 AS (a int, b numeric(10,2));',
+ regexp => qr/^
+ \QCREATE VARIABLE dump_test.variable1 AS (\E
+ \n\s+\Qa integer,\E
+ \n\s+\Qb numeric(10,2)\E
+ \n\);
+ /xm,
+ like => {
+ binary_upgrade => 1,
+ clean => 1,
+ clean_if_exists => 1,
+ createdb => 1,
+ defaults => 1,
+ exclude_test_table => 1,
+ exclude_test_table_data => 1,
+ no_blobs => 1,
+ no_privs => 1,
+ no_owner => 1,
+ only_dump_test_schema => 1,
+ pg_dumpall_dbprivs => 1,
+ schema_only => 1,
+ section_pre_data => 1,
+ test_schema_plus_blobs => 1,
+ with_oids => 1, },
+ unlike => {
+ exclude_dump_test_schema => 1,
+ only_dump_test_table => 1,
+ pg_dumpall_globals => 1,
+ pg_dumpall_globals_clean => 1,
+ role => 1,
+ section_post_data => 1, }, },
+
+
'ALTER VIEW test_view SET DEFAULT' => {
create_order => 62,
create_sql =>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 4c85f43f09..63411a5fa4 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -794,6 +794,7 @@ exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
case 'i':
case 's':
case 'E':
+ case 'V':
success = listTables(&cmd[1], pattern, show_verbose, show_system);
break;
case 'r':
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 410131e5c7..b193d3ccf8 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -1828,6 +1828,10 @@ describeOneTableDetails(const char *schemaname,
printfPQExpBuffer(&title, _("Foreign table \"%s.%s\""),
schemaname, relationname);
break;
+ case RELKIND_VARIABLE:
+ printfPQExpBuffer(&title, _("schema variable \"%s.%s\""),
+ schemaname, relationname);
+ break;
case RELKIND_PARTITIONED_TABLE:
if (tableinfo.relpersistence == 'u')
printfPQExpBuffer(&title, _("Unlogged table \"%s.%s\""),
@@ -3401,6 +3405,7 @@ listDbRoleSettings(const char *pattern, const char *pattern2)
* m - materialized views
* s - sequences
* E - foreign table (Note: different from 'f', the relkind value)
+ * V - schema variable
* (any order of the above is fine)
*/
bool
@@ -3412,6 +3417,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
bool showMatViews = strchr(tabtypes, 'm') != NULL;
bool showSeq = strchr(tabtypes, 's') != NULL;
bool showForeign = strchr(tabtypes, 'E') != NULL;
+ bool showVariables = strchr(tabtypes, 'V') != NULL;
PQExpBufferData buf;
PGresult *res;
@@ -3419,8 +3425,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
static const bool translate_columns[] = {false, false, true, false, false, false, false};
/* If tabtypes is empty, we default to \dtvmsE (but see also command.c) */
- if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign))
- showTables = showViews = showMatViews = showSeq = showForeign = true;
+ if (!(showTables || showIndexes || showViews || showMatViews || showSeq || showForeign || showVariables))
+ showTables = showViews = showMatViews = showSeq = showForeign = showVariables = true;
initPQExpBuffer(&buf);
@@ -3441,6 +3447,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
" WHEN " CppAsString2(RELKIND_FOREIGN_TABLE) " THEN '%s'"
" WHEN " CppAsString2(RELKIND_PARTITIONED_TABLE) " THEN '%s'"
" WHEN " CppAsString2(RELKIND_PARTITIONED_INDEX) " THEN '%s'"
+ " WHEN " CppAsString2(RELKIND_VARIABLE) " THEN '%s'"
" END as \"%s\",\n"
" pg_catalog.pg_get_userbyid(c.relowner) as \"%s\"",
gettext_noop("Schema"),
@@ -3454,6 +3461,7 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
gettext_noop("foreign table"),
gettext_noop("table"), /* partitioned table */
gettext_noop("index"), /* partitioned index */
+ gettext_noop("schema variable"),
gettext_noop("Type"),
gettext_noop("Owner"));
@@ -3507,6 +3515,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
appendPQExpBufferStr(&buf, "'s',"); /* was RELKIND_SPECIAL */
if (showForeign)
appendPQExpBufferStr(&buf, CppAsString2(RELKIND_FOREIGN_TABLE) ",");
+ if (showVariables)
+ appendPQExpBufferStr(&buf, CppAsString2(RELKIND_VARIABLE) ",");
appendPQExpBufferStr(&buf, "''"); /* dummy */
appendPQExpBufferStr(&buf, ")\n");
diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h
index a4cc5efae0..c5f107d814 100644
--- a/src/bin/psql/describe.h
+++ b/src/bin/psql/describe.h
@@ -60,7 +60,7 @@ extern bool listTSTemplates(const char *pattern, bool verbose);
/* \l */
extern bool listAllDbs(const char *pattern, bool verbose);
-/* \dt, \di, \ds, \dS, etc. */
+/* \dt, \di, \ds, \dS, \dvar etc. */
extern bool listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSystem);
/* \dD */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 702e742af4..2da50f7290 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -167,7 +167,7 @@ slashUsage(unsigned short int pager)
* Use "psql --help=commands | wc" to count correctly. It's okay to count
* the USE_READLINE line even in builds without that.
*/
- output = PageOutput(125, pager ? &(pset.popt.topt) : NULL);
+ output = PageOutput(126, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
@@ -257,6 +257,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n"));
fprintf(output, _(" \\du[S+] [PATTERN] list roles\n"));
fprintf(output, _(" \\dv[S+] [PATTERN] list views\n"));
+ fprintf(output, _(" \\dV[S+] [PATTERN] list schema variables\n"));
fprintf(output, _(" \\dx[+] [PATTERN] list extensions\n"));
fprintf(output, _(" \\dy [PATTERN] list event triggers\n"));
fprintf(output, _(" \\l[+] [PATTERN] list databases\n"));
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b431efc983..ab14923392 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -805,6 +805,22 @@ static const SchemaQuery Query_for_list_of_statistics = {
NULL
};
+static const SchemaQuery Query_for_list_of_variables = {
+ /* min_server_version */
+ 0,
+ /* catname */
+ "pg_catalog.pg_class c",
+ /* selcondition */
+ "c.relkind IN ('V')",
+ /* viscondition */
+ "pg_catalog.pg_table_is_visible(c.oid)",
+ /* namespace */
+ "c.relnamespace",
+ /* result */
+ "pg_catalog.quote_ident(c.relname)",
+ /* qualresult */
+ NULL
+};
/*
* Queries to get lists of names of various kinds of things, possibly
@@ -1249,6 +1265,7 @@ static const pgsql_thing_t words_after_create[] = {
* TABLE ... */
{"USER", Query_for_list_of_roles " UNION SELECT 'MAPPING FOR'"},
{"USER MAPPING FOR", NULL, NULL, NULL},
+ {"VARIABLE", NULL, &Query_for_list_of_variables},
{"VIEW", NULL, NULL, &Query_for_list_of_views},
{NULL} /* end of list */
};
@@ -1604,7 +1621,7 @@ psql_completion(const char *text, int start, int end)
"ABORT", "ALTER", "ANALYZE", "BEGIN", "CALL", "CHECKPOINT", "CLOSE", "CLUSTER",
"COMMENT", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE",
"DELETE FROM", "DISCARD", "DO", "DROP", "END", "EXECUTE", "EXPLAIN",
- "FETCH", "GRANT", "IMPORT", "INSERT", "LISTEN", "LOAD", "LOCK",
+ "FETCH", "GRANT", "IMPORT", "INSERT", "LET", "LISTEN", "LOAD", "LOCK",
"MOVE", "NOTIFY", "PREPARE",
"REASSIGN", "REFRESH MATERIALIZED VIEW", "REINDEX", "RELEASE",
"RESET", "REVOKE", "ROLLBACK",
@@ -1623,7 +1640,7 @@ psql_completion(const char *text, int start, int end)
"\\dF", "\\dFd", "\\dFp", "\\dFt", "\\dg", "\\di", "\\dl", "\\dL",
"\\dm", "\\dn", "\\do", "\\dO", "\\dp",
"\\drds", "\\dRs", "\\dRp", "\\ds", "\\dS",
- "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
+ "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy", "\\dvar",
"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
"\\endif", "\\errverbose", "\\ev",
"\\f",
@@ -2835,6 +2852,14 @@ psql_completion(const char *text, int start, int end)
else if (Matches4("CREATE", "ROLE|USER|GROUP", MatchAny, "IN"))
COMPLETE_WITH_LIST2("GROUP", "ROLE");
+/* CREATE VARIABLE --- is allowed inside CREATE SCHEMA, so use TailMatches */
+ /* Complete CREATE VARIABLE with AS */
+ else if (TailMatches3("CREATE", "VARIABLE", MatchAny))
+ COMPLETE_WITH_CONST("AS");
+ /* Complete CREATE VARIABLE with AS types*/
+ else if (TailMatches4("CREATE", "VARIABLE", MatchAny, "AS"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_datatypes, NULL);
+
/* CREATE VIEW --- is allowed inside CREATE SCHEMA, so use TailMatches */
/* Complete CREATE VIEW with AS */
else if (TailMatches3("CREATE", "VIEW", MatchAny))
@@ -2990,6 +3015,12 @@ psql_completion(const char *text, int start, int end)
else if (Matches5("DROP", "RULE", MatchAny, "ON", MatchAny))
COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+ /* DROP VARIABLE */
+ else if (Matches2("DROP", "VARIABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+ else if (Matches3("DROP", "VARIABLE", MatchAny))
+ COMPLETE_WITH_LIST2("CASCADE", "RESTRICT");
+
/* EXECUTE */
else if (Matches1("EXECUTE"))
COMPLETE_WITH_QUERY(Query_for_list_of_prepared_statements);
@@ -3000,14 +3031,14 @@ psql_completion(const char *text, int start, int end)
* Complete EXPLAIN [ANALYZE] [VERBOSE] with list of EXPLAIN-able commands
*/
else if (Matches1("EXPLAIN"))
- COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
- "ANALYZE", "VERBOSE");
+ COMPLETE_WITH_LIST8("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+ "ANALYZE", "VERBOSE", "LET");
else if (Matches2("EXPLAIN", "ANALYZE"))
- COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
- "VERBOSE");
+ COMPLETE_WITH_LIST7("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE",
+ "VERBOSE", "LET");
else if (Matches2("EXPLAIN", "VERBOSE") ||
Matches3("EXPLAIN", "ANALYZE", "VERBOSE"))
- COMPLETE_WITH_LIST5("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE");
+ COMPLETE_WITH_LIST6("SELECT", "INSERT", "DELETE", "UPDATE", "DECLARE", "LET");
/* FETCH && MOVE */
/* Complete FETCH with one of FORWARD, BACKWARD, RELATIVE */
@@ -3116,6 +3147,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'"
@@ -3129,14 +3161,16 @@ psql_completion(const char *text, int start, int end)
" UNION SELECT 'SEQUENCE'"
" UNION SELECT 'TABLE'"
" UNION SELECT 'TABLESPACE'"
- " UNION SELECT 'TYPE'");
+ " UNION SELECT 'TYPE'"
+ " UNION SELECT 'VARIABLE'");
}
else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "ALL"))
- COMPLETE_WITH_LIST5("FUNCTIONS IN SCHEMA",
+ COMPLETE_WITH_LIST6("FUNCTIONS IN SCHEMA",
"PROCEDURES IN SCHEMA",
"ROUTINES IN SCHEMA",
"SEQUENCES IN SCHEMA",
- "TABLES IN SCHEMA");
+ "TABLES IN SCHEMA",
+ "VARIABLES IN SCHEMA");
else if (TailMatches4("GRANT|REVOKE", MatchAny, "ON", "FOREIGN"))
COMPLETE_WITH_LIST2("DATA WRAPPER", "SERVER");
@@ -3166,6 +3200,8 @@ psql_completion(const char *text, int start, int end)
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_sequences, NULL);
else if (TailMatches1("TABLE"))
COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_tsvmf, NULL);
+ else if (TailMatches1("VARIABLE"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
else if (TailMatches1("TABLESPACE"))
COMPLETE_WITH_QUERY(Query_for_list_of_tablespaces);
else if (TailMatches1("TYPE"))
@@ -3322,7 +3358,7 @@ psql_completion(const char *text, int start, int end)
/* PREPARE xx AS */
else if (Matches3("PREPARE", MatchAny, "AS"))
- COMPLETE_WITH_LIST4("SELECT", "UPDATE", "INSERT", "DELETE FROM");
+ COMPLETE_WITH_LIST5("SELECT", "UPDATE", "INSERT", "DELETE FROM", "LET");
/*
* PREPARE TRANSACTION is missing on purpose. It's intended for transaction
@@ -3545,6 +3581,14 @@ psql_completion(const char *text, int start, int end)
else if (TailMatches4("UPDATE", MatchAny, "SET", MatchAny))
COMPLETE_WITH_CONST("=");
+/* LET --- can be inside EXPLAIN, PREPARE etc */
+ /* If prev. word is LET suggest a list of variables */
+ else if (TailMatches1("LET"))
+ COMPLETE_WITH_SCHEMA_QUERY(Query_for_list_of_variables, NULL);
+ /* Complete LET with "=" */
+ else if (TailMatches2("LET", MatchAny))
+ COMPLETE_WITH_CONST("=");
+
/* USER MAPPING */
else if (Matches3("ALTER|CREATE|DROP", "USER", "MAPPING"))
COMPLETE_WITH_CONST("FOR");
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index dc6c415c58..04a5849056 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -104,6 +104,7 @@ typedef FormData_pg_class *Form_pg_class;
#define RELKIND_FOREIGN_TABLE 'f' /* foreign table */
#define RELKIND_PARTITIONED_TABLE 'p' /* partitioned table */
#define RELKIND_PARTITIONED_INDEX 'I' /* partitioned index */
+#define RELKIND_VARIABLE 'V' /* schema variable */
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
diff --git a/src/include/catalog/pg_default_acl.h b/src/include/catalog/pg_default_acl.h
index d0410f5586..56deef1a45 100644
--- a/src/include/catalog/pg_default_acl.h
+++ b/src/include/catalog/pg_default_acl.h
@@ -57,6 +57,7 @@ typedef FormData_pg_default_acl *Form_pg_default_acl;
#define DEFACLOBJ_FUNCTION 'f' /* function */
#define DEFACLOBJ_TYPE 'T' /* type */
#define DEFACLOBJ_NAMESPACE 'n' /* namespace */
+#define DEFACLOBJ_VARIABLE 'V' /* variable */
#endif /* EXPOSE_TO_CLIENT_CODE */
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 66c6c224a8..08e38c6186 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10202,4 +10202,18 @@
proisstrict => 'f', prorettype => 'bool', proargtypes => 'oid int4 int4 any',
proargmodes => '{i,i,i,v}', prosrc => 'satisfies_hash_partition' },
+#schema variables function interface
+{ oid => '6122', descr => 'returns value of schema variable',
+ proname => 'get_schema_variable', proisstrict => 'f', prorettype => 'anyelement',
+ proargtypes => 'regclass anyelement', prosrc => 'get_schema_variable' },
+{ oid => '6123', descr => 'set value of schema variable',
+ proname => 'set_schema_variable', proisstrict => 'f', prorettype => 'void',
+ proargtypes => 'regclass anyelement', prosrc => 'set_schema_variable' },
+{ oid => '6124', descr => 'get value of field of schema variable of composite type',
+ proname => 'get_schema_variable', proisstrict => 'f', prorettype => 'anyelement',
+ proargtypes => 'regclass name anyelement', prosrc => 'get_schema_variable_field' },
+{ oid => '6125', descr => 'set value of field of schema variable of composite type',
+ proname => 'set_schema_variable', proisstrict => 'f', prorettype => 'void',
+ proargtypes => 'regclass name anyelement', prosrc => 'set_schema_variable_field' },
+
]
diff --git a/src/include/commands/schemavar.h b/src/include/commands/schemavar.h
new file mode 100644
index 0000000000..fcd516d192
--- /dev/null
+++ b/src/include/commands/schemavar.h
@@ -0,0 +1,39 @@
+/*-------------------------------------------------------------------------
+ *
+ * schemavar.h
+ * prototypes for schemavar.c.
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/schemavar.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCHEMAVAR_H
+#define SCHEMAVAR_H
+
+#include "catalog/objectaddress.h"
+#include "nodes/parsenodes.h"
+#include "parser/parse_node.h"
+
+extern ObjectAddress DefineSchemaVariable(ParseState *pstate, CreateSchemaVarStmt *var);
+
+extern void ResetSchemaVariablesCache(void);
+
+extern char *get_schemavar_name(Oid varid);
+
+extern void SetSchemaVariable(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod);
+extern Datum GetSchemaVariable(Oid varid, bool *isNull, Oid typid, int32 typmod, int16 typlen, bool typbyval);
+extern void SetSchemaVariableSecure(Oid varid, Datum value, bool isNull, Oid typid, int32 typmod);
+extern Datum GetSchemaVariableSecure(Oid varid, bool *isNull, Oid typid, int32 typmod, int16 typlen, bool typbyval);
+
+extern void SetSchemaVariableField(Oid varid, const char *fieldname, Datum value, bool isNull, Oid typid, int32 typmod);
+extern Datum GetSchemaVariableField(Oid varid, const char *fieldname, bool *isNull, Oid typid, int32 typmod, int16 typlen, bool typbyval);
+extern void SetSchemaVariableFieldSecure(Oid varid, const char *fieldname, Datum value, bool isNull, Oid typid, int32 typmod);
+extern Datum GetSchemaVariableFieldSecure(Oid varid, const char *fieldname, bool *isNull, Oid typid, int32 typmod, int16 typlen, bool typbyval);
+
+extern void schemavar_get_typ_typmod(Oid varid, Oid *typ, int32 *typmod);
+extern bool is_schemavar(Oid varid, bool *is_rowtype);
+
+#endif
\ No newline at end of file
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index f7b1f77616..58d282dc92 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -138,6 +138,7 @@ typedef enum ExprEvalOp
EEOP_PARAM_EXEC,
EEOP_PARAM_EXTERN,
EEOP_PARAM_CALLBACK,
+ EEOP_PARAM_SCHEMA_VARIABLE,
/* return CaseTestExpr value */
EEOP_CASE_TESTVAL,
@@ -344,11 +345,14 @@ typedef struct ExprEvalStep
TupleDesc argdesc;
} nulltest_row;
- /* for EEOP_PARAM_EXEC/EXTERN */
+ /* for EEOP_PARAM_EXEC/EXTERN/VARIABLE */
struct
{
- int paramid; /* numeric ID for parameter */
- Oid paramtype; /* OID of parameter's datatype */
+ int paramid; /* numeric ID for parameter */
+ Oid paramtype; /* OID of parameter's datatype */
+ int32 paramtypmod; /* typmod of param (not used yet) */
+ int16 paramtyplen; /* expected length */
+ bool paramtypbyval; /* is passed by value */
} param;
/* for EEOP_PARAM_CALLBACK */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index adb159a6da..806b58f6ca 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -348,6 +348,7 @@ typedef enum NodeTag
T_CreateTableAsStmt,
T_CreateSeqStmt,
T_AlterSeqStmt,
+ T_CreateSchemaVarStmt,
T_VariableSetStmt,
T_VariableShowStmt,
T_DiscardStmt,
@@ -419,6 +420,7 @@ typedef enum NodeTag
T_CreateStatsStmt,
T_AlterCollationStmt,
T_CallStmt,
+ T_LetStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
@@ -661,6 +663,7 @@ typedef enum CmdType
CMD_UPDATE, /* update stmt */
CMD_INSERT, /* insert stmt */
CMD_DELETE,
+ CMD_LET,
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 6390f7e8c1..46ba22f10c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1505,6 +1505,15 @@ typedef struct UpdateStmt
WithClause *withClause; /* WITH clause */
} UpdateStmt;
+typedef struct LetStmt
+{
+ NodeTag type;
+ List *target; /* list of fields */
+ Node *selectStmt; /* the source SELECT/VALUES, or NULL */
+ int location;
+} LetStmt;
+
+
/* ----------------------
* Select Statement
*
@@ -1682,6 +1691,7 @@ typedef enum ObjectType
OBJECT_TSTEMPLATE,
OBJECT_TYPE,
OBJECT_USER_MAPPING,
+ OBJECT_VARIABLE,
OBJECT_VIEW
} ObjectType;
@@ -2497,6 +2507,20 @@ typedef struct AlterSeqStmt
bool missing_ok; /* skip error if a role is missing? */
} AlterSeqStmt;
+/* ----------------------
+ * Create VARIABLE Statement
+ * ----------------------
+ */
+typedef struct CreateSchemaVarStmt
+{
+ NodeTag type;
+ RangeVar *variable; /* the variable to create */
+ TypeName *typeName; /* the scalar variable type */
+ Node *defexpr; /* default expression */
+ List *fields; /* the fields of composite variable type */
+ bool if_not_exists; /* just do nothing if it already exists? */
+} CreateSchemaVarStmt;
+
/* ----------------------
* Create {Aggregate|Operator|Type} Statement
* ----------------------
@@ -3231,7 +3255,8 @@ typedef enum DiscardMode
DISCARD_ALL,
DISCARD_PLANS,
DISCARD_SEQUENCES,
- DISCARD_TEMP
+ DISCARD_TEMP,
+ DISCARD_VARIABLES
} DiscardMode;
typedef struct DiscardStmt
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index f90aa7b2a1..44af7c3fbd 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -230,13 +230,17 @@ typedef struct Const
* of the `paramid' field contain the SubLink's subLinkId, and
* the low-order 16 bits contain the column number. (This type
* of Param is also converted to PARAM_EXEC during planning.)
+ *
+ * PARAM_SCHEMA_VARIABLE: The parameter is a access to schema variable
+ * paramid holds varid.
*/
typedef enum ParamKind
{
PARAM_EXTERN,
PARAM_EXEC,
PARAM_SUBLINK,
- PARAM_MULTIEXPR
+ PARAM_MULTIEXPR,
+ PARAM_SCHEMA_VARIABLE
} ParamKind;
typedef struct Param
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 23db40147b..d3ed3f4d0f 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -231,6 +231,7 @@ PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
PG_KEYWORD("leakproof", LEAKPROOF, UNRESERVED_KEYWORD)
PG_KEYWORD("least", LEAST, COL_NAME_KEYWORD)
PG_KEYWORD("left", LEFT, TYPE_FUNC_NAME_KEYWORD)
+PG_KEYWORD("let", LET, UNRESERVED_KEYWORD)
PG_KEYWORD("level", LEVEL, UNRESERVED_KEYWORD)
PG_KEYWORD("like", LIKE, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("limit", LIMIT, RESERVED_KEYWORD)
@@ -434,6 +435,8 @@ PG_KEYWORD("validator", VALIDATOR, UNRESERVED_KEYWORD)
PG_KEYWORD("value", VALUE_P, UNRESERVED_KEYWORD)
PG_KEYWORD("values", VALUES, COL_NAME_KEYWORD)
PG_KEYWORD("varchar", VARCHAR, COL_NAME_KEYWORD)
+PG_KEYWORD("variable", VARIABLE, UNRESERVED_KEYWORD)
+PG_KEYWORD("variables", VARIABLES, UNRESERVED_KEYWORD)
PG_KEYWORD("variadic", VARIADIC, RESERVED_KEYWORD)
PG_KEYWORD("varying", VARYING, UNRESERVED_KEYWORD)
PG_KEYWORD("verbose", VERBOSE, TYPE_FUNC_NAME_KEYWORD)
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 0230543810..d25c061088 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -135,6 +135,8 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
* to process them like UPDATE. (Note this can change intra-statement, for
* cases like INSERT ON CONFLICT UPDATE.)
*
+ * p_is_let: true to process assignment expressions like LET.
+ *
* p_windowdefs: list of WindowDefs representing WINDOW and OVER clauses.
* We collect these while transforming expressions and then transform them
* afterwards (so that any resjunk tlist items needed for the sort/group
@@ -184,6 +186,7 @@ struct ParseState
Relation p_target_relation; /* INSERT/UPDATE/DELETE target rel */
RangeTblEntry *p_target_rangetblentry; /* target rel's RTE */
bool p_is_insert; /* process assignment like INSERT not UPDATE */
+ bool p_is_let; /* process assignment LET stmt */
List *p_windowdefs; /* raw representations of window clauses */
ParseExprKind p_expr_kind; /* what kind of expression we're parsing */
int p_next_resno; /* next targetlist resno to assign */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index b9792acdae..c894147d06 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -129,4 +129,10 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+extern RangeVar *makeRangeVarForTargetOfSchemaVariable(ParseState *pstate,
+ char *field1, char *field2, char *field3,
+ int location, char **fieldname, int *fieldname_pos, bool noerror);
+
+extern Node *toSchemaVariable(ParseState *pstate, char *nspname, char *varname, char *fieldname, int location);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/parser/parse_target.h b/src/include/parser/parse_target.h
index ec6e0c102f..72eb3cd7b6 100644
--- a/src/include/parser/parse_target.h
+++ b/src/include/parser/parse_target.h
@@ -43,4 +43,16 @@ extern TupleDesc expandRecordVariable(ParseState *pstate, Var *var,
extern char *FigureColname(Node *node);
extern char *FigureIndexColname(Node *node);
+extern Node *transformAssignmentIndirection(ParseState *pstate,
+ Node *basenode,
+ const char *targetName,
+ bool targetIsArray,
+ Oid targetTypeId,
+ int32 targetTypMod,
+ Oid targetCollation,
+ ListCell *indirection,
+ Node *rhs,
+ int location);
+
+
#endif /* PARSE_TARGET_H */
diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h
index f4d4be8d0d..d0737a9e4b 100644
--- a/src/include/utils/acl.h
+++ b/src/include/utils/acl.h
@@ -166,6 +166,7 @@ typedef ArrayType Acl;
#define ACL_ALL_RIGHTS_SCHEMA (ACL_USAGE|ACL_CREATE)
#define ACL_ALL_RIGHTS_TABLESPACE (ACL_CREATE)
#define ACL_ALL_RIGHTS_TYPE (ACL_USAGE)
+#define ACL_ALL_RIGHTS_VARIABLE (ACL_SELECT|ACL_UPDATE)
/* operation codes for pg_*_aclmask */
typedef enum
diff --git a/src/test/regress/expected/schema_variables.out b/src/test/regress/expected/schema_variables.out
new file mode 100644
index 0000000000..ab1d3ac66d
--- /dev/null
+++ b/src/test/regress/expected/schema_variables.out
@@ -0,0 +1,456 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+DROP VARIABLE var1, var2;
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+SELECT set_schema_variable('var1', 333);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable
+---------------------
+ 333
+(1 row)
+
+SELECT set_schema_variable('var1', 333::integer);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable
+---------------------
+ 333
+(1 row)
+
+SELECT set_schema_variable('var1', '333.55'::text);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable
+---------------------
+ 333.55
+(1 row)
+
+SELECT get_schema_variable('var1', null::int);
+ get_schema_variable
+---------------------
+ 334
+(1 row)
+
+SELECT get_schema_variable('var1', null::text);
+ get_schema_variable
+---------------------
+ 333.55
+(1 row)
+
+-- access rights test
+CREATE ROLE var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+ERROR: permission denied for schema variable var1
+SELECT get_schema_variable('var1', null::numeric);
+ERROR: permission denied for schema variable var1
+SET ROLE TO DEFAULT;
+GRANT SELECT ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+ERROR: permission denied for schema variable var1
+-- should to work
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable
+---------------------
+ 333.55
+(1 row)
+
+SET ROLE TO DEFAULT;
+GRANT UPDATE ON VARIABLE var1 TO var_test_role;
+SET ROLE TO var_test_role;
+-- should to work
+SELECT set_schema_variable('var1', '1000'::text);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT get_schema_variable('var1', null::numeric);
+ get_schema_variable
+---------------------
+ 1000
+(1 row)
+
+SET ROLE TO DEFAULT;
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+CREATE VARIABLE var AS integer;
+SELECT set_schema_variable('public.var', 1234);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT public.var;
+ var
+------
+ 1234
+(1 row)
+
+DO $$
+BEGIN
+ RAISE NOTICE 'public.var is = %', public.var;
+END;
+$$;
+NOTICE: public.var is = 1234
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+ SELECT public.var;
+$$ LANGUAGE sql SECURITY DEFINER;
+SELECT secure_var();
+ secure_var
+------------
+ 1234
+(1 row)
+
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT public.var;
+ERROR: permission denied for schema variable var
+-- should to work;
+SELECT secure_var();
+ secure_var
+------------
+ 1234
+(1 row)
+
+SET ROLE TO DEFAULT;
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var;
+ QUERY PLAN
+-----------------------------------------------
+ Function Scan on pg_catalog.generate_series g
+ Output: v
+ Function Call: generate_series(1, 100)
+ Filter: (g.v = var)
+(4 rows)
+
+CREATE VIEW schema_var_view AS SELECT var;
+SELECT * FROM schema_var_view;
+ var
+------
+ 1234
+(1 row)
+
+\c -
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+ var
+-----
+
+(1 row)
+
+LET var1 = pi();
+SELECT var1;
+ var1
+------------------
+ 3.14159265358979
+(1 row)
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+ QUERY PLAN
+----------------------------------
+ Let on public.var1
+ -> Result
+ Output: 3.14159265358979
+(3 rows)
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+EXECUTE var_pp(100, 1.23456);
+SELECT var1;
+ var1
+-----------
+ 101.23456
+(1 row)
+
+CREATE VARIABLE var3 AS int;
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+ LET public.var3 = COALESCE(public.var3 + $1, $1);
+ RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+SELECT inc(1);
+ inc
+-----
+ 1
+(1 row)
+
+SELECT inc(1);
+ inc
+-----
+ 2
+(1 row)
+
+SELECT inc(1);
+ inc
+-----
+ 3
+(1 row)
+
+SELECT inc(1) FROM generate_series(1,10);
+ inc
+-----
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+(10 rows)
+
+SET ROLE TO var_test_role;
+-- should to fail
+LET var3 = 0;
+ERROR: permission denied for schema variable var3
+SET ROLE TO DEFAULT;
+DROP VIEW schema_var_view;
+DROP VARIABLE var CASCADE;
+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 (x int, y int, z numeric(10,2));
+\d v1
+schema variable "public.v1"
+ Column | Type
+--------+---------------
+ x | integer
+ y | integer
+ z | numeric(10,2)
+
+\d v2
+schema variable "public.v2"
+ Column | Type
+--------+---------------
+ x | integer
+ y | integer
+ z | numeric(10,2)
+
+-- explicit casting is necessary
+LET v1 = (1,2,3.14)::sv_xyz;
+LET v2 = (10,20,3.14*10)::v2;
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14)::v1;
+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 get_schema_variable('v1', 'z', null::double precision);
+ get_schema_variable
+---------------------
+ 3.14
+(1 row)
+
+SELECT get_schema_variable('v2', 'z', null::double precision);
+ get_schema_variable
+---------------------
+ 31.4
+(1 row)
+
+-- should to fail
+SELECT get_schema_variable('v1', 'w', null::double precision);
+ERROR: the variable "v1" has not field "w"
+SELECT get_schema_variable('v2', 'w', null::double precision);
+ERROR: the variable "v2" has not field "w"
+CREATE VARIABLE v3 AS integer;
+SELECT get_schema_variable('v3', 'w', null::double precision);
+ERROR: the variable "v3" is not of row type
+SELECT set_schema_variable('v1', 'z', 3.14 * 2);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT v1;
+ v1
+------------
+ (1,2,6.28)
+(1 row)
+
+SELECT set_schema_variable('v1', 'z', null::double precision);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT v1;
+ v1
+--------
+ (1,2,)
+(1 row)
+
+SELECT set_schema_variable('v1', 'z', 3.3333::double precision);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT set_schema_variable('v1', 'x', 10000.22);
+ set_schema_variable
+---------------------
+
+(1 row)
+
+SELECT v1;
+ v1
+----------------
+ (10000,2,3.33)
+(1 row)
+
+SELECT v1.x + v1.z;
+ ?column?
+----------
+ 10003.33
+(1 row)
+
+SELECT v2.x + v2.z;
+ ?column?
+----------
+ 41.40
+(1 row)
+
+-- access to composite fields should be safe too
+-- should to fail
+SET ROLE TO var_test_role;
+SELECT v2.x;
+ERROR: permission denied for schema variable v2
+SET ROLE TO DEFAULT;
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+DROP VARIABLE v3;
+DROP ROLE var_test_role;
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+ relname
+----------
+ pg_class
+(1 row)
+
+-- should to fail
+SELECT varx.xxx;
+ERROR: missing FROM-clause entry for table "varx"
+LINE 1: SELECT varx.xxx;
+ ^
+-- 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 VARIABLE v1 AS (a int, b numeric, c text);
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+ v1
+----------------------------
+ (1,3.14159265358979,hello)
+(1 row)
+
+LET v1.b = 10.2222;
+SELECT v1;
+ v1
+-------------------
+ (1,10.2222,hello)
+(1 row)
+
+-- should to fail
+LET v1.x = 10;
+ERROR: cannot assign to field "x" of column "x" because there is no such column in data type v1
+DROP VARIABLE v1;
+-- 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 VARIABLE va2 AS (a numeric, b numeric[]);
+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;
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+ v1
+------------------
+ 6.28318530717958
+(1 row)
+
+CREATE VARIABLE v2 AS (a numeric, b text DEFAULT 'hello');
+LET public.v2.a = pi();
+SELECT v2;
+ v2
+--------------------------
+ (3.14159265358979,hello)
+(1 row)
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 16f979c8d9..9bf379b87b 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# NB: temp.sql does a reconnect which transiently uses 2 connections,
# so keep this parallel group to at most 19 tests
# ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml schema_variables
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 42632be675..42bf4ecb3f 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -191,3 +191,4 @@ test: partition_aggregate
test: event_trigger
test: fast_default
test: stats
+test: schema_variables
diff --git a/src/test/regress/sql/schema_variables.sql b/src/test/regress/sql/schema_variables.sql
new file mode 100644
index 0000000000..27bbd1fdc3
--- /dev/null
+++ b/src/test/regress/sql/schema_variables.sql
@@ -0,0 +1,251 @@
+CREATE VARIABLE var1 AS integer;
+CREATE TEMP VARIABLE var2 AS text;
+
+DROP VARIABLE var1, var2;
+
+-- functional interface
+CREATE VARIABLE var1 AS numeric;
+SELECT set_schema_variable('var1', 333);
+SELECT get_schema_variable('var1', null::numeric);
+
+SELECT set_schema_variable('var1', 333::integer);
+SELECT get_schema_variable('var1', null::numeric);
+
+SELECT set_schema_variable('var1', '333.55'::text);
+SELECT get_schema_variable('var1', null::numeric);
+SELECT get_schema_variable('var1', null::int);
+SELECT get_schema_variable('var1', null::text);
+
+-- access rights test
+
+CREATE ROLE var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+SELECT get_schema_variable('var1', null::numeric);
+
+SET ROLE TO DEFAULT;
+
+GRANT SELECT ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+-- should to fail
+SELECT set_schema_variable('var1', '1000'::text);
+-- should to work
+SELECT get_schema_variable('var1', null::numeric);
+
+SET ROLE TO DEFAULT;
+
+GRANT UPDATE ON VARIABLE var1 TO var_test_role;
+
+SET ROLE TO var_test_role;
+
+-- should to work
+SELECT set_schema_variable('var1', '1000'::text);
+SELECT get_schema_variable('var1', null::numeric);
+
+SET ROLE TO DEFAULT;
+
+REVOKE ALL ON VARIABLE var1 FROM var_test_role;
+
+CREATE VARIABLE var AS integer;
+
+SELECT set_schema_variable('public.var', 1234);
+
+SELECT public.var;
+
+DO $$
+BEGIN
+ RAISE NOTICE 'public.var is = %', public.var;
+END;
+$$;
+
+CREATE OR REPLACE FUNCTION secure_var()
+RETURNS int AS $$
+ SELECT public.var;
+$$ LANGUAGE sql SECURITY DEFINER;
+
+SELECT secure_var();
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+SELECT public.var;
+
+-- should to work;
+SELECT secure_var();
+
+SET ROLE TO DEFAULT;
+
+EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM generate_series(1,100) g(v) WHERE v = var;
+
+CREATE VIEW schema_var_view AS SELECT var;
+
+SELECT * FROM schema_var_view;
+
+\c -
+
+-- should to work still, but var will be empty
+SELECT * FROM schema_var_view;
+
+LET var1 = pi();
+
+SELECT var1;
+
+-- we can look on execution plan
+EXPLAIN (VERBOSE, COSTS OFF) LET var1 = pi();
+
+-- LET can be prepared
+PREPARE var_pp(int, numeric) AS LET var1 = $1 + $2;
+
+EXECUTE var_pp(100, 1.23456);
+
+SELECT var1;
+
+CREATE VARIABLE var3 AS int;
+
+CREATE OR REPLACE FUNCTION inc(int)
+RETURNS int AS $$
+BEGIN
+ LET public.var3 = COALESCE(public.var3 + $1, $1);
+ RETURN var3;
+END;
+$$ LANGUAGE plpgsql;
+
+SELECT inc(1);
+SELECT inc(1);
+SELECT inc(1);
+
+SELECT inc(1) FROM generate_series(1,10);
+
+SET ROLE TO var_test_role;
+
+-- should to fail
+LET var3 = 0;
+
+SET ROLE TO DEFAULT;
+
+DROP VIEW schema_var_view;
+
+DROP VARIABLE var CASCADE;
+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 (x int, y int, z numeric(10,2));
+
+\d v1
+\d v2
+
+-- explicit casting is necessary
+LET v1 = (1,2,3.14)::sv_xyz;
+LET v2 = (10,20,3.14*10)::v2;
+
+-- should to work too - there are prepared casts
+LET v1 = (1,2,3.14)::v1;
+
+SELECT v1;
+SELECT v2;
+SELECT (v1).*;
+SELECT (v2).*;
+
+SELECT get_schema_variable('v1', 'z', null::double precision);
+SELECT get_schema_variable('v2', 'z', null::double precision);
+
+-- should to fail
+SELECT get_schema_variable('v1', 'w', null::double precision);
+SELECT get_schema_variable('v2', 'w', null::double precision);
+
+CREATE VARIABLE v3 AS integer;
+SELECT get_schema_variable('v3', 'w', null::double precision);
+
+SELECT set_schema_variable('v1', 'z', 3.14 * 2);
+SELECT v1;
+SELECT set_schema_variable('v1', 'z', null::double precision);
+SELECT v1;
+SELECT set_schema_variable('v1', 'z', 3.3333::double precision);
+SELECT set_schema_variable('v1', 'x', 10000.22);
+SELECT v1;
+
+SELECT v1.x + v1.z;
+SELECT v2.x + v2.z;
+
+-- access to composite fields should be safe too
+-- should to fail
+SET ROLE TO var_test_role;
+
+SELECT v2.x;
+
+SET ROLE TO DEFAULT;
+
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+DROP VARIABLE v3;
+
+DROP ROLE var_test_role;
+
+-- scalar variables should not be in conflict with qualified column
+CREATE VARIABLE varx AS text;
+SELECT varx.relname FROM pg_class varx WHERE varx.relname = 'pg_class';
+
+-- should to fail
+SELECT varx.xxx;
+
+
+-- variables can be updated under RO transaction
+
+BEGIN;
+SET TRANSACTION READ ONLY;
+LET varx = 'hello';
+COMMIT;
+
+SELECT varx;
+
+DROP VARIABLE varx;
+
+CREATE VARIABLE v1 AS (a int, b numeric, c text);
+LET v1 = (1, pi(), 'hello');
+SELECT v1;
+LET v1.b = 10.2222;
+SELECT v1;
+
+-- should to fail
+LET v1.x = 10;
+
+DROP VARIABLE v1;
+
+-- arrays are supported
+CREATE VARIABLE va1 AS numeric[];
+LET va1 = ARRAY[1.1,2.1];
+LET va1[1] = 10.1;
+SELECT va1;
+
+CREATE VARIABLE va2 AS (a numeric, b numeric[]);
+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;
+
+-- default values
+CREATE VARIABLE v1 AS numeric DEFAULT pi();
+LET v1 = v1 * 2;
+SELECT v1;
+
+CREATE VARIABLE v2 AS (a numeric, b text DEFAULT 'hello');
+LET public.v2.a = pi();
+SELECT v2;
+
+DROP VARIABLE v1;
+DROP VARIABLE v2;
+
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 54850ee4d6..51e0a179b6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -431,6 +431,7 @@ CreateReplicationSlotCmd
CreateRoleStmt
CreateSchemaStmt
CreateSchemaStmtContext
+CreateSchemaVarStmt
CreateSeqStmt
CreateStatsStmt
CreateStmt