mirror of https://github.com/citusdata/citus.git
1594 lines
49 KiB
C
1594 lines
49 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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 "catalog/pg_ts_template.h"
|
|
#include "commands/comment.h"
|
|
#include "commands/defrem.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 List * GetDistributedTextSearchDictionaryNames(DropStmt *stmt);
|
|
static DefineStmt * GetTextSearchConfigDefineStmt(Oid tsconfigOid);
|
|
static DefineStmt * GetTextSearchDictionaryDefineStmt(Oid tsdictOid);
|
|
static List * GetTextSearchDictionaryInitOptions(HeapTuple tup, Form_pg_ts_dict dict);
|
|
static List * GetTextSearchConfigCommentStmt(Oid tsconfigOid);
|
|
static List * GetTextSearchDictionaryCommentStmt(Oid tsconfigOid);
|
|
static List * get_ts_parser_namelist(Oid tsparserOid);
|
|
static List * GetTextSearchConfigMappingStmt(Oid tsconfigOid);
|
|
static List * GetTextSearchConfigOwnerStmts(Oid tsconfigOid);
|
|
static List * GetTextSearchDictionaryOwnerStmts(Oid tsdictOid);
|
|
|
|
static List * get_ts_dict_namelist(Oid tsdictOid);
|
|
static List * get_ts_template_namelist(Oid tstemplateOid);
|
|
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;
|
|
}
|
|
|
|
/* check creation against multi-statement transaction policy */
|
|
if (!ShouldPropagateCreateInCoordinatedTransction())
|
|
{
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PostprocessCreateTextSearchDictionaryStmt is called after the TEXT SEARCH DICTIONARY has been
|
|
* created locally.
|
|
*/
|
|
List *
|
|
PostprocessCreateTextSearchDictionaryStmt(Node *node, const char *queryString)
|
|
{
|
|
DefineStmt *stmt = castNode(DefineStmt, node);
|
|
Assert(stmt->kind == OBJECT_TSDICTIONARY);
|
|
|
|
if (!ShouldPropagate())
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
/* check creation against multi-statement transaction policy */
|
|
if (!ShouldPropagateCreateInCoordinatedTransction())
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
EnsureCoordinator();
|
|
EnsureSequentialMode(OBJECT_TSDICTIONARY);
|
|
|
|
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
|
EnsureDependenciesExistOnAllNodes(&address);
|
|
|
|
QualifyTreeNode(node);
|
|
const char *createTSDictionaryStmtSql = DeparseTreeNode(node);
|
|
|
|
/*
|
|
* To prevent recursive propagation in mx architecture, we disable ddl
|
|
* propagation before sending the command to workers.
|
|
*/
|
|
List *commands = list_make3(DISABLE_DDL_PROPAGATION,
|
|
(void *) createTSDictionaryStmtSql,
|
|
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;
|
|
}
|
|
|
|
|
|
List *
|
|
GetCreateTextSearchDictionaryStatements(const ObjectAddress *address)
|
|
{
|
|
Assert(address->classId == TSDictionaryRelationId);
|
|
List *stmts = NIL;
|
|
|
|
/* CREATE TEXT SEARCH DICTIONARY ...*/
|
|
stmts = lappend(stmts, GetTextSearchDictionaryDefineStmt(address->objectId));
|
|
|
|
/* ALTER TEXT SEARCH DICTIONARY ... OWNER TO ...*/
|
|
stmts = list_concat(stmts, GetTextSearchDictionaryOwnerStmts(address->objectId));
|
|
|
|
/* COMMENT ON TEXT SEARCH DICTIONARY ... */
|
|
stmts = list_concat(stmts, GetTextSearchDictionaryCommentStmt(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));
|
|
}
|
|
|
|
|
|
/*
|
|
* CreateTextSearchDictDDLCommandsIdempotent creates a list of ddl commands to recreate
|
|
* a TEXT SEARCH CONFIGURATION object in an idempotent manner on workers.
|
|
*/
|
|
List *
|
|
CreateTextSearchDictDDLCommandsIdempotent(const ObjectAddress *address)
|
|
{
|
|
List *stmts = GetCreateTextSearchDictionaryStatements(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 configurations 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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessDropTextSearchDictionaryStmt prepares the statements we need to send to
|
|
* the workers. After we have dropped the dictionaries 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 *
|
|
PreprocessDropTextSearchDictionaryStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
DropStmt *stmt = castNode(DropStmt, node);
|
|
Assert(stmt->removeType == OBJECT_TSDICTIONARY);
|
|
|
|
if (!ShouldPropagate())
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
List *distributedObjects = GetDistributedTextSearchDictionaryNames(stmt);
|
|
if (list_length(distributedObjects) == 0)
|
|
{
|
|
/* no distributed objects to remove */
|
|
return NIL;
|
|
}
|
|
|
|
EnsureCoordinator();
|
|
EnsureSequentialMode(OBJECT_TSDICTIONARY);
|
|
|
|
/*
|
|
* 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 containing 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 distributed */
|
|
continue;
|
|
}
|
|
|
|
ObjectAddress address = { 0 };
|
|
ObjectAddressSet(address, TSConfigRelationId, tsconfigOid);
|
|
if (!IsObjectDistributed(&address))
|
|
{
|
|
continue;
|
|
}
|
|
distributedObjects = lappend(distributedObjects, objName);
|
|
}
|
|
return distributedObjects;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetDistributedTextSearchDictionaryNames iterates over all text search dictionaries
|
|
* dropped, and create a list containing all dictionaries that are distributed.
|
|
*/
|
|
static List *
|
|
GetDistributedTextSearchDictionaryNames(DropStmt *stmt)
|
|
{
|
|
List *objName = NULL;
|
|
List *distributedObjects = NIL;
|
|
foreach_ptr(objName, stmt->objects)
|
|
{
|
|
Oid tsdictOid = get_ts_dict_oid(objName, stmt->missing_ok);
|
|
if (!OidIsValid(tsdictOid))
|
|
{
|
|
/* skip missing dictionary names, they can't be distributed */
|
|
continue;
|
|
}
|
|
|
|
ObjectAddress address = { 0 };
|
|
ObjectAddressSet(address, TSDictionaryRelationId, tsdictOid);
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessAlterTextSearchDictionaryStmt verifies if the dictionary 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 *
|
|
PreprocessAlterTextSearchDictionaryStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
AlterTSDictionaryStmt *stmt = castNode(AlterTSDictionaryStmt, node);
|
|
|
|
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
|
if (!ShouldPropagateObject(&address))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
EnsureCoordinator();
|
|
EnsureSequentialMode(OBJECT_TSDICTIONARY);
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessRenameTextSearchDictionaryStmt verifies if the dictionary 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 *
|
|
PreprocessRenameTextSearchDictionaryStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
|
Assert(stmt->renameType == OBJECT_TSDICTIONARY);
|
|
|
|
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
|
if (!ShouldPropagateObject(&address))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
EnsureCoordinator();
|
|
EnsureSequentialMode(OBJECT_TSDICTIONARY);
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessAlterTextSearchDictionarySchemaStmt verifies if the dictionary 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 *
|
|
PreprocessAlterTextSearchDictionarySchemaStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext
|
|
processUtilityContext)
|
|
{
|
|
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
|
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
|
|
|
|
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt,
|
|
stmt->missing_ok);
|
|
if (!ShouldPropagateObject(&address))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
EnsureCoordinator();
|
|
EnsureSequentialMode(OBJECT_TSDICTIONARY);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* PostprocessAlterTextSearchDictionarySchemaStmt 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 dictionary do exist. This
|
|
* is solely to propagate the new schema (and all its dependencies) if it was not already
|
|
* distributed in the cluster.
|
|
*/
|
|
List *
|
|
PostprocessAlterTextSearchDictionarySchemaStmt(Node *node, const char *queryString)
|
|
{
|
|
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
|
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessTextSearchDictionaryCommentStmt propagates any comment on a distributed
|
|
* dictionary to the workers. Since comments for dictionaries are promenently shown
|
|
* when listing all text search dictionaries this is purely a cosmetic thing when
|
|
* running in MX.
|
|
*/
|
|
List *
|
|
PreprocessTextSearchDictionaryCommentStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext processUtilityContext)
|
|
{
|
|
CommentStmt *stmt = castNode(CommentStmt, node);
|
|
Assert(stmt->objtype == OBJECT_TSDICTIONARY);
|
|
|
|
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
|
if (!ShouldPropagateObject(&address))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
EnsureCoordinator();
|
|
EnsureSequentialMode(OBJECT_TSDICTIONARY);
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
/*
|
|
* PreprocessAlterTextSearchDictionaryOwnerStmt verifies if the dictionary 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 *
|
|
PreprocessAlterTextSearchDictionaryOwnerStmt(Node *node, const char *queryString,
|
|
ProcessUtilityContext
|
|
processUtilityContext)
|
|
{
|
|
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
|
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
|
|
|
|
ObjectAddress address = GetObjectAddressFromParseTree((Node *) stmt, false);
|
|
if (!ShouldPropagateObject(&address))
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
EnsureCoordinator();
|
|
EnsureSequentialMode(OBJECT_TSDICTIONARY);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* PostprocessAlterTextSearchDictionaryOwnerStmt 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 dictionary do exist. This
|
|
* is solely to propagate the new owner (and all its dependencies) if it was not already
|
|
* distributed in the cluster.
|
|
*/
|
|
List *
|
|
PostprocessAlterTextSearchDictionaryOwnerStmt(Node *node, const char *queryString)
|
|
{
|
|
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
|
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetTextSearchDictionaryDefineStmt returns the DefineStmt for a TEXT SEARCH DICTIONARY
|
|
* based on the dictionary as defined in the catalog identified by tsdictOid.
|
|
*
|
|
* This statement will contain the template along with all initilaization options.
|
|
*/
|
|
static DefineStmt *
|
|
GetTextSearchDictionaryDefineStmt(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);
|
|
|
|
DefineStmt *stmt = makeNode(DefineStmt);
|
|
stmt->kind = OBJECT_TSDICTIONARY;
|
|
stmt->defnames = get_ts_dict_namelist(tsdictOid);
|
|
stmt->definition = GetTextSearchDictionaryInitOptions(tup, dict);
|
|
|
|
ReleaseSysCache(tup);
|
|
return stmt;
|
|
}
|
|
|
|
|
|
/*
|
|
* GetTextSearchDictionaryInitOptions returns the list of DefElem for the initialization
|
|
* options for a TEXT SEARCH DICTIONARY.
|
|
*
|
|
* The initialization options contain both the template name, and template specific key,
|
|
* value pairs that are supplied when the dictionary was first created.
|
|
*/
|
|
static List *
|
|
GetTextSearchDictionaryInitOptions(HeapTuple tup, Form_pg_ts_dict dict)
|
|
{
|
|
List *templateNameList = get_ts_template_namelist(dict->dicttemplate);
|
|
TypeName *templateTypeName = makeTypeNameFromNameList(templateNameList);
|
|
DefElem *templateDefElem = makeDefElem("template", (Node *) templateTypeName, -1);
|
|
|
|
Relation TSDictionaryRelation = table_open(TSDictionaryRelationId, AccessShareLock);
|
|
TupleDesc TSDictDescription = RelationGetDescr(TSDictionaryRelation);
|
|
bool isnull = false;
|
|
Datum dictinitoption = heap_getattr(tup, Anum_pg_ts_dict_dictinitoption,
|
|
TSDictDescription, &isnull);
|
|
|
|
List *initOptionDefElemList = NIL;
|
|
if (!isnull)
|
|
{
|
|
initOptionDefElemList = deserialize_deflist(dictinitoption);
|
|
}
|
|
|
|
table_close(TSDictionaryRelation, AccessShareLock);
|
|
|
|
return lcons(templateDefElem, initOptionDefElemList);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
|
|
/*
|
|
* GetTextSearchDictionaryCommentStmt returns a list containing all entries to recreate a
|
|
* comment on the dictionary identified by tsconfigOid. The list could be empty if
|
|
* there is no comment on a dictionary.
|
|
*
|
|
* The reason for a list is for easy use when building a list of all statements to invoke
|
|
* to recreate the text search dictionary. An empty list can easily be concatinated
|
|
* without inspection, contrary to a NULL ptr if we would return the CommentStmt struct.
|
|
*/
|
|
static List *
|
|
GetTextSearchDictionaryCommentStmt(Oid tsdictOid)
|
|
{
|
|
char *comment = GetComment(tsdictOid, TSDictionaryRelationId, 0);
|
|
if (!comment)
|
|
{
|
|
return NIL;
|
|
}
|
|
|
|
CommentStmt *stmt = makeNode(CommentStmt);
|
|
stmt->objtype = OBJECT_TSDICTIONARY;
|
|
|
|
stmt->object = (Node *) get_ts_dict_namelist(tsdictOid);
|
|
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 convenience 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);
|
|
}
|
|
|
|
|
|
/*
|
|
* GetTextSearchDictionaryOwnerStmts returns a potentially empty list of statements to change
|
|
* the ownership of a TEXT SEARCH DICTIONARY object.
|
|
*
|
|
* The list is for convenience when building a full list of statements to recreate the
|
|
* dictionary.
|
|
*/
|
|
static List *
|
|
GetTextSearchDictionaryOwnerStmts(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);
|
|
|
|
AlterOwnerStmt *stmt = makeNode(AlterOwnerStmt);
|
|
stmt->objectType = OBJECT_TSDICTIONARY;
|
|
stmt->object = (Node *) get_ts_dict_namelist(tsdictOid);
|
|
stmt->newowner = GetRoleSpecObjectForUser(dict->dictowner);
|
|
|
|
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_template_namelist based on the tstemplateOid this function creates the namelist
|
|
* that identifies the template in a fully qualified manner, irregardless of the schema
|
|
* existing on the search_path.
|
|
*/
|
|
static List *
|
|
get_ts_template_namelist(Oid tstemplateOid)
|
|
{
|
|
HeapTuple tup = SearchSysCache1(TSTEMPLATEOID, ObjectIdGetDatum(tstemplateOid));
|
|
if (!HeapTupleIsValid(tup)) /* should not happen */
|
|
{
|
|
elog(ERROR, "cache lookup failed for text search template %u", tstemplateOid);
|
|
}
|
|
Form_pg_ts_template template = (Form_pg_ts_template) GETSTRUCT(tup);
|
|
|
|
char *schema = get_namespace_name(template->tmplnamespace);
|
|
char *templateName = pstrdup(NameStr(template->tmplname));
|
|
List *names = list_make2(makeString(schema), makeString(templateName));
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
* CreateTextSearchDictObjectAddress resolves the ObjectAddress for the object
|
|
* being created. If missing_pk is false the function will error, explaining to the user
|
|
* the text search dictionary described in the statement doesn't exist.
|
|
*/
|
|
ObjectAddress
|
|
CreateTextSearchDictObjectAddress(Node *node, bool missing_ok)
|
|
{
|
|
DefineStmt *stmt = castNode(DefineStmt, node);
|
|
Assert(stmt->kind == OBJECT_TSDICTIONARY);
|
|
|
|
Oid objid = get_ts_dict_oid(stmt->defnames, missing_ok);
|
|
|
|
ObjectAddress address = { 0 };
|
|
ObjectAddressSet(address, TSDictionaryRelationId, 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* RenameTextSearchDictionaryStmtObjectAddress resolves the ObjectAddress for the TEXT
|
|
* SEARCH DICTIONARY being renamed. Optionally errors if the dictionary does not
|
|
* exist based on the missing_ok flag passed in by the caller.
|
|
*/
|
|
ObjectAddress
|
|
RenameTextSearchDictionaryStmtObjectAddress(Node *node, bool missing_ok)
|
|
{
|
|
RenameStmt *stmt = castNode(RenameStmt, node);
|
|
Assert(stmt->renameType == OBJECT_TSDICTIONARY);
|
|
|
|
Oid objid = get_ts_dict_oid(castNode(List, stmt->object), missing_ok);
|
|
|
|
ObjectAddress address = { 0 };
|
|
ObjectAddressSet(address, TSDictionaryRelationId, 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterTextSearchDictionaryStmtObjectAddress 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
|
|
AlterTextSearchDictionaryStmtObjectAddress(Node *node, bool missing_ok)
|
|
{
|
|
AlterTSDictionaryStmt *stmt = castNode(AlterTSDictionaryStmt, node);
|
|
|
|
Oid objid = get_ts_dict_oid(stmt->dictname, missing_ok);
|
|
|
|
ObjectAddress address = { 0 };
|
|
ObjectAddressSet(address, TSDictionaryRelationId, 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterTextSearchDictionarySchemaStmtObjectAddress resolves the ObjectAddress for the
|
|
* TEXT SEARCH DICTIONARY being moved to a different schema. Optionally errors if the
|
|
* dictionary 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
|
|
AlterTextSearchDictionarySchemaStmtObjectAddress(Node *node, bool missing_ok)
|
|
{
|
|
AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node);
|
|
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
|
|
|
|
Oid objid = get_ts_dict_oid(castNode(List, stmt->object), true);
|
|
|
|
if (!OidIsValid(objid))
|
|
{
|
|
/*
|
|
* couldn't find the text search dictionary, 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 *dict_name = NULL;
|
|
DeconstructQualifiedName(castNode(List, stmt->object), &schemaname, &dict_name);
|
|
|
|
char *newSchemaName = stmt->newschema;
|
|
List *names = list_make2(makeString(newSchemaName), makeString(dict_name));
|
|
objid = get_ts_dict_oid(names, true);
|
|
|
|
if (!missing_ok && !OidIsValid(objid))
|
|
{
|
|
/*
|
|
* if the text search dict 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 dictionary \"%s\" does not exist",
|
|
NameListToString(castNode(List, stmt->object)))));
|
|
}
|
|
}
|
|
|
|
ObjectAddress sequenceAddress = { 0 };
|
|
ObjectAddressSet(sequenceAddress, TSDictionaryRelationId, 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;
|
|
}
|
|
|
|
|
|
/*
|
|
* TextSearchDictCommentObjectAddress resolves the ObjectAddress for the TEXT SEARCH
|
|
* DICTIONARY on which the comment is placed. Optionally errors if the dictionary does not
|
|
* exist based on the missing_ok flag passed in by the caller.
|
|
*/
|
|
ObjectAddress
|
|
TextSearchDictCommentObjectAddress(Node *node, bool missing_ok)
|
|
{
|
|
CommentStmt *stmt = castNode(CommentStmt, node);
|
|
Assert(stmt->objtype == OBJECT_TSDICTIONARY);
|
|
|
|
Oid objid = get_ts_dict_oid(castNode(List, stmt->object), missing_ok);
|
|
|
|
ObjectAddress address = { 0 };
|
|
ObjectAddressSet(address, TSDictionaryRelationId, 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);
|
|
}
|
|
|
|
|
|
/*
|
|
* AlterTextSearchDictOwnerObjectAddress resolves the ObjectAddress for the TEXT
|
|
* SEARCH DICTIONARY 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
|
|
AlterTextSearchDictOwnerObjectAddress(Node *node, bool missing_ok)
|
|
{
|
|
AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node);
|
|
Relation relation = NULL;
|
|
|
|
Assert(stmt->objectType == OBJECT_TSDICTIONARY);
|
|
|
|
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++;
|
|
}
|
|
}
|