summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorPeter Eisentraut <peter_e@gmx.net>2015-04-26 10:33:14 -0400
committerPeter Eisentraut <peter_e@gmx.net>2015-04-26 10:33:14 -0400
commitcac76582053ef8ea07df65fed0757f352da23705 (patch)
tree6ae01041aa61db9d686638b9d4c3ccd30d7c6487 /src/backend
parentf320cbb615e0374b18836337713239da58705cf3 (diff)
downloadpostgresql-cac76582053ef8ea07df65fed0757f352da23705.tar.gz
Add transforms feature
This provides a mechanism for specifying conversions between SQL data types and procedural languages. As examples, there are transforms for hstore and ltree for PL/Perl and PL/Python. reviews by Pavel Stěhule and Andres Freund
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/catalog/Makefile1
-rw-r--r--src/backend/catalog/dependency.c8
-rw-r--r--src/backend/catalog/information_schema.sql34
-rw-r--r--src/backend/catalog/objectaddress.c65
-rw-r--r--src/backend/catalog/pg_aggregate.c1
-rw-r--r--src/backend/catalog/pg_proc.c46
-rw-r--r--src/backend/commands/dropcmds.c8
-rw-r--r--src/backend/commands/event_trigger.c3
-rw-r--r--src/backend/commands/functioncmds.c343
-rw-r--r--src/backend/commands/proclang.c3
-rw-r--r--src/backend/commands/typecmds.c1
-rw-r--r--src/backend/nodes/copyfuncs.c17
-rw-r--r--src/backend/nodes/equalfuncs.c15
-rw-r--r--src/backend/parser/gram.y90
-rw-r--r--src/backend/tcop/utility.c16
-rw-r--r--src/backend/utils/adt/ruleutils.c39
-rw-r--r--src/backend/utils/cache/lsyscache.c71
-rw-r--r--src/backend/utils/cache/syscache.c23
-rw-r--r--src/backend/utils/fmgr/funcapi.c44
19 files changed, 799 insertions, 29 deletions
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a403c64360..c73f20d6a5 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -41,6 +41,7 @@ POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h pg_policy.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
+ pg_transform.h \
toasting.h indexing.h \
)
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index bacb242e4e..6271f8f5a0 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -47,6 +47,7 @@
#include "catalog/pg_proc.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
@@ -1265,6 +1266,10 @@ doDeletion(const ObjectAddress *object, int flags)
RemovePolicyById(object->objectId);
break;
+ case OCLASS_TRANSFORM:
+ DropTransformById(object->objectId);
+ break;
+
default:
elog(ERROR, "unrecognized object class: %u",
object->classId);
@@ -2373,6 +2378,9 @@ getObjectClass(const ObjectAddress *object)
case PolicyRelationId:
return OCLASS_POLICY;
+
+ case TransformRelationId:
+ return OCLASS_TRANSFORM;
}
/* shouldn't get here */
diff --git a/src/backend/catalog/information_schema.sql b/src/backend/catalog/information_schema.sql
index 3c6a049b6d..6e1b241956 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1928,7 +1928,39 @@ GRANT SELECT ON tables TO PUBLIC;
* TRANSFORMS view
*/
--- feature not supported
+CREATE VIEW transforms AS
+ SELECT CAST(current_database() AS sql_identifier) AS udt_catalog,
+ CAST(nt.nspname AS sql_identifier) AS udt_schema,
+ CAST(t.typname AS sql_identifier) AS udt_name,
+ CAST(current_database() AS sql_identifier) AS specific_catalog,
+ CAST(np.nspname AS sql_identifier) AS specific_schema,
+ CAST(p.proname || '_' || CAST(p.oid AS text) AS sql_identifier) AS specific_name,
+ CAST(l.lanname AS sql_identifier) AS group_name,
+ CAST('FROM SQL' AS character_data) AS transform_type
+ FROM pg_type t JOIN pg_transform x ON t.oid = x.trftype
+ JOIN pg_language l ON x.trflang = l.oid
+ JOIN pg_proc p ON x.trffromsql = p.oid
+ JOIN pg_namespace nt ON t.typnamespace = nt.oid
+ JOIN pg_namespace np ON p.pronamespace = np.oid
+
+ UNION
+
+ SELECT CAST(current_database() AS sql_identifier) AS udt_catalog,
+ CAST(nt.nspname AS sql_identifier) AS udt_schema,
+ CAST(t.typname AS sql_identifier) AS udt_name,
+ CAST(current_database() AS sql_identifier) AS specific_catalog,
+ CAST(np.nspname AS sql_identifier) AS specific_schema,
+ CAST(p.proname || '_' || CAST(p.oid AS text) AS sql_identifier) AS specific_name,
+ CAST(l.lanname AS sql_identifier) AS group_name,
+ CAST('TO SQL' AS character_data) AS transform_type
+ FROM pg_type t JOIN pg_transform x ON t.oid = x.trftype
+ JOIN pg_language l ON x.trflang = l.oid
+ JOIN pg_proc p ON x.trftosql = p.oid
+ JOIN pg_namespace nt ON t.typnamespace = nt.oid
+ JOIN pg_namespace np ON p.pronamespace = np.oid
+
+ ORDER BY udt_catalog, udt_schema, udt_name, group_name, transform_type -- some sensible grouping for interactive use
+;
/*
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 30cb699072..5e1bda4ed2 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -45,6 +45,7 @@
#include "catalog/pg_policy.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
@@ -335,6 +336,12 @@ static const ObjectPropertyType ObjectProperty[] =
true
},
{
+ TransformRelationId,
+ TransformOidIndexId,
+ TRFOID,
+ InvalidAttrNumber
+ },
+ {
TriggerRelationId,
TriggerOidIndexId,
-1,
@@ -760,6 +767,19 @@ get_object_address(ObjectType objtype, List *objname, List *objargs,
address.objectSubId = 0;
}
break;
+ case OBJECT_TRANSFORM:
+ {
+ TypeName *typename = (TypeName *) linitial(objname);
+ char *langname = (char *) linitial(objargs);
+ Oid type_id = LookupTypeNameOid(NULL, typename, missing_ok);
+ Oid lang_id = get_language_oid(langname, missing_ok);
+
+ address.classId = TransformRelationId;
+ address.objectId =
+ get_transform_oid(type_id, lang_id, missing_ok);
+ address.objectSubId = 0;
+ }
+ break;
case OBJECT_TSPARSER:
address.classId = TSParserRelationId;
address.objectId = get_ts_parser_oid(objname, missing_ok);
@@ -2006,6 +2026,15 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
format_type_be(targettypeid))));
}
break;
+ case OBJECT_TRANSFORM:
+ {
+ TypeName *typename = (TypeName *) linitial(objname);
+ Oid typeid = typenameTypeId(NULL, typename);
+
+ if (!pg_type_ownercheck(typeid, roleid))
+ aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
+ }
+ break;
case OBJECT_TABLESPACE:
if (!pg_tablespace_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TABLESPACE,
@@ -2467,19 +2496,10 @@ getObjectDescription(const ObjectAddress *object)
}
case OCLASS_LANGUAGE:
- {
- HeapTuple langTup;
+ appendStringInfo(&buffer, _("language %s"),
+ get_language_name(object->objectId, false));
+ break;
- langTup = SearchSysCache1(LANGOID,
- ObjectIdGetDatum(object->objectId));
- if (!HeapTupleIsValid(langTup))
- elog(ERROR, "cache lookup failed for language %u",
- object->objectId);
- appendStringInfo(&buffer, _("language %s"),
- NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname));
- ReleaseSysCache(langTup);
- break;
- }
case OCLASS_LARGEOBJECT:
appendStringInfo(&buffer, _("large object %u"),
object->objectId);
@@ -2667,6 +2687,27 @@ getObjectDescription(const ObjectAddress *object)
break;
}
+ case OCLASS_TRANSFORM:
+ {
+ HeapTuple trfTup;
+ Form_pg_transform trfForm;
+
+ trfTup = SearchSysCache1(TRFOID,
+ ObjectIdGetDatum(object->objectId));
+ if (!HeapTupleIsValid(trfTup))
+ elog(ERROR, "could not find tuple for transform %u",
+ object->objectId);
+
+ trfForm = (Form_pg_transform) GETSTRUCT(trfTup);
+
+ appendStringInfo(&buffer, _("transform for %s language %s"),
+ format_type_be(trfForm->trftype),
+ get_language_name(trfForm->trflang, false));
+
+ ReleaseSysCache(trfTup);
+ break;
+ }
+
case OCLASS_TRIGGER:
{
Relation trigDesc;
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index df7bdfb03c..5f211dacde 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -545,6 +545,7 @@ AggregateCreate(const char *aggName,
parameterModes, /* parameterModes */
parameterNames, /* parameterNames */
parameterDefaults, /* parameterDefaults */
+ PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */
1, /* procost */
0); /* prorows */
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index fd5060aa7c..122982951e 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -23,7 +23,9 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "commands/defrem.h"
#include "executor/functions.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
@@ -59,7 +61,7 @@ static bool match_prosrc_to_literal(const char *prosrc, const char *literal,
/* ----------------------------------------------------------------
* ProcedureCreate
*
- * Note: allParameterTypes, parameterModes, parameterNames, and proconfig
+ * Note: allParameterTypes, parameterModes, parameterNames, trftypes, and proconfig
* are either arrays of the proper types or NULL. We declare them Datum,
* not "ArrayType *", to avoid importing array.h into pg_proc_fn.h.
* ----------------------------------------------------------------
@@ -86,6 +88,7 @@ ProcedureCreate(const char *procedureName,
Datum parameterModes,
Datum parameterNames,
List *parameterDefaults,
+ Datum trftypes,
Datum proconfig,
float4 procost,
float4 prorows)
@@ -116,6 +119,7 @@ ProcedureCreate(const char *procedureName,
ObjectAddress myself,
referenced;
int i;
+ Oid trfid;
/*
* sanity checks
@@ -360,6 +364,10 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_proargdefaults - 1] = CStringGetTextDatum(nodeToString(parameterDefaults));
else
nulls[Anum_pg_proc_proargdefaults - 1] = true;
+ if (trftypes != PointerGetDatum(NULL))
+ values[Anum_pg_proc_protrftypes - 1] = trftypes;
+ else
+ nulls[Anum_pg_proc_protrftypes - 1] = true;
values[Anum_pg_proc_prosrc - 1] = CStringGetTextDatum(prosrc);
if (probin)
values[Anum_pg_proc_probin - 1] = CStringGetTextDatum(probin);
@@ -624,6 +632,15 @@ ProcedureCreate(const char *procedureName,
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ /* dependency on transform used by return type, if any */
+ if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
+ {
+ referenced.classId = TransformRelationId;
+ referenced.objectId = trfid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
/* dependency on parameter types */
for (i = 0; i < allParamCount; i++)
{
@@ -631,6 +648,15 @@ ProcedureCreate(const char *procedureName,
referenced.objectId = allParams[i];
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependency on transform used by parameter type, if any */
+ if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
+ {
+ referenced.classId = TransformRelationId;
+ referenced.objectId = trfid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
}
/* dependency on parameter default expressions */
@@ -1128,3 +1154,21 @@ fail:
*newcursorpos = newcp;
return false;
}
+
+List *
+oid_array_to_list(Datum datum)
+{
+ ArrayType *array = DatumGetArrayTypeP(datum);
+ Datum *values;
+ int nelems;
+ int i;
+ List *result = NIL;
+
+ deconstruct_array(array,
+ OIDOID,
+ sizeof(Oid), true, 'i',
+ &values, NULL, &nelems);
+ for (i = 0; i < nelems; i++)
+ result = lappend_oid(result, values[i]);
+ return result;
+}
diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c
index a1b0d4d2fa..80a0eb6e9e 100644
--- a/src/backend/commands/dropcmds.c
+++ b/src/backend/commands/dropcmds.c
@@ -366,6 +366,14 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs)
}
}
break;
+ case OBJECT_TRANSFORM:
+ if (!type_in_list_does_not_exist_skipping(objname, &msg, &name))
+ {
+ msg = gettext_noop("transform for type %s language %s does not exist, skipping");
+ name = TypeNameToString((TypeName *) linitial(objname));
+ args = (char *) linitial(objargs);
+ }
+ break;
case OBJECT_TRIGGER:
if (!owningrel_does_not_exist_skipping(objname, &msg, &name))
{
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index f07fd06337..0110b0603d 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -98,6 +98,7 @@ static event_trigger_support_data event_trigger_support[] = {
{"SERVER", true},
{"TABLE", true},
{"TABLESPACE", false},
+ {"TRANSFORM", true},
{"TRIGGER", true},
{"TEXT SEARCH CONFIGURATION", true},
{"TEXT SEARCH DICTIONARY", true},
@@ -1090,6 +1091,7 @@ EventTriggerSupportsObjectType(ObjectType obtype)
case OBJECT_SEQUENCE:
case OBJECT_TABCONSTRAINT:
case OBJECT_TABLE:
+ case OBJECT_TRANSFORM:
case OBJECT_TRIGGER:
case OBJECT_TSCONFIGURATION:
case OBJECT_TSDICTIONARY:
@@ -1137,6 +1139,7 @@ EventTriggerSupportsObjectClass(ObjectClass objclass)
case OCLASS_REWRITE:
case OCLASS_TRIGGER:
case OCLASS_SCHEMA:
+ case OCLASS_TRANSFORM:
case OCLASS_TSPARSER:
case OCLASS_TSDICT:
case OCLASS_TSTEMPLATE:
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index f4725056da..9a92fdcff7 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -45,6 +45,7 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_proc_fn.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "catalog/pg_type_fn.h"
#include "commands/alter.h"
@@ -583,6 +584,7 @@ static void
compute_attributes_sql_style(List *options,
List **as,
char **language,
+ Node **transform,
bool *windowfunc_p,
char *volatility_p,
bool *strict_p,
@@ -595,6 +597,7 @@ compute_attributes_sql_style(List *options,
ListCell *option;
DefElem *as_item = NULL;
DefElem *language_item = NULL;
+ DefElem *transform_item = NULL;
DefElem *windowfunc_item = NULL;
DefElem *volatility_item = NULL;
DefElem *strict_item = NULL;
@@ -624,6 +627,14 @@ compute_attributes_sql_style(List *options,
errmsg("conflicting or redundant options")));
language_item = defel;
}
+ else if (strcmp(defel->defname, "transform") == 0)
+ {
+ if (transform_item)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ transform_item = defel;
+ }
else if (strcmp(defel->defname, "window") == 0)
{
if (windowfunc_item)
@@ -671,6 +682,8 @@ compute_attributes_sql_style(List *options,
}
/* process optional items */
+ if (transform_item)
+ *transform = transform_item->arg;
if (windowfunc_item)
*windowfunc_p = intVal(windowfunc_item->arg);
if (volatility_item)
@@ -807,7 +820,6 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
}
-
/*
* CreateFunction
* Execute a CREATE FUNCTION utility statement.
@@ -822,6 +834,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
char *language;
Oid languageOid;
Oid languageValidator;
+ Node *transformDefElem = NULL;
char *funcname;
Oid namespaceId;
AclResult aclresult;
@@ -831,6 +844,8 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
ArrayType *parameterNames;
List *parameterDefaults;
Oid variadicArgType;
+ List *trftypes_list = NIL;
+ ArrayType *trftypes;
Oid requiredResultType;
bool isWindowFunc,
isStrict,
@@ -866,7 +881,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
/* override attributes from explicit list */
compute_attributes_sql_style(stmt->options,
- &as_clause, &language,
+ &as_clause, &language, &transformDefElem,
&isWindowFunc, &volatility,
&isStrict, &security, &isLeakProof,
&proconfig, &procost, &prorows);
@@ -915,6 +930,23 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("only superuser can define a leakproof function")));
+ if (transformDefElem)
+ {
+ ListCell *lc;
+
+ Assert(IsA(transformDefElem, List));
+
+ foreach (lc, (List *) transformDefElem)
+ {
+ Oid typeid = typenameTypeId(NULL, lfirst(lc));
+ Oid elt = get_base_element_type(typeid);
+ typeid = elt ? elt : typeid;
+
+ get_transform_oid(typeid, languageOid, false);
+ trftypes_list = lappend_oid(trftypes_list, typeid);
+ }
+ }
+
/*
* Convert remaining parameters of CREATE to form wanted by
* ProcedureCreate.
@@ -958,6 +990,25 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
returnsSet = false;
}
+ if (list_length(trftypes_list) > 0)
+ {
+ ListCell *lc;
+ Datum *arr;
+ int i;
+
+ arr = palloc(list_length(trftypes_list) * sizeof(Datum));
+ i = 0;
+ foreach (lc, trftypes_list)
+ arr[i++] = ObjectIdGetDatum(lfirst_oid(lc));
+ trftypes = construct_array(arr, list_length(trftypes_list),
+ OIDOID, sizeof(Oid), true, 'i');
+ }
+ else
+ {
+ /* store SQL NULL instead of emtpy array */
+ trftypes = NULL;
+ }
+
compute_attributes_with_style(stmt->withClause, &isStrict, &volatility);
interpret_AS_clause(languageOid, language, funcname, as_clause,
@@ -1014,6 +1065,7 @@ CreateFunction(CreateFunctionStmt *stmt, const char *queryString)
PointerGetDatum(parameterModes),
PointerGetDatum(parameterNames),
parameterDefaults,
+ PointerGetDatum(trftypes),
PointerGetDatum(proconfig),
procost,
prorows);
@@ -1653,6 +1705,293 @@ DropCastById(Oid castOid)
heap_close(relation, RowExclusiveLock);
}
+
+static void
+check_transform_function(Form_pg_proc procstruct)
+{
+ if (procstruct->provolatile == PROVOLATILE_VOLATILE)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not be volatile")));
+ if (procstruct->proisagg)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not be an aggregate function")));
+ if (procstruct->proiswindow)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not be a window function")));
+ if (procstruct->proretset)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must not return a set")));
+ if (procstruct->pronargs != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("transform function must take one argument")));
+ if (procstruct->proargtypes.values[0] != INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("first argument of transform function must be type \"internal\"")));
+}
+
+
+/*
+ * CREATE TRANSFORM
+ */
+Oid
+CreateTransform(CreateTransformStmt *stmt)
+{
+ Oid typeid;
+ char typtype;
+ Oid langid;
+ Oid fromsqlfuncid;
+ Oid tosqlfuncid;
+ AclResult aclresult;
+ Form_pg_proc procstruct;
+ Datum values[Natts_pg_transform];
+ bool nulls[Natts_pg_transform];
+ bool replaces[Natts_pg_transform];
+ Oid transformid;
+ HeapTuple tuple;
+ HeapTuple newtuple;
+ Relation relation;
+ ObjectAddress myself,
+ referenced;
+ bool is_replace;
+
+ /*
+ * Get the type
+ */
+ typeid = typenameTypeId(NULL, stmt->type_name);
+ typtype = get_typtype(typeid);
+
+ if (typtype == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("data type %s is a pseudo-type",
+ TypeNameToString(stmt->type_name))));
+
+ if (typtype == TYPTYPE_DOMAIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("data type %s is a domain",
+ TypeNameToString(stmt->type_name))));
+
+ if (!pg_type_ownercheck(typeid, GetUserId()))
+ aclcheck_error_type(ACLCHECK_NOT_OWNER, typeid);
+
+ aclresult = pg_type_aclcheck(typeid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typeid);
+
+ /*
+ * Get the language
+ */
+ langid = get_language_oid(stmt->lang, false);
+
+ aclresult = pg_language_aclcheck(langid, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_LANGUAGE, stmt->lang);
+
+ /*
+ * Get the functions
+ */
+ if (stmt->fromsql)
+ {
+ fromsqlfuncid = LookupFuncNameTypeNames(stmt->fromsql->funcname, stmt->fromsql->funcargs, false);
+
+ if (!pg_proc_ownercheck(fromsqlfuncid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
+
+ aclresult = pg_proc_aclcheck(fromsqlfuncid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->fromsql->funcname));
+
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(fromsqlfuncid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", fromsqlfuncid);
+ procstruct = (Form_pg_proc) GETSTRUCT(tuple);
+ if (procstruct->prorettype != INTERNALOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("return data type of FROM SQL function must be \"internal\"")));
+ check_transform_function(procstruct);
+ ReleaseSysCache(tuple);
+ }
+ else
+ fromsqlfuncid = InvalidOid;
+
+ if (stmt->tosql)
+ {
+ tosqlfuncid = LookupFuncNameTypeNames(stmt->tosql->funcname, stmt->tosql->funcargs, false);
+
+ if (!pg_proc_ownercheck(tosqlfuncid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
+
+ aclresult = pg_proc_aclcheck(tosqlfuncid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC, NameListToString(stmt->tosql->funcname));
+
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(tosqlfuncid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", tosqlfuncid);
+ procstruct = (Form_pg_proc) GETSTRUCT(tuple);
+ if (procstruct->prorettype != typeid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("return data type of TO SQL function must be the transform data type")));
+ check_transform_function(procstruct);
+ ReleaseSysCache(tuple);
+ }
+ else
+ tosqlfuncid = InvalidOid;
+
+ /*
+ * Ready to go
+ */
+ values[Anum_pg_transform_trftype - 1] = ObjectIdGetDatum(typeid);
+ values[Anum_pg_transform_trflang - 1] = ObjectIdGetDatum(langid);
+ values[Anum_pg_transform_trffromsql - 1] = ObjectIdGetDatum(fromsqlfuncid);
+ values[Anum_pg_transform_trftosql - 1] = ObjectIdGetDatum(tosqlfuncid);
+
+ MemSet(nulls, false, sizeof(nulls));
+
+ relation = heap_open(TransformRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCache2(TRFTYPELANG,
+ ObjectIdGetDatum(typeid),
+ ObjectIdGetDatum(langid));
+ if (HeapTupleIsValid(tuple))
+ {
+ if (!stmt->replace)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("transform for type %s language %s already exists",
+ format_type_be(typeid),
+ stmt->lang)));
+
+ MemSet(replaces, false, sizeof(replaces));
+ replaces[Anum_pg_transform_trffromsql - 1] = true;
+ replaces[Anum_pg_transform_trftosql - 1] = true;
+
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces);
+ simple_heap_update(relation, &newtuple->t_self, newtuple);
+
+ transformid = HeapTupleGetOid(tuple);
+ ReleaseSysCache(tuple);
+ is_replace = true;
+ }
+ else
+ {
+ newtuple = heap_form_tuple(RelationGetDescr(relation), values, nulls);
+ transformid = simple_heap_insert(relation, newtuple);
+ is_replace = false;
+ }
+
+ CatalogUpdateIndexes(relation, newtuple);
+
+ if (is_replace)
+ deleteDependencyRecordsFor(TransformRelationId, transformid, true);
+
+ /* make dependency entries */
+ myself.classId = TransformRelationId;
+ myself.objectId = transformid;
+ myself.objectSubId = 0;
+
+ /* dependency on language */
+ referenced.classId = LanguageRelationId;
+ referenced.objectId = langid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependency on type */
+ referenced.classId = TypeRelationId;
+ referenced.objectId = typeid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ /* dependencies on functions */
+ if (OidIsValid(fromsqlfuncid))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = fromsqlfuncid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+ if (OidIsValid(tosqlfuncid))
+ {
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = tosqlfuncid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+ }
+
+ /* dependency on extension */
+ recordDependencyOnCurrentExtension(&myself, is_replace);
+
+ /* Post creation hook for new transform */
+ InvokeObjectPostCreateHook(TransformRelationId, transformid, 0);
+
+ heap_freetuple(newtuple);
+
+ heap_close(relation, RowExclusiveLock);
+
+ return transformid;
+}
+
+
+/*
+ * get_transform_oid - given type OID and language OID, look up a transform OID
+ *
+ * If missing_ok is false, throw an error if the transform is not found. If
+ * true, just return InvalidOid.
+ */
+Oid
+get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok)
+{
+ Oid oid;
+
+ oid = GetSysCacheOid2(TRFTYPELANG,
+ ObjectIdGetDatum(type_id),
+ ObjectIdGetDatum(lang_id));
+ if (!OidIsValid(oid) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("transform for type %s language \"%s\" does not exist",
+ format_type_be(type_id),
+ get_language_name(lang_id, false))));
+ return oid;
+}
+
+
+void
+DropTransformById(Oid transformOid)
+{
+ Relation relation;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ HeapTuple tuple;
+
+ relation = heap_open(TransformRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&scankey,
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(transformOid));
+ scan = systable_beginscan(relation, TransformOidIndexId, true,
+ NULL, 1, &scankey);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for transform %u", transformOid);
+ simple_heap_delete(relation, &tuple->t_self);
+
+ systable_endscan(scan);
+ heap_close(relation, RowExclusiveLock);
+}
+
+
/*
* Subroutine for ALTER FUNCTION/AGGREGATE SET SCHEMA/RENAME
*
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 11e6213e80..3995a575e4 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -141,6 +141,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
PointerGetDatum(NULL),
NIL,
PointerGetDatum(NULL),
+ PointerGetDatum(NULL),
1,
0);
handlerOid = tmpAddr.objectId;
@@ -179,6 +180,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
PointerGetDatum(NULL),
NIL,
PointerGetDatum(NULL),
+ PointerGetDatum(NULL),
1,
0);
inlineOid = tmpAddr.objectId;
@@ -220,6 +222,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
PointerGetDatum(NULL),
NIL,
PointerGetDatum(NULL),
+ PointerGetDatum(NULL),
1,
0);
valOid = tmpAddr.objectId;
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 7d8561022a..ab13be225c 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1616,6 +1616,7 @@ makeRangeConstructors(const char *name, Oid namespace,
PointerGetDatum(NULL), /* parameterModes */
PointerGetDatum(NULL), /* parameterNames */
NIL, /* parameterDefaults */
+ PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */
1.0, /* procost */
0.0); /* prorows */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 59c755d7a5..1685efe254 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3625,6 +3625,20 @@ _copyImportForeignSchemaStmt(const ImportForeignSchemaStmt *from)
return newnode;
}
+static CreateTransformStmt *
+_copyCreateTransformStmt(const CreateTransformStmt *from)
+{
+ CreateTransformStmt *newnode = makeNode(CreateTransformStmt);
+
+ COPY_SCALAR_FIELD(replace);
+ COPY_NODE_FIELD(type_name);
+ COPY_STRING_FIELD(lang);
+ COPY_NODE_FIELD(fromsql);
+ COPY_NODE_FIELD(tosql);
+
+ return newnode;
+}
+
static CreateTrigStmt *
_copyCreateTrigStmt(const CreateTrigStmt *from)
{
@@ -4568,6 +4582,9 @@ copyObject(const void *from)
case T_ImportForeignSchemaStmt:
retval = _copyImportForeignSchemaStmt(from);
break;
+ case T_CreateTransformStmt:
+ retval = _copyCreateTransformStmt(from);
+ break;
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3bc81762af..578ead5463 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1780,6 +1780,18 @@ _equalImportForeignSchemaStmt(const ImportForeignSchemaStmt *a, const ImportFore
}
static bool
+_equalCreateTransformStmt(const CreateTransformStmt *a, const CreateTransformStmt *b)
+{
+ COMPARE_SCALAR_FIELD(replace);
+ COMPARE_NODE_FIELD(type_name);
+ COMPARE_STRING_FIELD(lang);
+ COMPARE_NODE_FIELD(fromsql);
+ COMPARE_NODE_FIELD(tosql);
+
+ return true;
+}
+
+static bool
_equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
{
COMPARE_STRING_FIELD(trigname);
@@ -2991,6 +3003,9 @@ equal(const void *a, const void *b)
case T_ImportForeignSchemaStmt:
retval = _equalImportForeignSchemaStmt(a, b);
break;
+ case T_CreateTransformStmt:
+ retval = _equalCreateTransformStmt(a, b);
+ break;
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5818858a29..962a69d22a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -241,12 +241,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
- CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
+ CreateAssertStmt CreateTransformStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatePolicyStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
DropPolicyStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
+ DropTransformStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt ImportForeignSchemaStmt IndexStmt InsertStmt
ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt
@@ -366,6 +367,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
opt_enum_val_list enum_val_list table_func_column_list
create_generic_options alter_generic_options
relation_expr_list dostmt_opt_list
+ transform_element_list transform_type_list
%type <list> opt_fdw_options fdw_options
%type <defelt> fdw_option
@@ -611,12 +613,12 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETOF SHARE
- SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME STABLE STANDALONE_P START
+ SHOW SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P START
STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P SUBSTRING
SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN TIME TIMESTAMP
- TO TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P
+ TO TRAILING TRANSACTION TRANSFORM TREAT TRIGGER TRIM TRUE_P
TRUNCATE TRUSTED TYPE_P TYPES_P
UNBOUNDED UNCOMMITTED UNENCRYPTED UNION UNIQUE UNKNOWN UNLISTEN UNLOGGED
@@ -790,6 +792,7 @@ stmt :
| CreateSeqStmt
| CreateStmt
| CreateTableSpaceStmt
+ | CreateTransformStmt
| CreateTrigStmt
| CreateEventTrigStmt
| CreateRoleStmt
@@ -815,6 +818,7 @@ stmt :
| DropRuleStmt
| DropStmt
| DropTableSpaceStmt
+ | DropTransformStmt
| DropTrigStmt
| DropRoleStmt
| DropUserStmt
@@ -4083,6 +4087,16 @@ AlterExtensionContentsStmt:
n->objname = list_make1(makeString($6));
$$ = (Node *)n;
}
+ | ALTER EXTENSION name add_drop TRANSFORM FOR Typename LANGUAGE name
+ {
+ AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
+ n->extname = $3;
+ n->action = $4;
+ n->objtype = OBJECT_TRANSFORM;
+ n->objname = list_make1($7);
+ n->objargs = list_make1($9);
+ $$ = (Node *)n;
+ }
| ALTER EXTENSION name add_drop TYPE_P Typename
{
AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt);
@@ -5736,6 +5750,15 @@ CommentStmt:
n->comment = $6;
$$ = (Node *) n;
}
+ | COMMENT ON TRANSFORM FOR Typename LANGUAGE name IS comment_text
+ {
+ CommentStmt *n = makeNode(CommentStmt);
+ n->objtype = OBJECT_TRANSFORM;
+ n->objname = list_make1($5);
+ n->objargs = list_make1($7);
+ n->comment = $9;
+ $$ = (Node *) n;
+ }
| COMMENT ON TRIGGER name ON any_name IS comment_text
{
CommentStmt *n = makeNode(CommentStmt);
@@ -7015,6 +7038,10 @@ createfunc_opt_item:
{
$$ = makeDefElem("language", (Node *)makeString($2));
}
+ | TRANSFORM transform_type_list
+ {
+ $$ = makeDefElem("transform", (Node *)$2);
+ }
| WINDOW
{
$$ = makeDefElem("window", (Node *)makeInteger(TRUE));
@@ -7032,6 +7059,11 @@ func_as: Sconst { $$ = list_make1(makeString($1)); }
}
;
+transform_type_list:
+ FOR TYPE_P Typename { $$ = list_make1($3); }
+ | transform_type_list ',' FOR TYPE_P Typename { $$ = lappend($1, $5); }
+ ;
+
opt_definition:
WITH definition { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
@@ -7299,6 +7331,56 @@ opt_if_exists: IF_P EXISTS { $$ = TRUE; }
/*****************************************************************************
*
+ * CREATE TRANSFORM / DROP TRANSFORM
+ *
+ *****************************************************************************/
+
+CreateTransformStmt: CREATE opt_or_replace TRANSFORM FOR Typename LANGUAGE name '(' transform_element_list ')'
+ {
+ CreateTransformStmt *n = makeNode(CreateTransformStmt);
+ n->replace = $2;
+ n->type_name = $5;
+ n->lang = $7;
+ n->fromsql = linitial($9);
+ n->tosql = lsecond($9);
+ $$ = (Node *)n;
+ }
+ ;
+
+transform_element_list: FROM SQL_P WITH FUNCTION function_with_argtypes ',' TO SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2($5, $11);
+ }
+ | TO SQL_P WITH FUNCTION function_with_argtypes ',' FROM SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2($11, $5);
+ }
+ | FROM SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2($5, NULL);
+ }
+ | TO SQL_P WITH FUNCTION function_with_argtypes
+ {
+ $$ = list_make2(NULL, $5);
+ }
+ ;
+
+
+DropTransformStmt: DROP TRANSFORM opt_if_exists FOR Typename LANGUAGE name opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_TRANSFORM;
+ n->objects = list_make1(list_make1($5));
+ n->arguments = list_make1(list_make1($7));
+ n->behavior = $8;
+ n->missing_ok = $3;
+ $$ = (Node *)n;
+ }
+ ;
+
+
+/*****************************************************************************
+ *
* QUERY:
*
* REINDEX type <name>
@@ -13460,6 +13542,7 @@ unreserved_keyword:
| SIMPLE
| SKIP
| SNAPSHOT
+ | SQL_P
| STABLE
| STANDALONE_P
| START
@@ -13479,6 +13562,7 @@ unreserved_keyword:
| TEMPORARY
| TEXT_P
| TRANSACTION
+ | TRANSFORM
| TRIGGER
| TRUNCATE
| TRUSTED
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index fd09d3ad9e..31e9d4cf8b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -174,6 +174,7 @@ check_xact_readonly(Node *parsetree)
case T_CreateTableAsStmt:
case T_RefreshMatViewStmt:
case T_CreateTableSpaceStmt:
+ case T_CreateTransformStmt:
case T_CreateTrigStmt:
case T_CompositeTypeStmt:
case T_CreateEnumStmt:
@@ -1314,6 +1315,10 @@ ProcessUtilitySlow(Node *parsetree,
DefineOpFamily((CreateOpFamilyStmt *) parsetree);
break;
+ case T_CreateTransformStmt:
+ CreateTransform((CreateTransformStmt *) parsetree);
+ break;
+
case T_AlterOpFamilyStmt:
AlterOpFamily((AlterOpFamilyStmt *) parsetree);
break;
@@ -2004,6 +2009,9 @@ CreateCommandTag(Node *parsetree)
case OBJECT_POLICY:
tag = "DROP POLICY";
break;
+ case OBJECT_TRANSFORM:
+ tag = "DROP TRANSFORM";
+ break;
default:
tag = "???";
}
@@ -2263,6 +2271,10 @@ CreateCommandTag(Node *parsetree)
}
break;
+ case T_CreateTransformStmt:
+ tag = "CREATE TRANSFORM";
+ break;
+
case T_CreateTrigStmt:
tag = "CREATE TRIGGER";
break;
@@ -2888,6 +2900,10 @@ GetCommandLogLevel(Node *parsetree)
lev = LOGSTMT_DDL;
break;
+ case T_CreateTransformStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_AlterOpFamilyStmt:
lev = LOGSTMT_DDL;
break;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 29b5b1b894..5ffb712472 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -306,6 +306,7 @@ static text *pg_get_expr_worker(text *expr, Oid relid, const char *relname,
static int print_function_arguments(StringInfo buf, HeapTuple proctup,
bool print_table_args, bool print_defaults);
static void print_function_rettype(StringInfo buf, HeapTuple proctup);
+static void print_function_trftypes(StringInfo buf, HeapTuple proctup);
static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
Bitmapset *rels_used);
static bool refname_is_unique(char *refname, deparse_namespace *dpns,
@@ -1912,9 +1913,7 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
StringInfoData buf;
StringInfoData dq;
HeapTuple proctup;
- HeapTuple langtup;
Form_pg_proc proc;
- Form_pg_language lang;
Datum tmp;
bool isnull;
const char *prosrc;
@@ -1937,12 +1936,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an aggregate function", name)));
- /* Need its pg_language tuple for the language name */
- langtup = SearchSysCache1(LANGOID, ObjectIdGetDatum(proc->prolang));
- if (!HeapTupleIsValid(langtup))
- elog(ERROR, "cache lookup failed for language %u", proc->prolang);
- lang = (Form_pg_language) GETSTRUCT(langtup);
-
/*
* We always qualify the function name, to ensure the right function gets
* replaced.
@@ -1953,8 +1946,11 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
(void) print_function_arguments(&buf, proctup, false, true);
appendStringInfoString(&buf, ")\n RETURNS ");
print_function_rettype(&buf, proctup);
+
+ print_function_trftypes(&buf, proctup);
+
appendStringInfo(&buf, "\n LANGUAGE %s\n",
- quote_identifier(NameStr(lang->lanname)));
+ quote_identifier(get_language_name(proc->prolang, false)));
/* Emit some miscellaneous options on one line */
oldlen = buf.len;
@@ -2074,7 +2070,6 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
appendStringInfoChar(&buf, '\n');
- ReleaseSysCache(langtup);
ReleaseSysCache(proctup);
PG_RETURN_TEXT_P(string_to_text(buf.data));
@@ -2351,6 +2346,30 @@ is_input_argument(int nth, const char *argmodes)
}
/*
+ * Append used transformated types to specified buffer
+ */
+static void
+print_function_trftypes(StringInfo buf, HeapTuple proctup)
+{
+ Oid *trftypes;
+ int ntypes;
+
+ ntypes = get_func_trftypes(proctup, &trftypes);
+ if (ntypes > 0)
+ {
+ int i;
+
+ appendStringInfoString(buf, "\n TRANSFORM ");
+ for (i = 0; i < ntypes; i++)
+ {
+ if (i != 0)
+ appendStringInfoString(buf, ", ");
+ appendStringInfo(buf, "FOR TYPE %s", format_type_be(trftypes[i]));
+ }
+ }
+}
+
+/*
* Get textual representation of a function argument's default value. The
* second argument of this function is the argument number among all arguments
* (i.e. proallargtypes, *not* proargtypes), starting with 1, because that's
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 6a39863890..1dc293297d 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -24,12 +24,14 @@
#include "catalog/pg_amproc.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
+#include "catalog/pg_language.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_range.h"
#include "catalog/pg_statistic.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -977,6 +979,30 @@ get_constraint_name(Oid conoid)
return NULL;
}
+/* ---------- LANGUAGE CACHE ---------- */
+
+char *
+get_language_name(Oid langoid, bool missing_ok)
+{
+ HeapTuple tp;
+
+ tp = SearchSysCache1(LANGOID, ObjectIdGetDatum(langoid));
+ if (HeapTupleIsValid(tp))
+ {
+ Form_pg_language lantup = (Form_pg_language) GETSTRUCT(tp);
+ char *result;
+
+ result = pstrdup(NameStr(lantup->lanname));
+ ReleaseSysCache(tp);
+ return result;
+ }
+
+ if (!missing_ok)
+ elog(ERROR, "cache lookup failed for language %u",
+ langoid);
+ return NULL;
+}
+
/* ---------- OPCLASS CACHE ---------- */
/*
@@ -1743,6 +1769,51 @@ get_rel_tablespace(Oid relid)
}
+/* ---------- TRANSFORM CACHE ---------- */
+
+Oid
+get_transform_fromsql(Oid typid, Oid langid, List *trftypes)
+{
+ HeapTuple tup;
+
+ if (!list_member_oid(trftypes, typid))
+ return InvalidOid;
+
+ tup = SearchSysCache2(TRFTYPELANG, typid, langid);
+ if (HeapTupleIsValid(tup))
+ {
+ Oid funcid;
+
+ funcid = ((Form_pg_transform) GETSTRUCT(tup))->trffromsql;
+ ReleaseSysCache(tup);
+ return funcid;
+ }
+ else
+ return InvalidOid;
+}
+
+Oid
+get_transform_tosql(Oid typid, Oid langid, List *trftypes)
+{
+ HeapTuple tup;
+
+ if (!list_member_oid(trftypes, typid))
+ return InvalidOid;
+
+ tup = SearchSysCache2(TRFTYPELANG, typid, langid);
+ if (HeapTupleIsValid(tup))
+ {
+ Oid funcid;
+
+ funcid = ((Form_pg_transform) GETSTRUCT(tup))->trftosql;
+ ReleaseSysCache(tup);
+ return funcid;
+ }
+ else
+ return InvalidOid;
+}
+
+
/* ---------- TYPE CACHE ---------- */
/*
diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c
index bd271680e5..644bbcc167 100644
--- a/src/backend/utils/cache/syscache.c
+++ b/src/backend/utils/cache/syscache.c
@@ -56,6 +56,7 @@
#include "catalog/pg_shseclabel.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_transform.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_config_map.h"
#include "catalog/pg_ts_dict.h"
@@ -653,6 +654,28 @@ static const struct cachedesc cacheinfo[] = {
},
4
},
+ {TransformRelationId, /* TRFOID */
+ TransformOidIndexId,
+ 1,
+ {
+ ObjectIdAttributeNumber,
+ 0,
+ 0,
+ 0,
+ },
+ 16
+ },
+ {TransformRelationId, /* TRFTYPELANG */
+ TransformTypeLangIndexId,
+ 2,
+ {
+ Anum_pg_transform_trftype,
+ Anum_pg_transform_trflang,
+ 0,
+ 0,
+ },
+ 16
+ },
{TSConfigMapRelationId, /* TSCONFIGMAP */
TSConfigMapIndexId,
3,
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 204e124355..ebd7ddd387 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -877,6 +877,50 @@ get_func_arg_info(HeapTuple procTup,
return numargs;
}
+/*
+ * get_func_trftypes
+ *
+ * Returns a number of transformated types used by function.
+ */
+int
+get_func_trftypes(HeapTuple procTup,
+ Oid **p_trftypes)
+{
+
+ Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+ Datum protrftypes;
+ ArrayType *arr;
+ int nelems;
+ bool isNull;
+
+ protrftypes = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_protrftypes,
+ &isNull);
+ if (!isNull)
+ {
+ /*
+ * We expect the arrays to be 1-D arrays of the right types; verify
+ * that. For the OID and char arrays, we don't need to use
+ * deconstruct_array() since the array data is just going to look like
+ * a C array of values.
+ */
+ arr = DatumGetArrayTypeP(protrftypes); /* ensure not toasted */
+ nelems = ARR_DIMS(arr)[0];
+ if (ARR_NDIM(arr) != 1 ||
+ nelems < 0 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != OIDOID)
+ elog(ERROR, "protrftypes is not a 1-D Oid array");
+ Assert(nelems >= procStruct->pronargs);
+ *p_trftypes = (Oid *) palloc(nelems * sizeof(Oid));
+ memcpy(*p_trftypes, ARR_DATA_PTR(arr),
+ nelems * sizeof(Oid));
+
+ return nelems;
+ }
+ else
+ return 0;
+}
/*
* get_func_input_arg_names