ALTER OBJECT any_name SET SCHEMA name
Hi,
In the road to the extension patch, we already found some parts that
have to be separated into their own patch. Here's another one. It
occurred to me while implementing the pg_extension_objects() SRF that if
we can list all objects that belong to an extension, certainly we also
are able to move them to another schema.
As soon as we have that ability, we are able to provide for relocatable
extensions with the following command:
ALTER EXTENSION ext SET SCHEMA name;
ALTER EXTENSION ext SET SCHEMA foo TO bar;
I think that would end the open debate about search_path vs extension,
because each user would be able to relocate his local extensions easily,
wherever the main script has installed them (often enough, public).
Please find attached a work-in-progress patch (it's missing
documentation) implementing support for setting a new schema to SQL
objects of types conversion, operator, operator class, operator family,
text search parser, dictionary, template and configuration.
If there's will to apply such a patch, I'll finish it by writing the
necessary documentation for the 8 new SQL commands.
Note: CreateCommandTag() already has support for tags for ALTER TEXT
SEARCH <OBJECT> … SET SCHEMA …, but the implementation I've not
found, in the grammar nor in tsearchcmds.c. It's in the patch.
As usual, you can also get to the development version by using git:
http://git.postgresql.org/gitweb?p=postgresql-extension.git;a=shortlog;h=refs/heads/set_schema
git --no-pager diff master..|diffstat
backend/catalog/pg_namespace.c | 38 ++++
backend/commands/alter.c | 32 ++++
backend/commands/conversioncmds.c | 84 ++++++++++
backend/commands/opclasscmds.c | 215 +++++++++++++++++++++++++++
backend/commands/operatorcmds.c | 90 +++++++++++
backend/commands/tsearchcmds.c | 295 ++++++++++++++++++++++++++++++++++++++
backend/parser/gram.y | 67 ++++++++
backend/tcop/utility.c | 12 +
include/catalog/pg_namespace.h | 2
include/commands/conversioncmds.h | 5
include/commands/defrem.h | 23 ++
11 files changed, 863 insertions(+)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
set_schema.3.patchtext/x-patchDownload
*** a/src/backend/catalog/pg_namespace.c
--- b/src/backend/catalog/pg_namespace.c
***************
*** 17,24 ****
--- 17,26 ----
#include "access/heapam.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
+ #include "catalog/namespace.h"
#include "catalog/pg_namespace.h"
#include "utils/builtins.h"
+ #include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
***************
*** 77,79 **** NamespaceCreate(const char *nspName, Oid ownerId)
--- 79,117 ----
return nspoid;
}
+
+ /*
+ * Check new namespace validity in ALTER OBJECT ... SET SCHEMA ... and
+ * ereport(ERROR, ...) in case of any problem.
+ */
+ void
+ CheckSetNamespace(Oid oldNspOid, Oid nspOid,
+ const char *name, const char *objtype)
+ {
+ if (oldNspOid == nspOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("%s \"%s\" already exists in schema \"%s\"",
+ objtype, name, get_namespace_name(nspOid))));
+
+ /* disallow renaming into or out of temp schemas */
+ if (isAnyTempNamespace(nspOid) || isAnyTempNamespace(oldNspOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of temporary schemas")));
+
+ /* same for TOAST schema */
+ if (nspOid == PG_TOAST_NAMESPACE || oldNspOid == PG_TOAST_NAMESPACE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot move objects into or out of TOAST schema")));
+
+ /* check for duplicate name (more friendly than unique-index failure) */
+ if (SearchSysCacheExists2(TYPENAMENSP,
+ CStringGetDatum(name),
+ ObjectIdGetDatum(nspOid)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("%s \"%s\" already exists in schema \"%s\"",
+ objtype, name, get_namespace_name(nspOid))));
+ }
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 182,192 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
--- 182,208 ----
stmt->newschema);
break;
+ case OBJECT_CONVERSION:
+ AlterConversionNamespace(stmt->object, stmt->newschema);
+ break;
+
case OBJECT_FUNCTION:
AlterFunctionNamespace(stmt->object, stmt->objarg, false,
stmt->newschema);
break;
+ case OBJECT_OPERATOR:
+ AlterOperatorNamespace(stmt->object, stmt->objarg, stmt->newschema);
+ break;
+
+ case OBJECT_OPCLASS:
+ AlterOpClassNamespace(stmt->object, stmt->objarg, stmt->newschema);
+ break;
+
+ case OBJECT_OPFAMILY:
+ AlterOpFamilyNamespace(stmt->object, stmt->objarg, stmt->newschema);
+ break;
+
case OBJECT_SEQUENCE:
case OBJECT_TABLE:
case OBJECT_VIEW:
***************
*** 195,200 **** ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt)
--- 211,232 ----
stmt->objectType, AccessExclusiveLock);
break;
+ case OBJECT_TSPARSER:
+ AlterTSParserNamespace(stmt->object, stmt->newschema);
+ break;
+
+ case OBJECT_TSDICTIONARY:
+ AlterTSDictionaryNamespace(stmt->object, stmt->newschema);
+ break;
+
+ case OBJECT_TSTEMPLATE:
+ AlterTSTemplateNamespace(stmt->object, stmt->newschema);
+ break;
+
+ case OBJECT_TSCONFIGURATION:
+ AlterTSConfigurationNamespace(stmt->object, stmt->newschema);
+ break;
+
case OBJECT_TYPE:
case OBJECT_DOMAIN:
AlterTypeNamespace(stmt->object, stmt->newschema);
*** a/src/backend/commands/conversioncmds.c
--- b/src/backend/commands/conversioncmds.c
***************
*** 19,24 ****
--- 19,25 ----
#include "catalog/indexing.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_conversion_fn.h"
+ #include "catalog/pg_namespace.h"
#include "catalog/pg_type.h"
#include "commands/conversioncmds.h"
#include "mb/pg_wchar.h"
***************
*** 326,328 **** AlterConversionOwner_internal(Relation rel, Oid conversionOid, Oid newOwnerId)
--- 327,412 ----
heap_freetuple(tup);
}
+
+ /*
+ * Execute ALTER CONVERSION SET SCHEMA
+ */
+ void
+ AlterConversionNamespace(List *name, const char *newschema)
+ {
+ Oid conversionOid, nspOid;
+ Relation rel;
+
+ rel = heap_open(ConversionRelationId, RowExclusiveLock);
+
+ conversionOid = get_conversion_oid(name, false);
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterConversionNamespace_internal(rel, conversionOid, nspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ /*
+ * Change conversion owner, by oid
+ */
+ void
+ AlterConversionNamespace_oid(Oid conversionOid, Oid newNspOid)
+ {
+ Relation rel;
+
+ rel = heap_open(ConversionRelationId, RowExclusiveLock);
+
+ AlterConversionNamespace_internal(rel, conversionOid, newNspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterConversionNamespace_internal(Relation rel, Oid conversionOid, Oid nspOid)
+ {
+ Oid oldNspOid;
+ HeapTuple tup;
+ Form_pg_conversion convForm;
+
+ Assert(RelationGetRelid(rel) == ConversionRelationId);
+
+ tup = SearchSysCacheCopy1(CONVOID, ObjectIdGetDatum(conversionOid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for conversion %u", conversionOid);
+
+ convForm = (Form_pg_conversion) GETSTRUCT(tup);
+ oldNspOid = convForm->connamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid,
+ NameStr(convForm->conname), "conversion");
+
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ AclResult aclresult;
+
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_conversion_ownercheck(HeapTupleGetOid(tup), GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
+ NameStr(convForm->conname));
+
+ /* owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(convForm->connamespace,
+ GetUserId(),
+ ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(convForm->connamespace));
+ }
+
+ convForm->connamespace = nspOid;
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(ConversionRelationId, conversionOid,
+ NamespaceRelationId, oldNspOid, nspOid);
+ }
*** a/src/backend/commands/opclasscmds.c
--- b/src/backend/commands/opclasscmds.c
***************
*** 1912,1917 **** AlterOpClassOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
--- 1912,2007 ----
}
/*
+ * ALTER OPERATOR CLASS any_name USING access_method SET SCHEMA name
+ */
+ void
+ AlterOpClassNamespace(List *name, List *argam, const char *newschema)
+ {
+ Oid amOid;
+ char *access_method = linitial(argam);
+ Relation rel;
+ HeapTuple tup, origtup;
+ Oid nspOid;
+
+ Assert(list_length(argam) == 1);
+
+ amOid = get_am_oid(access_method, false);
+
+ rel = heap_open(OperatorClassRelationId, RowExclusiveLock);
+
+ /* Look up the opclass. */
+ origtup = OpClassCacheLookup(amOid, name, false);
+ tup = heap_copytuple(origtup);
+ ReleaseSysCache(origtup);
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterOpClassNamespace_internal(rel, tup, nspOid);
+
+ heap_freetuple(tup);
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid)
+ {
+ HeapTuple tup;
+ Relation rel;
+
+ rel = heap_open(OperatorClassRelationId, RowExclusiveLock);
+
+ tup = SearchSysCacheCopy1(CLAOID, ObjectIdGetDatum(opclassOid));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for opclass %u", opclassOid);
+
+ AlterOpClassOwner_internal(rel, tup, newNspOid);
+
+ heap_freetuple(tup);
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterOpClassNamespace_internal(Relation rel, HeapTuple tup, Oid nspOid)
+ {
+ Oid oldNspOid;
+ Form_pg_opclass opcForm;
+
+ opcForm = (Form_pg_opclass) GETSTRUCT(tup);
+ oldNspOid = opcForm->opcnamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid,
+ NameStr(opcForm->opcname), "operator class");
+
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ AclResult aclresult;
+
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_opclass_ownercheck(HeapTupleGetOid(tup), GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS,
+ NameStr(opcForm->opcname));
+
+ /* New owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(nspOid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(nspOid));
+ }
+
+ /* tup is a copy, so we can scribble directly on it */
+ opcForm->opcnamespace = nspOid;
+
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(OperatorClassRelationId, HeapTupleGetOid(tup),
+ NamespaceRelationId, oldNspOid, nspOid);
+ }
+
+ /*
* Change opfamily owner by name
*/
void
***************
*** 2067,2069 **** get_am_oid(const char *amname, bool missing_ok)
--- 2157,2284 ----
errmsg("access method \"%s\" does not exist", amname)));
return oid;
}
+
+ /*
+ * ALTER OPERATOR FAMILY any_name USING access_method SET SCHEMA name
+ */
+ void
+ AlterOpFamilyNamespace(List *name, List *argam, const char *newschema)
+ {
+ Oid amOid;
+ char *access_method = linitial(argam);
+ Relation rel;
+ HeapTuple tup;
+ char *opfname, *schemaname;
+ Oid nspOid;
+
+ Assert(list_length(argam) == 1);
+ amOid = get_am_oid(access_method, false);
+
+ rel = heap_open(OperatorFamilyRelationId, RowExclusiveLock);
+
+ /*
+ * Look up the opfamily
+ */
+ DeconstructQualifiedName(name, &schemaname, &opfname);
+
+ if (schemaname)
+ {
+ Oid namespaceOid;
+
+ namespaceOid = LookupExplicitNamespace(schemaname);
+
+ tup = SearchSysCacheCopy3(OPFAMILYAMNAMENSP,
+ ObjectIdGetDatum(amOid),
+ PointerGetDatum(opfname),
+ ObjectIdGetDatum(namespaceOid));
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("operator family \"%s\" does not exist for access method \"%s\"",
+ opfname, access_method)));
+ }
+ else
+ {
+ Oid opfOid;
+
+ opfOid = OpfamilynameGetOpfid(amOid, opfname);
+ if (!OidIsValid(opfOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("operator family \"%s\" does not exist for access method \"%s\"",
+ opfname, access_method)));
+
+ tup = SearchSysCacheCopy1(OPFAMILYOID, ObjectIdGetDatum(opfOid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for opfamily %u", opfOid);
+ }
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterOpFamilyNamespace_internal(rel, tup, nspOid);
+
+ heap_freetuple(tup);
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid)
+ {
+ HeapTuple tup;
+ Relation rel;
+
+ rel = heap_open(OperatorFamilyRelationId, RowExclusiveLock);
+
+ tup = SearchSysCacheCopy1(OPFAMILYOID, ObjectIdGetDatum(opfamilyOid));
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for opfamily %u", opfamilyOid);
+
+ AlterOpFamilyOwner_internal(rel, tup, newNspOid);
+
+ heap_freetuple(tup);
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterOpFamilyNamespace_internal(Relation rel, HeapTuple tup, Oid nspOid)
+ {
+ Oid oldNspOid;
+ Form_pg_opfamily opfForm;
+
+ Assert(tup->t_tableOid == OperatorFamilyRelationId);
+ Assert(RelationGetRelid(rel) == OperatorFamilyRelationId);
+
+ opfForm = (Form_pg_opfamily) GETSTRUCT(tup);
+ oldNspOid = opfForm->opfnamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid,
+ NameStr(opfForm->opfname), "operator family");
+
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ AclResult aclresult;
+
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_opfamily_ownercheck(HeapTupleGetOid(tup), GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPFAMILY,
+ NameStr(opfForm->opfname));
+
+ /* owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(nspOid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(nspOid));
+ }
+
+ /* tup is a copy, so we can scribble directly on it */
+ opfForm->opfnamespace = nspOid;
+
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(OperatorFamilyRelationId, HeapTupleGetOid(tup),
+ NamespaceRelationId, oldNspOid, nspOid);
+ }
*** a/src/backend/commands/operatorcmds.c
--- b/src/backend/commands/operatorcmds.c
***************
*** 39,44 ****
--- 39,45 ----
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "catalog/pg_operator.h"
+ #include "catalog/pg_namespace.h"
#include "catalog/pg_type.h"
#include "commands/defrem.h"
#include "miscadmin.h"
***************
*** 452,454 **** AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId)
--- 453,544 ----
heap_freetuple(tup);
}
+
+ /*
+ * Execute ALTER OPERATOR SET SCHEMA
+ */
+ void
+ AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid)
+ {
+ Relation rel;
+
+ rel = heap_open(OperatorRelationId, RowExclusiveLock);
+
+ AlterOperatorOwner_internal(rel, operOid, newNspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterOperatorNamespace(List *names, List *argtypes, const char *newschema)
+ {
+ List *operatorName = names;
+ TypeName *typeName1 = (TypeName *) linitial(argtypes);
+ TypeName *typeName2 = (TypeName *) lsecond(argtypes);
+ Oid operOid, nspOid;
+ Relation rel;
+
+ rel = heap_open(OperatorRelationId, RowExclusiveLock);
+
+ Assert(list_length(argtypes) == 2);
+ operOid = LookupOperNameTypeNames(NULL, operatorName,
+ typeName1, typeName2,
+ false, -1);
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterOperatorNamespace_internal(rel, operOid, nspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterOperatorNamespace_internal(Relation rel, Oid operOid, Oid nspOid)
+ {
+ Form_pg_operator oprForm;
+ Oid oldNspOid;
+ HeapTuple tup;
+
+ Assert(RelationGetRelid(rel) == OperatorRelationId);
+
+ tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(operOid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for operator %u", operOid);
+
+ oprForm = (Form_pg_operator) GETSTRUCT(tup);
+ oldNspOid = oprForm->oprnamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid, NameStr(oprForm->oprname), "operator");
+
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ AclResult aclresult;
+
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_oper_ownercheck(operOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
+ NameStr(oprForm->oprname));
+
+ /* owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(oprForm->oprnamespace,
+ GetUserId(),
+ ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(oprForm->oprnamespace));
+ }
+
+ /* tup is a copy, so we can scribble directly on it */
+ oprForm->oprnamespace = nspOid;
+
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(OperatorRelationId, operOid,
+ NamespaceRelationId, oldNspOid, nspOid);
+
+ heap_freetuple(tup);
+ }
*** a/src/backend/commands/tsearchcmds.c
--- b/src/backend/commands/tsearchcmds.c
***************
*** 393,398 **** RenameTSParser(List *oldname, const char *newname)
--- 393,465 ----
heap_freetuple(tup);
}
+ /*
+ * ALTER TEXT SEARCH PARSER any_name SET SCHEMA name
+ */
+ void
+ AlterTSParserNamespace(List *name, const char *newschema)
+ {
+ Oid prsId, nspOid;
+ Relation rel;
+
+ rel = heap_open(TSParserRelationId, RowExclusiveLock);
+
+ prsId = get_ts_parser_oid(name, false);
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterTSParserNamespace_internal(rel, prsId, nspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid)
+ {
+ Relation rel;
+
+ rel = heap_open(TSParserRelationId, RowExclusiveLock);
+
+ AlterTSParserNamespace_internal(rel, prsId, newNspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSParserNamespace_internal(Relation rel, Oid prsId, Oid nspOid)
+ {
+ HeapTuple tup;
+ Oid oldNspOid;
+ Form_pg_ts_parser prs;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to rename text search parsers")));
+
+ tup = SearchSysCacheCopy1(TSPARSEROID, ObjectIdGetDatum(prsId));
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search parser %u", prsId);
+
+ prs = (Form_pg_ts_parser) GETSTRUCT(tup);
+ oldNspOid = prs->prsnamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid,
+ NameStr(prs->prsname), "text search parser");
+
+ prs->prsnamespace = nspOid;
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(TSParserRelationId, prsId,
+ NamespaceRelationId, oldNspOid, nspOid);
+
+ heap_freetuple(tup);
+ }
+
/* ---------------------- TS Dictionary commands -----------------------*/
/*
***************
*** 620,625 **** RenameTSDictionary(List *oldname, const char *newname)
--- 687,772 ----
}
/*
+ * ALTER TEXT SEARCH PARSER any_name SET SCHEMA name
+ */
+ void
+ AlterTSDictionaryNamespace(List *name, const char *newschema)
+ {
+ Oid dictId, nspOid;
+ Relation rel;
+
+ rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ dictId = get_ts_dict_oid(name, false);
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterTSDictionaryNamespace_internal(rel, dictId, nspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid)
+ {
+ Relation rel;
+
+ rel = heap_open(TSDictionaryRelationId, RowExclusiveLock);
+
+ AlterTSDictionaryNamespace_internal(rel, dictId, newNspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSDictionaryNamespace_internal(Relation rel, Oid dictId, Oid nspOid)
+ {
+ HeapTuple tup;
+ Oid oldNspOid;
+ Form_pg_ts_dict dict;
+
+ tup = SearchSysCacheCopy1(TSDICTOID, ObjectIdGetDatum(dictId));
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search dictionary %u",
+ dictId);
+
+ dict = ((Form_pg_ts_dict) GETSTRUCT(tup));
+ oldNspOid = dict->dictnamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid,
+ NameStr(dict->dictname), "text search dictionary");
+
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ AclResult aclresult;
+
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_ts_dict_ownercheck(dictId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSDICTIONARY,
+ NameStr(dict->dictname));
+
+ /* owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(oldNspOid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(oldNspOid));
+ }
+
+ dict->dictnamespace = nspOid;
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(TSDictionaryRelationId, dictId,
+ NamespaceRelationId, oldNspOid, nspOid);
+
+ heap_freetuple(tup);
+ }
+
+ /*
* DROP TEXT SEARCH DICTIONARY
*/
void
***************
*** 1100,1105 **** RenameTSTemplate(List *oldname, const char *newname)
--- 1247,1321 ----
}
/*
+ * ALTER TEXT SEARCH TEMPLATE any_name SET SCHEMA name
+ */
+ void
+ AlterTSTemplateNamespace(List *name, const char *newschema)
+ {
+ Oid tmplId, nspOid;
+ Relation rel;
+
+ rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
+
+ tmplId = get_ts_template_oid(name, false);
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterTSTemplateNamespace_internal(rel, tmplId, nspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid)
+ {
+ Relation rel;
+
+ rel = heap_open(TSTemplateRelationId, RowExclusiveLock);
+
+ AlterTSTemplateNamespace_internal(rel, tmplId, newNspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSTemplateNamespace_internal(Relation rel, Oid tmplId, Oid nspOid)
+ {
+ HeapTuple tup;
+ Oid oldNspOid;
+ Form_pg_ts_template tmpl;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("must be superuser to rename text search templates")));
+
+ tup = SearchSysCacheCopy1(TSTEMPLATEOID, ObjectIdGetDatum(tmplId));
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search template %u",
+ tmplId);
+
+ tmpl = (Form_pg_ts_template) GETSTRUCT(tup);
+ oldNspOid = tmpl->tmplnamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid,
+ NameStr(tmpl->tmplname), "text search template");
+
+ tmpl->tmplnamespace = nspOid;
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(TSTemplateRelationId, tmplId,
+ NamespaceRelationId, oldNspOid, nspOid);
+
+ heap_freetuple(tup);
+ }
+
+
+ /*
* DROP TEXT SEARCH TEMPLATE
*/
void
***************
*** 1498,1503 **** RenameTSConfiguration(List *oldname, const char *newname)
--- 1714,1798 ----
}
/*
+ * ALTER TEXT SEARCH CONFIGURATION any_name SET SCHEMA name
+ */
+ void
+ AlterTSConfigurationNamespace(List *name, const char *newschema)
+ {
+ Oid cfgId, nspOid;
+ Relation rel;
+
+ rel = heap_open(TSConfigRelationId, RowExclusiveLock);
+
+ cfgId = get_ts_config_oid(name, false);
+
+ /* get schema OID */
+ nspOid = LookupCreationNamespace(newschema);
+
+ AlterTSConfigurationNamespace_internal(rel, cfgId, nspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid)
+ {
+ Relation rel;
+
+ rel = heap_open(TSConfigRelationId, RowExclusiveLock);
+
+ AlterTSConfigurationNamespace_internal(rel, cfgId, newNspOid);
+
+ heap_close(rel, NoLock);
+ }
+
+ void
+ AlterTSConfigurationNamespace_internal(Relation rel, Oid cfgId, Oid nspOid)
+ {
+ HeapTuple tup;
+ Oid oldNspOid;
+ Form_pg_ts_config cfg;
+
+ tup = SearchSysCacheCopy1(TSCONFIGOID, ObjectIdGetDatum(cfgId));
+
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for text search configuration %u",
+ cfgId);
+
+ cfg = (Form_pg_ts_config) GETSTRUCT(tup);
+ oldNspOid = cfg->cfgnamespace;
+
+ CheckSetNamespace(oldNspOid, nspOid,
+ NameStr(cfg->cfgname), "text search configuration");
+
+ /* Superusers can always do it */
+ if (!superuser())
+ {
+ AclResult aclresult;
+
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_ts_config_ownercheck(cfgId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TSCONFIGURATION,
+ NameStr(cfg->cfgname));
+
+ /* owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(oldNspOid, GetUserId(), ACL_CREATE);
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(oldNspOid));
+ }
+
+ cfg->cfgnamespace = nspOid;
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(TSConfigRelationId, cfgId,
+ NamespaceRelationId, oldNspOid, nspOid);
+
+ heap_freetuple(tup);
+ }
+
+ /*
* DROP TEXT SEARCH CONFIGURATION
*/
void
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 6040,6045 **** AlterObjectSchemaStmt:
--- 6040,6053 ----
n->newschema = $7;
$$ = (Node *)n;
}
+ | ALTER CONVERSION_P any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_CONVERSION;
+ n->object = $3;
+ n->newschema = $6;
+ $$ = (Node *)n;
+ }
| ALTER DOMAIN_P any_name SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
***************
*** 6057,6062 **** AlterObjectSchemaStmt:
--- 6065,6097 ----
n->newschema = $6;
$$ = (Node *)n;
}
+ | ALTER OPERATOR any_operator oper_argtypes SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_OPERATOR;
+ n->object = $3;
+ n->objarg = $4;
+ n->newschema = $7;
+ $$ = (Node *)n;
+ }
+ | ALTER OPERATOR CLASS any_name USING access_method SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_OPCLASS;
+ n->object = $4;
+ n->objarg = list_make1($6);
+ n->newschema = $9;
+ $$ = (Node *)n;
+ }
+ | ALTER OPERATOR FAMILY any_name USING access_method SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_OPFAMILY;
+ n->object = $4;
+ n->objarg = list_make1($6);
+ n->newschema = $9;
+ $$ = (Node *)n;
+ }
| ALTER TABLE relation_expr SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
***************
*** 6065,6070 **** AlterObjectSchemaStmt:
--- 6100,6137 ----
n->newschema = $6;
$$ = (Node *)n;
}
+ | ALTER TEXT_P SEARCH PARSER any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_TSPARSER;
+ n->object = $5;
+ n->newschema = $8;
+ $$ = (Node *)n;
+ }
+ | ALTER TEXT_P SEARCH DICTIONARY any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_TSDICTIONARY;
+ n->object = $5;
+ n->newschema = $8;
+ $$ = (Node *)n;
+ }
+ | ALTER TEXT_P SEARCH TEMPLATE any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_TSTEMPLATE;
+ n->object = $5;
+ n->newschema = $8;
+ $$ = (Node *)n;
+ }
+ | ALTER TEXT_P SEARCH CONFIGURATION any_name SET SCHEMA name
+ {
+ AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
+ n->objectType = OBJECT_TSCONFIGURATION;
+ n->object = $5;
+ n->newschema = $8;
+ $$ = (Node *)n;
+ }
| ALTER SEQUENCE qualified_name SET SCHEMA name
{
AlterObjectSchemaStmt *n = makeNode(AlterObjectSchemaStmt);
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 1694,1702 **** CreateCommandTag(Node *parsetree)
--- 1694,1714 ----
case OBJECT_AGGREGATE:
tag = "ALTER AGGREGATE";
break;
+ case OBJECT_CONVERSION:
+ tag = "ALTER CONVERSION";
+ break;
case OBJECT_DOMAIN:
tag = "ALTER DOMAIN";
break;
+ case OBJECT_OPERATOR:
+ tag = "ALTER OPERATOR";
+ break;
+ case OBJECT_OPCLASS:
+ tag = "ALTER OPERATOR CLASS";
+ break;
+ case OBJECT_OPFAMILY:
+ tag = "ALTER OPERATOR FAMILY";
+ break;
case OBJECT_FUNCTION:
tag = "ALTER FUNCTION";
break;
*** a/src/include/catalog/pg_namespace.h
--- b/src/include/catalog/pg_namespace.h
***************
*** 78,82 **** DESCR("standard public schema");
--- 78,84 ----
* prototypes for functions in pg_namespace.c
*/
extern Oid NamespaceCreate(const char *nspName, Oid ownerId);
+ extern void CheckSetNamespace(Oid oldNspOid, Oid nspOid,
+ const char *name, const char *objtype);
#endif /* PG_NAMESPACE_H */
*** a/src/include/commands/conversioncmds.h
--- b/src/include/commands/conversioncmds.h
***************
*** 16,26 ****
--- 16,31 ----
#define CONVERSIONCMDS_H
#include "nodes/parsenodes.h"
+ #include "utils/relcache.h"
extern void CreateConversionCommand(CreateConversionStmt *parsetree);
extern void DropConversionsCommand(DropStmt *drop);
extern void RenameConversion(List *name, const char *newname);
extern void AlterConversionOwner(List *name, Oid newOwnerId);
extern void AlterConversionOwner_oid(Oid conversionOid, Oid newOwnerId);
+ extern void AlterConversionNamespace(List *name, const char *newschema);
+ extern void AlterConversionNamespace_oid(Oid conversionOid, Oid newNspOid);
+ extern void AlterConversionNamespace_internal(Relation rel, Oid conversionOid, Oid nspOid);
+
#endif /* CONVERSIONCMDS_H */
*** a/src/include/commands/defrem.h
--- b/src/include/commands/defrem.h
***************
*** 14,20 ****
--- 14,22 ----
#ifndef DEFREM_H
#define DEFREM_H
+ #include "access/htup.h"
#include "nodes/parsenodes.h"
+ #include "utils/relcache.h"
/* commands/indexcmds.c */
***************
*** 78,83 **** extern void AlterOperatorOwner(List *name, TypeName *typeName1,
--- 80,88 ----
extern void AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId);
extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok);
extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok);
+ extern void AlterOperatorNamespace_oid(Oid operOid, Oid newNspOid);
+ extern void AlterOperatorNamespace(List *names, List *argtypes, const char *newschema);
+ extern void AlterOperatorNamespace_internal(Relation rel, Oid operoid, Oid nspOid);
/* commands/aggregatecmds.c */
extern void DefineAggregate(List *name, List *args, bool oldstyle,
***************
*** 100,114 **** extern void RenameOpClass(List *name, const char *access_method, const char *new
--- 105,128 ----
extern void RenameOpFamily(List *name, const char *access_method, const char *newname);
extern void AlterOpClassOwner(List *name, const char *access_method, Oid newOwnerId);
extern void AlterOpClassOwner_oid(Oid opclassOid, Oid newOwnerId);
+ extern void AlterOpClassNamespace(List *name, List *argam, const char *newschema);
+ extern void AlterOpClassNamespace_oid(Oid opclassOid, Oid newNspOid);
+ extern void AlterOpClassNamespace_internal(Relation rel, HeapTuple tup, Oid nspOid);
extern void AlterOpFamilyOwner(List *name, const char *access_method, Oid newOwnerId);
extern void AlterOpFamilyOwner_oid(Oid opfamilyOid, Oid newOwnerId);
extern Oid get_am_oid(const char *amname, bool missing_ok);
+ extern void AlterOpFamilyNamespace(List *name, List *argam, const char *newschema);
+ extern void AlterOpFamilyNamespace_oid(Oid opfamilyOid, Oid newNspOid);
+ extern void AlterOpFamilyNamespace_internal(Relation rel, HeapTuple tup, Oid nspOid);
/* commands/tsearchcmds.c */
extern void DefineTSParser(List *names, List *parameters);
extern void RenameTSParser(List *oldname, const char *newname);
extern void RemoveTSParsers(DropStmt *drop);
extern void RemoveTSParserById(Oid prsId);
+ extern void AlterTSParserNamespace(List *name, const char *newschema);
+ extern void AlterTSParserNamespace_oid(Oid prsId, Oid newNspOid);
+ extern void AlterTSParserNamespace_internal(Relation rel, Oid prsId, Oid nspOid);
extern void DefineTSDictionary(List *names, List *parameters);
extern void RenameTSDictionary(List *oldname, const char *newname);
***************
*** 116,129 **** extern void RemoveTSDictionaries(DropStmt *drop);
--- 130,152 ----
extern void RemoveTSDictionaryById(Oid dictId);
extern void AlterTSDictionary(AlterTSDictionaryStmt *stmt);
extern void AlterTSDictionaryOwner(List *name, Oid newOwnerId);
+ extern void AlterTSDictionaryNamespace(List *name, const char *newschema);
+ extern void AlterTSDictionaryNamespace_oid(Oid dictId, Oid newNspOid);
+ extern void AlterTSDictionaryNamespace_internal(Relation rel, Oid dictId, Oid nspOid);
extern void DefineTSTemplate(List *names, List *parameters);
extern void RenameTSTemplate(List *oldname, const char *newname);
+ extern void AlterTSTemplateNamespace(List *name, const char *newschema);
+ extern void AlterTSTemplateNamespace_oid(Oid tmplId, Oid newNspOid);
+ extern void AlterTSTemplateNamespace_internal(Relation rel, Oid tmplId, Oid nspOid);
extern void RemoveTSTemplates(DropStmt *stmt);
extern void RemoveTSTemplateById(Oid tmplId);
extern void DefineTSConfiguration(List *names, List *parameters);
extern void RenameTSConfiguration(List *oldname, const char *newname);
+ extern void AlterTSConfigurationNamespace(List *name, const char *newschema);
+ extern void AlterTSConfigurationNamespace_oid(Oid cfgId, Oid newNspOid);
+ extern void AlterTSConfigurationNamespace_internal(Relation rel, Oid cfgId, Oid nspOid);
extern void RemoveTSConfigurations(DropStmt *stmt);
extern void RemoveTSConfigurationById(Oid cfgId);
extern void AlterTSConfiguration(AlterTSConfigurationStmt *stmt);
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
As soon as we have that ability, we are able to provide for relocatable
extensions with the following command:
ALTER EXTENSION ext SET SCHEMA name;
ALTER EXTENSION ext SET SCHEMA foo TO bar;
I think that would end the open debate about search_path vs extension,
because each user would be able to relocate his local extensions easily,
wherever the main script has installed them (often enough, public).
I'm not sure whether that really fixes anything, or just provides people
with a larger-caliber foot-gun. See for example recent complaints about
citext misbehaving if it's not in the public schema (or more generally,
any schema not in the search path). I think we'd need to think a bit
harder about the behavior of objects that aren't in the search path
before creating a facility like this, since it seems to be tantamount
to promising that extensions won't break when pushed around to different
schemas.
I'm also a bit less than enthused about the implementation approach.
If we're going to have a policy that every object type must support
ALTER SET SCHEMA, I think it might be time to refactor, rather than
copying-and-pasting similar boilerplate code for every one.
regards, tom lane
Hi,
Thanks for your review!
Tom Lane <tgl@sss.pgh.pa.us> writes:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
ALTER EXTENSION ext SET SCHEMA name;
ALTER EXTENSION ext SET SCHEMA foo TO bar;I think that would end the open debate about search_path vs extension,
because each user would be able to relocate his local extensions easily,
wherever the main script has installed them (often enough, public).I'm not sure whether that really fixes anything, or just provides people
with a larger-caliber foot-gun. See for example recent complaints about
citext misbehaving if it's not in the public schema (or more generally,
any schema not in the search path). I think we'd need to think a bit
harder about the behavior of objects that aren't in the search path
before creating a facility like this, since it seems to be tantamount
to promising that extensions won't break when pushed around to different
schemas.
Well AFAIK we have only two choices here. Either we impose the schema
where to find any extension's object or we offer more facilities for
users to support their choices.
In the former case, we have problems with upgrades and hosting several
versions of the same extension at the same time (think PostGIS 1.4 and
1.5 e.g.). Not being able to choose a schema where to host an
extension's objects only makes sense when the user can't set the
search_path, which is what we do with pg_catalog.
In the latter case, following your example, all it would take the user
to fix his setup would be a SET SCHEMA command on the citext extension.
The only goal of this proposal is not to have to rethink object
visibility, by offering the tools users need to manage the situation.
All in all, I don't think it'll be ever possible to both support
search_path flexibility and extension robustness when search_path
changes.
What we could do is offer extension's author a way to find their
operator or functions or whatever dynamically in SQL, so that writing
robust pure-SQL functions is possible. What comes to mind now would be a
way to call a function/operator/... by OID at the SQL level. Not pretty
but with the pg_extension_objects() SRF and maybe a layer atop that,
that would do the trick. Brain dumping still.
I'm also a bit less than enthused about the implementation approach.
If we're going to have a policy that every object type must support
ALTER SET SCHEMA, I think it might be time to refactor, rather than
copying-and-pasting similar boilerplate code for every one.
I've begun the effort with the CheckSetNamespace() function, about half
of the rest of the code could receive some abstraction too (syscache
searches, tuple access and editing, dependency changing, but ACL checks
looks harder. How much easier would it be with defmacro...
I'll have a try at it soon, happy to hear what you have in mind here
before I start, so that I follow your guidance.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--On 30. Oktober 2010 18:59:30 -0400 Tom Lane <tgl@sss.pgh.pa.us> wrote:
I'm not sure whether that really fixes anything, or just provides people
with a larger-caliber foot-gun. See for example recent complaints about
citext misbehaving if it's not in the public schema (or more generally,
any schema not in the search path). I think we'd need to think a bit
harder about the behavior of objects that aren't in the search path
before creating a facility like this, since it seems to be tantamount
to promising that extensions won't break when pushed around to different
schemas.I'm also a bit less than enthused about the implementation approach.
If we're going to have a policy that every object type must support
ALTER SET SCHEMA, I think it might be time to refactor, rather than
copying-and-pasting similar boilerplate code for every one.
This reminds me of a small discussion we had some years ago when i targeted
this for the sake of completeness of ASS (see
<http://archives.postgresql.org/pgsql-patches/2006-06/msg00021.php>).
I didn't follow the previous discussions about EXTENSION very closely, but
to amend the idea made in the mentioned thread, couldn't we just invent a
facility to move classes of objects belonging to an extension, only?
--
Thanks
Bernd
Bernd Helmle <mailings@oopsware.de> writes:
This reminds me of a small discussion we had some years ago when i targeted
this for the sake of completeness of ASS (see
<http://archives.postgresql.org/pgsql-patches/2006-06/msg00021.php>).
Discovered it, thanks for the pointer.
I didn't follow the previous discussions about EXTENSION very closely, but
to amend the idea made in the mentioned thread, couldn't we just invent a
facility to move classes of objects belonging to an extension, only?
Well under the hood you still need about the same code. In the current
patch version I have, that would mean forgetting about a ~20 lines long
function and keeping the other two, 10+30 lines about.
Now if someone has a idea on how to have a single routine that knows how
to deal with any object type and change its namespace, I'm all ears, and
willing to have a try at it. Coding in C is for me a twice a year
exercise, so whatever I come up with will be... improvable, I fear.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
On 31.10.2010 14:46, Dimitri Fontaine wrote:
What we could do is offer extension's author a way to find their
operator or functions or whatever dynamically in SQL, so that writing
robust pure-SQL functions is possible. What comes to mind now would be a
way to call a function/operator/... by OID at the SQL level. Not pretty
but with the pg_extension_objects() SRF and maybe a layer atop that,
that would do the trick. Brain dumping still.
How about something like:
CREATE EXTENSION myextension ... SCHEMA myschema;
And in the .sql file in the extension you could have special markers for
the schema, something like:
CREATE FUNCTION otherfunction() AS ...;
CREATE FUNCTION foo() AS $$ SELECT 'foo', @extschema@.otherfunction() $$;
@extschema@ would be search&replaced at CREATE EXTENSION time with the
schema specified by the user.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
CREATE EXTENSION myextension ... SCHEMA myschema;
And in the .sql file in the extension you could have special markers for the
schema, something like:
That's exactly the road I want to avoid, because of the script parsing issues.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Sorry for the interruption, our program now continues...
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
That's exactly the road I want to avoid, because of the script parsing issues.
In particular, embedded and/or dynamic calls in PLs will get hairy if
not turing complete and outright impossible to solve.
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
On 31.10.2010 19:38, Dimitri Fontaine wrote:
Sorry for the interruption, our program now continues...
Dimitri Fontaine<dimitri@2ndQuadrant.fr> writes:
That's exactly the road I want to avoid, because of the script parsing issues.
In particular, embedded and/or dynamic calls in PLs will get hairy if
not turing complete and outright impossible to solve.
Sorry, I don't follow. Got an example?
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
In particular, embedded and/or dynamic calls in PLs will get hairy if
not turing complete and outright impossible to solve.Sorry, I don't follow. Got an example?
Well, who's to say the following hypothetical plpgsql example should be
forgiven only in an exception's script?
v_sql := 'SELECT * FROM ' || p_fun || '()';
FOR rec in EXECUTE v_sql
LOOP
…
END LOOP;
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
On Sun, Oct 31, 2010 at 12:45 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Bernd Helmle <mailings@oopsware.de> writes:
This reminds me of a small discussion we had some years ago when i targeted
this for the sake of completeness of ASS (see
<http://archives.postgresql.org/pgsql-patches/2006-06/msg00021.php>).Discovered it, thanks for the pointer.
I didn't follow the previous discussions about EXTENSION very closely, but
to amend the idea made in the mentioned thread, couldn't we just invent a
facility to move classes of objects belonging to an extension, only?Well under the hood you still need about the same code. In the current
patch version I have, that would mean forgetting about a ~20 lines long
function and keeping the other two, 10+30 lines about.Now if someone has a idea on how to have a single routine that knows how
to deal with any object type and change its namespace, I'm all ears, and
willing to have a try at it. Coding in C is for me a twice a year
exercise, so whatever I come up with will be... improvable, I fear.
I guess it's a question of how much special case code there is. The
new objectaddress.c code knows how to deal with lots of different
object types, but for example in the case of ALTER TABLE .. SET
SCHEMA, what's there now is enmeshed in a complex structure so as to
allow multiple ALTER TABLE subcommands in a single statement, and it
has further special-case logic to handle moving the rowtype and
related indexes, sequences, and constraints. It seems hard to fit
that into a general framework, but maybe it could be done for other
object types.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 31.10.2010 20:19, Dimitri Fontaine wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
In particular, embedded and/or dynamic calls in PLs will get hairy if
not turing complete and outright impossible to solve.Sorry, I don't follow. Got an example?
Well, who's to say the following hypothetical plpgsql example should be
forgiven only in an exception's script?v_sql := 'SELECT * FROM ' || p_fun || '()';
FOR rec in EXECUTE v_sql
LOOP
…
END LOOP;
If I understand that correctly, the idea is that p_fun holds the name of
a function that's in the same schema as the extension? You would write
that as
v_sql := 'SELECT * FROM @extschema@.' || p_fun || '()';
FOR rec in EXECUTE v_sql
LOOP
…
END LOOP;
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
...
related indexes, sequences, and constraints. It seems hard to fit
that into a general framework, but maybe it could be done for other
object types.
My guess is that we're talking about having a generic code that would
get exercised directly from src/backend/commands/alter.c in the function
ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt), rather than
having a switch() statement and very similar functions implemented all
over the place to do the actual bit editing.
Let's see some details, if we're getting there.
This line of code is repeated in lots of places, but always a little different:
+ conversionOid = get_conversion_oid(name, false);
+ operOid = LookupOperNameTypeNames(NULL, operatorName,
+ typeName1, typeName2,
+ false, -1);
+ prsId = get_ts_parser_oid(name, false);
+ dictId = get_ts_dict_oid(name, false);
And for operator class and operator family, we're dealing with HeapTuple
objects directly, rather than Oids, because the lookup ain't so
easy. But all in all that code could get moved into another layer in the
generic function, in a object type switch, if we get to HeapTuple based
internal API, because next example shows that in most cases we get the
tuple from the Oid in the same way.
In the following code part, I can see how to get generic code with some
parameters for the first 3 lines, but not so much for the other 2 lines:
+ tup = SearchSysCacheCopy1(CONVOID, ObjectIdGetDatum(conversionOid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for conversion %u", conversionOid);
+
+ convForm = (Form_pg_conversion) GETSTRUCT(tup);
+ oldNspOid = convForm->connamespace;
Then there's this code which looks even harder to get generic:
+ /* Otherwise, must be owner of the existing object */
+ if (!pg_conversion_ownercheck(HeapTupleGetOid(tup), GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CONVERSION,
+ NameStr(convForm->conname));
Now the following part could get moved easily to the CheckSetNamespace()
function introduced in the patch, with a boolean to trigger the check,
because as it is not all objects are open to non-superuser changes.
+ /* New owner must have CREATE privilege on namespace */
+ aclresult = pg_namespace_aclcheck(nspOid, GetUserId(), ACL_CREATE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
+ get_namespace_name(nspOid));
Then in the end of the function, we have the real work, where I can see
a generic function for the heap update and the dependency tracking, but
not for the tuple editing itself:
+ /* tup is a copy, so we can scribble directly on it */
+ opfForm->opfnamespace = nspOid;
+
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ /* update dependencies to point to the new schema */
+ changeDependencyFor(OperatorFamilyRelationId, HeapTupleGetOid(tup),
+ NamespaceRelationId, oldNspOid, nspOid);
So it seems to get down to C'fu related to handling Form_pg_* pointers
to get to values and change them then simple_heap_update the tuple. The
other parts are all about the same code with different RelationId and
such.
Well of course you think you could just have a pointer as argument, but
how to get the pointer is something we'd prefer generic too. Maybe we
need a 3-step code here, in aforementioned ExecAlterObjectSchemaStmt:
1. call a generic function that switch on stmt->objectType and
returns an HeapTuple
2. switch on stmt->objectType to get oldNspOid from the tuple and to
check for permissions
3. call another generic function to do the namespace related checks
then the heap update etc
This way the only specific code we need to maintain are the second step
here, object dependent, and in the first function too, but that's not an
entire API like in current patch.
Well of course there's the question of how to do that in a way that
allows some other backend's code to get the action done with Oids as
input rather than a statement, but that looks workable: get from classid
to objectype (aren't they equal?) then call the step 1. function to get
the HeapTyple, and proceed. So in fact the 3 steps are a separate
function that is called either from ExecAlterObjectSchemaStmt or from
the extension's code.
And there's also the question whether that has anything to do with what
Tom would want to see happening, too (or I'll be talking about those
ideas with gcc rather than with the list) :)
Comments?
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
If I understand that correctly, the idea is that p_fun holds the name of a
function that's in the same schema as the extension? You would write that asv_sql := 'SELECT * FROM @extschema@.' || p_fun || '()';
Fair enough. Now what about the citext example, where IIRC the failure
is not on function names but operators and opclass not found, etc.
Forcing extension's author to get to always use the following notation
seems to me like pushing it:
- WHERE foo = bar
+ WHERE foo operator(@extschema@.=) bar
Also, those examples are plpgsql but extensions are free to depend on
plperl or plpython, or even some pljs or plscheme out there.
The alternative is to give DBAs the tools to move their local extensions
in some schema that is part of their edited search_path, should they
remove public from there.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
On 31.10.2010 21:42, Dimitri Fontaine wrote:
Heikki Linnakangas<heikki.linnakangas@enterprisedb.com> writes:
If I understand that correctly, the idea is that p_fun holds the name of a
function that's in the same schema as the extension? You would write that asv_sql := 'SELECT * FROM @extschema@.' || p_fun || '()';
Fair enough. Now what about the citext example, where IIRC the failure
is not on function names but operators and opclass not found, etc.
Just do "SET search_path=@extschema@" at the beginning of the install
script, just like we have "SET search_path=public" there now.
Forcing extension's author to get to always use the following notation
seems to me like pushing it:- WHERE foo = bar + WHERE foo operator(@extschema@.=) barAlso, those examples are plpgsql but extensions are free to depend on
plperl or plpython, or even some pljs or plscheme out there.
Well, in case of functions you can always do "CREATE FUNCTION ... AS $$
... $$ SET search_path=@extschema".
"ALTER ... SET SCHEMA" wouldn't do anything for SQL statements embedded
in plperl or plpython anyway.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
Just do "SET search_path=@extschema@" at the beginning of the install
script, just like we have "SET search_path=public" there now.
Well there's the installation itself then the "runtime", as you say
later...
Well, in case of functions you can always do "CREATE FUNCTION ... AS $$
... $$ SET search_path=@extschema".
Fair enough.
"ALTER ... SET SCHEMA" wouldn't do anything for SQL statements embedded in
plperl or plpython anyway.
That's why I was thinking about adding the possibility to:
- easily find your function's etc OID, that's already mainly done
- be able to call/use those objects per OID
Ok that sucks somehow. I think it's better than @extschema@ replacing in
the extension's script parsing, though.
Maybe we should just shut down this attempt at working on search_path
and extensions together, again. I though it was a simple and good enough
solution though, and that it would avoid the usual rat holes. But we're
deep in them already.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
On Sun, Oct 31, 2010 at 5:46 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
Just do "SET search_path=@extschema@" at the beginning of the install
script, just like we have "SET search_path=public" there now.Well there's the installation itself then the "runtime", as you say
later...Well, in case of functions you can always do "CREATE FUNCTION ... AS $$
... $$ SET search_path=@extschema".Fair enough.
"ALTER ... SET SCHEMA" wouldn't do anything for SQL statements embedded in
plperl or plpython anyway.That's why I was thinking about adding the possibility to:
- easily find your function's etc OID, that's already mainly done
- be able to call/use those objects per OIDOk that sucks somehow.
Yeah, I think that sucks a lot. I don't see what's wrong with
Heikki's solution, actually.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
Yeah, I think that sucks a lot. I don't see what's wrong with
Heikki's solution, actually.
Coding the parser and replace. If all it takes is calling our replace
function on the all-in-memory query string that we have in
pg_execute_from_file() function, I can have a try at it.
Main wrong point is that it puts the burden on the extension's authors
rather than on the one who manages the search_path for its
applications...
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Heikki Linnakangas <heikki.linnakangas@enterprisedb.com> writes:
How about something like:
CREATE EXTENSION myextension ... SCHEMA myschema;
And in the .sql file in the extension you could have special markers for the
schema, something like:CREATE FUNCTION otherfunction() AS ...;
CREATE FUNCTION foo() AS $$ SELECT 'foo', @extschema@.otherfunction() $$;@extschema@ would be search&replaced at CREATE EXTENSION time with the
schema specified by the user.
Please find attached v12 of the patch, which implements that idea.
And a new pg_execute_from_file patch version too: the function now has a
second (documented) variant accepting a VARIADIC text[] argument where
to put pairs of name and value for the placeholders in the script.
I guess it would be cleaner with hstore in core, but we're not there
yet, so meanwhile it's a variable length array.
The CREATE EXTENSION ... WITH SCHEMA ... command will then use the
variadic form of pg_execute_from_file() with a single variable in there,
the proposed @extschema@. When the option is not used, the placeholder
is still set, hard-coded to 'public'.
Contrib scripts have been all changed this way:
- SET search_path = public;
+ SET search_path = @extschema@;
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
pg_execute_from_file.v4.patchtext/x-patchDownload
*** a/doc/src/sgml/func.sgml
--- b/doc/src/sgml/func.sgml
***************
*** 13897,13902 **** postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup());
--- 13897,13911 ----
<entry><type>record</type></entry>
<entry>Return information about a file</entry>
</row>
+ <row>
+ <entry>
+ <literal><function>pg_execute_from_file(<parameter>filename</> <type>text</>
+ [, <parameter>variable</parameter> <type>text</type>, <parameter>value</parameter> <type>text</type>
+ [, ...] ]) )</function></literal>
+ </entry>
+ <entry><type>void</type></entry>
+ <entry>Executes the <acronym>SQL</> commands contained in a file, replacing given placeholders.</entry>
+ </row>
</tbody>
</tgroup>
</table>
***************
*** 13935,13940 **** SELECT (pg_stat_file('filename')).modification;
--- 13944,13963 ----
</programlisting>
</para>
+ <indexterm>
+ <primary>pg_execute_from_file</primary>
+ </indexterm>
+ <para>
+ <function>pg_execute_from_file</> makes the server
+ execute <acronym>SQL</> commands to be found in a file. This function is
+ reserved to superusers.
+ </para>
+ <para>
+ The script might contain placeholders that will be replaced by the
+ values given in the <literal>VARIADIC</literal> arguments, which must be
+ a pair of variable names and values.
+ </para>
+
<para>
The functions shown in <xref linkend="functions-advisory-locks"> manage
advisory locks. For details about proper use of these functions, see
***************
*** 13957,13962 **** SELECT (pg_stat_file('filename')).modification;
--- 13980,13986 ----
<entry><type>void</type></entry>
<entry>Obtain exclusive advisory lock</entry>
</row>
+
<row>
<entry>
<literal><function>pg_advisory_lock(<parameter>key1</> <type>int</>, <parameter>key2</> <type>int</>)</function></literal>
*** a/src/backend/utils/adt/genfile.c
--- b/src/backend/utils/adt/genfile.c
***************
*** 7,12 ****
--- 7,13 ----
* Copyright (c) 2004-2010, PostgreSQL Global Development Group
*
* Author: Andreas Pflug <pgadmin@pse-consulting.de>
+ * Dimitri Fontaine <dimitri@2ndQuadrant.fr>
*
* IDENTIFICATION
* src/backend/utils/adt/genfile.c
***************
*** 21,31 ****
--- 22,34 ----
#include <dirent.h>
#include "catalog/pg_type.h"
+ #include "executor/spi.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "storage/fd.h"
+ #include "utils/array.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/timestamp.h"
***************
*** 264,266 **** pg_ls_dir(PG_FUNCTION_ARGS)
--- 267,389 ----
SRF_RETURN_DONE(funcctx);
}
+
+ /*
+ * Read a file then execute the SQL commands it contains.
+ *
+ * There's a variant accepting a VARIADIC text parameter containing
+ * placeholder variables and values, one after the other (so the variadic
+ * array length must be even).
+ *
+ * The main use case of the replacement facility is for setting the
+ * extension's schema, using @pg_extschema@ variable and a user given
+ * schema.
+ */
+ Datum
+ pg_execute_from_file(PG_FUNCTION_ARGS)
+ {
+ text *filename_t = PG_GETARG_TEXT_P(0);
+ char *filename;
+ FILE *file;
+ int64 fsize = -1, nbytes;
+ struct stat fst;
+ char *query_string = NULL;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to get file information"))));
+
+ /*
+ * Only superuser can call pg_execute_from_file, and CREATE EXTENSION
+ * uses that too. Don't double check the PATH. Also note that
+ * extension's install files are not in $PGDATA but `pg_config
+ * --sharedir`.
+ */
+ filename = text_to_cstring(filename_t);
+
+ if (stat(filename, &fst) < 0)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", filename)));
+
+ fsize = Int64GetDatum((int64) fst.st_size);
+
+ if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL)
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not open file \"%s\" for reading: %m",
+ filename)));
+
+ if (ferror(file))
+ ereport(ERROR,
+ (errcode_for_file_access(),
+ errmsg("could not read file \"%s\": %m", filename)));
+
+ query_string = (char *)palloc((fsize+1)*sizeof(char));
+ memset(query_string, 0, fsize+1);
+ nbytes = fread(query_string, 1, (size_t) fsize, file);
+ pg_verifymbstr(query_string, nbytes, false);
+ FreeFile(file);
+
+ /*
+ * Replace any given variable with its value, by calling replace_text
+ * over each pair of arguments.
+ */
+ if (PG_NARGS() == 2)
+ {
+ text *src = cstring_to_text(query_string);
+ ArrayType *array = PG_GETARG_ARRAYTYPE_P(1);
+ Datum *replacements;
+ int nrep;
+ int i;
+
+ Assert(ARR_ELEMTYPE(array) == TEXTOID);
+
+ deconstruct_array(array, TEXTOID, -1, false, 'i',
+ &replacements, NULL, &nrep);
+
+ if (nrep % 2 != 0)
+ ereport(ERROR,
+ (errmsg("Expected pairs of variable names and values"),
+ errdetail("Please give an even number of replacement parameters")));
+
+ for (i = 0; i < nrep; i+=2)
+ {
+ Datum rep;
+
+ elog(DEBUG1,
+ "pg_execute_from_file replaces '%s' with '%s'",
+ text_to_cstring(DatumGetTextP(replacements[i])),
+ text_to_cstring(DatumGetTextP(replacements[i+1])));
+
+ rep = DirectFunctionCall3(replace_text,
+ PointerGetDatum(src),
+ replacements[i], replacements[i+1]);
+ src = DatumGetTextP(rep);
+ }
+ query_string = text_to_cstring(src);
+ elog(DEBUG2, "pg_execute_from_file: %s", query_string);
+ }
+
+ /*
+ * We abuse some internal knowledge from spi.h here. As we don't know
+ * which queries are going to get executed, we don't know what to expect
+ * as an OK return code from SPI_execute(). We assume that
+ * SPI_OK_CONNECT, SPI_OK_FINISH and SPI_OK_FETCH are quite improbable,
+ * though, and the errors are negatives. So a valid return code is
+ * considered to be SPI_OK_UTILITY or anything from there.
+ */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed");
+
+ if (SPI_execute(query_string, false, 0) < SPI_OK_UTILITY)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("File '%s' could not be executed", filename)));
+
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed");
+
+ PG_RETURN_VOID();
+ }
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 3386,3399 **** DESCR("reload configuration files");
DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
DESCR("rotate log file");
! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ ));
DESCR("return file information");
! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ ));
DESCR("read text from a file");
! DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ ));
DESCR("list all files in a directory");
! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ ));
DESCR("sleep for the specified time in seconds");
DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ ));
DESCR("convert boolean to text");
--- 3386,3403 ----
DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ ));
DESCR("rotate log file");
! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ ));
DESCR("return file information");
! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ ));
DESCR("read text from a file");
! DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ ));
DESCR("list all files in a directory");
! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ ));
DESCR("sleep for the specified time in seconds");
+ DATA(insert OID = 3627 ( pg_execute_from_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "25" _null_ _null_ _null_ _null_ pg_execute_from_file _null_ _null_ _null_ ));
+ DESCR("execute queries read from a file");
+ DATA(insert OID = 3928 ( pg_execute_from_file PGNSP PGUID 12 1 0 25 f f f t f v 2 0 2278 "25 25" "{25,25}" "{i,v}" _null_ _null_ pg_execute_from_file _null_ _null_ _null_ ));
+ DESCR("execute queries read from a file");
DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ ));
DESCR("convert boolean to text");
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 442,447 **** extern Datum pg_relation_filepath(PG_FUNCTION_ARGS);
--- 442,448 ----
extern Datum pg_stat_file(PG_FUNCTION_ARGS);
extern Datum pg_read_file(PG_FUNCTION_ARGS);
extern Datum pg_ls_dir(PG_FUNCTION_ARGS);
+ extern Datum pg_execute_from_file(PG_FUNCTION_ARGS);
/* misc.c */
extern Datum current_database(PG_FUNCTION_ARGS);
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Please find attached v12 of the patch, which implements that idea.
And v13 now. v12 was intended to see what you think about the new
pg_execute_from_file placeholder API and replace_text usage, v13 fixes
the pg_dump support by adding dependencies.
Also, I've been changing the \dx output to show the schema where an
extension's objects are living rather than the custom_variable_classes
that most users won't care about, I think.
Then, I think the ALTER EXTENSION foo SET SCHEMA name still has a use
case, so I've prepared a simple patch to show the API usage before we
get to refactor it all following Tom's asking. So there's a initial
patch to see that in action. I had to rework AlterFunctionNamespace()
API so that I can call it from elsewhere in the backend where I have
Oids, so here's an updated set_schema.4.patch. We will have to extend
the APIs for relations and types the same way, but it's already possible
to test the patch with some extensions this way.
Producing those patches (the alter_extension patch is an incremental
patch that sits atop both the extension and the set_schema one) is made
easy enough with git, I'm impressed by this tool.
http://git.postgresql.org/gitweb?p=postgresql-extension.git;a=summary
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
extension.v13.patch.gzapplication/octet-streamDownload
�B��Lextension.v13.patch ��y_�H�8����j���a��C�;�4w0zy���+ltcKnIaf�����T�������fz�-�^�N��<z�H�k�8���z����h��������p�euuU\O+��������l
�%�.���p��gB�n��e(�)���_�B�(����vq!�^�w�j%��y,�?_�O;�g����������v�:���������o�n8�q�iw�_�������g�/�_A��Mf=o����Ho�����u���]Z������k�d8�\�|��.6��������N���Ez,�*��sh@��eA��q�{�hD��+T L������L�^�R1�q��Mt�q��� ��Z�B�P�9P\gIto��
(\j�{�-������v7S��n�d-����]1�>|�?�bG�������W��U �4�{~��@� ��e��L����������_\�����5��(H��WO���o�'��
E��OE:��$#��W )��8H��K�#�q 0'�@)6�m�����N��vxvB�=���p���������n�T�A�����b!WS��I oa�����T� p%����_���%�C����wzr={����E���-�. ��O������%�G�=t�������I�=:����`����M��K��:�s����8���8�M�Q�X���AL{������\78��,�f�p��!}� ���������ZM�I
'r=���tT���[��Y8
���8������|Gg���N�*����AxS�L�Y7�c��:� �jl�F�a����O{�S1����8����Ei\o���n�z@�@������?�<��L��������U���z:Y�����E�����t����:�%�c�����u�^w����}J��D�������Q���HEV�QJPE h�k��/��]���G��9GE��Q���O�,�VW����$:�/�I�_sVb��E���p�d
v�S*����� !^��x�����[^s���sUM�@��J��(+n�W����t�y��Lz�y��9�.X��B��8x���"X�����zk��#�*���wT���]�
h��ux| K��v�?��+O��)�urTb��k#��N.�b�5/�B����A���}���������{��7�AG<���AD���z2q����S�+a�I��|F��#I0N8���0��h�(�)������>����B�Oz$�!�6&i����{���t�\���]�r�ig��N�uN�g^���a��JA{���Cl��\U��Pj����
�"��?�b��x�Q�� ����,'c_�q �����`�#�5\��$�H�� S�������=z��v������s�]w�zF��G�S��(�V��8H�����bZ��?�����?!�@Bv
��Sv���4� Ak��WR���h�T���qViU�_
��MO��xE���,�;4x
�~��
h������,��x��e1������An~�R���T[ �T4�eH��0 9�0��
�����w�P�g��B���8���h*N���e�k��G U`�����^v��?@5,�M��4�����r�"_v�����}���s���4�1����8����eJ?��3]�,8]t����.�z�"��B����:�����z�>2����'�:���j�f������pA9�M � �� \�^�� ����@e&@`:I~���lc�sc�i�rb�9*��uf7�iY�K�L�0�I0���6% ����_A����@��0�����&E���v�Sv�6�
&���=�f#?��Va��B�1�4I��6b��?����;���bv�
M����A
��j���X�j� �
����R8
�~fa�J1� 7���R���N��U�Qa~T5��/�Po�(��P
�N?�&�
t������N^�W��������b!J����
��)�Ta��[7��)0������\���0A��U
�d���X��7k�<��p� Y��ki��M7n��5��~9��Vd����w�������d�c����`��7Y����FKb��'�Q���������UMh�z�ja�Q��0�������
��x>����.��O�a��`��?vo���6^JC*��z����#�Q?�"L�q$�x��gj\.��`���7���[8 �������:�E��,\�?��p��y[.��tz�����mo`k���
���6��6��������@3]h' n`�����J�]�XE�BP.�=�����C�"�����8!��� y��2=���i<Z�>����'�?m���(b�W�80n������U+(�E�Gu�����h�Q �i�
�8�T�p���)���O��:�}g9?�<��������x8q�,C��q9��I.w�r�|t%���%�|�R/ �����E�G"�T�X�\S��4�$��}.�����|
TI�W_��%.��WFUt�r�8;c'q�#7��� ��{k5h7��_.�����VD�f��i�(+����g ��D�����D}^���p���������o�<��x��U'X��
0,�����:�#?�o0N��&g s�K��TZ��?�f����`����j�� �$�/�������B���h���t���qx��
���P���~���]���u�iH��W�EMr��~5|^(��C/Mx�9�m�@E��{��7J���$��[��u��r��x��"s'E�; ����������|�����7��1���;�8A��'[����������(`�1r)|'K�c���W^shd�|�C[�8����-�8�\����B�sx 1�(
|�!A!�
���S��T3>�C�:P�2��T���\f��Lo�3�7�V����6MZ�9G^-�(���7p(� ��P�oT��.�#�A��L�P��7���4J$���W�"�5-L����j�l9��d�t�'A_pYA�@( ���f��"����(��� ����O+%�P-�XE���/{�!��k>���r�8��
��H�~zO�T�b��*��X�*>M��}5x�Z��d���f�Q�TL@9Q{�y]_=M|����B(j�?!�r��C��N�:�}&e�3c:%��S����J�����Xj����\���6
r���&�R\[�|�[
-��%K����]�� ��v#� {�>��t���E��"�g�V!���.���n�3QLZ i�&�Pl�d���/d�������tYr3������]�L4�u+����K6�������h��|��h�����)��{�%A[Yr�g
<)`g*���`����
-�T
|�a��;�u�j;���������Ol!���+EO���C��6���f9�q��QMq;�{�������a|7�[�����`(#ck�Z��W�d'��}"��m��%����e�s���q3 �x�[u�#U�� ��a=�B�/�+\���F�n|���e�Q^��(��p7��8��i�~���x�v*��S[��~������w���f
�T.1�~)�:[}���GK���X�k�7��3x0vP-L�o1Zj�bf�+�b�H��� D�F�Z u��_�>�]�T7�e��|X��St��|�M��Nz��]]�^�.n������c�?�b��5��0��/A���&��������H�m����s`���t�v������e5��`��(X,_gh�DoVIkm�h@G��V ���AI��R����#I��;"������%���}��Jd.
�o�Q%�!���>��,��>��� ��U���6z9'�J�y9����>�"���b\�y����>�!i�'0����j�Q��G���������r~lcW�����;L����>3������B��=�8����o�r
a���7������d0� ���C'6������$8����$>\�E
��5��O'�A�N�x�q=�����S,&#t�E8��g'��r����HM��;�r��2��Q�a?�#L� �������X#����}<w~�vuLB���`r3��!�c��x��h
����Bn3GpV#6����i
��m���"3���^^���,���g����������" �Ip����Z2G����`�R�/��c���!���#�4�����7}R�mw@�{�+
.�F�p(��A`�9��x�7�Y������&�B�CV
*��L�"6��QW����b ^n�� oc\�zy�g ����B@=kj������B2���!���j�����e�DG��Bp,�[ ��������e .@A�Mw��#?��� � ��pZA�|s��9�P�X�����MR����l%��,���_$��zq��g� ��Z�!*�*e��s����f��T�*<���_��P���Ce�\��n�+J�k���$�KL��!��tNDK�� �
wcw�P���/�^+vy�@�QP�CE�)���"�J���(��c3���Ka��y�>�B:��MEs��Z�] 6���#_!����^1����0��F@�����f�&I��(@��D��%q
h��� ���&����L)8?
�n��@C��a7��~�S�s�q��$Kad�@>��o>���9�w��4��@=���Ud���F��"+�6������1���C1����V��I�x$����W���L/� ��������I��I���m�=G���9���E�\ &��U�mJ���S�����J[HF��J��bz>E,j�V��{o�0��*)9��F#�����S'��nccz�4~7b�*� J�h���NI,>4�=B'�@�I\�cJV�R���l������~o,���y�]�:��L��I��zAu.�����9nPU���Uk(w��/;�:����]@�p3_]���1r����Ak�k�p=��FV�/�}��bJ���i�����(^�=��WC��W_���V���j2S�1;6��b�%�7��Ay�NUYl~U���A��A���&_��eIKO����yf����T�2 ����W>qX0o�n�����V���w�����$��z��^�|���a�F�pu�����?&���4��(IE
W/���q���6�.�8*�BP���CKO�����4��E��lpx7V2=�;����3��
5�R�S��UY��������~�t�"z��3S~?�Q)����?�*Aq���OD.��>�x�.��=�iX��5���g����q����{�%�J��sr9���f�n����k��G�����p
���
�%�2��3]��������0�����M�"��k)lC�������h�-�>�<��&p����XH<��l<�q��=xL��f�O��0fC]������qL�G<�&���SnpM��wp��60=��C�5bAI�R���[���y�L�D�� O��� o��^���!�rs� ��a*��E������\r��e�����a<������P�&�'c �a�lyS-4<�
���%�&���!���r�BGO��{��%0�U�.4�\U�(��A���<]3��Q���������-������*E4�a���"?���������jOD�X<=���/��"3@���3�Qb3$��kX���b���!�A\����?�n�����z�no���'�0m�l%� �������=l#��M��a��2�sO���g�f�[����>��*M$H��CY�X�P�P���O�T��y���9cYa����_t�������T��'���7�*
�V�<����FD�B+te����vY�����m�mm����������3������Q���
�;������*����-�sr�Dj�qE�s1��iWK#��SG]h�*�����=-��cujZ�[��vs��5�[�=o���i� ,r��5\[2��.FjTv�&4�P��T�H`K�,��)�In$�>�����XH��6> �� i�E �9n�r0,^���
?���C~;���p7y����s.�_M<�W�)� I5 !`/�5�`?���s�8�%����'�0i2�����m�����u������ ��F�>�r���A�i$���\��0��+��ezV�C`��q���� `�r4���3����9�������Rh�}���pB"������� ��B�D�b�EpX�]Q��,q�h�|�t_���66�v���k��j��Ls�s,����y>aV�QL�,� p���|wz(��E�������n8i&(��{d@�(���D����\�Pg`��~/������?N����8�\H�m9G?����z���) ����"����C6g0�x��(=(.k�>��U!!i���-6L8)EBG�B��8��(U"�F
�ZTX)>?j�R������F��w.�~`VK������*����(C~�a_�sDchN)�h�H��e��:�{����:?\���/���Xp�@������Ao���/|c�GkA
K�4 h8���l��0H�� F������f*��xw��e)@��!���)i�c���#���R60��
�J��Sa� `��E��Mp/��m�P��c��{�;3W������i�{��� ���n�j�-�Z~�\��������$�4A����2�*$P�n-dx}���-h�y�VX�T����Fr'��_`����X��sQfJ7pTr�X~+���)d
�%�
�g����
�R�d�/��9��1+�����%���=8��Z�L<����|�r���K�S��(#��+��`D�� ����8�����WQ���U�<���5��#gQ/�`��2q5�})"�F�����@m��� 7�G����*��Uhj��K��n|�k�9�����������jb`>s���9�ct=�'�[�~)���x:d���p��M����E@M�IW�^ �����Q��$�����Q~��4{�9���s���,��@+��l����?�-�g��z�h^�G���3e�(R���I�WQ�s ���q��:�����v&�z1�F}����';�������"�����;T���6��LTy~���y��v�<�.8&V�����YX�5��QN)����H;��w���wC������R��ZD|~MP��f��}>��������a��t+��M-'�l����&���L�C��_6��?�"B���8m8���b
H�1���Kt��o������fcwo�kn��K�q�f���_E�}�>�������h*�n���"�����K�D*��������l'�n6-�TAl��8��V����Y�����u8k����U����<6�O�Er������s�\!o�@L�$6��'���d���3Q�e�'3�s7�F�w�{]��������<����q�����_Z��y�j�T�R���R�����M�.W������I�,�\3�}Z&�J����RA7l�m�k����B� ]S�hOW�N���
3�����M�����B"Q�� F����E�Z��-Q�.��A�q�tH�=�E�I�����,[�����$26�������Bk�Z'd�N�R&-iI�����[{��f�7a�q�]8����I�R~�Kf��(����������@�~r3�h�������Z5�����
�
���*dj���N���W��9o9�Ie+840�P
+���I��/�y������iU5+�V�V�I����\C�2�T�i�4
jQ�����:-\
��t�i��(�L�J���!%���E�D@���;������Q4����X$�J�O��J~jRq'��!��#RMI5f,��/'�Lg��ij�`�$�U)�P����%�YI��$�Sr_>���Y����_5I�� h1��G���RR>��%��z�.Z��R9'��J�l�Df�7|�6s�7�Y�q��e���� o��)[g �����~s&��`����p4�\�1N%�4�+��{�}dwA�8�.��Q�Jh��K��--k�-�c��.Cqk����6�����BN��� �z�L�Q 1�������4c�1G�J��N�p����nC`$���f���SS�j���-I�;����ME���k�B�I6���|���V���B�����n��QH��H�>V�o�����
��h�'O�~�����I���"2���q����0����29��$U
I���Cm�pY�;����'��<����a�m���JOfK����4�v��Y����7\�o��Q��(���J/nV����� �I��,���*�H��Q�)R��z!H,~:��A���o��l��������"��,��fd��*���vt&����h�:>���/�T�E\��>���y�F������<m\�I-��D&$������
CII�q�mt�~7
�(�����O�`�������p�b�G��sjtc��-Z�="�$�i�]�����x;��� ��s�!~�!��wC���$@Z?L{1p�t ��mf�8d�V��U�:�v3������H��P���V���������0�?��ao���7e�h����d��6L��~�R�-�t#�o��z+8Y��-f5->���1 ��+dH[$��n��is}geH�[�d�.)\��'�
���p+�
��WB�'�)�f!�Bu��nL��N3'�C���e�P�U,P��9D� kp(�2�R�].���X���s�a��Te;I���� ����1I� ��C��J��.�����t?��%�8�#�s�C��\��
�m�;'Y�)wQ��\�+S�u�������X5�����lNU�/���{��py�T7�0�c0
r*+�"��{��R5� �����%���^�%}�D����W�aKXR�<B�� ��U��o-�(T�X`
lP�9!��2��0�)p��-�(T-BE�c��n�c��tsS{O��$�������s��
uH��|pWW%��0!B'�-]e�Y����`1@�J��*���l�e��:p�����d�|��Z�C78��u���Uq�G7L���AS�wR�������'���o�����9Hz���'�8N���[��v$
��e�������
S5����(M��a���N��x��������?Jl���^]���&����G�������������E*�9����@���w1�����r{�������W���6=�6�����XXH��J�������*g�7�0RT�~i�"�S,�g��B��# ��e��)��
�1�T$���K�}���1�K|�"�\o���|E��'�7%K�� �;i[)�U�}�E=�,�/[1��<T�9d'�<�pR��� x�����u����=?W`Uv��}����M�r�0v���F��!�$}����&������nks�jAQqX
`�m���k���A����#{D���KI�WaV+O���]��R>F�����m<�Z6�B���������:�z}���!R)���#��K���Kj�k��{����&�n��m��K�4=8!:'$KU:� �'
��O7���������~�����A�pjZ�*�V�k���gkz�R���}�z��`��
�3e����XJ�.c�L��!/�����oY���h���32�+�����P" �[?�1���:��G�2�=T.;d��V2q6�Eg�����B��@�����`�t�� )�Y���D/��������c�E�����3�������W���=j�<�:����%ph�������{����BX�1�hZ�������ib�MR�=�]�v]�E�/�GET�����`8�z���@m���/���>�e+n�Fqo�A�����"]+�����PJA6��t2'��<qL>��������1��&A���M���-�H�R8����WZA��T��*q�\�ZOc���;���v��}9*T�U������1���`��������$���<[#tx�N�� ��s��%KE�tM-T���}�Y��b3O���]~~���^t 1��kX�a�n�k��e��Ds
���9�g*�Q�����C�gGg������.8s�-6� ��R%�p9Y�(�R����SFf7'~E�{����eG�%L�������|:�t���Q[�[\�;�����^�T\-C�$���� �3g��4�f^�s��6�>��r�GT�<'��U��G�fB2c��������=O��KN�[�j�4d���C����������6��h��-l�1X�q��I���a/D��X'���E��4���Z�m�d���'i�5*��\1F?�4uX>������6,e�J���q �}�i)D��y]b�b��<1�5�|~a����ac0^��G'�/c�f�S/gN�+s%�g�[e1f�Y_M^y�|�yI]��Tn�X��N�T1K8�I�z�F�H��iX
}_42���+���SR��m�z��
�hYY_x/9�))��+��l ��k���M��Z7�d�D�O�����l,��X�LLK�Y>������b�n��mk�����4>����38I�Q�o����:�J��*���!6����v#o��# UH��l��?`���_ SE���r���w2&;G6%{�B&X
����z����z��u>
�*�`[�M(��Q�}k_F��U������� N�b����[j�,vqy>���)����O��o���/��~����i���3+�U#����b���B�>*m�=9#��`IFc��t�{���������N�=�\cC�#�sDR���'����%��{W9.).��j�C�w��������9x��O�)�f(S9#��0\�M�����C }io\���H�&��Fx���>�8���o��o��o|��u�^@��a��4]�?J��Vh���2��������,��������$�������]v�S��l��_'�p��y��q�uo ��*�
���W7_omz[��D� �]�������^����'��3��8�N���"�4����{��AC��F}|�B^cp��(�j��S��~��0�\*}4Z�������L�%t��������w�rc>�Yzt&Q-x�����;&��#�g�!�IU�V�q[��jt�%�q(��u�����Q�0g����P�����,�]%���-@/ ��'�����IsS4��x�t0ln��sDF�����I�Z*(���\��z2��a�8D��#�� L�U&f��S����B�!s3/�z���[�O���'�~U�8����M<��� ��S���,�\���/��%��������~-��1z$G��@M��p������,_E�=�%X��!f�����EE�� �����C��A59,�UeB�R D��t)�����������aF������W��?���Y�9��@��[t��iUVY���Q$/C�~�=�j������}^~��`hoh�� ����j�H���*��J�G�t
,���r\�70�IK��?�������W���-����p�|�Ts����B�s��Sh�a=<w���b�4��+Q|cy������V�����a��j�d���gV�%���m�cg�x$�#4�o`��m�}y<�}������%#��/�>~<}�d��[���q@��� ����<��q7������R�����Y�����TUp���++�x ��o�-�����x���<4j?�,`]bp���X����V��� ��)i'������C�@F":z�/��3 ��6����@I@����O�QF�fisM�1�@���g.����r��(��K�9*��:7pr��@6��� ��$�U �������D��,��.�#�AN�P�s��u!���y���L7JI�N�^����$]�������-��'%��� Jn���S��k��(Gk~�e���~�Q�R�}�6k�������-���s7���%����iy&�\�PA��^�Ynk�E�Yz}�a�������rjg��=o�S
����%������q�sA"�ok����c�7��&u�IR��'�7�>mjg�%������T�R��7Y�� D��6y�g�B� Bh��T#t�����Ao�_C*�����iY/�}���7�/�&�_=���Z�<4���ssn��gE���6��<�j�����Y�\�)�phE��(`,Xe#���Y\mie�����"����^��hP�7[��M�C����V I0*-�z]�X��8�{8��lH
h�z9X��eg�k6�Uf�"���1��9G�����`��c����$�cb�G?7B&��~�V�������b2[5�v�0�H�����@�h��%������`e�X�0a�s�VA*� W���I�����
�_�J�k���z�;�����Cqh�c�E�t����0��r���Bv\�`����W@vI?I7���8
x�:C������X�������+�������Q�F��*����/O^u�.�_��,��[__o�p��" �2�}����m�����]��&����������>g����!���\]��66Km`���-�^�/�dvK�hA�!������S�v�-��� X�)0���� y��=�
y��>��8x�X���U����[�4C3���$�&��[��H�5��Uk��+�J��R�C��u �9��CS\2��p�#ui�r�r�)Srv����x���'3:�������(��}\���nZ(�/�1���N�[-������,IA�sq���*����~���t:�%D�ZY�HF�0���G���/�~���*����F�������n�}�c�B�����5�7}�kO����9�PB%�?e\���0�cVA������}��Q/��`����*'��g
���8/,�����^'��i8~�I���d|= P6��Q�����#�h*�u��}�A������G���_4�{���[�_H�*_*)��Q�4^$m�
��e��~D)�d����w���*���,�����f�B�&���a�6z����T4hY����������r�s=���H� -��1�,��)�����W{���dK;@M������@\�P���|���K��?��MTG��I;)P�� R�Q�*���g���X�4c��/�N�e�f8�%
�Q�2F(b]��R������d+�M�� ���2�p���� ���=����u@��U�-K��y��@�)��$9d6t�5�
E��O
�� !>{&x�.k��|#�s.�_���M�6w��@�"�����Aw]7�����d2h�q/���5��*���*�����H�xE=�W�]�aU�>6Z}�`h����s��f=<�g�N����-��\��t`�y��f�b�e��ww
�ak��/G~ �Z����\3T��1KI�5�}��}8�\��]<.w�7� U�x=q�h%#Og����t.������fa1
��9�tk{�1��y�9������� �������[�<��vm[�Je��.\�O������/.�.<�P��o&c1�C��c�7Y�T�%Q��Q�C:��h��g�'��e��t�������<��9��������a[���"��!�*�����{���_��pxonz>z�O<�x��u�ung;?H�;��!.e�]N�|:�����O�]@��Q��7��Q�
����=f]9�Ww�`���d���(@�`2��H��]�#���S+��
!�W����p�I�4�"�B����$
�g8��Sq�:!
u,�5
� NJM��E6���0�e?a����S|�C�A�a��j�-xq$�6�~�����!5es�����+�7�[C0�f��Y�e����|��.��Kb�^�"�k���'�O���z$G�;�o�@���\;����V]��R��Gy��291v�.���o���2d�5����`���#���<�CD�$x���B�o(�������
U��VD6����a�;�B���MB�C����2�����`�E�����X������"�5~,J �W(}"���R���pk<����A�0]5h�:��/AVo�A��T �V��OGIl�c
}�e��
\P�~P�G���D� !l����S{d����N�hn�X�K��A�awP�gW�����J�hheU^�?n���1#8Zn��#-�=;c�yM��^�aS�]���~C�f��!y�TM������uwP��B�@z����`��cp���K9���4{�Mo�)oz>��D�)H�M� r)o ��^s��k�dP p��a�`}?�(��1�|�9��qD�tgX�xu��}��E�$����� ��0�����������C�EH��4�87 G����/D���(�J�q�}�����d�eKl[�+��I��,!�
}����S����u�^��u�/���_��hu%�t�T����s�����.��q�B�v��p�a�5��q������5 ��d��%rv_��h�,��f(���]/%��O�Nuj��a���5�)����T��rF��u�P���s��l��x���y�����c���D}��v#��=�����"Q��D���rN]���5�dRy"���z� b����|O�"�����Jg�6��9%I&�����, ��I��o�:c2��yE�3�h��� ���^������OrBcK�!�<)�%q��4#_������E[��]J7�
�h^]t�lc�A�@E0-��5(���YT�^�,��!�t��s�F��|0;�'K���
cB[��8r��Q$��x�� #��47J�!�aL����e��F�^_rKC�AL!�d%�5�B*�t��
�nN%���B0A��X�E�ad��T"Tr~s��b���UAUm�x[-���E���(h!���&=�Cd�.��77ek�w����:��E!���&����d}0�2�H(z��`���SU����D�@�m��K�=)��;�� �'5��n��D�%i��������e]�^t�O�%�1$��JP��m��wg'������\no{��f�Zv'ZX!:�OYW,�S����=h���'���Qz�+�)��Q�k�:��
��mG�����V�aV����~
1�+:z�V�V�����P�?'�~��\��������m47��o�7�����j�dH�nW+���NM�(}�hB�������i�eK}<u������F�Gz���q��UC���1V loly�*��|��S���y��7������"��sH>�%5�'�ar{?;>���j= 3#�+~���K-I9K]2������"i�����D��.�S�d
f���I�4��F�
:-�[���a�?w��U��}J�Z���~aV��B���E�QX��s��%j��)�� #����0O�.G0�@B "���H�D3[� tz���������~*$fQ�ye��fO�����*s���+���|*V���
��;j��N�]�6�����U`;\N���d��P>���"�U�(5����a�|g��p-�U$_�kn����<������aJ ��r2����e�q]z��k�����,�!���3�Mh_?g�FR����Y��f� ]]�M�nY)o>�����e��h���3X�%��-o���O~�H�rH
���V����06��������2��r��@�����aL���S@BMM��
��(|�����#����Y�n����L�E�����~;@��=�GSZt�u�w~|��T�� �qY��c)�`D�g���|B�?�fV��w"
� �)J�$���s8����=Nb�w�X��]
�]���j��l#Z�d ����S��e���?T�3�)�`�&�Z�q���s�)c� ��#��#ChM�U�� ����8�6Z�1P;r+�(}��%'k|�5�
�M/IG��Uh_����6�
<����XFz�j�<ND�P
���=P�h��#������4Va�l������.^�/;�W��wJc"����*'GS+����B�R�^����C�`��
:�*#% |�����5r�x|�U�<IF���D��~������*�!p��.�8��FV��=���<����7�������[!�6c�!�uZ+C��:u"2��O�yE�&��-�^]��&E�������h���P(�������w��>f]�fA�M��Z��0~�Tpy
��'a; W��N}[��Tk-��|����&���:�a;fNB��@z
���:V���*�����t����4H8gG6���J�^
T?A��T�C_��9�T���O��������D�<��k!fp�*e��Cb�s29���L�:%i���������1��L�����n�3�w�j��P���� ��ggws������|�"�Fu7�!�>
���8Q�Q�?/�O��O�J����HY� �[�����?���3`�a I$���`t�t��E��gp�1������K@`���W�*�ds��V&@���O^�w�ph���m��-����������'��������h*='= �tl �2N lz��??�����=���#?���4�]����"�l�Uam�� ���WA�(*@��B��x��S[������&Z<-9�K#_�\���hJ��Q��mxY�d����
�8=>qK4)�>���C�w���]J�~�A�T�����5u�s��~<n��V&lO�a��Y(��#�)
�,��>i^�������D�E����
��u�v���q[miUhK���w��h[\�y��f�8�y� �������LE+�IL�=���Y���q�Lq.��S�tZ(�R�^mz��b�c��C(:��j�q3�����/���h�Y���?\K�����-��z��(�S1Qb��p|����E����oh"fN�{���k��op���V<2��lC��'�J�/� S~U4��I�Y�[��|"���I�Y��>��1������|c#�
������(M�|��O����?s7�G���i�1/�>����Q**�\fM��d�L<J�S��b��
� �I��jP3{xv����s�����q���{~q���I�U;��1n�(��-���:���1�HB�n���r�^_�t�\�t��r����R�����������3����Vg��i'���[6G'��8����N�����Z]a[Z�{C*�����������*����}� x�%{Z���Lu�����Hs���Uy���~����>