diff options
author | Peter Eisentraut <peter_e@gmx.net> | 2015-04-26 10:33:14 -0400 |
---|---|---|
committer | Peter Eisentraut <peter_e@gmx.net> | 2015-04-26 10:33:14 -0400 |
commit | cac76582053ef8ea07df65fed0757f352da23705 (patch) | |
tree | 6ae01041aa61db9d686638b9d4c3ccd30d7c6487 /src/backend/commands/functioncmds.c | |
parent | f320cbb615e0374b18836337713239da58705cf3 (diff) | |
download | postgresql-cac76582053ef8ea07df65fed0757f352da23705.tar.gz postgresql-cac76582053ef8ea07df65fed0757f352da23705.zip |
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/commands/functioncmds.c')
-rw-r--r-- | src/backend/commands/functioncmds.c | 343 |
1 files changed, 341 insertions, 2 deletions
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index f4725056da0..9a92fdcff7d 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 * |