mirror of https://github.com/citusdata/citus.git
Add support for TEXT SEARCH CONFIGURATION objects (#5685)
DESCRIPTION: Implement TEXT SEARCH CONFIGURATION propagation The change adds support to Citus for propagating TEXT SEARCH CONFIGURATION objects. TSConfig objects cannot always be created in one create statement, and instead require a create statement followed by many alter statements to get turned into the object they should represent. To support this we add functionality to the worker to create or replace objects based on a list of statements. When the lists of the local object and the remote object correspond 1:1 we skip the creation of the object and simply mark it distributed. This is especially important for TSConfig objects as initdb pre-populates databases with a dozen configurations (for many different languages). When the user creates a new TSConfig based on the copy of an existing configuration there is no direct link to the object copied from. Since there is no link we can't simply rely on propagating the dependencies to the worker and send a qualifiedpull/5721/head
parent
886db667ee
commit
ea86f9f94e
|
@ -241,6 +241,17 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
|
|||
return NIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Indices are created separately, however, they do show up in the dependency
|
||||
* list for a table since they will have potentially their own dependencies.
|
||||
* The commands will be added to both shards and metadata tables via the table
|
||||
* creation commands.
|
||||
*/
|
||||
if (relKind == RELKIND_INDEX)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
if (relKind == RELKIND_RELATION || relKind == RELKIND_PARTITIONED_TABLE ||
|
||||
relKind == RELKIND_FOREIGN_TABLE)
|
||||
{
|
||||
|
@ -317,6 +328,11 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency)
|
|||
return DDLCommands;
|
||||
}
|
||||
|
||||
case OCLASS_TSCONFIG:
|
||||
{
|
||||
return CreateTextSearchConfigDDLCommandsIdempotent(dependency);
|
||||
}
|
||||
|
||||
case OCLASS_TYPE:
|
||||
{
|
||||
return CreateTypeDDLCommandsIdempotent(dependency);
|
||||
|
|
|
@ -505,6 +505,62 @@ static DistributeObjectOps Sequence_Rename = {
|
|||
.address = RenameSequenceStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps TextSearchConfig_Alter = {
|
||||
.deparse = DeparseAlterTextSearchConfigurationStmt,
|
||||
.qualify = QualifyAlterTextSearchConfigurationStmt,
|
||||
.preprocess = PreprocessAlterTextSearchConfigurationStmt,
|
||||
.postprocess = NULL,
|
||||
.address = AlterTextSearchConfigurationStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps TextSearchConfig_AlterObjectSchema = {
|
||||
.deparse = DeparseAlterTextSearchConfigurationSchemaStmt,
|
||||
.qualify = QualifyAlterTextSearchConfigurationSchemaStmt,
|
||||
.preprocess = PreprocessAlterTextSearchConfigurationSchemaStmt,
|
||||
.postprocess = PostprocessAlterTextSearchConfigurationSchemaStmt,
|
||||
.address = AlterTextSearchConfigurationSchemaStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps TextSearchConfig_AlterOwner = {
|
||||
.deparse = DeparseAlterTextSearchConfigurationOwnerStmt,
|
||||
.qualify = QualifyAlterTextSearchConfigurationOwnerStmt,
|
||||
.preprocess = PreprocessAlterTextSearchConfigurationOwnerStmt,
|
||||
.postprocess = PostprocessAlterTextSearchConfigurationOwnerStmt,
|
||||
.address = AlterTextSearchConfigurationOwnerObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps TextSearchConfig_Comment = {
|
||||
.deparse = DeparseTextSearchConfigurationCommentStmt,
|
||||
.qualify = QualifyTextSearchConfigurationCommentStmt,
|
||||
.preprocess = PreprocessTextSearchConfigurationCommentStmt,
|
||||
.postprocess = NULL,
|
||||
.address = TextSearchConfigurationCommentObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps TextSearchConfig_Define = {
|
||||
.deparse = DeparseCreateTextSearchStmt,
|
||||
.qualify = NULL,
|
||||
.preprocess = NULL,
|
||||
.postprocess = PostprocessCreateTextSearchConfigurationStmt,
|
||||
.address = CreateTextSearchConfigurationObjectAddress,
|
||||
.markDistributed = true,
|
||||
};
|
||||
static DistributeObjectOps TextSearchConfig_Drop = {
|
||||
.deparse = DeparseDropTextSearchConfigurationStmt,
|
||||
.qualify = QualifyDropTextSearchConfigurationStmt,
|
||||
.preprocess = PreprocessDropTextSearchConfigurationStmt,
|
||||
.postprocess = NULL,
|
||||
.address = NULL,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps TextSearchConfig_Rename = {
|
||||
.deparse = DeparseRenameTextSearchConfigurationStmt,
|
||||
.qualify = QualifyRenameTextSearchConfigurationStmt,
|
||||
.preprocess = PreprocessRenameTextSearchConfigurationStmt,
|
||||
.postprocess = NULL,
|
||||
.address = RenameTextSearchConfigurationStmtObjectAddress,
|
||||
.markDistributed = false,
|
||||
};
|
||||
static DistributeObjectOps Trigger_AlterObjectDepends = {
|
||||
.deparse = NULL,
|
||||
.qualify = NULL,
|
||||
|
@ -811,6 +867,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Table_AlterObjectSchema;
|
||||
}
|
||||
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
{
|
||||
return &TextSearchConfig_AlterObjectSchema;
|
||||
}
|
||||
|
||||
case OBJECT_TYPE:
|
||||
{
|
||||
return &Type_AlterObjectSchema;
|
||||
|
@ -868,6 +929,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Statistics_AlterOwner;
|
||||
}
|
||||
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
{
|
||||
return &TextSearchConfig_AlterOwner;
|
||||
}
|
||||
|
||||
case OBJECT_TYPE:
|
||||
{
|
||||
return &Type_AlterOwner;
|
||||
|
@ -949,11 +1015,33 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Any_AlterTableMoveAll;
|
||||
}
|
||||
|
||||
case T_AlterTSConfigurationStmt:
|
||||
{
|
||||
return &TextSearchConfig_Alter;
|
||||
}
|
||||
|
||||
case T_ClusterStmt:
|
||||
{
|
||||
return &Any_Cluster;
|
||||
}
|
||||
|
||||
case T_CommentStmt:
|
||||
{
|
||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
||||
switch (stmt->objtype)
|
||||
{
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
{
|
||||
return &TextSearchConfig_Comment;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return &NoDistributeOps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case T_CompositeTypeStmt:
|
||||
{
|
||||
return &Any_CompositeType;
|
||||
|
@ -1014,6 +1102,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Collation_Define;
|
||||
}
|
||||
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
{
|
||||
return &TextSearchConfig_Define;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
return &NoDistributeOps;
|
||||
|
@ -1091,6 +1184,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Table_Drop;
|
||||
}
|
||||
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
{
|
||||
return &TextSearchConfig_Drop;
|
||||
}
|
||||
|
||||
case OBJECT_TYPE:
|
||||
{
|
||||
return &Type_Drop;
|
||||
|
@ -1190,6 +1288,11 @@ GetDistributeObjectOps(Node *node)
|
|||
return &Statistics_Rename;
|
||||
}
|
||||
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
{
|
||||
return &TextSearchConfig_Rename;
|
||||
}
|
||||
|
||||
case OBJECT_TYPE:
|
||||
{
|
||||
return &Type_Rename;
|
||||
|
|
|
@ -725,12 +725,6 @@ PostprocessIndexStmt(Node *node, const char *queryString)
|
|||
{
|
||||
IndexStmt *indexStmt = castNode(IndexStmt, node);
|
||||
|
||||
/* we are only processing CONCURRENT index statements */
|
||||
if (!indexStmt->concurrent)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
/* this logic only applies to the coordinator */
|
||||
if (!IsCoordinator())
|
||||
{
|
||||
|
@ -747,14 +741,36 @@ PostprocessIndexStmt(Node *node, const char *queryString)
|
|||
return NIL;
|
||||
}
|
||||
|
||||
Oid indexRelationId = get_relname_relid(indexStmt->idxname, schemaId);
|
||||
|
||||
/* ensure dependencies of index exist on all nodes */
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, RelationRelationId, indexRelationId);
|
||||
EnsureDependenciesExistOnAllNodes(&address);
|
||||
|
||||
/* furtheron we are only processing CONCURRENT index statements */
|
||||
if (!indexStmt->concurrent)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* EnsureDependenciesExistOnAllNodes could have distributed objects that are required
|
||||
* by this index. During the propagation process an active snapshout might be left as
|
||||
* a side effect of inserting the local tuples via SPI. To not leak a snapshot like
|
||||
* that we will pop any snapshot if we have any right before we commit.
|
||||
*/
|
||||
if (ActiveSnapshotSet())
|
||||
{
|
||||
PopActiveSnapshot();
|
||||
}
|
||||
|
||||
/* commit the current transaction and start anew */
|
||||
CommitTransactionCommand();
|
||||
StartTransactionCommand();
|
||||
|
||||
/* get the affected relation and index */
|
||||
Relation relation = table_openrv(indexStmt->relation, ShareUpdateExclusiveLock);
|
||||
Oid indexRelationId = get_relname_relid(indexStmt->idxname,
|
||||
schemaId);
|
||||
Relation indexRelation = index_open(indexRelationId, RowExclusiveLock);
|
||||
|
||||
/* close relations but retain locks */
|
||||
|
|
|
@ -0,0 +1,935 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* text_search.c
|
||||
* Commands for creating and altering TEXT SEARCH objects
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/genam.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/objectaddress.h"
|
||||
#include "catalog/pg_ts_config.h"
|
||||
#include "catalog/pg_ts_config_map.h"
|
||||
#include "catalog/pg_ts_dict.h"
|
||||
#include "catalog/pg_ts_parser.h"
|
||||
#include "commands/comment.h"
|
||||
#include "commands/extension.h"
|
||||
#include "fmgr.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "tsearch/ts_cache.h"
|
||||
#include "tsearch/ts_public.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/metadata_sync.h"
|
||||
#include "distributed/multi_executor.h"
|
||||
#include "distributed/relation_access_tracking.h"
|
||||
#include "distributed/worker_create_or_replace.h"
|
||||
|
||||
|
||||
static List * GetDistributedTextSearchConfigurationNames(DropStmt *stmt);
|
||||
static DefineStmt * GetTextSearchConfigDefineStmt(Oid tsconfigOid);
|
||||
static List * GetTextSearchConfigCommentStmt(Oid tsconfigOid);
|
||||
static List * get_ts_parser_namelist(Oid tsparserOid);
|
||||
static List * GetTextSearchConfigMappingStmt(Oid tsconfigOid);
|
||||
static List * GetTextSearchConfigOwnerStmts(Oid tsconfigOid);
|
||||
|
||||
static List * get_ts_dict_namelist(Oid tsdictOid);
|
||||
static Oid get_ts_config_parser_oid(Oid tsconfigOid);
|
||||
static char * get_ts_parser_tokentype_name(Oid parserOid, int32 tokentype);
|
||||
|
||||
/*
|
||||
* PostprocessCreateTextSearchConfigurationStmt is called after the TEXT SEARCH
|
||||
* CONFIGURATION has been created locally.
|
||||
*
|
||||
* Contrary to many other objects a text search configuration is often created as a copy
|
||||
* of an existing configuration. After the copy there is no relation to the configuration
|
||||
* that has been copied. This prevents our normal approach of ensuring dependencies to
|
||||
* exist before forwarding a close ressemblance of the statement the user executed.
|
||||
*
|
||||
* Instead we recreate the object based on what we find in our own catalog, hence the
|
||||
* amount of work we perform in the postprocess function, contrary to other objects.
|
||||
*/
|
||||
List *
|
||||
PostprocessCreateTextSearchConfigurationStmt(Node *node, const char *queryString)
|
||||
{
|
||||
DefineStmt *stmt = castNode(DefineStmt, node);
|
||||
Assert(stmt->kind == OBJECT_TSCONFIGURATION);
|
||||
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the create command is a part of a multi-statement transaction that is not in
|
||||
* sequential mode, don't propagate. Instead we will rely on back filling.
|
||||
*/
|
||||
if (IsMultiStatementTransaction())
|
||||
{
|
||||
if (MultiShardConnectionType != SEQUENTIAL_CONNECTION)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
||||
EnsureDependenciesExistOnAllNodes(&address);
|
||||
|
||||
/*
|
||||
* TEXT SEARCH CONFIGURATION objects are more complex with their mappings and the
|
||||
* possibility of copying from existing templates that we will require the idempotent
|
||||
* recreation commands to be run for successful propagation
|
||||
*/
|
||||
List *commands = CreateTextSearchConfigDDLCommandsIdempotent(&address);
|
||||
|
||||
commands = lcons(DISABLE_DDL_PROPAGATION, commands);
|
||||
commands = lappend(commands, ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
List *
|
||||
GetCreateTextSearchConfigStatements(const ObjectAddress *address)
|
||||
{
|
||||
Assert(address->classId == TSConfigRelationId);
|
||||
List *stmts = NIL;
|
||||
|
||||
/* CREATE TEXT SEARCH CONFIGURATION ...*/
|
||||
stmts = lappend(stmts, GetTextSearchConfigDefineStmt(address->objectId));
|
||||
|
||||
/* ALTER TEXT SEARCH CONFIGURATION ... OWNER TO ...*/
|
||||
stmts = list_concat(stmts, GetTextSearchConfigOwnerStmts(address->objectId));
|
||||
|
||||
/* COMMENT ON TEXT SEARCH CONFIGURATION ... */
|
||||
stmts = list_concat(stmts, GetTextSearchConfigCommentStmt(address->objectId));
|
||||
|
||||
|
||||
/* ALTER TEXT SEARCH CONFIGURATION ... ADD MAPPING FOR ... WITH ... */
|
||||
stmts = list_concat(stmts, GetTextSearchConfigMappingStmt(address->objectId));
|
||||
|
||||
return stmts;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateTextSearchConfigDDLCommandsIdempotent creates a list of ddl commands to recreate
|
||||
* a TEXT SERACH CONFIGURATION object in an idempotent manner on workers.
|
||||
*/
|
||||
List *
|
||||
CreateTextSearchConfigDDLCommandsIdempotent(const ObjectAddress *address)
|
||||
{
|
||||
List *stmts = GetCreateTextSearchConfigStatements(address);
|
||||
List *sqls = DeparseTreeNodes(stmts);
|
||||
return list_make1(WrapCreateOrReplaceList(sqls));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessDropTextSearchConfigurationStmt prepares the statements we need to send to
|
||||
* the workers. After we have dropped the schema's locally they also got removed from
|
||||
* pg_dist_object so it is important to do all distribution checks before the change is
|
||||
* made locally.
|
||||
*/
|
||||
List *
|
||||
PreprocessDropTextSearchConfigurationStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
DropStmt *stmt = castNode(DropStmt, node);
|
||||
Assert(stmt->removeType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
if (!ShouldPropagate())
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
List *distributedObjects = GetDistributedTextSearchConfigurationNames(stmt);
|
||||
if (list_length(distributedObjects) == 0)
|
||||
{
|
||||
/* no distributed objects to remove */
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
|
||||
|
||||
/*
|
||||
* Temporarily replace the list of objects being dropped with only the list
|
||||
* containing the distributed objects. After we have created the sql statement we
|
||||
* restore the original list of objects to execute on locally.
|
||||
*
|
||||
* Because searchpaths on coordinator and workers might not be in sync we fully
|
||||
* qualify the list before deparsing. This is safe because qualification doesn't
|
||||
* change the original names in place, but insteads creates new ones.
|
||||
*/
|
||||
List *originalObjects = stmt->objects;
|
||||
stmt->objects = distributedObjects;
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
const char *dropStmtSql = DeparseTreeNode((Node *) stmt);
|
||||
stmt->objects = originalObjects;
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) dropStmtSql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_METADATA_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetDistributedTextSearchConfigurationNames iterates over all text search configurations
|
||||
* dropped, and create a list containign all configurations that are distributed.
|
||||
*/
|
||||
static List *
|
||||
GetDistributedTextSearchConfigurationNames(DropStmt *stmt)
|
||||
{
|
||||
List *objName = NULL;
|
||||
List *distributedObjects = NIL;
|
||||
foreach_ptr(objName, stmt->objects)
|
||||
{
|
||||
Oid tsconfigOid = get_ts_config_oid(objName, stmt->missing_ok);
|
||||
if (!OidIsValid(tsconfigOid))
|
||||
{
|
||||
/* skip missing configuration names, they can't be dirstibuted */
|
||||
continue;
|
||||
}
|
||||
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, TSConfigRelationId, tsconfigOid);
|
||||
if (!IsObjectDistributed(&address))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
distributedObjects = lappend(distributedObjects, objName);
|
||||
}
|
||||
return distributedObjects;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterTextSearchConfigurationStmt verifies if the configuration being altered
|
||||
* is distributed in the cluster. If that is the case it will prepare the list of commands
|
||||
* to send to the worker to apply the same changes remote.
|
||||
*/
|
||||
List *
|
||||
PreprocessAlterTextSearchConfigurationStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
AlterTSConfigurationStmt *stmt = castNode(AlterTSConfigurationStmt, node);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
||||
if (!ShouldPropagateObject(&address))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
const char *alterStmtSql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) alterStmtSql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_METADATA_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessRenameTextSearchConfigurationStmt verifies if the configuration being altered
|
||||
* is distributed in the cluster. If that is the case it will prepare the list of commands
|
||||
* to send to the worker to apply the same changes remote.
|
||||
*/
|
||||
List *
|
||||
PreprocessRenameTextSearchConfigurationStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
Assert(stmt->renameType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
||||
if (!ShouldPropagateObject(&address))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
|
||||
char *ddlCommand = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) ddlCommand,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_METADATA_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterTextSearchConfigurationSchemaStmt verifies if the configuration being
|
||||
* altered is distributed in the cluster. If that is the case it will prepare the list of
|
||||
* commands to send to the worker to apply the same changes remote.
|
||||
*/
|
||||
List *
|
||||
PreprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
|
||||
stmt->missing_ok);
|
||||
if (!ShouldPropagateObject(&address))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
const char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_METADATA_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PostprocessAlterTextSearchConfigurationSchemaStmt is invoked after the schema has been
|
||||
* changed locally. Since changing the schema could result in new dependencies being found
|
||||
* for this object we re-ensure all the dependencies for the configuration do exist. This
|
||||
* is solely to propagate the new schema (and all its dependencies) if it was not already
|
||||
* distributed in the cluster.
|
||||
*/
|
||||
List *
|
||||
PostprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *queryString)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
|
||||
stmt->missing_ok);
|
||||
if (!ShouldPropagateObject(&address))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
/* dependencies have changed (schema) let's ensure they exist */
|
||||
EnsureDependenciesExistOnAllNodes(&address);
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessTextSearchConfigurationCommentStmt propagates any comment on a distributed
|
||||
* configuration to the workers. Since comments for configurations are promenently shown
|
||||
* when listing all text search configurations this is purely a cosmetic thing when
|
||||
* running in MX.
|
||||
*/
|
||||
List *
|
||||
PreprocessTextSearchConfigurationCommentStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext processUtilityContext)
|
||||
{
|
||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_TSCONFIGURATION);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
||||
if (!ShouldPropagateObject(&address))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
const char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_METADATA_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PreprocessAlterTextSearchConfigurationOwnerStmt verifies if the configuration being
|
||||
* altered is distributed in the cluster. If that is the case it will prepare the list of
|
||||
* commands to send to the worker to apply the same changes remote.
|
||||
*/
|
||||
List *
|
||||
PreprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
||||
if (!ShouldPropagateObject(&address))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
EnsureCoordinator();
|
||||
EnsureSequentialMode(OBJECT_TSCONFIGURATION);
|
||||
|
||||
QualifyTreeNode((Node *) stmt);
|
||||
char *sql = DeparseTreeNode((Node *) stmt);
|
||||
|
||||
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
||||
(void *) sql,
|
||||
ENABLE_DDL_PROPAGATION);
|
||||
|
||||
return NodeDDLTaskList(NON_COORDINATOR_NODES, commands);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* PostprocessAlterTextSearchConfigurationOwnerStmt is invoked after the owner has been
|
||||
* changed locally. Since changing the owner could result in new dependencies being found
|
||||
* for this object we re-ensure all the dependencies for the configuration do exist. This
|
||||
* is solely to propagate the new owner (and all its dependencies) if it was not already
|
||||
* distributed in the cluster.
|
||||
*/
|
||||
List *
|
||||
PostprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *queryString)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
||||
if (!ShouldPropagateObject(&address))
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
/* dependencies have changed (owner) let's ensure they exist */
|
||||
EnsureDependenciesExistOnAllNodes(&address);
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetTextSearchConfigDefineStmt returns the DefineStmt for a TEXT SEARCH CONFIGURATION
|
||||
* based on the configuration as defined in the catalog identified by tsconfigOid.
|
||||
*
|
||||
* This statement will only contain the parser, as all other properties for text search
|
||||
* configurations are stored as mappings in a different catalog.
|
||||
*/
|
||||
static DefineStmt *
|
||||
GetTextSearchConfigDefineStmt(Oid tsconfigOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(tsconfigOid));
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for text search configuration %u",
|
||||
tsconfigOid);
|
||||
}
|
||||
Form_pg_ts_config config = (Form_pg_ts_config) GETSTRUCT(tup);
|
||||
|
||||
DefineStmt *stmt = makeNode(DefineStmt);
|
||||
stmt->kind = OBJECT_TSCONFIGURATION;
|
||||
|
||||
stmt->defnames = get_ts_config_namelist(tsconfigOid);
|
||||
|
||||
List *parserNameList = get_ts_parser_namelist(config->cfgparser);
|
||||
TypeName *parserTypeName = makeTypeNameFromNameList(parserNameList);
|
||||
stmt->definition = list_make1(makeDefElem("parser", (Node *) parserTypeName, -1));
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
return stmt;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetTextSearchConfigCommentStmt returns a list containing all entries to recreate a
|
||||
* comment on the configuration identified by tsconfigOid. The list could be empty if
|
||||
* there is no comment on a configuration.
|
||||
*
|
||||
* The reason for a list is for easy use when building a list of all statements to invoke
|
||||
* to recreate the text search configuration. An empty list can easily be concatinated
|
||||
* without inspection, contrary to a NULL ptr if we would return the CommentStmt struct.
|
||||
*/
|
||||
static List *
|
||||
GetTextSearchConfigCommentStmt(Oid tsconfigOid)
|
||||
{
|
||||
char *comment = GetComment(tsconfigOid, TSConfigRelationId, 0);
|
||||
if (!comment)
|
||||
{
|
||||
return NIL;
|
||||
}
|
||||
|
||||
CommentStmt *stmt = makeNode(CommentStmt);
|
||||
stmt->objtype = OBJECT_TSCONFIGURATION;
|
||||
|
||||
stmt->object = (Node *) get_ts_config_namelist(tsconfigOid);
|
||||
stmt->comment = comment;
|
||||
return list_make1(stmt);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetTextSearchConfigMappingStmt returns a list of all mappings from token_types to
|
||||
* dictionaries configured on a text search configuration identified by tsconfigOid.
|
||||
*
|
||||
* Many mappings can exist on a configuration which all require their own statement to
|
||||
* recreate.
|
||||
*/
|
||||
static List *
|
||||
GetTextSearchConfigMappingStmt(Oid tsconfigOid)
|
||||
{
|
||||
ScanKeyData mapskey = { 0 };
|
||||
|
||||
/* mapcfg = tsconfigOid */
|
||||
ScanKeyInit(&mapskey,
|
||||
Anum_pg_ts_config_map_mapcfg,
|
||||
BTEqualStrategyNumber, F_OIDEQ,
|
||||
ObjectIdGetDatum(tsconfigOid));
|
||||
|
||||
Relation maprel = table_open(TSConfigMapRelationId, AccessShareLock);
|
||||
Relation mapidx = index_open(TSConfigMapIndexId, AccessShareLock);
|
||||
SysScanDesc mapscan = systable_beginscan_ordered(maprel, mapidx, NULL, 1, &mapskey);
|
||||
|
||||
List *stmts = NIL;
|
||||
AlterTSConfigurationStmt *stmt = NULL;
|
||||
|
||||
/*
|
||||
* We iterate the config mappings on the index order filtered by mapcfg. Meaning we
|
||||
* get equal maptokentype's in 1 run. By comparing the current tokentype to the last
|
||||
* we know when we can create a new stmt and append the previous constructed one to
|
||||
* the list.
|
||||
*/
|
||||
int lastTokType = -1;
|
||||
|
||||
/*
|
||||
* We read all mappings filtered by config id, hence we only need to load the name
|
||||
* once and can reuse for every statement.
|
||||
*/
|
||||
List *configName = get_ts_config_namelist(tsconfigOid);
|
||||
|
||||
Oid parserOid = get_ts_config_parser_oid(tsconfigOid);
|
||||
|
||||
HeapTuple maptup = NULL;
|
||||
while ((maptup = systable_getnext_ordered(mapscan, ForwardScanDirection)) != NULL)
|
||||
{
|
||||
Form_pg_ts_config_map cfgmap = (Form_pg_ts_config_map) GETSTRUCT(maptup);
|
||||
if (lastTokType != cfgmap->maptokentype)
|
||||
{
|
||||
/* creating a new statement, appending the previous one (if existing) */
|
||||
if (stmt != NULL)
|
||||
{
|
||||
stmts = lappend(stmts, stmt);
|
||||
}
|
||||
|
||||
stmt = makeNode(AlterTSConfigurationStmt);
|
||||
stmt->cfgname = configName;
|
||||
stmt->kind = ALTER_TSCONFIG_ADD_MAPPING;
|
||||
stmt->tokentype = list_make1(makeString(
|
||||
get_ts_parser_tokentype_name(parserOid,
|
||||
cfgmap->
|
||||
maptokentype)));
|
||||
|
||||
lastTokType = cfgmap->maptokentype;
|
||||
}
|
||||
|
||||
stmt->dicts = lappend(stmt->dicts, get_ts_dict_namelist(cfgmap->mapdict));
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have ran atleast 1 iteration above we have the last stmt not added to the
|
||||
* stmts list.
|
||||
*/
|
||||
if (stmt != NULL)
|
||||
{
|
||||
stmts = lappend(stmts, stmt);
|
||||
stmt = NULL;
|
||||
}
|
||||
|
||||
systable_endscan_ordered(mapscan);
|
||||
index_close(mapidx, NoLock);
|
||||
table_close(maprel, NoLock);
|
||||
|
||||
return stmts;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetTextSearchConfigOwnerStmts returns a potentially empty list of statements to change
|
||||
* the ownership of a TEXT SEARCH CONFIGURATION object.
|
||||
*
|
||||
* The list is for convenienve when building a full list of statements to recreate the
|
||||
* configuration.
|
||||
*/
|
||||
static List *
|
||||
GetTextSearchConfigOwnerStmts(Oid tsconfigOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(tsconfigOid));
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for text search configuration %u",
|
||||
tsconfigOid);
|
||||
}
|
||||
Form_pg_ts_config config = (Form_pg_ts_config) GETSTRUCT(tup);
|
||||
|
||||
AlterOwnerStmt *stmt = makeNode(AlterOwnerStmt);
|
||||
stmt->objectType = OBJECT_TSCONFIGURATION;
|
||||
stmt->object = (Node *) get_ts_config_namelist(tsconfigOid);
|
||||
stmt->newowner = GetRoleSpecObjectForUser(config->cfgowner);
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
return list_make1(stmt);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_ts_config_namelist based on the tsconfigOid this function creates the namelist that
|
||||
* identifies the configuration in a fully qualified manner, irregardless of the schema
|
||||
* existing on the search_path.
|
||||
*/
|
||||
List *
|
||||
get_ts_config_namelist(Oid tsconfigOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(tsconfigOid));
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for text search configuration %u",
|
||||
tsconfigOid);
|
||||
}
|
||||
Form_pg_ts_config config = (Form_pg_ts_config) GETSTRUCT(tup);
|
||||
|
||||
char *schema = get_namespace_name(config->cfgnamespace);
|
||||
char *configName = pstrdup(NameStr(config->cfgname));
|
||||
List *names = list_make2(makeString(schema), makeString(configName));
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_ts_dict_namelist based on the tsdictOid this function creates the namelist that
|
||||
* identifies the dictionary in a fully qualified manner, irregardless of the schema
|
||||
* existing on the search_path.
|
||||
*/
|
||||
static List *
|
||||
get_ts_dict_namelist(Oid tsdictOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(tsdictOid));
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for text search dictionary %u", tsdictOid);
|
||||
}
|
||||
Form_pg_ts_dict dict = (Form_pg_ts_dict) GETSTRUCT(tup);
|
||||
|
||||
char *schema = get_namespace_name(dict->dictnamespace);
|
||||
char *dictName = pstrdup(NameStr(dict->dictname));
|
||||
List *names = list_make2(makeString(schema), makeString(dictName));
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_ts_config_parser_oid based on the tsconfigOid this function returns the Oid of the
|
||||
* parser used in the configuration.
|
||||
*/
|
||||
static Oid
|
||||
get_ts_config_parser_oid(Oid tsconfigOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(tsconfigOid));
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for text search configuration %u", tsconfigOid);
|
||||
}
|
||||
Form_pg_ts_config config = (Form_pg_ts_config) GETSTRUCT(tup);
|
||||
Oid parserOid = config->cfgparser;
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
return parserOid;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_ts_parser_tokentype_name returns the name of the token as known to the parser by
|
||||
* its tokentype identifier. The parser used to resolve the token name is identified by
|
||||
* parserOid and should be the same that emitted the tokentype to begin with.
|
||||
*/
|
||||
static char *
|
||||
get_ts_parser_tokentype_name(Oid parserOid, int32 tokentype)
|
||||
{
|
||||
TSParserCacheEntry *parserCache = lookup_ts_parser_cache(parserOid);
|
||||
if (!OidIsValid(parserCache->lextypeOid))
|
||||
{
|
||||
elog(ERROR, "method lextype isn't defined for text search parser %u", parserOid);
|
||||
}
|
||||
|
||||
/* take lextypes from parser */
|
||||
LexDescr *tokenlist = (LexDescr *) DatumGetPointer(
|
||||
OidFunctionCall1(parserCache->lextypeOid, Int32GetDatum(0)));
|
||||
|
||||
/* and find the one with lexid = tokentype */
|
||||
int tokenIndex = 0;
|
||||
while (tokenlist && tokenlist[tokenIndex].lexid)
|
||||
{
|
||||
if (tokenlist[tokenIndex].lexid == tokentype)
|
||||
{
|
||||
return pstrdup(tokenlist[tokenIndex].alias);
|
||||
}
|
||||
tokenIndex++;
|
||||
}
|
||||
|
||||
/* we haven't found the token */
|
||||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
||||
errmsg("token type \"%d\" does not exist in parser", tokentype)));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_ts_parser_namelist based on the tsparserOid this function creates the namelist that
|
||||
* identifies the parser in a fully qualified manner, irregardless of the schema existing
|
||||
* on the search_path.
|
||||
*/
|
||||
static List *
|
||||
get_ts_parser_namelist(Oid tsparserOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSPARSEROID, ObjectIdGetDatum(tsparserOid));
|
||||
if (!HeapTupleIsValid(tup)) /* should not happen */
|
||||
{
|
||||
elog(ERROR, "cache lookup failed for text search parser %u",
|
||||
tsparserOid);
|
||||
}
|
||||
Form_pg_ts_parser parser = (Form_pg_ts_parser) GETSTRUCT(tup);
|
||||
|
||||
char *schema = get_namespace_name(parser->prsnamespace);
|
||||
char *parserName = pstrdup(NameStr(parser->prsname));
|
||||
List *names = list_make2(makeString(schema), makeString(parserName));
|
||||
|
||||
ReleaseSysCache(tup);
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateTextSearchConfigurationObjectAddress resolves the ObjectAddress for the object
|
||||
* being created. If missing_pk is false the function will error, explaining to the user
|
||||
* the text search configuration described in the statement doesn't exist.
|
||||
*/
|
||||
ObjectAddress
|
||||
CreateTextSearchConfigurationObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
DefineStmt *stmt = castNode(DefineStmt, node);
|
||||
Assert(stmt->kind == OBJECT_TSCONFIGURATION);
|
||||
|
||||
Oid objid = get_ts_config_oid(stmt->defnames, missing_ok);
|
||||
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, TSConfigRelationId, objid);
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RenameTextSearchConfigurationStmtObjectAddress resolves the ObjectAddress for the TEXT
|
||||
* SEARCH CONFIGURATION being renamed. Optionally errors if the configuration does not
|
||||
* exist based on the missing_ok flag passed in by the caller.
|
||||
*/
|
||||
ObjectAddress
|
||||
RenameTextSearchConfigurationStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
Assert(stmt->renameType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
Oid objid = get_ts_config_oid(castNode(List, stmt->object), missing_ok);
|
||||
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, TSConfigRelationId, objid);
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AlterTextSearchConfigurationStmtObjectAddress resolves the ObjectAddress for the TEXT
|
||||
* SEARCH CONFIGURATION being altered. Optionally errors if the configuration does not
|
||||
* exist based on the missing_ok flag passed in by the caller.
|
||||
*/
|
||||
ObjectAddress
|
||||
AlterTextSearchConfigurationStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
AlterTSConfigurationStmt *stmt = castNode(AlterTSConfigurationStmt, node);
|
||||
|
||||
Oid objid = get_ts_config_oid(stmt->cfgname, missing_ok);
|
||||
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, TSConfigRelationId, objid);
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AlterTextSearchConfigurationSchemaStmtObjectAddress resolves the ObjectAddress for the
|
||||
* TEXT SEARCH CONFIGURATION being moved to a different schema. Optionally errors if the
|
||||
* configuration does not exist based on the missing_ok flag passed in by the caller.
|
||||
*
|
||||
* This can be called, either before or after the move of schema has been executed, hence
|
||||
* the triple checking before the error might be thrown. Errors for non-existing schema's
|
||||
* in edgecases will be raised by postgres while executing the move.
|
||||
*/
|
||||
ObjectAddress
|
||||
AlterTextSearchConfigurationSchemaStmtObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
Oid objid = get_ts_config_oid(castNode(List, stmt->object), true);
|
||||
|
||||
if (!OidIsValid(objid))
|
||||
{
|
||||
/*
|
||||
* couldn't find the text search configuration, might have already been moved to
|
||||
* the new schema, we construct a new sequence name that uses the new schema to
|
||||
* search in.
|
||||
*/
|
||||
char *schemaname = NULL;
|
||||
char *config_name = NULL;
|
||||
DeconstructQualifiedName(castNode(List, stmt->object), &schemaname, &config_name);
|
||||
|
||||
char *newSchemaName = stmt->newschema;
|
||||
List *names = list_make2(makeString(newSchemaName), makeString(config_name));
|
||||
objid = get_ts_config_oid(names, true);
|
||||
|
||||
if (!missing_ok && !OidIsValid(objid))
|
||||
{
|
||||
/*
|
||||
* if the text search config id is still invalid we couldn't find it, error
|
||||
* with the same message postgres would error with if missing_ok is false
|
||||
* (not ok to miss)
|
||||
*/
|
||||
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_UNDEFINED_OBJECT),
|
||||
errmsg("text search configuration \"%s\" does not exist",
|
||||
NameListToString(castNode(List, stmt->object)))));
|
||||
}
|
||||
}
|
||||
|
||||
ObjectAddress sequenceAddress = { 0 };
|
||||
ObjectAddressSet(sequenceAddress, TSConfigRelationId, objid);
|
||||
return sequenceAddress;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* TextSearchConfigurationCommentObjectAddress resolves the ObjectAddress for the TEXT
|
||||
* SEARCH CONFIGURATION on which the comment is placed. Optionally errors if the
|
||||
* configuration does not exist based on the missing_ok flag passed in by the caller.
|
||||
*/
|
||||
ObjectAddress
|
||||
TextSearchConfigurationCommentObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_TSCONFIGURATION);
|
||||
|
||||
Oid objid = get_ts_config_oid(castNode(List, stmt->object), missing_ok);
|
||||
|
||||
ObjectAddress address = { 0 };
|
||||
ObjectAddressSet(address, TSConfigRelationId, objid);
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AlterTextSearchConfigurationOwnerObjectAddress resolves the ObjectAddress for the TEXT
|
||||
* SEARCH CONFIGURATION for which the owner is changed. Optionally errors if the
|
||||
* configuration does not exist based on the missing_ok flag passed in by the caller.
|
||||
*/
|
||||
ObjectAddress
|
||||
AlterTextSearchConfigurationOwnerObjectAddress(Node *node, bool missing_ok)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
Relation relation = NULL;
|
||||
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
return get_object_address(stmt->objectType, stmt->object, &relation, AccessShareLock,
|
||||
missing_ok);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GenerateBackupNameForTextSearchConfiguration generates a safe name that is not in use
|
||||
* already that can be used to rename an existing TEXT SEARCH CONFIGURATION to allow the
|
||||
* configuration with a specific name to be created, even if this would not have been
|
||||
* possible due to name collisions.
|
||||
*/
|
||||
char *
|
||||
GenerateBackupNameForTextSearchConfiguration(const ObjectAddress *address)
|
||||
{
|
||||
Assert(address->classId == TSConfigRelationId);
|
||||
List *names = get_ts_config_namelist(address->objectId);
|
||||
|
||||
RangeVar *rel = makeRangeVarFromNameList(names);
|
||||
|
||||
char *newName = palloc0(NAMEDATALEN);
|
||||
char suffix[NAMEDATALEN] = { 0 };
|
||||
char *baseName = rel->relname;
|
||||
int baseLength = strlen(baseName);
|
||||
int count = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
int suffixLength = SafeSnprintf(suffix, NAMEDATALEN - 1, "(citus_backup_%d)",
|
||||
count);
|
||||
|
||||
/* trim the base name at the end to leave space for the suffix and trailing \0 */
|
||||
baseLength = Min(baseLength, NAMEDATALEN - suffixLength - 1);
|
||||
|
||||
/* clear newName before copying the potentially trimmed baseName and suffix */
|
||||
memset(newName, 0, NAMEDATALEN);
|
||||
strncpy_s(newName, NAMEDATALEN, baseName, baseLength);
|
||||
strncpy_s(newName + baseLength, NAMEDATALEN - baseLength, suffix,
|
||||
suffixLength);
|
||||
|
||||
|
||||
rel->relname = newName;
|
||||
List *newNameList = MakeNameListFromRangeVar(rel);
|
||||
|
||||
Oid tsconfigOid = get_ts_config_oid(newNameList, true);
|
||||
if (!OidIsValid(tsconfigOid))
|
||||
{
|
||||
return newName;
|
||||
}
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "distributed/commands.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
|
||||
/*
|
||||
* DeparseTreeNode aims to be the inverse of postgres' ParseTreeNode. Currently with
|
||||
|
@ -35,3 +36,20 @@ DeparseTreeNode(Node *stmt)
|
|||
|
||||
return ops->deparse(stmt);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseTreeNodes deparses all stmts in the list from the statement datastructure into
|
||||
* sql statements.
|
||||
*/
|
||||
List *
|
||||
DeparseTreeNodes(List *stmts)
|
||||
{
|
||||
List *sqls = NIL;
|
||||
Node *stmt = NULL;
|
||||
foreach_ptr(stmt, stmts)
|
||||
{
|
||||
sqls = lappend(sqls, DeparseTreeNode(stmt));
|
||||
}
|
||||
return sqls;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,377 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* deparse_text_search.c
|
||||
* All routines to deparse text search statements.
|
||||
* This file contains all entry points specific for text search statement deparsing.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "catalog/namespace.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
#include "distributed/citus_ruleutils.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
|
||||
static void AppendDefElemList(StringInfo buf, List *defelms);
|
||||
|
||||
static void AppendStringInfoTokentypeList(StringInfo buf, List *tokentypes);
|
||||
static void AppendStringInfoDictnames(StringInfo buf, List *dicts);
|
||||
|
||||
|
||||
/*
|
||||
* DeparseCreateTextSearchStmt returns the sql for a DefineStmt defining a TEXT SEARCH
|
||||
* CONFIGURATION
|
||||
*
|
||||
* Although the syntax is mutually exclusive on the two arguments that can be passed in
|
||||
* the deparser will syntactically correct multiple definitions if provided. *
|
||||
*/
|
||||
char *
|
||||
DeparseCreateTextSearchStmt(Node *node)
|
||||
{
|
||||
DefineStmt *stmt = castNode(DefineStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
const char *identifier = NameListToQuotedString(stmt->defnames);
|
||||
appendStringInfo(&buf, "CREATE TEXT SEARCH CONFIGURATION %s ", identifier);
|
||||
appendStringInfoString(&buf, "(");
|
||||
AppendDefElemList(&buf, stmt->definition);
|
||||
appendStringInfoString(&buf, ");");
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendDefElemList specialization to append a comma separated list of definitions to a
|
||||
* define statement.
|
||||
*
|
||||
* Currently only supports String and TypeName entries. Will error on others.
|
||||
*/
|
||||
static void
|
||||
AppendDefElemList(StringInfo buf, List *defelems)
|
||||
{
|
||||
DefElem *defelem = NULL;
|
||||
bool first = true;
|
||||
foreach_ptr(defelem, defelems)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
appendStringInfoString(buf, ", ");
|
||||
}
|
||||
first = false;
|
||||
|
||||
/* extract identifier from defelem */
|
||||
const char *identifier = NULL;
|
||||
switch (nodeTag(defelem->arg))
|
||||
{
|
||||
case T_String:
|
||||
{
|
||||
identifier = quote_identifier(strVal(defelem->arg));
|
||||
break;
|
||||
}
|
||||
|
||||
case T_TypeName:
|
||||
{
|
||||
TypeName *typeName = castNode(TypeName, defelem->arg);
|
||||
identifier = NameListToQuotedString(typeName->names);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
ereport(ERROR, (errmsg("unexpected argument during deparsing of "
|
||||
"TEXT SEARCH CONFIGURATION definition")));
|
||||
}
|
||||
}
|
||||
|
||||
/* stringify */
|
||||
appendStringInfo(buf, "%s = %s", defelem->defname, identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseDropTextSearchConfigurationStmt returns the sql representation for a DROP TEXT
|
||||
* SEARCH CONFIGURATION ... statment. Supports dropping multiple configurations at once.
|
||||
*/
|
||||
char *
|
||||
DeparseDropTextSearchConfigurationStmt(Node *node)
|
||||
{
|
||||
DropStmt *stmt = castNode(DropStmt, node);
|
||||
Assert(stmt->removeType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
appendStringInfoString(&buf, "DROP TEXT SEARCH CONFIGURATION ");
|
||||
List *nameList = NIL;
|
||||
bool first = true;
|
||||
foreach_ptr(nameList, stmt->objects)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
appendStringInfoString(&buf, ", ");
|
||||
}
|
||||
first = false;
|
||||
|
||||
appendStringInfoString(&buf, NameListToQuotedString(nameList));
|
||||
}
|
||||
|
||||
if (stmt->behavior == DROP_CASCADE)
|
||||
{
|
||||
appendStringInfoString(&buf, " CASCADE");
|
||||
}
|
||||
|
||||
appendStringInfoString(&buf, ";");
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseRenameTextSearchConfigurationStmt returns the sql representation of a ALTER TEXT
|
||||
* SEARCH CONFIGURATION ... RENAME TO ... statement.
|
||||
*/
|
||||
char *
|
||||
DeparseRenameTextSearchConfigurationStmt(Node *node)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
Assert(stmt->renameType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
char *identifier = NameListToQuotedString(castNode(List, stmt->object));
|
||||
appendStringInfo(&buf, "ALTER TEXT SEARCH CONFIGURATION %s RENAME TO %s;",
|
||||
identifier, quote_identifier(stmt->newname));
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseAlterTextSearchConfigurationStmt returns the ql representation of any generic
|
||||
* ALTER TEXT SEARCH CONFIGURATION .... statement. The statements supported include:
|
||||
* - ALTER TEXT SEARCH CONFIGURATIONS ... ADD MAPPING FOR [, ...] WITH [, ...]
|
||||
* - ALTER TEXT SEARCH CONFIGURATIONS ... ALTER MAPPING FOR [, ...] WITH [, ...]
|
||||
* - ALTER TEXT SEARCH CONFIGURATIONS ... ALTER MAPPING REPLACE ... WITH ...
|
||||
* - ALTER TEXT SEARCH CONFIGURATIONS ... ALTER MAPPING FOR [, ...] REPLACE ... WITH ...
|
||||
* - ALTER TEXT SEARCH CONFIGURATIONS ... DROP MAPPING [ IF EXISTS ] FOR ...
|
||||
*/
|
||||
char *
|
||||
DeparseAlterTextSearchConfigurationStmt(Node *node)
|
||||
{
|
||||
AlterTSConfigurationStmt *stmt = castNode(AlterTSConfigurationStmt, node);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
char *identifier = NameListToQuotedString(castNode(List, stmt->cfgname));
|
||||
appendStringInfo(&buf, "ALTER TEXT SEARCH CONFIGURATION %s", identifier);
|
||||
|
||||
switch (stmt->kind)
|
||||
{
|
||||
case ALTER_TSCONFIG_ADD_MAPPING:
|
||||
{
|
||||
appendStringInfoString(&buf, " ADD MAPPING FOR ");
|
||||
AppendStringInfoTokentypeList(&buf, stmt->tokentype);
|
||||
|
||||
appendStringInfoString(&buf, " WITH ");
|
||||
AppendStringInfoDictnames(&buf, stmt->dicts);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN:
|
||||
{
|
||||
appendStringInfoString(&buf, " ALTER MAPPING FOR ");
|
||||
AppendStringInfoTokentypeList(&buf, stmt->tokentype);
|
||||
|
||||
appendStringInfoString(&buf, " WITH ");
|
||||
AppendStringInfoDictnames(&buf, stmt->dicts);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ALTER_TSCONFIG_REPLACE_DICT:
|
||||
case ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN:
|
||||
{
|
||||
appendStringInfoString(&buf, " ALTER MAPPING");
|
||||
if (list_length(stmt->tokentype) > 0)
|
||||
{
|
||||
appendStringInfoString(&buf, " FOR ");
|
||||
AppendStringInfoTokentypeList(&buf, stmt->tokentype);
|
||||
}
|
||||
|
||||
if (list_length(stmt->dicts) != 2)
|
||||
{
|
||||
elog(ERROR, "unexpected number of dictionaries while deparsing ALTER "
|
||||
"TEXT SEARCH CONFIGURATION ... ALTER MAPPING [FOR ...] REPLACE "
|
||||
"statement.");
|
||||
}
|
||||
|
||||
appendStringInfo(&buf, " REPLACE %s",
|
||||
NameListToQuotedString(linitial(stmt->dicts)));
|
||||
|
||||
appendStringInfo(&buf, " WITH %s",
|
||||
NameListToQuotedString(lsecond(stmt->dicts)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ALTER_TSCONFIG_DROP_MAPPING:
|
||||
{
|
||||
appendStringInfoString(&buf, " DROP MAPPING");
|
||||
|
||||
if (stmt->missing_ok)
|
||||
{
|
||||
appendStringInfoString(&buf, " IF EXISTS");
|
||||
}
|
||||
|
||||
appendStringInfoString(&buf, " FOR ");
|
||||
AppendStringInfoTokentypeList(&buf, stmt->tokentype);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
elog(ERROR, "unable to deparse unsupported ALTER TEXT SEARCH STATEMENT");
|
||||
}
|
||||
}
|
||||
|
||||
appendStringInfoString(&buf, ";");
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseAlterTextSearchConfigurationSchemaStmt returns the sql statement representing
|
||||
* ALTER TEXT SEARCH CONFIGURATION ... SET SCHEMA ... statements.
|
||||
*/
|
||||
char *
|
||||
DeparseAlterTextSearchConfigurationSchemaStmt(Node *node)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
appendStringInfo(&buf, "ALTER TEXT SEARCH CONFIGURATION %s SET SCHEMA %s;",
|
||||
NameListToQuotedString(castNode(List, stmt->object)),
|
||||
quote_identifier(stmt->newschema));
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseTextSearchConfigurationCommentStmt returns the sql statement representing
|
||||
* COMMENT ON TEXT SEARCH CONFIGURATION ... IS ...
|
||||
*/
|
||||
char *
|
||||
DeparseTextSearchConfigurationCommentStmt(Node *node)
|
||||
{
|
||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_TSCONFIGURATION);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
appendStringInfo(&buf, "COMMENT ON TEXT SEARCH CONFIGURATION %s IS ",
|
||||
NameListToQuotedString(castNode(List, stmt->object)));
|
||||
|
||||
if (stmt->comment == NULL)
|
||||
{
|
||||
appendStringInfoString(&buf, "NULL");
|
||||
}
|
||||
else
|
||||
{
|
||||
appendStringInfoString(&buf, quote_literal_cstr(stmt->comment));
|
||||
}
|
||||
|
||||
appendStringInfoString(&buf, ";");
|
||||
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendStringInfoTokentypeList specializes in adding a comma separated list of
|
||||
* token_tyoe's to TEXT SEARCH CONFIGURATION commands
|
||||
*/
|
||||
static void
|
||||
AppendStringInfoTokentypeList(StringInfo buf, List *tokentypes)
|
||||
{
|
||||
Value *tokentype = NULL;
|
||||
bool first = true;
|
||||
foreach_ptr(tokentype, tokentypes)
|
||||
{
|
||||
if (nodeTag(tokentype) != T_String)
|
||||
{
|
||||
elog(ERROR,
|
||||
"unexpected tokentype for deparsing in text search configuration");
|
||||
}
|
||||
|
||||
if (!first)
|
||||
{
|
||||
appendStringInfoString(buf, ", ");
|
||||
}
|
||||
first = false;
|
||||
|
||||
appendStringInfoString(buf, strVal(tokentype));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* AppendStringInfoDictnames specializes in appending a comma separated list of
|
||||
* dictionaries to TEXT SEARCH CONFIGURATION commands.
|
||||
*/
|
||||
static void
|
||||
AppendStringInfoDictnames(StringInfo buf, List *dicts)
|
||||
{
|
||||
List *dictNames = NIL;
|
||||
bool first = true;
|
||||
foreach_ptr(dictNames, dicts)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
appendStringInfoString(buf, ", ");
|
||||
}
|
||||
first = false;
|
||||
|
||||
char *dictIdentifier = NameListToQuotedString(dictNames);
|
||||
appendStringInfoString(buf, dictIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* DeparseAlterTextSearchConfigurationOwnerStmt returns the sql statement representing
|
||||
* ALTER TEXT SEARCH CONFIGURATION ... ONWER TO ... commands.
|
||||
*/
|
||||
char *
|
||||
DeparseAlterTextSearchConfigurationOwnerStmt(Node *node)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
|
||||
appendStringInfo(&buf, "ALTER TEXT SEARCH CONFIGURATION %s OWNER TO %s;",
|
||||
NameListToQuotedString(castNode(List, stmt->object)),
|
||||
RoleSpecString(stmt->newowner, true));
|
||||
|
||||
return buf.data;
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* qualify_text_search_stmts.c
|
||||
* Functions specialized in fully qualifying all text search statements. These
|
||||
* functions are dispatched from qualify.c
|
||||
*
|
||||
* Fully qualifying text search statements consists of adding the schema name
|
||||
* to the subject of the types as well as any other branch of the parsetree.
|
||||
*
|
||||
* Goal would be that the deparser functions for these statements can
|
||||
* serialize the statement without any external lookups.
|
||||
*
|
||||
* Copyright (c) Citus Data, Inc.
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/htup_details.h"
|
||||
#include "catalog/namespace.h"
|
||||
#include "catalog/pg_ts_config.h"
|
||||
#include "catalog/pg_ts_dict.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
|
||||
static Oid get_ts_config_namespace(Oid tsconfigOid);
|
||||
static Oid get_ts_dict_namespace(Oid tsdictOid);
|
||||
|
||||
|
||||
/*
|
||||
* QualifyDropTextSearchConfigurationStmt adds any missing schema names to text search
|
||||
* configurations being dropped. All configurations are expected to exists before fully
|
||||
* qualifying the statement. Errors will be raised for objects not existing. Non-existing
|
||||
* objects are expected to not be distributed.
|
||||
*/
|
||||
void
|
||||
QualifyDropTextSearchConfigurationStmt(Node *node)
|
||||
{
|
||||
DropStmt *stmt = castNode(DropStmt, node);
|
||||
Assert(stmt->removeType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
List *qualifiedObjects = NIL;
|
||||
List *objName = NIL;
|
||||
|
||||
foreach_ptr(objName, stmt->objects)
|
||||
{
|
||||
char *schemaName = NULL;
|
||||
char *tsconfigName = NULL;
|
||||
DeconstructQualifiedName(objName, &schemaName, &tsconfigName);
|
||||
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid tsconfigOid = get_ts_config_oid(objName, false);
|
||||
Oid namespaceOid = get_ts_config_namespace(tsconfigOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
objName = list_make2(makeString(schemaName),
|
||||
makeString(tsconfigName));
|
||||
}
|
||||
|
||||
qualifiedObjects = lappend(qualifiedObjects, objName);
|
||||
}
|
||||
|
||||
stmt->objects = qualifiedObjects;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyAlterTextSearchConfigurationStmt adds the schema name (if missing) to the name
|
||||
* of the text search configurations, as well as the dictionaries referenced.
|
||||
*/
|
||||
void
|
||||
QualifyAlterTextSearchConfigurationStmt(Node *node)
|
||||
{
|
||||
AlterTSConfigurationStmt *stmt = castNode(AlterTSConfigurationStmt, node);
|
||||
|
||||
char *schemaName = NULL;
|
||||
char *objName = NULL;
|
||||
DeconstructQualifiedName(stmt->cfgname, &schemaName, &objName);
|
||||
|
||||
/* fully qualify the cfgname being altered */
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid tsconfigOid = get_ts_config_oid(stmt->cfgname, false);
|
||||
Oid namespaceOid = get_ts_config_namespace(tsconfigOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
stmt->cfgname = list_make2(makeString(schemaName),
|
||||
makeString(objName));
|
||||
}
|
||||
|
||||
/* fully qualify the dicts */
|
||||
bool useNewDicts = false;
|
||||
List *dicts = NULL;
|
||||
List *dictName = NIL;
|
||||
foreach_ptr(dictName, stmt->dicts)
|
||||
{
|
||||
DeconstructQualifiedName(dictName, &schemaName, &objName);
|
||||
|
||||
/* fully qualify the cfgname being altered */
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid dictOid = get_ts_dict_oid(dictName, false);
|
||||
Oid namespaceOid = get_ts_dict_namespace(dictOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
useNewDicts = true;
|
||||
dictName = list_make2(makeString(schemaName), makeString(objName));
|
||||
}
|
||||
|
||||
dicts = lappend(dicts, dictName);
|
||||
}
|
||||
|
||||
if (useNewDicts)
|
||||
{
|
||||
/* swap original dicts with the new list */
|
||||
stmt->dicts = dicts;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* we don't use the new list, everything was already qualified, free-ing */
|
||||
list_free(dicts);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyRenameTextSearchConfigurationStmt adds the schema name (if missing) to the
|
||||
* configuration being renamed. The new name will kept be without schema name since this
|
||||
* command cannot be used to change the schema of a configuration.
|
||||
*/
|
||||
void
|
||||
QualifyRenameTextSearchConfigurationStmt(Node *node)
|
||||
{
|
||||
RenameStmt *stmt = castNode(RenameStmt, node);
|
||||
Assert(stmt->renameType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
char *schemaName = NULL;
|
||||
char *objName = NULL;
|
||||
DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName);
|
||||
|
||||
/* fully qualify the cfgname being altered */
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid tsconfigOid = get_ts_config_oid(castNode(List, stmt->object), false);
|
||||
Oid namespaceOid = get_ts_config_namespace(tsconfigOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
stmt->object = (Node *) list_make2(makeString(schemaName),
|
||||
makeString(objName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyAlterTextSearchConfigurationSchemaStmt adds the schema name (if missing) for the
|
||||
* text search being moved to a new schema.
|
||||
*/
|
||||
void
|
||||
QualifyAlterTextSearchConfigurationSchemaStmt(Node *node)
|
||||
{
|
||||
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
char *schemaName = NULL;
|
||||
char *objName = NULL;
|
||||
DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName);
|
||||
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid tsconfigOid = get_ts_config_oid(castNode(List, stmt->object), false);
|
||||
Oid namespaceOid = get_ts_config_namespace(tsconfigOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
stmt->object = (Node *) list_make2(makeString(schemaName),
|
||||
makeString(objName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyTextSearchConfigurationCommentStmt adds the schema name (if missing) to the
|
||||
* configuration name on which the comment is created.
|
||||
*/
|
||||
void
|
||||
QualifyTextSearchConfigurationCommentStmt(Node *node)
|
||||
{
|
||||
CommentStmt *stmt = castNode(CommentStmt, node);
|
||||
Assert(stmt->objtype == OBJECT_TSCONFIGURATION);
|
||||
|
||||
char *schemaName = NULL;
|
||||
char *objName = NULL;
|
||||
DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName);
|
||||
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid tsconfigOid = get_ts_config_oid(castNode(List, stmt->object), false);
|
||||
Oid namespaceOid = get_ts_config_namespace(tsconfigOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
stmt->object = (Node *) list_make2(makeString(schemaName),
|
||||
makeString(objName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* QualifyAlterTextSearchConfigurationOwnerStmt adds the schema name (if missing) to the
|
||||
* configuration for which the owner is changing.
|
||||
*/
|
||||
void
|
||||
QualifyAlterTextSearchConfigurationOwnerStmt(Node *node)
|
||||
{
|
||||
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
||||
Assert(stmt->objectType == OBJECT_TSCONFIGURATION);
|
||||
|
||||
char *schemaName = NULL;
|
||||
char *objName = NULL;
|
||||
DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName);
|
||||
|
||||
if (!schemaName)
|
||||
{
|
||||
Oid tsconfigOid = get_ts_config_oid(castNode(List, stmt->object), false);
|
||||
Oid namespaceOid = get_ts_config_namespace(tsconfigOid);
|
||||
schemaName = get_namespace_name(namespaceOid);
|
||||
|
||||
stmt->object = (Node *) list_make2(makeString(schemaName),
|
||||
makeString(objName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_ts_config_namespace returns the oid of the namespace which is housing the text
|
||||
* search configuration identified by tsconfigOid.
|
||||
*/
|
||||
static Oid
|
||||
get_ts_config_namespace(Oid tsconfigOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSCONFIGOID, ObjectIdGetDatum(tsconfigOid));
|
||||
|
||||
if (HeapTupleIsValid(tup))
|
||||
{
|
||||
Form_pg_ts_config cfgform = (Form_pg_ts_config) GETSTRUCT(tup);
|
||||
Oid namespaceOid = cfgform->cfgnamespace;
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
return namespaceOid;
|
||||
}
|
||||
|
||||
return InvalidOid;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* get_ts_dict_namespace returns the oid of the namespace which is housing the text
|
||||
* search dictionary identified by tsdictOid.
|
||||
*/
|
||||
static Oid
|
||||
get_ts_dict_namespace(Oid tsdictOid)
|
||||
{
|
||||
HeapTuple tup = SearchSysCache1(TSDICTOID, ObjectIdGetDatum(tsdictOid));
|
||||
|
||||
if (HeapTupleIsValid(tup))
|
||||
{
|
||||
Form_pg_ts_dict cfgform = (Form_pg_ts_dict) GETSTRUCT(tup);
|
||||
Oid namespaceOid = cfgform->dictnamespace;
|
||||
ReleaseSysCache(tup);
|
||||
|
||||
return namespaceOid;
|
||||
}
|
||||
|
||||
return InvalidOid;
|
||||
}
|
|
@ -770,6 +770,11 @@ GetObjectTypeString(ObjectType objType)
|
|||
return "schema";
|
||||
}
|
||||
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
{
|
||||
return "text search configuration";
|
||||
}
|
||||
|
||||
case OBJECT_TYPE:
|
||||
{
|
||||
return "type";
|
||||
|
|
|
@ -124,6 +124,7 @@ typedef struct ViewDependencyNode
|
|||
static List * GetRelationSequenceDependencyList(Oid relationId);
|
||||
static List * GetRelationTriggerFunctionDependencyList(Oid relationId);
|
||||
static List * GetRelationStatsSchemaDependencyList(Oid relationId);
|
||||
static List * GetRelationIndicesDependencyList(Oid relationId);
|
||||
static DependencyDefinition * CreateObjectAddressDependencyDef(Oid classId, Oid objectId);
|
||||
static List * CreateObjectAddressDependencyDefList(Oid classId, List *objectIdList);
|
||||
static ObjectAddress DependencyDefinitionObjectAddress(DependencyDefinition *definition);
|
||||
|
@ -639,6 +640,11 @@ SupportedDependencyByCitus(const ObjectAddress *address)
|
|||
return true;
|
||||
}
|
||||
|
||||
case OCLASS_TSCONFIG:
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
case OCLASS_TYPE:
|
||||
{
|
||||
switch (get_typtype(address->objectId))
|
||||
|
@ -686,7 +692,8 @@ SupportedDependencyByCitus(const ObjectAddress *address)
|
|||
relKind == RELKIND_RELATION ||
|
||||
relKind == RELKIND_PARTITIONED_TABLE ||
|
||||
relKind == RELKIND_FOREIGN_TABLE ||
|
||||
relKind == RELKIND_SEQUENCE)
|
||||
relKind == RELKIND_SEQUENCE ||
|
||||
relKind == RELKIND_INDEX)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@ -1005,6 +1012,17 @@ ExpandCitusSupportedTypes(ObjectAddressCollector *collector, ObjectAddress targe
|
|||
List *sequenceDependencyList = GetRelationSequenceDependencyList(relationId);
|
||||
|
||||
result = list_concat(result, sequenceDependencyList);
|
||||
|
||||
/*
|
||||
* Tables could have indexes. Indexes themself could have dependencies that
|
||||
* need to be propagated. eg. TEXT SEARCH CONFIGRUATIONS. Here we add the
|
||||
* addresses of all indices to the list of objects to vist, as to make sure we
|
||||
* create all objects required by the indices before we create the table
|
||||
* including indices.
|
||||
*/
|
||||
|
||||
List *indexDependencyList = GetRelationIndicesDependencyList(relationId);
|
||||
result = list_concat(result, indexDependencyList);
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -1048,6 +1066,28 @@ GetRelationStatsSchemaDependencyList(Oid relationId)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* CollectIndexOids implements PGIndexProcessor to create a list of all index oids
|
||||
*/
|
||||
static void
|
||||
CollectIndexOids(Form_pg_index formPgIndex, List **oids, int flags)
|
||||
{
|
||||
*oids = lappend_oid(*oids, formPgIndex->indexrelid);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetRelationIndicesDependencyList creates a list of ObjectAddressDependencies for the
|
||||
* indexes on a given relation.
|
||||
*/
|
||||
static List *
|
||||
GetRelationIndicesDependencyList(Oid relationId)
|
||||
{
|
||||
List *indexIds = ExecuteFunctionOnEachTableIndex(relationId, CollectIndexOids, 0);
|
||||
return CreateObjectAddressDependencyDefList(RelationRelationId, indexIds);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetRelationTriggerFunctionDependencyList returns a list of DependencyDefinition
|
||||
* objects for the functions that triggers of the relation with relationId depends.
|
||||
|
|
|
@ -405,6 +405,21 @@ GetDistributedObjectAddressList(void)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* GetRoleSpecObjectForUser creates a RoleSpec object for the given roleOid.
|
||||
*/
|
||||
RoleSpec *
|
||||
GetRoleSpecObjectForUser(Oid roleOid)
|
||||
{
|
||||
RoleSpec *roleSpec = makeNode(RoleSpec);
|
||||
roleSpec->roletype = OidIsValid(roleOid) ? ROLESPEC_CSTRING : ROLESPEC_PUBLIC;
|
||||
roleSpec->rolename = OidIsValid(roleOid) ? GetUserNameFromId(roleOid, false) : NULL;
|
||||
roleSpec->location = -1;
|
||||
|
||||
return roleSpec;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* UpdateDistributedObjectColocationId gets an old and a new colocationId
|
||||
* and updates the colocationId of all tuples in citus.pg_dist_object which
|
||||
|
|
|
@ -410,6 +410,7 @@ ErrorIfCurrentUserCanNotDistributeObject(ObjectType type, ObjectAddress *addr,
|
|||
case OBJECT_FUNCTION:
|
||||
case OBJECT_PROCEDURE:
|
||||
case OBJECT_AGGREGATE:
|
||||
case OBJECT_TSCONFIGURATION:
|
||||
case OBJECT_TYPE:
|
||||
case OBJECT_FOREIGN_SERVER:
|
||||
case OBJECT_SEQUENCE:
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "udfs/get_global_active_transactions/11.0-1.sql"
|
||||
|
||||
#include "udfs/citus_worker_stat_activity/11.0-1.sql"
|
||||
#include "udfs/worker_create_or_replace_object/11.0-1.sql"
|
||||
|
||||
CREATE VIEW citus.citus_worker_stat_activity AS
|
||||
SELECT * FROM pg_catalog.citus_worker_stat_activity();
|
||||
|
|
|
@ -21,13 +21,7 @@ ALTER FUNCTION citus.restore_isolation_tester_func SET SCHEMA citus_internal;
|
|||
GRANT USAGE ON SCHEMA citus TO public;
|
||||
|
||||
#include "udfs/pg_dist_shard_placement_trigger_func/9.0-1.sql"
|
||||
|
||||
CREATE OR REPLACE FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
RETURNS bool
|
||||
LANGUAGE C STRICT
|
||||
AS 'MODULE_PATHNAME', $$worker_create_or_replace_object$$;
|
||||
COMMENT ON FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
IS 'takes a sql CREATE statement, before executing the create it will check if an object with that name already exists and safely replaces that named object with the new object';
|
||||
#include "udfs/worker_create_or_replace_object/9.0-1.sql"
|
||||
|
||||
CREATE OR REPLACE FUNCTION pg_catalog.master_unmark_object_distributed(classid oid, objid oid, objsubid int)
|
||||
RETURNS void
|
||||
|
|
|
@ -208,4 +208,7 @@ SELECT * FROM pg_catalog.citus_worker_stat_activity();
|
|||
ALTER VIEW citus.citus_worker_stat_activity SET SCHEMA pg_catalog;
|
||||
GRANT SELECT ON pg_catalog.citus_worker_stat_activity TO PUBLIC;
|
||||
|
||||
DROP FUNCTION pg_catalog.worker_create_or_replace_object(text[]);
|
||||
#include "../udfs/worker_create_or_replace_object/9.0-1.sql"
|
||||
|
||||
RESET search_path;
|
||||
|
|
15
src/backend/distributed/sql/udfs/worker_create_or_replace_object/11.0-1.sql
generated
Normal file
15
src/backend/distributed/sql/udfs/worker_create_or_replace_object/11.0-1.sql
generated
Normal file
|
@ -0,0 +1,15 @@
|
|||
CREATE OR REPLACE FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
RETURNS bool
|
||||
LANGUAGE C STRICT
|
||||
AS 'MODULE_PATHNAME', $$worker_create_or_replace_object$$;
|
||||
|
||||
COMMENT ON FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
IS 'takes a sql CREATE statement, before executing the create it will check if an object with that name already exists and safely replaces that named object with the new object';
|
||||
|
||||
CREATE OR REPLACE FUNCTION pg_catalog.worker_create_or_replace_object(statements text[])
|
||||
RETURNS bool
|
||||
LANGUAGE C STRICT
|
||||
AS 'MODULE_PATHNAME', $$worker_create_or_replace_object_array$$;
|
||||
|
||||
COMMENT ON FUNCTION pg_catalog.worker_create_or_replace_object(statements text[])
|
||||
IS 'takes a lost of sql statements, before executing these it will check if the object already exists in that exact state otherwise replaces that named object with the new object';
|
|
@ -0,0 +1,6 @@
|
|||
CREATE OR REPLACE FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
RETURNS bool
|
||||
LANGUAGE C STRICT
|
||||
AS 'MODULE_PATHNAME', $$worker_create_or_replace_object$$;
|
||||
COMMENT ON FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
IS 'takes a sql CREATE statement, before executing the create it will check if an object with that name already exists and safely replaces that named object with the new object';
|
|
@ -0,0 +1,15 @@
|
|||
CREATE OR REPLACE FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
RETURNS bool
|
||||
LANGUAGE C STRICT
|
||||
AS 'MODULE_PATHNAME', $$worker_create_or_replace_object$$;
|
||||
|
||||
COMMENT ON FUNCTION pg_catalog.worker_create_or_replace_object(statement text)
|
||||
IS 'takes a sql CREATE statement, before executing the create it will check if an object with that name already exists and safely replaces that named object with the new object';
|
||||
|
||||
CREATE OR REPLACE FUNCTION pg_catalog.worker_create_or_replace_object(statements text[])
|
||||
RETURNS bool
|
||||
LANGUAGE C STRICT
|
||||
AS 'MODULE_PATHNAME', $$worker_create_or_replace_object_array$$;
|
||||
|
||||
COMMENT ON FUNCTION pg_catalog.worker_create_or_replace_object(statements text[])
|
||||
IS 'takes a lost of sql statements, before executing these it will check if the object already exists in that exact state otherwise replaces that named object with the new object';
|
|
@ -13,8 +13,10 @@
|
|||
#include "catalog/dependency.h"
|
||||
#include "catalog/pg_collation.h"
|
||||
#include "catalog/pg_proc.h"
|
||||
#include "catalog/pg_ts_config.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "fmgr.h"
|
||||
#include "funcapi.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodes.h"
|
||||
#include "parser/parse_type.h"
|
||||
|
@ -28,13 +30,17 @@
|
|||
#include "distributed/commands.h"
|
||||
#include "distributed/commands/utility_hook.h"
|
||||
#include "distributed/deparser.h"
|
||||
#include "distributed/listutils.h"
|
||||
#include "distributed/metadata/distobject.h"
|
||||
#include "distributed/worker_create_or_replace.h"
|
||||
#include "distributed/worker_protocol.h"
|
||||
|
||||
static const char * CreateStmtByObjectAddress(const ObjectAddress *address);
|
||||
static List * CreateStmtListByObjectAddress(const ObjectAddress *address);
|
||||
static bool CompareStringList(List *list1, List *list2);
|
||||
|
||||
PG_FUNCTION_INFO_V1(worker_create_or_replace_object);
|
||||
PG_FUNCTION_INFO_V1(worker_create_or_replace_object_array);
|
||||
static bool WorkerCreateOrReplaceObject(List *sqlStatements);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -51,6 +57,37 @@ WrapCreateOrReplace(const char *sql)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* WrapCreateOrReplaceList takes a list of sql commands and wraps it in a call to citus'
|
||||
* udf to create or replace the existing object based on its create commands.
|
||||
*/
|
||||
char *
|
||||
WrapCreateOrReplaceList(List *sqls)
|
||||
{
|
||||
StringInfoData textArrayLitteral = { 0 };
|
||||
initStringInfo(&textArrayLitteral);
|
||||
|
||||
appendStringInfoString(&textArrayLitteral, "ARRAY[");
|
||||
const char *sql = NULL;
|
||||
bool first = true;
|
||||
foreach_ptr(sql, sqls)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
appendStringInfoString(&textArrayLitteral, ", ");
|
||||
}
|
||||
appendStringInfoString(&textArrayLitteral, quote_literal_cstr(sql));
|
||||
first = false;
|
||||
}
|
||||
appendStringInfoString(&textArrayLitteral, "]::text[]");
|
||||
|
||||
StringInfoData buf = { 0 };
|
||||
initStringInfo(&buf);
|
||||
appendStringInfo(&buf, CREATE_OR_REPLACE_COMMAND, textArrayLitteral.data);
|
||||
return buf.data;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* worker_create_or_replace_object(statement text)
|
||||
*
|
||||
|
@ -73,35 +110,102 @@ Datum
|
|||
worker_create_or_replace_object(PG_FUNCTION_ARGS)
|
||||
{
|
||||
text *sqlStatementText = PG_GETARG_TEXT_P(0);
|
||||
const char *sqlStatement = text_to_cstring(sqlStatementText);
|
||||
Node *parseTree = ParseTreeNode(sqlStatement);
|
||||
char *sqlStatement = text_to_cstring(sqlStatementText);
|
||||
List *sqlStatements = list_make1(sqlStatement);
|
||||
|
||||
PG_RETURN_BOOL(WorkerCreateOrReplaceObject(sqlStatements));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* worker_create_or_replace_object(statements text[])
|
||||
*
|
||||
* function is called, by the coordinator, with a CREATE statement for an object. This
|
||||
* function implements the CREATE ... IF NOT EXISTS functionality for objects that do not
|
||||
* have this functionality or where their implementation is not sufficient.
|
||||
*
|
||||
* Besides checking if an object of said name exists it tries to compare the object to be
|
||||
* created with the one in the local catalog. If there is a difference the one in the local
|
||||
* catalog will be renamed after which the statement can be executed on this worker to
|
||||
* create the object. If more statements are provided, all are compared in order with the
|
||||
* statements generated on the worker. This works assuming a) both citus versions are the
|
||||
* same, b) the objects are exactly the same.
|
||||
*
|
||||
* Renaming has two purposes
|
||||
* - free the identifier for creation
|
||||
* - non destructive if there is data store that would be destroyed if the object was
|
||||
* used in a table on this node, eg. types. If the type would be dropped with a cascade
|
||||
* it would drop any column holding user data for this type.
|
||||
*/
|
||||
Datum
|
||||
worker_create_or_replace_object_array(PG_FUNCTION_ARGS)
|
||||
{
|
||||
List *sqlStatements = NIL;
|
||||
Datum *textArray = NULL;
|
||||
int length = 0;
|
||||
deconstruct_array(PG_GETARG_ARRAYTYPE_P(0), TEXTOID, -1, false, 'i', &textArray,
|
||||
NULL, &length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
sqlStatements = lappend(sqlStatements, TextDatumGetCString(textArray[i]));
|
||||
}
|
||||
|
||||
if (list_length(sqlStatements) < 1)
|
||||
{
|
||||
ereport(ERROR, (errmsg("expected atleast 1 statement to be provided")));
|
||||
}
|
||||
|
||||
PG_RETURN_BOOL(WorkerCreateOrReplaceObject(sqlStatements));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* WorkerCreateOrReplaceObject implements the logic used by both variants of
|
||||
* worker_create_or_replace_object to either create the object or coming to the conclusion
|
||||
* the object already exists in the correct state.
|
||||
*
|
||||
* Returns true if the object has been created, false if it was already in the exact state
|
||||
* it was asked for.
|
||||
*/
|
||||
static bool
|
||||
WorkerCreateOrReplaceObject(List *sqlStatements)
|
||||
{
|
||||
/*
|
||||
* since going to the drop statement might require some resolving we will do a check
|
||||
* if the type actually exists instead of adding the IF EXISTS keyword to the
|
||||
* statement.
|
||||
* To check which object we are changing we find the object address from the first
|
||||
* statement passed into the UDF. Later we will check if all object addresses are the
|
||||
* same.
|
||||
*
|
||||
* Although many of the objects will only have one statement in this call, more
|
||||
* complex objects might come with a list of statements. We assume they all are on the
|
||||
* same subject.
|
||||
*/
|
||||
Node *parseTree = ParseTreeNode(linitial(sqlStatements));
|
||||
ObjectAddress address = GetObjectAddressFromParseTree(parseTree, true);
|
||||
if (ObjectExists(&address))
|
||||
{
|
||||
const char *localSqlStatement = CreateStmtByObjectAddress(&address);
|
||||
/*
|
||||
* Object with name from statement is already found locally, check if states are
|
||||
* identical. If objects differ we will rename the old object (non- destructively)
|
||||
* as to make room to create the new object according to the spec sent.
|
||||
*/
|
||||
|
||||
if (strcmp(sqlStatement, localSqlStatement) == 0)
|
||||
/*
|
||||
* Based on the local catalog we generate the list of commands we would send to
|
||||
* recreate our version of the object. This we can compare to what the coordinator
|
||||
* sent us. If they match we don't do anything.
|
||||
*/
|
||||
List *localSqlStatements = CreateStmtListByObjectAddress(&address);
|
||||
if (CompareStringList(sqlStatements, localSqlStatements))
|
||||
{
|
||||
/*
|
||||
* TODO string compare is a poor man's comparison, but calling equal on the
|
||||
* parsetree's returns false because there is extra information list character
|
||||
* position of some sort
|
||||
*/
|
||||
|
||||
/*
|
||||
* parseTree sent by the coordinator is the same as we would create for our
|
||||
* object, therefore we can omit the create statement locally and not create
|
||||
* the object as it already exists.
|
||||
* statements sent by the coordinator are the same as we would create for our
|
||||
* object, therefore we can omit the statements locally and not create the
|
||||
* object as it already exists in the correct shape.
|
||||
*
|
||||
* We let the coordinator know we didn't create the object.
|
||||
*/
|
||||
PG_RETURN_BOOL(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
char *newName = GenerateBackupNameForCollision(&address);
|
||||
|
@ -113,12 +217,47 @@ worker_create_or_replace_object(PG_FUNCTION_ARGS)
|
|||
NULL, None_Receiver, NULL);
|
||||
}
|
||||
|
||||
/* apply create statement locally */
|
||||
ProcessUtilityParseTree(parseTree, sqlStatement, PROCESS_UTILITY_QUERY, NULL,
|
||||
None_Receiver, NULL);
|
||||
/* apply all statement locally */
|
||||
char *sqlStatement = NULL;
|
||||
foreach_ptr(sqlStatement, sqlStatements)
|
||||
{
|
||||
parseTree = ParseTreeNode(sqlStatement);
|
||||
ProcessUtilityParseTree(parseTree, sqlStatement, PROCESS_UTILITY_QUERY, NULL,
|
||||
None_Receiver, NULL);
|
||||
|
||||
/* TODO verify all statements are about exactly 1 subject, mostly a sanity check
|
||||
* to prevent unintentional use of this UDF, needs to come after the local
|
||||
* execution to be able to actually resolve the ObjectAddress of the newly created
|
||||
* object */
|
||||
}
|
||||
|
||||
/* type has been created */
|
||||
PG_RETURN_BOOL(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static bool
|
||||
CompareStringList(List *list1, List *list2)
|
||||
{
|
||||
if (list_length(list1) != list_length(list2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ListCell *cell1 = NULL;
|
||||
ListCell *cell2 = NULL;
|
||||
forboth(cell1, list1, cell2, list2)
|
||||
{
|
||||
const char *str1 = lfirst(cell1);
|
||||
const char *str2 = lfirst(cell2);
|
||||
|
||||
if (strcmp(str1, str2) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -130,24 +269,38 @@ worker_create_or_replace_object(PG_FUNCTION_ARGS)
|
|||
* therefore you cannot equal this tree against parsed statement. Instead it can be
|
||||
* deparsed to do a string comparison.
|
||||
*/
|
||||
static const char *
|
||||
CreateStmtByObjectAddress(const ObjectAddress *address)
|
||||
static List *
|
||||
CreateStmtListByObjectAddress(const ObjectAddress *address)
|
||||
{
|
||||
switch (getObjectClass(address))
|
||||
{
|
||||
case OCLASS_COLLATION:
|
||||
{
|
||||
return CreateCollationDDL(address->objectId);
|
||||
return list_make1(CreateCollationDDL(address->objectId));
|
||||
}
|
||||
|
||||
case OCLASS_PROC:
|
||||
{
|
||||
return GetFunctionDDLCommand(address->objectId, false);
|
||||
return list_make1(GetFunctionDDLCommand(address->objectId, false));
|
||||
}
|
||||
|
||||
case OCLASS_TSCONFIG:
|
||||
{
|
||||
/*
|
||||
* We do support TEXT SEARCH CONFIGURATION, however, we can't recreate the
|
||||
* object in 1 command. Since the returned text is compared to the create
|
||||
* statement sql we always want the sql to be different compared to the
|
||||
* canonical creation sql we return here, hence we return an empty string, as
|
||||
* that should never match the sql we have passed in for the creation.
|
||||
*/
|
||||
|
||||
List *stmts = GetCreateTextSearchConfigStatements(address);
|
||||
return DeparseTreeNodes(stmts);
|
||||
}
|
||||
|
||||
case OCLASS_TYPE:
|
||||
{
|
||||
return DeparseTreeNode(CreateTypeStmtByObjectAddress(address));
|
||||
return list_make1(DeparseTreeNode(CreateTypeStmtByObjectAddress(address)));
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -179,6 +332,11 @@ GenerateBackupNameForCollision(const ObjectAddress *address)
|
|||
return GenerateBackupNameForProcCollision(address);
|
||||
}
|
||||
|
||||
case OCLASS_TSCONFIG:
|
||||
{
|
||||
return GenerateBackupNameForTextSearchConfiguration(address);
|
||||
}
|
||||
|
||||
case OCLASS_TYPE:
|
||||
{
|
||||
return GenerateBackupNameForTypeCollision(address);
|
||||
|
@ -256,6 +414,25 @@ CreateRenameTypeStmt(const ObjectAddress *address, char *newName)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateRenameTextSearchStmt creates a rename statement for a text search configuration
|
||||
* based on its ObjectAddress. The rename statement will rename the existing object on its
|
||||
* address to the value provided in newName.
|
||||
*/
|
||||
static RenameStmt *
|
||||
CreateRenameTextSearchStmt(const ObjectAddress *address, char *newName)
|
||||
{
|
||||
Assert(address->classId == TSConfigRelationId);
|
||||
RenameStmt *stmt = makeNode(RenameStmt);
|
||||
|
||||
stmt->renameType = OBJECT_TSCONFIGURATION;
|
||||
stmt->object = (Node *) get_ts_config_namelist(address->objectId);
|
||||
stmt->newname = newName;
|
||||
|
||||
return stmt;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* CreateRenameTypeStmt creates a rename statement for a type based on its ObjectAddress.
|
||||
* The rename statement will rename the existing object on its address to the value
|
||||
|
@ -325,6 +502,11 @@ CreateRenameStatement(const ObjectAddress *address, char *newName)
|
|||
return CreateRenameProcStmt(address, newName);
|
||||
}
|
||||
|
||||
case OCLASS_TSCONFIG:
|
||||
{
|
||||
return CreateRenameTextSearchStmt(address, newName);
|
||||
}
|
||||
|
||||
case OCLASS_TYPE:
|
||||
{
|
||||
return CreateRenameTypeStmt(address, newName);
|
||||
|
|
|
@ -465,6 +465,54 @@ extern Oid GetSequenceOid(Oid relationId, AttrNumber attnum);
|
|||
extern bool ConstrTypeUsesIndex(ConstrType constrType);
|
||||
|
||||
|
||||
/* text_search.c - forward declarations */
|
||||
extern List * PostprocessCreateTextSearchConfigurationStmt(Node *node,
|
||||
const char *queryString);
|
||||
extern List * GetCreateTextSearchConfigStatements(const ObjectAddress *address);
|
||||
extern List * CreateTextSearchConfigDDLCommandsIdempotent(const ObjectAddress *address);
|
||||
extern List * PreprocessDropTextSearchConfigurationStmt(Node *node,
|
||||
const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext);
|
||||
extern List * PreprocessAlterTextSearchConfigurationStmt(Node *node,
|
||||
const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext);
|
||||
extern List * PreprocessRenameTextSearchConfigurationStmt(Node *node,
|
||||
const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext);
|
||||
extern List * PreprocessAlterTextSearchConfigurationSchemaStmt(Node *node,
|
||||
const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext);
|
||||
extern List * PostprocessAlterTextSearchConfigurationSchemaStmt(Node *node,
|
||||
const char *queryString);
|
||||
extern List * PreprocessTextSearchConfigurationCommentStmt(Node *node,
|
||||
const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext);
|
||||
extern List * PreprocessAlterTextSearchConfigurationOwnerStmt(Node *node,
|
||||
const char *queryString,
|
||||
ProcessUtilityContext
|
||||
processUtilityContext);
|
||||
extern List * PostprocessAlterTextSearchConfigurationOwnerStmt(Node *node,
|
||||
const char *queryString);
|
||||
extern ObjectAddress CreateTextSearchConfigurationObjectAddress(Node *node,
|
||||
bool missing_ok);
|
||||
extern ObjectAddress RenameTextSearchConfigurationStmtObjectAddress(Node *node,
|
||||
bool missing_ok);
|
||||
extern ObjectAddress AlterTextSearchConfigurationStmtObjectAddress(Node *node,
|
||||
bool missing_ok);
|
||||
extern ObjectAddress AlterTextSearchConfigurationSchemaStmtObjectAddress(Node *node,
|
||||
bool missing_ok);
|
||||
extern ObjectAddress TextSearchConfigurationCommentObjectAddress(Node *node,
|
||||
bool missing_ok);
|
||||
extern ObjectAddress AlterTextSearchConfigurationOwnerObjectAddress(Node *node,
|
||||
bool missing_ok);
|
||||
extern char * GenerateBackupNameForTextSearchConfiguration(const ObjectAddress *address);
|
||||
extern List * get_ts_config_namelist(Oid tsconfigOid);
|
||||
|
||||
/* truncate.c - forward declarations */
|
||||
extern void PreprocessTruncateStatement(TruncateStmt *truncateStatement);
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@ extern void AssertObjectTypeIsFunctional(ObjectType type);
|
|||
|
||||
extern void QualifyTreeNode(Node *stmt);
|
||||
extern char * DeparseTreeNode(Node *stmt);
|
||||
extern List * DeparseTreeNodes(List *stmts);
|
||||
|
||||
/* forward declarations for deparse_attribute_stmts.c */
|
||||
extern char * DeparseRenameAttributeStmt(Node *);
|
||||
|
@ -59,6 +60,15 @@ extern char * DeparseAlterTableStmt(Node *node);
|
|||
|
||||
extern void QualifyAlterTableSchemaStmt(Node *stmt);
|
||||
|
||||
/* foward declarations fro deparse_text_search.c */
|
||||
extern char * DeparseCreateTextSearchStmt(Node *node);
|
||||
extern char * DeparseDropTextSearchConfigurationStmt(Node *node);
|
||||
extern char * DeparseRenameTextSearchConfigurationStmt(Node *node);
|
||||
extern char * DeparseAlterTextSearchConfigurationStmt(Node *node);
|
||||
extern char * DeparseAlterTextSearchConfigurationSchemaStmt(Node *node);
|
||||
extern char * DeparseTextSearchConfigurationCommentStmt(Node *node);
|
||||
extern char * DeparseAlterTextSearchConfigurationOwnerStmt(Node *node);
|
||||
|
||||
/* forward declarations for deparse_schema_stmts.c */
|
||||
extern char * DeparseCreateSchemaStmt(Node *node);
|
||||
extern char * DeparseDropSchemaStmt(Node *node);
|
||||
|
@ -140,6 +150,14 @@ extern char * DeparseAlterExtensionStmt(Node *stmt);
|
|||
/* forward declarations for deparse_database_stmts.c */
|
||||
extern char * DeparseAlterDatabaseOwnerStmt(Node *node);
|
||||
|
||||
/* forward declatations for depatse_text_search_stmts.c */
|
||||
extern void QualifyDropTextSearchConfigurationStmt(Node *node);
|
||||
extern void QualifyAlterTextSearchConfigurationStmt(Node *node);
|
||||
extern void QualifyRenameTextSearchConfigurationStmt(Node *node);
|
||||
extern void QualifyAlterTextSearchConfigurationSchemaStmt(Node *node);
|
||||
extern void QualifyTextSearchConfigurationCommentStmt(Node *node);
|
||||
extern void QualifyAlterTextSearchConfigurationOwnerStmt(Node *node);
|
||||
|
||||
/* forward declarations for deparse_sequence_stmts.c */
|
||||
extern char * DeparseDropSequenceStmt(Node *node);
|
||||
extern char * DeparseRenameSequenceStmt(Node *node);
|
||||
|
|
|
@ -30,8 +30,8 @@ extern bool IsObjectAddressOwnedByExtension(const ObjectAddress *target,
|
|||
ObjectAddress *extensionAddress);
|
||||
extern ObjectAddress PgGetObjectAddress(char *ttype, ArrayType *namearr,
|
||||
ArrayType *argsarr);
|
||||
|
||||
extern List * GetDistributedObjectAddressList(void);
|
||||
extern RoleSpec * GetRoleSpecObjectForUser(Oid roleOid);
|
||||
extern void UpdateDistributedObjectColocationId(uint32 oldColocationId, uint32
|
||||
newColocationId);
|
||||
#endif /* CITUS_METADATA_DISTOBJECT_H */
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#define CREATE_OR_REPLACE_COMMAND "SELECT worker_create_or_replace_object(%s);"
|
||||
|
||||
extern char * WrapCreateOrReplace(const char *sql);
|
||||
extern char * WrapCreateOrReplaceList(List *sqls);
|
||||
extern char * GenerateBackupNameForCollision(const ObjectAddress *address);
|
||||
extern RenameStmt * CreateRenameStatement(const ObjectAddress *address, char *newName);
|
||||
|
||||
|
|
|
@ -1011,9 +1011,10 @@ SELECT * FROM multi_extension.print_extension_changes();
|
|||
| function citus_shard_indexes_on_worker() SETOF record
|
||||
| function citus_shards_on_worker() SETOF record
|
||||
| function create_distributed_function(regprocedure,text,text,boolean) void
|
||||
| function worker_create_or_replace_object(text[]) boolean
|
||||
| function worker_drop_sequence_dependency(text) void
|
||||
| function worker_drop_shell_table(text) void
|
||||
(15 rows)
|
||||
(16 rows)
|
||||
|
||||
DROP TABLE multi_extension.prev_objects, multi_extension.extension_diff;
|
||||
-- show running version
|
||||
|
|
|
@ -0,0 +1,489 @@
|
|||
CREATE SCHEMA text_search;
|
||||
CREATE SCHEMA text_search2;
|
||||
SET search_path TO text_search;
|
||||
-- create a new configruation from scratch
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config ( parser = default );
|
||||
CREATE TABLE t1(id int, name text);
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
DROP TABLE t1;
|
||||
DROP TEXT SEARCH CONFIGURATION my_text_search_config;
|
||||
-- try to create table and index in 1 transaction
|
||||
BEGIN;
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config ( parser = default );
|
||||
CREATE TABLE t1(id int, name text);
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
ABORT;
|
||||
-- try again, should not fail with my_text_search_config being retained on the worker
|
||||
BEGIN;
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config ( parser = default );
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION my_text_search_config IS 'on demand propagation of text search object with a comment';
|
||||
CREATE TABLE t1(id int, name text);
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.my_text_search_config'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | on demand propagation of text search object with a comment
|
||||
localhost | 57638 | t | on demand propagation of text search object with a comment
|
||||
(2 rows)
|
||||
|
||||
-- verify that changing anything on a managed TEXT SEARCH CONFIGURATION fails after parallel execution
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION my_text_search_config IS 'this comment can''t be set right now';
|
||||
ERROR: cannot run text search configuration command because there was a parallel operation on a distributed table in the transaction
|
||||
DETAIL: When running command on/for a distributed text search configuration, Citus needs to perform all operations over a single connection per node to ensure consistency.
|
||||
HINT: Try re-running the transaction with "SET LOCAL citus.multi_shard_modify_mode TO 'sequential';"
|
||||
ABORT;
|
||||
-- create an index on an already distributed table
|
||||
BEGIN;
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config2 ( parser = default );
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION my_text_search_config2 IS 'on demand propagation of text search object with a comment 2';
|
||||
CREATE TABLE t1(id int, name text);
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config2'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.my_text_search_config2'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | on demand propagation of text search object with a comment 2
|
||||
localhost | 57638 | t | on demand propagation of text search object with a comment 2
|
||||
(2 rows)
|
||||
|
||||
ABORT;
|
||||
-- should be able to create a configuration based on a copy of an existing configuration
|
||||
CREATE TEXT SEARCH CONFIGURATION french_noaccent ( COPY = french );
|
||||
CREATE TABLE t2(id int, name text);
|
||||
CREATE INDEX t2_search_name ON t2 USING gin (to_tsvector('text_search.french_noaccent'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t2', 'id');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
-- spot check that french_noaccent copied settings from french
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'comment tu t''appelle') WHERE alias = 'asciiword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciiword,french_stem)
|
||||
localhost | 57638 | t | (asciiword,french_stem)
|
||||
(2 rows)
|
||||
|
||||
-- makes no sense, however we expect that the dictionary for the first token changes accordingly
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ALTER MAPPING FOR asciiword WITH dutch_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'comment tu t''appelle') WHERE alias = 'asciiword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciiword,dutch_stem)
|
||||
localhost | 57638 | t | (asciiword,dutch_stem)
|
||||
(2 rows)
|
||||
|
||||
-- do the same but we will replace all french dictionaries
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciihword,french_stem)
|
||||
localhost | 57638 | t | (asciihword,french_stem)
|
||||
(2 rows)
|
||||
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ALTER MAPPING REPLACE french_stem WITH dutch_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciihword,dutch_stem)
|
||||
localhost | 57638 | t | (asciihword,dutch_stem)
|
||||
(2 rows)
|
||||
|
||||
-- once more but now back via yet a different DDL command
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ALTER MAPPING FOR asciihword REPLACE dutch_stem WITH french_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciihword,french_stem)
|
||||
localhost | 57638 | t | (asciihword,french_stem)
|
||||
(2 rows)
|
||||
|
||||
-- drop a mapping
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent DROP MAPPING FOR asciihword;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciihword,)
|
||||
localhost | 57638 | t | (asciihword,)
|
||||
(2 rows)
|
||||
|
||||
-- also with exists, doesn't change anything, but should not error
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent DROP MAPPING IF EXISTS FOR asciihword;
|
||||
NOTICE: mapping for token type "asciihword" does not exist, skipping
|
||||
-- Comment on a text search configuration
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION french_noaccent IS 'a text configuration that is butcherd to test all edge cases';
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.french_noaccent'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | a text configuration that is butcherd to test all edge cases
|
||||
localhost | 57638 | t | a text configuration that is butcherd to test all edge cases
|
||||
(2 rows)
|
||||
|
||||
-- Remove a comment
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION french_noaccent IS NULL;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.french_noaccent'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t |
|
||||
localhost | 57638 | t |
|
||||
(2 rows)
|
||||
|
||||
-- verify adding 2 dictionaries for two tokes at once
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent DROP MAPPING IF EXISTS FOR asciiword, asciihword;
|
||||
NOTICE: mapping for token type "asciihword" does not exist, skipping
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ADD MAPPING FOR asciiword, asciihword WITH french_stem, dutch_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionaries) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciiword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciiword,"{french_stem,dutch_stem}")
|
||||
localhost | 57638 | t | (asciiword,"{french_stem,dutch_stem}")
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionaries) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | (asciihword,"{french_stem,dutch_stem}")
|
||||
localhost | 57638 | t | (asciihword,"{french_stem,dutch_stem}")
|
||||
(2 rows)
|
||||
|
||||
--verify we can drop cascade a configuration that is in use
|
||||
-- verify it is in use
|
||||
DROP TEXT SEARCH CONFIGURATION text_search.french_noaccent;
|
||||
ERROR: cannot drop text search configuration french_noaccent because other objects depend on it
|
||||
DETAIL: index t2_search_name depends on text search configuration french_noaccent
|
||||
HINT: Use DROP ... CASCADE to drop the dependent objects too.
|
||||
-- drop cascade
|
||||
DROP TEXT SEARCH CONFIGURATION text_search.french_noaccent CASCADE;
|
||||
NOTICE: drop cascades to index t2_search_name
|
||||
-- verify the configuration is dropped from the workers
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.french_noaccent'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | f | ERROR: text search configuration "text_search.french_noaccent" does not exist
|
||||
localhost | 57638 | f | ERROR: text search configuration "text_search.french_noaccent" does not exist
|
||||
(2 rows)
|
||||
|
||||
SET client_min_messages TO 'warning';
|
||||
SELECT * FROM run_command_on_workers($$CREATE ROLE text_search_owner;$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | CREATE ROLE
|
||||
localhost | 57638 | t | CREATE ROLE
|
||||
(2 rows)
|
||||
|
||||
CREATE ROLE text_search_owner;
|
||||
RESET client_min_messages;
|
||||
CREATE TEXT SEARCH CONFIGURATION changed_owner ( PARSER = default );
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT cfgowner::regrole
|
||||
FROM pg_ts_config
|
||||
WHERE oid = 'text_search.changed_owner'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | postgres
|
||||
localhost | 57638 | t | postgres
|
||||
(2 rows)
|
||||
|
||||
ALTER TEXT SEARCH CONFIGURATION changed_owner OWNER TO text_search_owner;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT cfgowner::regrole
|
||||
FROM pg_ts_config
|
||||
WHERE oid = 'text_search.changed_owner'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search_owner
|
||||
localhost | 57638 | t | text_search_owner
|
||||
(2 rows)
|
||||
|
||||
-- redo test with propagating object after it was created and changed of owner
|
||||
SET citus.enable_ddl_propagation TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION changed_owner2 ( PARSER = default );
|
||||
ALTER TEXT SEARCH CONFIGURATION changed_owner2 OWNER TO text_search_owner;
|
||||
RESET citus.enable_ddl_propagation;
|
||||
-- verify object doesn't exist before propagating
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.changed_owner2'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | f | ERROR: text search configuration "text_search.changed_owner2" does not exist
|
||||
localhost | 57638 | f | ERROR: text search configuration "text_search.changed_owner2" does not exist
|
||||
(2 rows)
|
||||
|
||||
-- distribute configuration
|
||||
CREATE TABLE t3(id int, name text);
|
||||
CREATE INDEX t3_search_name ON t3 USING gin (to_tsvector('text_search.changed_owner2'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t3', 'name');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
-- verify config owner
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT cfgowner::regrole
|
||||
FROM pg_ts_config
|
||||
WHERE oid = 'text_search.changed_owner2'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search_owner
|
||||
localhost | 57638 | t | text_search_owner
|
||||
(2 rows)
|
||||
|
||||
-- rename tests
|
||||
CREATE TEXT SEARCH CONFIGURATION change_name ( PARSER = default );
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search.change_name'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search.change_name
|
||||
localhost | 57638 | t | text_search.change_name
|
||||
(2 rows)
|
||||
|
||||
ALTER TEXT SEARCH CONFIGURATION change_name RENAME TO changed_name;
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search.changed_name'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search.changed_name
|
||||
localhost | 57638 | t | text_search.changed_name
|
||||
(2 rows)
|
||||
|
||||
-- test move of schema
|
||||
CREATE TEXT SEARCH CONFIGURATION change_schema ( PARSER = default );
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search.change_schema'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search.change_schema
|
||||
localhost | 57638 | t | text_search.change_schema
|
||||
(2 rows)
|
||||
|
||||
ALTER TEXT SEARCH CONFIGURATION change_schema SET SCHEMA text_search2;
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search2.change_schema'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search2.change_schema
|
||||
localhost | 57638 | t | text_search2.change_schema
|
||||
(2 rows)
|
||||
|
||||
-- verify we get an error that the configuration change_schema is not found, even though the object address will be
|
||||
-- found in its new schema, and is distributed
|
||||
ALTER TEXT SEARCH CONFIGURATION change_schema SET SCHEMA text_search2;
|
||||
ERROR: text search configuration "change_schema" does not exist
|
||||
-- should tell us that text_search.does_not_exist does not exist, covers a complex edgecase
|
||||
-- in resolving the object address
|
||||
ALTER TEXT SEARCH CONFIGURATION text_search.does_not_exist SET SCHEMA text_search2;
|
||||
ERROR: text search configuration "text_search.does_not_exist" does not exist
|
||||
-- verify edgecases in deparsers
|
||||
CREATE TEXT SEARCH CONFIGURATION config1 ( PARSER = default );
|
||||
CREATE TEXT SEARCH CONFIGURATION config2 ( PARSER = default );
|
||||
SET citus.enable_ddl_propagation TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION config3 ( PARSER = default );
|
||||
RESET citus.enable_ddl_propagation;
|
||||
-- verify config1, config2 exist on workers, config3 not
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config1'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search.config1
|
||||
localhost | 57638 | t | text_search.config1
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config2'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search.config2
|
||||
localhost | 57638 | t | text_search.config2
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config3'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | f | ERROR: text search configuration "text_search.config3" does not exist
|
||||
localhost | 57638 | f | ERROR: text search configuration "text_search.config3" does not exist
|
||||
(2 rows)
|
||||
|
||||
-- DROP all config's, only 1&2 are distributed, they should propagate well to remotes
|
||||
DROP TEXT SEARCH CONFIGURATION config1, config2, config3;
|
||||
-- verify all existing ones have been removed (checking config3 for consistency)
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config1'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | f | ERROR: text search configuration "text_search.config1" does not exist
|
||||
localhost | 57638 | f | ERROR: text search configuration "text_search.config1" does not exist
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config2'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | f | ERROR: text search configuration "text_search.config2" does not exist
|
||||
localhost | 57638 | f | ERROR: text search configuration "text_search.config2" does not exist
|
||||
(2 rows)
|
||||
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config3'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | f | ERROR: text search configuration "text_search.config3" does not exist
|
||||
localhost | 57638 | f | ERROR: text search configuration "text_search.config3" does not exist
|
||||
(2 rows)
|
||||
|
||||
-- verify they are all removed locally
|
||||
SELECT 'text_search.config1'::regconfig;
|
||||
ERROR: text search configuration "text_search.config1" does not exist
|
||||
SELECT 'text_search.config2'::regconfig;
|
||||
ERROR: text search configuration "text_search.config2" does not exist
|
||||
SELECT 'text_search.config3'::regconfig;
|
||||
ERROR: text search configuration "text_search.config3" does not exist
|
||||
-- verify that indexes created concurrently that would propagate a TEXT SEARCH CONFIGURATION object
|
||||
SET citus.enable_ddl_propagation TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION concurrent_index_config ( PARSER = default );
|
||||
RESET citus.enable_ddl_propagation;
|
||||
-- verify it doesn't exist on the workers
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.concurrent_index_config'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | f | ERROR: text search configuration "text_search.concurrent_index_config" does not exist
|
||||
localhost | 57638 | f | ERROR: text search configuration "text_search.concurrent_index_config" does not exist
|
||||
(2 rows)
|
||||
|
||||
-- create distributed table that then concurrently would have an index created.
|
||||
CREATE TABLE t4(id int, name text);
|
||||
SELECT create_distributed_table('t4', 'name');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
CREATE INDEX CONCURRENTLY t4_search_name ON t4 USING gin (to_tsvector('text_search.concurrent_index_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
-- now the configuration should be on the worker, and the above index creation shouldn't have failed.
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.concurrent_index_config'::regconfig; $$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | text_search.concurrent_index_config
|
||||
localhost | 57638 | t | text_search.concurrent_index_config
|
||||
(2 rows)
|
||||
|
||||
-- verify the objid is correctly committed locally due to the somewhat convoluted commit and new transaction starting when creating an index concurrently
|
||||
SELECT pg_catalog.pg_identify_object_as_address(classid, objid, objsubid)
|
||||
FROM citus.pg_dist_object
|
||||
WHERE classid = 3602 AND objid = 'text_search.concurrent_index_config'::regconfig::oid;
|
||||
pg_identify_object_as_address
|
||||
---------------------------------------------------------------------
|
||||
("text search configuration","{text_search,concurrent_index_config}",{})
|
||||
(1 row)
|
||||
|
||||
-- verify old text search configurations get renamed if they are not the same as the newly propagated configuration.
|
||||
-- We do this by creating configurations on the workers as a copy from a different existing catalog.
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
set citus.enable_metadata_sync TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_wrongly ( copy = dutch );
|
||||
reset citus.enable_metadata_sync;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | SET
|
||||
localhost | 57638 | t | SET
|
||||
(2 rows)
|
||||
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_wrongly ( copy = french );
|
||||
-- now we expect manually_created_wrongly(citus_backup_XXX) to show up when querying the configurations
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT array_agg(cfgname) FROM pg_ts_config WHERE cfgname LIKE 'manually_created_wrongly%';
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | {manually_created_wrongly(citus_backup_0),manually_created_wrongly}
|
||||
localhost | 57638 | t | {manually_created_wrongly(citus_backup_0),manually_created_wrongly}
|
||||
(2 rows)
|
||||
|
||||
-- verify the objects get reused appropriately when the specification is the same
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
set citus.enable_metadata_sync TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_correct ( copy = french );
|
||||
reset citus.enable_metadata_sync;
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | SET
|
||||
localhost | 57638 | t | SET
|
||||
(2 rows)
|
||||
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_correct ( copy = french );
|
||||
-- now we don't expect manually_created_correct(citus_backup_XXX) to show up when querying the configurations as the
|
||||
-- original one is reused
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT array_agg(cfgname) FROM pg_ts_config WHERE cfgname LIKE 'manually_created_correct%';
|
||||
$$) ORDER BY 1,2;
|
||||
nodename | nodeport | success | result
|
||||
---------------------------------------------------------------------
|
||||
localhost | 57637 | t | {manually_created_correct}
|
||||
localhost | 57638 | t | {manually_created_correct}
|
||||
(2 rows)
|
||||
|
||||
CREATE SCHEMA "Text Search Requiring Quote's";
|
||||
CREATE TEXT SEARCH CONFIGURATION "Text Search Requiring Quote's"."Quoted Config Name" ( parser = default );
|
||||
CREATE TABLE t5(id int, name text);
|
||||
CREATE INDEX t5_search_name ON t5 USING gin (to_tsvector('"Text Search Requiring Quote''s"."Quoted Config Name"'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t5', 'name');
|
||||
create_distributed_table
|
||||
---------------------------------------------------------------------
|
||||
|
||||
(1 row)
|
||||
|
||||
SET client_min_messages TO 'warning';
|
||||
DROP SCHEMA text_search, text_search2, "Text Search Requiring Quote's" CASCADE;
|
||||
DROP ROLE text_search_owner;
|
|
@ -203,6 +203,7 @@ ORDER BY 1;
|
|||
function worker_cleanup_job_schema_cache()
|
||||
function worker_create_or_alter_role(text,text,text)
|
||||
function worker_create_or_replace_object(text)
|
||||
function worker_create_or_replace_object(text[])
|
||||
function worker_create_schema(bigint,text)
|
||||
function worker_create_truncate_trigger(regclass)
|
||||
function worker_drop_distributed_table(text)
|
||||
|
@ -267,5 +268,5 @@ ORDER BY 1;
|
|||
view citus_worker_stat_activity
|
||||
view pg_dist_shard_placement
|
||||
view time_partitions
|
||||
(251 rows)
|
||||
(252 rows)
|
||||
|
||||
|
|
|
@ -313,7 +313,7 @@ test: ssl_by_default
|
|||
# ---------
|
||||
# object distribution tests
|
||||
# ---------
|
||||
test: distributed_types distributed_types_conflict disable_object_propagation distributed_types_xact_add_enum_value
|
||||
test: distributed_types distributed_types_conflict disable_object_propagation distributed_types_xact_add_enum_value text_search
|
||||
test: check_mx
|
||||
test: distributed_functions distributed_functions_conflict
|
||||
test: distributed_collations
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
CREATE SCHEMA text_search;
|
||||
CREATE SCHEMA text_search2;
|
||||
SET search_path TO text_search;
|
||||
|
||||
-- create a new configruation from scratch
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config ( parser = default );
|
||||
CREATE TABLE t1(id int, name text);
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
|
||||
DROP TABLE t1;
|
||||
DROP TEXT SEARCH CONFIGURATION my_text_search_config;
|
||||
|
||||
-- try to create table and index in 1 transaction
|
||||
BEGIN;
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config ( parser = default );
|
||||
CREATE TABLE t1(id int, name text);
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
ABORT;
|
||||
|
||||
-- try again, should not fail with my_text_search_config being retained on the worker
|
||||
BEGIN;
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config ( parser = default );
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION my_text_search_config IS 'on demand propagation of text search object with a comment';
|
||||
CREATE TABLE t1(id int, name text);
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.my_text_search_config'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
-- verify that changing anything on a managed TEXT SEARCH CONFIGURATION fails after parallel execution
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION my_text_search_config IS 'this comment can''t be set right now';
|
||||
ABORT;
|
||||
|
||||
-- create an index on an already distributed table
|
||||
BEGIN;
|
||||
CREATE TEXT SEARCH CONFIGURATION my_text_search_config2 ( parser = default );
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION my_text_search_config2 IS 'on demand propagation of text search object with a comment 2';
|
||||
CREATE TABLE t1(id int, name text);
|
||||
SELECT create_distributed_table('t1', 'name');
|
||||
CREATE INDEX t1_search_name ON t1 USING gin (to_tsvector('text_search.my_text_search_config2'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.my_text_search_config2'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
ABORT;
|
||||
|
||||
-- should be able to create a configuration based on a copy of an existing configuration
|
||||
CREATE TEXT SEARCH CONFIGURATION french_noaccent ( COPY = french );
|
||||
CREATE TABLE t2(id int, name text);
|
||||
CREATE INDEX t2_search_name ON t2 USING gin (to_tsvector('text_search.french_noaccent'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t2', 'id');
|
||||
|
||||
-- spot check that french_noaccent copied settings from french
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'comment tu t''appelle') WHERE alias = 'asciiword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
-- makes no sense, however we expect that the dictionary for the first token changes accordingly
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ALTER MAPPING FOR asciiword WITH dutch_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'comment tu t''appelle') WHERE alias = 'asciiword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
-- do the same but we will replace all french dictionaries
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ALTER MAPPING REPLACE french_stem WITH dutch_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
-- once more but now back via yet a different DDL command
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ALTER MAPPING FOR asciihword REPLACE dutch_stem WITH french_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
-- drop a mapping
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent DROP MAPPING FOR asciihword;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionary) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
-- also with exists, doesn't change anything, but should not error
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent DROP MAPPING IF EXISTS FOR asciihword;
|
||||
|
||||
-- Comment on a text search configuration
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION french_noaccent IS 'a text configuration that is butcherd to test all edge cases';
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.french_noaccent'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
-- Remove a comment
|
||||
COMMENT ON TEXT SEARCH CONFIGURATION french_noaccent IS NULL;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT obj_description('text_search.french_noaccent'::regconfig);
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
-- verify adding 2 dictionaries for two tokes at once
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent DROP MAPPING IF EXISTS FOR asciiword, asciihword;
|
||||
ALTER TEXT SEARCH CONFIGURATION french_noaccent ADD MAPPING FOR asciiword, asciihword WITH french_stem, dutch_stem;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionaries) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciiword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT ROW(alias,dictionaries) FROM ts_debug('text_search.french_noaccent', 'un chou-fleur') WHERE alias = 'asciihword' LIMIT 1;
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
--verify we can drop cascade a configuration that is in use
|
||||
-- verify it is in use
|
||||
DROP TEXT SEARCH CONFIGURATION text_search.french_noaccent;
|
||||
-- drop cascade
|
||||
DROP TEXT SEARCH CONFIGURATION text_search.french_noaccent CASCADE;
|
||||
-- verify the configuration is dropped from the workers
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.french_noaccent'::regconfig; $$) ORDER BY 1,2;
|
||||
|
||||
SET client_min_messages TO 'warning';
|
||||
SELECT * FROM run_command_on_workers($$CREATE ROLE text_search_owner;$$) ORDER BY 1,2;
|
||||
CREATE ROLE text_search_owner;
|
||||
RESET client_min_messages;
|
||||
|
||||
CREATE TEXT SEARCH CONFIGURATION changed_owner ( PARSER = default );
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT cfgowner::regrole
|
||||
FROM pg_ts_config
|
||||
WHERE oid = 'text_search.changed_owner'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
ALTER TEXT SEARCH CONFIGURATION changed_owner OWNER TO text_search_owner;
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT cfgowner::regrole
|
||||
FROM pg_ts_config
|
||||
WHERE oid = 'text_search.changed_owner'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
-- redo test with propagating object after it was created and changed of owner
|
||||
SET citus.enable_ddl_propagation TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION changed_owner2 ( PARSER = default );
|
||||
ALTER TEXT SEARCH CONFIGURATION changed_owner2 OWNER TO text_search_owner;
|
||||
RESET citus.enable_ddl_propagation;
|
||||
-- verify object doesn't exist before propagating
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.changed_owner2'::regconfig; $$) ORDER BY 1,2;
|
||||
|
||||
-- distribute configuration
|
||||
CREATE TABLE t3(id int, name text);
|
||||
CREATE INDEX t3_search_name ON t3 USING gin (to_tsvector('text_search.changed_owner2'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t3', 'name');
|
||||
|
||||
-- verify config owner
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT cfgowner::regrole
|
||||
FROM pg_ts_config
|
||||
WHERE oid = 'text_search.changed_owner2'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
|
||||
-- rename tests
|
||||
CREATE TEXT SEARCH CONFIGURATION change_name ( PARSER = default );
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search.change_name'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
ALTER TEXT SEARCH CONFIGURATION change_name RENAME TO changed_name;
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search.changed_name'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
-- test move of schema
|
||||
CREATE TEXT SEARCH CONFIGURATION change_schema ( PARSER = default );
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search.change_schema'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
ALTER TEXT SEARCH CONFIGURATION change_schema SET SCHEMA text_search2;
|
||||
SELECT * FROM run_command_on_workers($$ -- verify the name exists on the worker
|
||||
SELECT 'text_search2.change_schema'::regconfig;
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
-- verify we get an error that the configuration change_schema is not found, even though the object address will be
|
||||
-- found in its new schema, and is distributed
|
||||
ALTER TEXT SEARCH CONFIGURATION change_schema SET SCHEMA text_search2;
|
||||
-- should tell us that text_search.does_not_exist does not exist, covers a complex edgecase
|
||||
-- in resolving the object address
|
||||
ALTER TEXT SEARCH CONFIGURATION text_search.does_not_exist SET SCHEMA text_search2;
|
||||
|
||||
|
||||
-- verify edgecases in deparsers
|
||||
CREATE TEXT SEARCH CONFIGURATION config1 ( PARSER = default );
|
||||
CREATE TEXT SEARCH CONFIGURATION config2 ( PARSER = default );
|
||||
SET citus.enable_ddl_propagation TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION config3 ( PARSER = default );
|
||||
RESET citus.enable_ddl_propagation;
|
||||
|
||||
-- verify config1, config2 exist on workers, config3 not
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config1'::regconfig; $$) ORDER BY 1,2;
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config2'::regconfig; $$) ORDER BY 1,2;
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config3'::regconfig; $$) ORDER BY 1,2;
|
||||
|
||||
-- DROP all config's, only 1&2 are distributed, they should propagate well to remotes
|
||||
DROP TEXT SEARCH CONFIGURATION config1, config2, config3;
|
||||
|
||||
-- verify all existing ones have been removed (checking config3 for consistency)
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config1'::regconfig; $$) ORDER BY 1,2;
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config2'::regconfig; $$) ORDER BY 1,2;
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.config3'::regconfig; $$) ORDER BY 1,2;
|
||||
-- verify they are all removed locally
|
||||
SELECT 'text_search.config1'::regconfig;
|
||||
SELECT 'text_search.config2'::regconfig;
|
||||
SELECT 'text_search.config3'::regconfig;
|
||||
|
||||
-- verify that indexes created concurrently that would propagate a TEXT SEARCH CONFIGURATION object
|
||||
SET citus.enable_ddl_propagation TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION concurrent_index_config ( PARSER = default );
|
||||
RESET citus.enable_ddl_propagation;
|
||||
|
||||
-- verify it doesn't exist on the workers
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.concurrent_index_config'::regconfig; $$) ORDER BY 1,2;
|
||||
|
||||
-- create distributed table that then concurrently would have an index created.
|
||||
CREATE TABLE t4(id int, name text);
|
||||
SELECT create_distributed_table('t4', 'name');
|
||||
CREATE INDEX CONCURRENTLY t4_search_name ON t4 USING gin (to_tsvector('text_search.concurrent_index_config'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
|
||||
-- now the configuration should be on the worker, and the above index creation shouldn't have failed.
|
||||
SELECT * FROM run_command_on_workers($$ SELECT 'text_search.concurrent_index_config'::regconfig; $$) ORDER BY 1,2;
|
||||
|
||||
-- verify the objid is correctly committed locally due to the somewhat convoluted commit and new transaction starting when creating an index concurrently
|
||||
SELECT pg_catalog.pg_identify_object_as_address(classid, objid, objsubid)
|
||||
FROM citus.pg_dist_object
|
||||
WHERE classid = 3602 AND objid = 'text_search.concurrent_index_config'::regconfig::oid;
|
||||
|
||||
-- verify old text search configurations get renamed if they are not the same as the newly propagated configuration.
|
||||
-- We do this by creating configurations on the workers as a copy from a different existing catalog.
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
set citus.enable_metadata_sync TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_wrongly ( copy = dutch );
|
||||
reset citus.enable_metadata_sync;
|
||||
$$) ORDER BY 1,2;
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_wrongly ( copy = french );
|
||||
|
||||
-- now we expect manually_created_wrongly(citus_backup_XXX) to show up when querying the configurations
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT array_agg(cfgname) FROM pg_ts_config WHERE cfgname LIKE 'manually_created_wrongly%';
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
-- verify the objects get reused appropriately when the specification is the same
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
set citus.enable_metadata_sync TO off;
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_correct ( copy = french );
|
||||
reset citus.enable_metadata_sync;
|
||||
$$) ORDER BY 1,2;
|
||||
CREATE TEXT SEARCH CONFIGURATION text_search.manually_created_correct ( copy = french );
|
||||
|
||||
-- now we don't expect manually_created_correct(citus_backup_XXX) to show up when querying the configurations as the
|
||||
-- original one is reused
|
||||
SELECT * FROM run_command_on_workers($$
|
||||
SELECT array_agg(cfgname) FROM pg_ts_config WHERE cfgname LIKE 'manually_created_correct%';
|
||||
$$) ORDER BY 1,2;
|
||||
|
||||
CREATE SCHEMA "Text Search Requiring Quote's";
|
||||
CREATE TEXT SEARCH CONFIGURATION "Text Search Requiring Quote's"."Quoted Config Name" ( parser = default );
|
||||
CREATE TABLE t5(id int, name text);
|
||||
CREATE INDEX t5_search_name ON t5 USING gin (to_tsvector('"Text Search Requiring Quote''s"."Quoted Config Name"'::regconfig, (COALESCE(name, ''::character varying))::text));
|
||||
SELECT create_distributed_table('t5', 'name');
|
||||
|
||||
SET client_min_messages TO 'warning';
|
||||
DROP SCHEMA text_search, text_search2, "Text Search Requiring Quote's" CASCADE;
|
||||
DROP ROLE text_search_owner;
|
Loading…
Reference in New Issue