diff --git a/src/backend/distributed/commands/dependencies.c b/src/backend/distributed/commands/dependencies.c index 01f561b65..bac90f7be 100644 --- a/src/backend/distributed/commands/dependencies.c +++ b/src/backend/distributed/commands/dependencies.c @@ -398,6 +398,11 @@ GetDependencyCreateDDLCommands(const ObjectAddress *dependency) return CreateTextSearchConfigDDLCommandsIdempotent(dependency); } + case OCLASS_TSDICT: + { + return CreateTextSearchDictDDLCommandsIdempotent(dependency); + } + case OCLASS_TYPE: { return CreateTypeDDLCommandsIdempotent(dependency); diff --git a/src/backend/distributed/commands/distribute_object_ops.c b/src/backend/distributed/commands/distribute_object_ops.c index 8b5fc3b96..ce4f70f7d 100644 --- a/src/backend/distributed/commands/distribute_object_ops.c +++ b/src/backend/distributed/commands/distribute_object_ops.c @@ -538,7 +538,7 @@ static DistributeObjectOps TextSearchConfig_Comment = { .markDistributed = false, }; static DistributeObjectOps TextSearchConfig_Define = { - .deparse = DeparseCreateTextSearchStmt, + .deparse = DeparseCreateTextSearchConfigurationStmt, .qualify = NULL, .preprocess = NULL, .postprocess = PostprocessCreateTextSearchConfigurationStmt, @@ -561,6 +561,62 @@ static DistributeObjectOps TextSearchConfig_Rename = { .address = RenameTextSearchConfigurationStmtObjectAddress, .markDistributed = false, }; +static DistributeObjectOps TextSearchDict_Alter = { + .deparse = DeparseAlterTextSearchDictionaryStmt, + .qualify = QualifyAlterTextSearchDictionaryStmt, + .preprocess = PreprocessAlterTextSearchDictionaryStmt, + .postprocess = NULL, + .address = AlterTextSearchDictionaryStmtObjectAddress, + .markDistributed = false, +}; +static DistributeObjectOps TextSearchDict_AlterObjectSchema = { + .deparse = DeparseAlterTextSearchDictionarySchemaStmt, + .qualify = QualifyAlterTextSearchDictionarySchemaStmt, + .preprocess = PreprocessAlterTextSearchDictionarySchemaStmt, + .postprocess = PostprocessAlterTextSearchDictionarySchemaStmt, + .address = AlterTextSearchDictionarySchemaStmtObjectAddress, + .markDistributed = false, +}; +static DistributeObjectOps TextSearchDict_AlterOwner = { + .deparse = DeparseAlterTextSearchDictionaryOwnerStmt, + .qualify = QualifyAlterTextSearchDictionaryOwnerStmt, + .preprocess = PreprocessAlterTextSearchDictionaryOwnerStmt, + .postprocess = PostprocessAlterTextSearchDictionaryOwnerStmt, + .address = AlterTextSearchDictOwnerObjectAddress, + .markDistributed = false, +}; +static DistributeObjectOps TextSearchDict_Comment = { + .deparse = DeparseTextSearchDictionaryCommentStmt, + .qualify = QualifyTextSearchDictionaryCommentStmt, + .preprocess = PreprocessTextSearchDictionaryCommentStmt, + .postprocess = NULL, + .address = TextSearchDictCommentObjectAddress, + .markDistributed = false, +}; +static DistributeObjectOps TextSearchDict_Define = { + .deparse = DeparseCreateTextSearchDictionaryStmt, + .qualify = NULL, + .preprocess = NULL, + .postprocess = PostprocessCreateTextSearchDictionaryStmt, + .address = CreateTextSearchDictObjectAddress, + .markDistributed = true, +}; +static DistributeObjectOps TextSearchDict_Drop = { + .deparse = DeparseDropTextSearchDictionaryStmt, + .qualify = QualifyDropTextSearchDictionaryStmt, + .preprocess = PreprocessDropTextSearchDictionaryStmt, + .postprocess = NULL, + .address = NULL, + .markDistributed = false, +}; +static DistributeObjectOps TextSearchDict_Rename = { + .deparse = DeparseRenameTextSearchDictionaryStmt, + .qualify = QualifyRenameTextSearchDictionaryStmt, + .preprocess = PreprocessRenameTextSearchDictionaryStmt, + .postprocess = NULL, + .address = RenameTextSearchDictionaryStmtObjectAddress, + .markDistributed = false, +}; static DistributeObjectOps Trigger_AlterObjectDepends = { .deparse = NULL, .qualify = NULL, @@ -872,6 +928,11 @@ GetDistributeObjectOps(Node *node) return &TextSearchConfig_AlterObjectSchema; } + case OBJECT_TSDICTIONARY: + { + return &TextSearchDict_AlterObjectSchema; + } + case OBJECT_TYPE: { return &Type_AlterObjectSchema; @@ -934,6 +995,11 @@ GetDistributeObjectOps(Node *node) return &TextSearchConfig_AlterOwner; } + case OBJECT_TSDICTIONARY: + { + return &TextSearchDict_AlterOwner; + } + case OBJECT_TYPE: { return &Type_AlterOwner; @@ -1020,6 +1086,11 @@ GetDistributeObjectOps(Node *node) return &TextSearchConfig_Alter; } + case T_AlterTSDictionaryStmt: + { + return &TextSearchDict_Alter; + } + case T_ClusterStmt: { return &Any_Cluster; @@ -1035,6 +1106,11 @@ GetDistributeObjectOps(Node *node) return &TextSearchConfig_Comment; } + case OBJECT_TSDICTIONARY: + { + return &TextSearchDict_Comment; + } + default: { return &NoDistributeOps; @@ -1107,6 +1183,11 @@ GetDistributeObjectOps(Node *node) return &TextSearchConfig_Define; } + case OBJECT_TSDICTIONARY: + { + return &TextSearchDict_Define; + } + default: { return &NoDistributeOps; @@ -1189,6 +1270,11 @@ GetDistributeObjectOps(Node *node) return &TextSearchConfig_Drop; } + case OBJECT_TSDICTIONARY: + { + return &TextSearchDict_Drop; + } + case OBJECT_TYPE: { return &Type_Drop; @@ -1293,6 +1379,11 @@ GetDistributeObjectOps(Node *node) return &TextSearchConfig_Rename; } + case OBJECT_TSDICTIONARY: + { + return &TextSearchDict_Rename; + } + case OBJECT_TYPE: { return &Type_Rename; diff --git a/src/backend/distributed/commands/text_search.c b/src/backend/distributed/commands/text_search.c index 53080c42b..f8f031c27 100644 --- a/src/backend/distributed/commands/text_search.c +++ b/src/backend/distributed/commands/text_search.c @@ -18,7 +18,9 @@ #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" @@ -40,13 +42,19 @@ 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); @@ -99,6 +107,48 @@ PostprocessCreateTextSearchConfigurationStmt(Node *node, const char *queryString } +/* + * 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) { @@ -122,6 +172,25 @@ GetCreateTextSearchConfigStatements(const ObjectAddress *address) } +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. @@ -135,9 +204,22 @@ CreateTextSearchConfigDDLCommandsIdempotent(const ObjectAddress *address) } +/* + * 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 schema's locally they also got removed from + * 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. */ @@ -186,9 +268,60 @@ PreprocessDropTextSearchConfigurationStmt(Node *node, const char *queryString, } +/* + * 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 containign all configurations that are distributed. + * dropped, and create a list containing all configurations that are distributed. */ static List * GetDistributedTextSearchConfigurationNames(DropStmt *stmt) @@ -200,7 +333,7 @@ GetDistributedTextSearchConfigurationNames(DropStmt *stmt) Oid tsconfigOid = get_ts_config_oid(objName, stmt->missing_ok); if (!OidIsValid(tsconfigOid)) { - /* skip missing configuration names, they can't be dirstibuted */ + /* skip missing configuration names, they can't be distributed */ continue; } @@ -216,6 +349,36 @@ GetDistributedTextSearchConfigurationNames(DropStmt *stmt) } +/* + * 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 @@ -247,6 +410,37 @@ PreprocessAlterTextSearchConfigurationStmt(Node *node, const char *queryString, } +/* + * 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 @@ -280,6 +474,39 @@ PreprocessRenameTextSearchConfigurationStmt(Node *node, const char *queryString, } +/* + * 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 @@ -314,6 +541,40 @@ PreprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *querySt } +/* + * 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 @@ -341,6 +602,33 @@ PostprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *queryS } +/* + * 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 @@ -374,6 +662,39 @@ PreprocessTextSearchConfigurationCommentStmt(Node *node, const char *queryString } +/* + * 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 @@ -407,6 +728,39 @@ PreprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *queryStr } +/* + * 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 @@ -433,6 +787,32 @@ PostprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *querySt } +/* + * 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. @@ -465,6 +845,65 @@ GetTextSearchConfigDefineStmt(Oid tsconfigOid) } +/* + * 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 @@ -492,6 +931,33 @@ GetTextSearchConfigCommentStmt(Oid tsconfigOid) } +/* + * 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. @@ -581,7 +1047,7 @@ GetTextSearchConfigMappingStmt(Oid tsconfigOid) * 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 + * The list is for convenience when building a full list of statements to recreate the * configuration. */ static List * @@ -605,6 +1071,34 @@ GetTextSearchConfigOwnerStmts(Oid tsconfigOid) } +/* + * 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 @@ -654,6 +1148,30 @@ get_ts_dict_namelist(Oid tsdictOid) } +/* + * 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. @@ -753,6 +1271,25 @@ CreateTextSearchConfigurationObjectAddress(Node *node, bool missing_ok) } +/* + * 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 @@ -772,6 +1309,25 @@ RenameTextSearchConfigurationStmtObjectAddress(Node *node, bool missing_ok) } +/* + * 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 @@ -790,6 +1346,24 @@ AlterTextSearchConfigurationStmtObjectAddress(Node *node, bool missing_ok) } +/* + * 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 @@ -843,6 +1417,59 @@ AlterTextSearchConfigurationSchemaStmtObjectAddress(Node *node, bool missing_ok) } +/* + * 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 @@ -862,6 +1489,25 @@ TextSearchConfigurationCommentObjectAddress(Node *node, bool missing_ok) } +/* + * 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 @@ -880,6 +1526,24 @@ AlterTextSearchConfigurationOwnerObjectAddress(Node *node, bool 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 diff --git a/src/backend/distributed/deparser/deparse_text_search.c b/src/backend/distributed/deparser/deparse_text_search.c index e1ac44f5a..aaa29163b 100644 --- a/src/backend/distributed/deparser/deparse_text_search.c +++ b/src/backend/distributed/deparser/deparse_text_search.c @@ -18,21 +18,21 @@ #include "distributed/deparser.h" #include "distributed/listutils.h" -static void AppendDefElemList(StringInfo buf, List *defelms); +static void AppendDefElemList(StringInfo buf, List *defelems, char *objectName); 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 + * DeparseCreateTextSearchConfigurationStmt 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) +DeparseCreateTextSearchConfigurationStmt(Node *node) { DefineStmt *stmt = castNode(DefineStmt, node); @@ -42,7 +42,7 @@ DeparseCreateTextSearchStmt(Node *node) const char *identifier = NameListToQuotedString(stmt->defnames); appendStringInfo(&buf, "CREATE TEXT SEARCH CONFIGURATION %s ", identifier); appendStringInfoString(&buf, "("); - AppendDefElemList(&buf, stmt->definition); + AppendDefElemList(&buf, stmt->definition, "CONFIGURATION"); appendStringInfoString(&buf, ");"); return buf.data; @@ -50,13 +50,40 @@ DeparseCreateTextSearchStmt(Node *node) /* - * AppendDefElemList specialization to append a comma separated list of definitions to a + * DeparseCreateTextSearchDictionaryStmt returns the sql for a DefineStmt defining a + * TEXT SEARCH DICTIONARY + * + * 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 * +DeparseCreateTextSearchDictionaryStmt(Node *node) +{ + DefineStmt *stmt = castNode(DefineStmt, node); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + const char *identifier = NameListToQuotedString(stmt->defnames); + appendStringInfo(&buf, "CREATE TEXT SEARCH DICTIONARY %s ", identifier); + appendStringInfoString(&buf, "("); + AppendDefElemList(&buf, stmt->definition, "DICTIONARY"); + appendStringInfoString(&buf, ");"); + + return buf.data; +} + + +/* + * AppendDefElemList is a helper to append a comma separated list of definitions to a * define statement. * * Currently only supports String and TypeName entries. Will error on others. + * + * The extra objectName parameter is used to create meaningful error messages. */ static void -AppendDefElemList(StringInfo buf, List *defelems) +AppendDefElemList(StringInfo buf, List *defelems, char *objectName) { DefElem *defelem = NULL; bool first = true; @@ -68,6 +95,20 @@ AppendDefElemList(StringInfo buf, List *defelems) } first = false; + /* + * There are some operations that can omit the argument. In that case, we only use + * the defname. + * + * For example, omitting [ = value ] in the next query results in resetting the + * option to defaults: + * ALTER TEXT SEARCH DICTIONARY name ( option [ = value ] ); + */ + if (defelem->arg == NULL) + { + appendStringInfo(buf, "%s", defelem->defname); + continue; + } + /* extract identifier from defelem */ const char *identifier = NULL; switch (nodeTag(defelem->arg)) @@ -88,7 +129,7 @@ AppendDefElemList(StringInfo buf, List *defelems) default: { ereport(ERROR, (errmsg("unexpected argument during deparsing of " - "TEXT SEARCH CONFIGURATION definition"))); + "TEXT SEARCH %s definition", objectName))); } } @@ -136,6 +177,44 @@ DeparseDropTextSearchConfigurationStmt(Node *node) } +/* + * DeparseDropTextSearchDictionaryStmt returns the sql representation for a DROP TEXT SEARCH + * DICTIONARY ... statment. Supports dropping multiple dictionaries at once. + */ +char * +DeparseDropTextSearchDictionaryStmt(Node *node) +{ + DropStmt *stmt = castNode(DropStmt, node); + Assert(stmt->removeType == OBJECT_TSDICTIONARY); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + appendStringInfoString(&buf, "DROP TEXT SEARCH DICTIONARY "); + 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. @@ -158,7 +237,28 @@ DeparseRenameTextSearchConfigurationStmt(Node *node) /* - * DeparseAlterTextSearchConfigurationStmt returns the ql representation of any generic + * DeparseRenameTextSearchDictionaryStmt returns the sql representation of a ALTER TEXT SEARCH + * DICTIONARY ... RENAME TO ... statement. + */ +char * +DeparseRenameTextSearchDictionaryStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + Assert(stmt->renameType == OBJECT_TSDICTIONARY); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + char *identifier = NameListToQuotedString(castNode(List, stmt->object)); + appendStringInfo(&buf, "ALTER TEXT SEARCH DICTIONARY %s RENAME TO %s;", + identifier, quote_identifier(stmt->newname)); + + return buf.data; +} + + +/* + * DeparseAlterTextSearchConfigurationStmt returns the sql 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 [, ...] @@ -253,6 +353,28 @@ DeparseAlterTextSearchConfigurationStmt(Node *node) } +/* + * DeparseAlterTextSearchConfigurationStmt returns the sql representation of any generic + * ALTER TEXT SEARCH DICTIONARY .... statement. The statements supported include + * - ALTER TEXT SEARCH DICTIONARY name ( option [ = value ] [, ... ] ) + */ +char * +DeparseAlterTextSearchDictionaryStmt(Node *node) +{ + AlterTSDictionaryStmt *stmt = castNode(AlterTSDictionaryStmt, node); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + char *identifier = NameListToQuotedString(castNode(List, stmt->dictname)); + appendStringInfo(&buf, "ALTER TEXT SEARCH DICTIONARY %s ( ", identifier); + + AppendDefElemList(&buf, stmt->options, "DICTIONARY"); + appendStringInfoString(&buf, " );"); + return buf.data; +} + + /* * DeparseAlterTextSearchConfigurationSchemaStmt returns the sql statement representing * ALTER TEXT SEARCH CONFIGURATION ... SET SCHEMA ... statements. @@ -274,6 +396,27 @@ DeparseAlterTextSearchConfigurationSchemaStmt(Node *node) } +/* + * DeparseAlterTextSearchDictionarySchemaStmt returns the sql statement representing ALTER TEXT + * SEARCH DICTIONARY ... SET SCHEMA ... statements. + */ +char * +DeparseAlterTextSearchDictionarySchemaStmt(Node *node) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + Assert(stmt->objectType == OBJECT_TSDICTIONARY); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + appendStringInfo(&buf, "ALTER TEXT SEARCH DICTIONARY %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 ... @@ -305,6 +448,37 @@ DeparseTextSearchConfigurationCommentStmt(Node *node) } +/* + * DeparseTextSearchDictionaryCommentStmt returns the sql statement representing + * COMMENT ON TEXT SEARCH DICTIONARY ... IS ... + */ +char * +DeparseTextSearchDictionaryCommentStmt(Node *node) +{ + CommentStmt *stmt = castNode(CommentStmt, node); + Assert(stmt->objtype == OBJECT_TSDICTIONARY); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + appendStringInfo(&buf, "COMMENT ON TEXT SEARCH DICTIONARY %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 @@ -375,3 +549,24 @@ DeparseAlterTextSearchConfigurationOwnerStmt(Node *node) return buf.data; } + + +/* + * DeparseAlterTextSearchDictionaryOwnerStmt returns the sql statement representing ALTER TEXT + * SEARCH DICTIONARY ... ONWER TO ... commands. + */ +char * +DeparseAlterTextSearchDictionaryOwnerStmt(Node *node) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + Assert(stmt->objectType == OBJECT_TSDICTIONARY); + + StringInfoData buf = { 0 }; + initStringInfo(&buf); + + appendStringInfo(&buf, "ALTER TEXT SEARCH DICTIONARY %s OWNER TO %s;", + NameListToQuotedString(castNode(List, stmt->object)), + RoleSpecString(stmt->newowner, true)); + + return buf.data; +} diff --git a/src/backend/distributed/deparser/qualify_text_search_stmts.c b/src/backend/distributed/deparser/qualify_text_search_stmts.c index 42c98039a..5e66b06ff 100644 --- a/src/backend/distributed/deparser/qualify_text_search_stmts.c +++ b/src/backend/distributed/deparser/qualify_text_search_stmts.c @@ -69,6 +69,44 @@ QualifyDropTextSearchConfigurationStmt(Node *node) } +/* + * QualifyDropTextSearchDictionaryStmt adds any missing schema names to text search + * dictionaries being dropped. All dictionaries 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 +QualifyDropTextSearchDictionaryStmt(Node *node) +{ + DropStmt *stmt = castNode(DropStmt, node); + Assert(stmt->removeType == OBJECT_TSDICTIONARY); + + List *qualifiedObjects = NIL; + List *objName = NIL; + + foreach_ptr(objName, stmt->objects) + { + char *schemaName = NULL; + char *tsdictName = NULL; + DeconstructQualifiedName(objName, &schemaName, &tsdictName); + + if (!schemaName) + { + Oid tsdictOid = get_ts_dict_oid(objName, false); + Oid namespaceOid = get_ts_dict_namespace(tsdictOid); + schemaName = get_namespace_name(namespaceOid); + + objName = list_make2(makeString(schemaName), + makeString(tsdictName)); + } + + 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. @@ -128,6 +166,32 @@ QualifyAlterTextSearchConfigurationStmt(Node *node) } +/* + * QualifyAlterTextSearchDictionaryStmt adds the schema name (if missing) to the name + * of the text search dictionary. + */ +void +QualifyAlterTextSearchDictionaryStmt(Node *node) +{ + AlterTSDictionaryStmt *stmt = castNode(AlterTSDictionaryStmt, node); + + char *schemaName = NULL; + char *objName = NULL; + DeconstructQualifiedName(stmt->dictname, &schemaName, &objName); + + /* fully qualify the dictname being altered */ + if (!schemaName) + { + Oid tsdictOid = get_ts_dict_oid(stmt->dictname, false); + Oid namespaceOid = get_ts_dict_namespace(tsdictOid); + schemaName = get_namespace_name(namespaceOid); + + stmt->dictname = list_make2(makeString(schemaName), + makeString(objName)); + } +} + + /* * QualifyRenameTextSearchConfigurationStmt adds the schema name (if missing) to the * configuration being renamed. The new name will kept be without schema name since this @@ -156,9 +220,37 @@ QualifyRenameTextSearchConfigurationStmt(Node *node) } +/* + * QualifyRenameTextSearchDictionaryStmt adds the schema name (if missing) to the + * dictionary being renamed. The new name will kept be without schema name since this + * command cannot be used to change the schema of a dictionary. + */ +void +QualifyRenameTextSearchDictionaryStmt(Node *node) +{ + RenameStmt *stmt = castNode(RenameStmt, node); + Assert(stmt->renameType == OBJECT_TSDICTIONARY); + + char *schemaName = NULL; + char *objName = NULL; + DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName); + + /* fully qualify the dictname being altered */ + if (!schemaName) + { + Oid tsdictOid = get_ts_dict_oid(castNode(List, stmt->object), false); + Oid namespaceOid = get_ts_dict_namespace(tsdictOid); + 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. + * text search config being moved to a new schema. */ void QualifyAlterTextSearchConfigurationSchemaStmt(Node *node) @@ -182,6 +274,32 @@ QualifyAlterTextSearchConfigurationSchemaStmt(Node *node) } +/* + * QualifyAlterTextSearchDictionarySchemaStmt adds the schema name (if missing) for the + * text search dictionary being moved to a new schema. + */ +void +QualifyAlterTextSearchDictionarySchemaStmt(Node *node) +{ + AlterObjectSchemaStmt *stmt = castNode(AlterObjectSchemaStmt, node); + Assert(stmt->objectType == OBJECT_TSDICTIONARY); + + char *schemaName = NULL; + char *objName = NULL; + DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName); + + if (!schemaName) + { + Oid tsdictOid = get_ts_dict_oid(castNode(List, stmt->object), false); + Oid namespaceOid = get_ts_dict_namespace(tsdictOid); + 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. @@ -208,6 +326,32 @@ QualifyTextSearchConfigurationCommentStmt(Node *node) } +/* + * QualifyTextSearchDictionaryCommentStmt adds the schema name (if missing) to the + * dictionary name on which the comment is created. + */ +void +QualifyTextSearchDictionaryCommentStmt(Node *node) +{ + CommentStmt *stmt = castNode(CommentStmt, node); + Assert(stmt->objtype == OBJECT_TSDICTIONARY); + + char *schemaName = NULL; + char *objName = NULL; + DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName); + + if (!schemaName) + { + Oid tsdictOid = get_ts_dict_oid(castNode(List, stmt->object), false); + Oid namespaceOid = get_ts_dict_namespace(tsdictOid); + 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. @@ -234,6 +378,32 @@ QualifyAlterTextSearchConfigurationOwnerStmt(Node *node) } +/* + * QualifyAlterTextSearchDictionaryOwnerStmt adds the schema name (if missing) to the + * dictionary for which the owner is changing. + */ +void +QualifyAlterTextSearchDictionaryOwnerStmt(Node *node) +{ + AlterOwnerStmt *stmt = castNode(AlterOwnerStmt, node); + Assert(stmt->objectType == OBJECT_TSDICTIONARY); + + char *schemaName = NULL; + char *objName = NULL; + DeconstructQualifiedName(castNode(List, stmt->object), &schemaName, &objName); + + if (!schemaName) + { + Oid tsdictOid = get_ts_dict_oid(castNode(List, stmt->object), false); + Oid namespaceOid = get_ts_dict_namespace(tsdictOid); + 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. diff --git a/src/backend/distributed/executor/multi_executor.c b/src/backend/distributed/executor/multi_executor.c index a47dc6a48..f3ee37c23 100644 --- a/src/backend/distributed/executor/multi_executor.c +++ b/src/backend/distributed/executor/multi_executor.c @@ -788,6 +788,11 @@ GetObjectTypeString(ObjectType objType) return "text search configuration"; } + case OBJECT_TSDICTIONARY: + { + return "text search dictionary"; + } + case OBJECT_TYPE: { return "type"; diff --git a/src/backend/distributed/metadata/dependency.c b/src/backend/distributed/metadata/dependency.c index 6fb74a5e4..38fe29c4d 100644 --- a/src/backend/distributed/metadata/dependency.c +++ b/src/backend/distributed/metadata/dependency.c @@ -676,6 +676,11 @@ SupportedDependencyByCitus(const ObjectAddress *address) return true; } + case OCLASS_TSDICT: + { + return true; + } + case OCLASS_TYPE: { switch (get_typtype(address->objectId)) @@ -857,9 +862,13 @@ GetUndistributableDependency(const ObjectAddress *objectAddress) if (!SupportedDependencyByCitus(dependency)) { /* - * Since roles should be handled manually with Citus community, skip them. + * Skip roles and text search templates. + * + * Roles should be handled manually with Citus community whereas text search + * templates should be handled manually in both community and enterprise */ - if (getObjectClass(dependency) != OCLASS_ROLE) + if (getObjectClass(dependency) != OCLASS_ROLE && + getObjectClass(dependency) != OCLASS_TSTEMPLATE) { return dependency; } diff --git a/src/backend/distributed/metadata/pg_get_object_address_12_13_14.c b/src/backend/distributed/metadata/pg_get_object_address_12_13_14.c index c2ec4db3a..26248f025 100644 --- a/src/backend/distributed/metadata/pg_get_object_address_12_13_14.c +++ b/src/backend/distributed/metadata/pg_get_object_address_12_13_14.c @@ -411,6 +411,7 @@ ErrorIfCurrentUserCanNotDistributeObject(ObjectType type, ObjectAddress *addr, case OBJECT_PROCEDURE: case OBJECT_AGGREGATE: case OBJECT_TSCONFIGURATION: + case OBJECT_TSDICTIONARY: case OBJECT_TYPE: case OBJECT_FOREIGN_SERVER: case OBJECT_SEQUENCE: diff --git a/src/backend/distributed/worker/worker_create_or_replace.c b/src/backend/distributed/worker/worker_create_or_replace.c index 6ce96bd9f..d0734f6c8 100644 --- a/src/backend/distributed/worker/worker_create_or_replace.c +++ b/src/backend/distributed/worker/worker_create_or_replace.c @@ -286,18 +286,16 @@ CreateStmtListByObjectAddress(const ObjectAddress *address) 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_TSDICT: + { + List *stmts = GetCreateTextSearchDictionaryStatements(address); + return DeparseTreeNodes(stmts); + } + case OCLASS_TYPE: { return list_make1(DeparseTreeNode(CreateTypeStmtByObjectAddress(address))); diff --git a/src/include/distributed/commands.h b/src/include/distributed/commands.h index 6d5b13d98..342692f47 100644 --- a/src/include/distributed/commands.h +++ b/src/include/distributed/commands.h @@ -479,49 +479,94 @@ extern bool ConstrTypeUsesIndex(ConstrType constrType); /* text_search.c - forward declarations */ extern List * PostprocessCreateTextSearchConfigurationStmt(Node *node, const char *queryString); +extern List * PostprocessCreateTextSearchDictionaryStmt(Node *node, + const char *queryString); extern List * GetCreateTextSearchConfigStatements(const ObjectAddress *address); +extern List * GetCreateTextSearchDictionaryStatements(const ObjectAddress *address); extern List * CreateTextSearchConfigDDLCommandsIdempotent(const ObjectAddress *address); +extern List * CreateTextSearchDictDDLCommandsIdempotent(const ObjectAddress *address); extern List * PreprocessDropTextSearchConfigurationStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); +extern List * PreprocessDropTextSearchDictionaryStmt(Node *node, + const char *queryString, + ProcessUtilityContext + processUtilityContext); extern List * PreprocessAlterTextSearchConfigurationStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); +extern List * PreprocessAlterTextSearchDictionaryStmt(Node *node, + const char *queryString, + ProcessUtilityContext + processUtilityContext); extern List * PreprocessRenameTextSearchConfigurationStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); +extern List * PreprocessRenameTextSearchDictionaryStmt(Node *node, + const char *queryString, + ProcessUtilityContext + processUtilityContext); extern List * PreprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); +extern List * PreprocessAlterTextSearchDictionarySchemaStmt(Node *node, + const char *queryString, + ProcessUtilityContext + processUtilityContext); extern List * PostprocessAlterTextSearchConfigurationSchemaStmt(Node *node, const char *queryString); +extern List * PostprocessAlterTextSearchDictionarySchemaStmt(Node *node, + const char *queryString); extern List * PreprocessTextSearchConfigurationCommentStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); +extern List * PreprocessTextSearchDictionaryCommentStmt(Node *node, + const char *queryString, + ProcessUtilityContext + processUtilityContext); extern List * PreprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *queryString, ProcessUtilityContext processUtilityContext); +extern List * PreprocessAlterTextSearchDictionaryOwnerStmt(Node *node, + const char *queryString, + ProcessUtilityContext + processUtilityContext); extern List * PostprocessAlterTextSearchConfigurationOwnerStmt(Node *node, const char *queryString); +extern List * PostprocessAlterTextSearchDictionaryOwnerStmt(Node *node, + const char *queryString); extern ObjectAddress CreateTextSearchConfigurationObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress CreateTextSearchDictObjectAddress(Node *node, + bool missing_ok); extern ObjectAddress RenameTextSearchConfigurationStmtObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress RenameTextSearchDictionaryStmtObjectAddress(Node *node, + bool missing_ok); extern ObjectAddress AlterTextSearchConfigurationStmtObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress AlterTextSearchDictionaryStmtObjectAddress(Node *node, + bool missing_ok); extern ObjectAddress AlterTextSearchConfigurationSchemaStmtObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress AlterTextSearchDictionarySchemaStmtObjectAddress(Node *node, + bool missing_ok); extern ObjectAddress TextSearchConfigurationCommentObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress TextSearchDictCommentObjectAddress(Node *node, + bool missing_ok); extern ObjectAddress AlterTextSearchConfigurationOwnerObjectAddress(Node *node, bool missing_ok); +extern ObjectAddress AlterTextSearchDictOwnerObjectAddress(Node *node, + bool missing_ok); extern char * GenerateBackupNameForTextSearchConfiguration(const ObjectAddress *address); +extern char * GenerateBackupNameForTextSearchDict(const ObjectAddress *address); extern List * get_ts_config_namelist(Oid tsconfigOid); /* truncate.c - forward declarations */ diff --git a/src/include/distributed/deparser.h b/src/include/distributed/deparser.h index e3b02cdfc..2c74f060c 100644 --- a/src/include/distributed/deparser.h +++ b/src/include/distributed/deparser.h @@ -63,14 +63,21 @@ 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); +/* forward declarations for deparse_text_search.c */ extern char * DeparseAlterTextSearchConfigurationOwnerStmt(Node *node); +extern char * DeparseAlterTextSearchConfigurationSchemaStmt(Node *node); +extern char * DeparseAlterTextSearchConfigurationStmt(Node *node); +extern char * DeparseAlterTextSearchDictionaryOwnerStmt(Node *node); +extern char * DeparseAlterTextSearchDictionarySchemaStmt(Node *node); +extern char * DeparseAlterTextSearchDictionaryStmt(Node *node); +extern char * DeparseCreateTextSearchConfigurationStmt(Node *node); +extern char * DeparseCreateTextSearchDictionaryStmt(Node *node); +extern char * DeparseDropTextSearchConfigurationStmt(Node *node); +extern char * DeparseDropTextSearchDictionaryStmt(Node *node); +extern char * DeparseRenameTextSearchConfigurationStmt(Node *node); +extern char * DeparseRenameTextSearchDictionaryStmt(Node *node); +extern char * DeparseTextSearchConfigurationCommentStmt(Node *node); +extern char * DeparseTextSearchDictionaryCommentStmt(Node *node); /* forward declarations for deparse_schema_stmts.c */ extern char * DeparseCreateSchemaStmt(Node *node); @@ -153,13 +160,19 @@ 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); +/* forward declatations for deparse_text_search_stmts.c */ extern void QualifyAlterTextSearchConfigurationOwnerStmt(Node *node); +extern void QualifyAlterTextSearchConfigurationSchemaStmt(Node *node); +extern void QualifyAlterTextSearchConfigurationStmt(Node *node); +extern void QualifyAlterTextSearchDictionaryOwnerStmt(Node *node); +extern void QualifyAlterTextSearchDictionarySchemaStmt(Node *node); +extern void QualifyAlterTextSearchDictionaryStmt(Node *node); +extern void QualifyDropTextSearchConfigurationStmt(Node *node); +extern void QualifyDropTextSearchDictionaryStmt(Node *node); +extern void QualifyRenameTextSearchConfigurationStmt(Node *node); +extern void QualifyRenameTextSearchDictionaryStmt(Node *node); +extern void QualifyTextSearchConfigurationCommentStmt(Node *node); +extern void QualifyTextSearchDictionaryCommentStmt(Node *node); /* forward declarations for deparse_sequence_stmts.c */ extern char * DeparseDropSequenceStmt(Node *node); diff --git a/src/test/regress/expected/propagate_extension_commands.out b/src/test/regress/expected/propagate_extension_commands.out index ce4d9279c..6ed59bf18 100644 --- a/src/test/regress/expected/propagate_extension_commands.out +++ b/src/test/regress/expected/propagate_extension_commands.out @@ -1,3 +1,11 @@ +-- print whether we're using version > 12 to make version-specific tests clear +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int > 12 AS version_above_twelve; + version_above_twelve +--------------------------------------------------------------------- + t +(1 row) + CREATE SCHEMA "extension'test"; -- use a schema name with escape character SET search_path TO "extension'test"; @@ -184,8 +192,6 @@ SELECT create_reference_table('ref_table_2'); CREATE FUNCTION dintdict_init(internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; CREATE FUNCTION dintdict_lexize(internal, internal, internal, internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); -CREATE TEXT SEARCH DICTIONARY intdict (TEMPLATE = intdict_template); -COMMENT ON TEXT SEARCH DICTIONARY intdict IS 'dictionary for integers'; SELECT run_command_on_workers($$ CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); $$); @@ -194,22 +200,8 @@ $$); (localhost,57637,t,"CREATE TEXT SEARCH TEMPLATE") (1 row) -SELECT run_command_on_workers($$ CREATE TEXT SEARCH DICTIONARY intdict (TEMPLATE = intdict_template); -$$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,"CREATE TEXT SEARCH DICTIONARY") -(1 row) - -SELECT run_command_on_workers($$ COMMENT ON TEXT SEARCH DICTIONARY intdict IS 'dictionary for integers'; -$$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,COMMENT) -(1 row) - CREATE EXTENSION dict_int FROM unpackaged; ERROR: CREATE EXTENSION ... FROM is no longer supported SELECT run_command_on_workers($$SELECT count(extnamespace) FROM pg_extension WHERE extname = 'dict_int'$$); @@ -224,7 +216,20 @@ SELECT run_command_on_workers($$SELECT extversion FROM pg_extension WHERE extnam (localhost,57637,t,"") (1 row) --- and add the other node +-- adding the second node will fail as the text search template needs to be created manually +SELECT 1 from master_add_node('localhost', :worker_2_port); +ERROR: text search template "public.intdict_template" does not exist +CONTEXT: while executing command on localhost:xxxxx +-- create the text search template manually on the worker +\c - - - :worker_2_port +SET citus.enable_metadata_sync TO false; +CREATE FUNCTION dintdict_init(internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; +CREATE FUNCTION dintdict_lexize(internal, internal, internal, internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; +CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); +RESET citus.enable_metadata_sync; +\c - - - :master_port +SET client_min_messages TO WARNING; +-- add the second node now SELECT 1 from master_add_node('localhost', :worker_2_port); ?column? --------------------------------------------------------------------- diff --git a/src/test/regress/expected/propagate_extension_commands_1.out b/src/test/regress/expected/propagate_extension_commands_1.out index 4d5559ffd..53877ca5e 100644 --- a/src/test/regress/expected/propagate_extension_commands_1.out +++ b/src/test/regress/expected/propagate_extension_commands_1.out @@ -1,3 +1,11 @@ +-- print whether we're using version > 12 to make version-specific tests clear +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int > 12 AS version_above_twelve; + version_above_twelve +--------------------------------------------------------------------- + f +(1 row) + CREATE SCHEMA "extension'test"; -- use a schema name with escape character SET search_path TO "extension'test"; @@ -184,8 +192,6 @@ SELECT create_reference_table('ref_table_2'); CREATE FUNCTION dintdict_init(internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; CREATE FUNCTION dintdict_lexize(internal, internal, internal, internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); -CREATE TEXT SEARCH DICTIONARY intdict (TEMPLATE = intdict_template); -COMMENT ON TEXT SEARCH DICTIONARY intdict IS 'dictionary for integers'; SELECT run_command_on_workers($$ CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); $$); @@ -194,22 +200,8 @@ $$); (localhost,57637,t,"CREATE TEXT SEARCH TEMPLATE") (1 row) -SELECT run_command_on_workers($$ CREATE TEXT SEARCH DICTIONARY intdict (TEMPLATE = intdict_template); -$$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,"CREATE TEXT SEARCH DICTIONARY") -(1 row) - -SELECT run_command_on_workers($$ COMMENT ON TEXT SEARCH DICTIONARY intdict IS 'dictionary for integers'; -$$); - run_command_on_workers ---------------------------------------------------------------------- - (localhost,57637,t,COMMENT) -(1 row) - CREATE EXTENSION dict_int FROM unpackaged; SELECT run_command_on_workers($$SELECT count(extnamespace) FROM pg_extension WHERE extname = 'dict_int'$$); run_command_on_workers @@ -223,7 +215,27 @@ SELECT run_command_on_workers($$SELECT extversion FROM pg_extension WHERE extnam (localhost,57637,t,1.0) (1 row) --- and add the other node +-- adding the second node will fail as the text search template needs to be created manually +SELECT 1 from master_add_node('localhost', :worker_2_port); + ?column? +--------------------------------------------------------------------- + 1 +(1 row) + +-- create the text search template manually on the worker +\c - - - :worker_2_port +SET citus.enable_metadata_sync TO false; +CREATE FUNCTION dintdict_init(internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; +ERROR: function "dintdict_init" already exists with same argument types +CREATE FUNCTION dintdict_lexize(internal, internal, internal, internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; +ERROR: function "dintdict_lexize" already exists with same argument types +CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); +ERROR: duplicate key value violates unique constraint "pg_ts_template_tmplname_index" +DETAIL: Key (tmplname, tmplnamespace)=(intdict_template, 2200) already exists. +RESET citus.enable_metadata_sync; +\c - - - :master_port +SET client_min_messages TO WARNING; +-- add the second node now SELECT 1 from master_add_node('localhost', :worker_2_port); ?column? --------------------------------------------------------------------- diff --git a/src/test/regress/expected/text_search.out b/src/test/regress/expected/text_search.out index 0cbb0b5bd..c0eb92f88 100644 --- a/src/test/regress/expected/text_search.out +++ b/src/test/regress/expected/text_search.out @@ -503,6 +503,122 @@ SELECT create_distributed_table('sensors', 'measureid'); (1 row) +-- create a new dictionary from scratch +CREATE TEXT SEARCH DICTIONARY my_english_dict ( + template = snowball, + language = english, + stopwords = english +); +-- verify that the dictionary definition is the same in all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(dictname, dictnamespace::regnamespace, dictowner::regrole, tmplname, dictinitoption) + FROM pg_ts_dict d JOIN pg_ts_template t ON ( d.dicttemplate = t.oid ) + WHERE dictname = 'my_english_dict'; +$$); + result +--------------------------------------------------------------------- + (my_english_dict,text_search,postgres,snowball,"language = 'english', stopwords = 'english'") + (my_english_dict,text_search,postgres,snowball,"language = 'english', stopwords = 'english'") + (my_english_dict,text_search,postgres,snowball,"language = 'english', stopwords = 'english'") +(3 rows) + +-- use the new dictionary in a configuration mapping +CREATE TEXT SEARCH CONFIGURATION my_english_config ( COPY = english ); +ALTER TEXT SEARCH CONFIGURATION my_english_config ALTER MAPPING FOR asciiword WITH my_english_dict; +-- verify that the dictionary is available on the worker nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(alias,dictionary) FROM ts_debug('text_search.my_english_config', 'The Brightest supernovaes') WHERE alias = 'asciiword' LIMIT 1; +$$); + result +--------------------------------------------------------------------- + (asciiword,text_search.my_english_dict) + (asciiword,text_search.my_english_dict) + (asciiword,text_search.my_english_dict) +(3 rows) + +-- comment on a text search dictionary +COMMENT ON TEXT SEARCH DICTIONARY my_english_dict IS 'a text search dictionary that is butchered to test all edge cases'; +SELECT result FROM run_command_on_all_nodes($$ + SELECT obj_description('text_search.my_english_dict'::regdictionary); +$$); + result +--------------------------------------------------------------------- + a text search dictionary that is butchered to test all edge cases + a text search dictionary that is butchered to test all edge cases + a text search dictionary that is butchered to test all edge cases +(3 rows) + +-- remove a comment +COMMENT ON TEXT SEARCH DICTIONARY my_english_dict IS NULL; +SELECT result FROM run_command_on_all_nodes($$ + SELECT obj_description('text_search.my_english_dict'::regdictionary); +$$); + result +--------------------------------------------------------------------- + + + +(3 rows) + +-- test various ALTER TEXT SEARCH DICTIONARY commands +ALTER TEXT SEARCH DICTIONARY my_english_dict RENAME TO my_turkish_dict; +ALTER TEXT SEARCH DICTIONARY my_turkish_dict (language = turkish, stopwords); +ALTER TEXT SEARCH DICTIONARY my_turkish_dict OWNER TO text_search_owner; +ALTER TEXT SEARCH DICTIONARY my_turkish_dict SET SCHEMA "Text Search Requiring Quote's"; +-- verify that the dictionary definition is the same in all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(dictname, dictnamespace::regnamespace, dictowner::regrole, tmplname, dictinitoption) + FROM pg_ts_dict d JOIN pg_ts_template t ON ( d.dicttemplate = t.oid ) + WHERE dictname = 'my_turkish_dict'; +$$); + result +--------------------------------------------------------------------- + (my_turkish_dict,"""Text Search Requiring Quote's""",text_search_owner,snowball,"language = 'turkish'") + (my_turkish_dict,"""Text Search Requiring Quote's""",text_search_owner,snowball,"language = 'turkish'") + (my_turkish_dict,"""Text Search Requiring Quote's""",text_search_owner,snowball,"language = 'turkish'") +(3 rows) + +-- verify that the configuration dictionary is changed in all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(alias,dictionary) FROM ts_debug('text_search.my_english_config', 'The Brightest supernovaes') WHERE alias = 'asciiword' LIMIT 1; +$$); + result +--------------------------------------------------------------------- + (asciiword,"""Text Search Requiring Quote's"".my_turkish_dict") + (asciiword,"""Text Search Requiring Quote's"".my_turkish_dict") + (asciiword,"""Text Search Requiring Quote's"".my_turkish_dict") +(3 rows) + +-- before testing drops, check that the dictionary exists on all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT '"Text Search Requiring Quote''s".my_turkish_dict'::regdictionary; +$$); + result +--------------------------------------------------------------------- + "Text Search Requiring Quote's".my_turkish_dict + "Text Search Requiring Quote's".my_turkish_dict + "Text Search Requiring Quote's".my_turkish_dict +(3 rows) + +ALTER TEXT SEARCH DICTIONARY "Text Search Requiring Quote's".my_turkish_dict SET SCHEMA text_search; +-- verify that we can drop the dictionary only with cascade option +DROP TEXT SEARCH DICTIONARY my_turkish_dict; +ERROR: cannot drop text search dictionary my_turkish_dict because other objects depend on it +DETAIL: text search configuration my_english_config depends on text search dictionary my_turkish_dict +HINT: Use DROP ... CASCADE to drop the dependent objects too. +DROP TEXT SEARCH DICTIONARY my_turkish_dict CASCADE; +NOTICE: drop cascades to text search configuration my_english_config +-- verify that it is dropped now +SELECT result FROM run_command_on_all_nodes($$ + SELECT 'my_turkish_dict'::regdictionary; +$$); + result +--------------------------------------------------------------------- + ERROR: text search dictionary "my_turkish_dict" does not exist + ERROR: text search dictionary "my_turkish_dict" does not exist + ERROR: text search dictionary "my_turkish_dict" does not exist +(3 rows) + SET client_min_messages TO 'warning'; DROP SCHEMA text_search, text_search2, "Text Search Requiring Quote's" CASCADE; DROP ROLE text_search_owner; diff --git a/src/test/regress/sql/propagate_extension_commands.sql b/src/test/regress/sql/propagate_extension_commands.sql index ec075c388..3a8037290 100644 --- a/src/test/regress/sql/propagate_extension_commands.sql +++ b/src/test/regress/sql/propagate_extension_commands.sql @@ -1,3 +1,7 @@ +-- print whether we're using version > 12 to make version-specific tests clear +SHOW server_version \gset +SELECT substring(:'server_version', '\d+')::int > 12 AS version_above_twelve; + CREATE SCHEMA "extension'test"; -- use a schema name with escape character @@ -114,26 +118,33 @@ SELECT create_reference_table('ref_table_2'); CREATE FUNCTION dintdict_init(internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; CREATE FUNCTION dintdict_lexize(internal, internal, internal, internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); -CREATE TEXT SEARCH DICTIONARY intdict (TEMPLATE = intdict_template); -COMMENT ON TEXT SEARCH DICTIONARY intdict IS 'dictionary for integers'; SELECT run_command_on_workers($$ CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); $$); -SELECT run_command_on_workers($$ CREATE TEXT SEARCH DICTIONARY intdict (TEMPLATE = intdict_template); -$$); - -SELECT run_command_on_workers($$ COMMENT ON TEXT SEARCH DICTIONARY intdict IS 'dictionary for integers'; -$$); CREATE EXTENSION dict_int FROM unpackaged; SELECT run_command_on_workers($$SELECT count(extnamespace) FROM pg_extension WHERE extname = 'dict_int'$$); SELECT run_command_on_workers($$SELECT extversion FROM pg_extension WHERE extname = 'dict_int'$$); --- and add the other node +-- adding the second node will fail as the text search template needs to be created manually +SELECT 1 from master_add_node('localhost', :worker_2_port); + +-- create the text search template manually on the worker +\c - - - :worker_2_port +SET citus.enable_metadata_sync TO false; +CREATE FUNCTION dintdict_init(internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; +CREATE FUNCTION dintdict_lexize(internal, internal, internal, internal) RETURNS internal AS 'dict_int.so' LANGUAGE C STRICT; +CREATE TEXT SEARCH TEMPLATE intdict_template (LEXIZE = dintdict_lexize, INIT = dintdict_init ); +RESET citus.enable_metadata_sync; + +\c - - - :master_port +SET client_min_messages TO WARNING; + +-- add the second node now SELECT 1 from master_add_node('localhost', :worker_2_port); -- show that the extension is created on both existing and new node diff --git a/src/test/regress/sql/text_search.sql b/src/test/regress/sql/text_search.sql index 88ba527b2..996c708be 100644 --- a/src/test/regress/sql/text_search.sql +++ b/src/test/regress/sql/text_search.sql @@ -276,6 +276,76 @@ CREATE TABLE sensors_a_partition PARTITION OF sensors FOR VALUES FROM ('2000-01- CREATE INDEX sensors_search_name ON sensors USING gin (to_tsvector('partial_index_test_config'::regconfig, (COALESCE(name, ''::character varying))::text)); SELECT create_distributed_table('sensors', 'measureid'); +-- create a new dictionary from scratch +CREATE TEXT SEARCH DICTIONARY my_english_dict ( + template = snowball, + language = english, + stopwords = english +); + +-- verify that the dictionary definition is the same in all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(dictname, dictnamespace::regnamespace, dictowner::regrole, tmplname, dictinitoption) + FROM pg_ts_dict d JOIN pg_ts_template t ON ( d.dicttemplate = t.oid ) + WHERE dictname = 'my_english_dict'; +$$); + +-- use the new dictionary in a configuration mapping +CREATE TEXT SEARCH CONFIGURATION my_english_config ( COPY = english ); +ALTER TEXT SEARCH CONFIGURATION my_english_config ALTER MAPPING FOR asciiword WITH my_english_dict; + +-- verify that the dictionary is available on the worker nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(alias,dictionary) FROM ts_debug('text_search.my_english_config', 'The Brightest supernovaes') WHERE alias = 'asciiword' LIMIT 1; +$$); + +-- comment on a text search dictionary +COMMENT ON TEXT SEARCH DICTIONARY my_english_dict IS 'a text search dictionary that is butchered to test all edge cases'; +SELECT result FROM run_command_on_all_nodes($$ + SELECT obj_description('text_search.my_english_dict'::regdictionary); +$$); + +-- remove a comment +COMMENT ON TEXT SEARCH DICTIONARY my_english_dict IS NULL; +SELECT result FROM run_command_on_all_nodes($$ + SELECT obj_description('text_search.my_english_dict'::regdictionary); +$$); + +-- test various ALTER TEXT SEARCH DICTIONARY commands +ALTER TEXT SEARCH DICTIONARY my_english_dict RENAME TO my_turkish_dict; +ALTER TEXT SEARCH DICTIONARY my_turkish_dict (language = turkish, stopwords); +ALTER TEXT SEARCH DICTIONARY my_turkish_dict OWNER TO text_search_owner; +ALTER TEXT SEARCH DICTIONARY my_turkish_dict SET SCHEMA "Text Search Requiring Quote's"; + +-- verify that the dictionary definition is the same in all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(dictname, dictnamespace::regnamespace, dictowner::regrole, tmplname, dictinitoption) + FROM pg_ts_dict d JOIN pg_ts_template t ON ( d.dicttemplate = t.oid ) + WHERE dictname = 'my_turkish_dict'; +$$); + +-- verify that the configuration dictionary is changed in all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT ROW(alias,dictionary) FROM ts_debug('text_search.my_english_config', 'The Brightest supernovaes') WHERE alias = 'asciiword' LIMIT 1; +$$); + +-- before testing drops, check that the dictionary exists on all nodes +SELECT result FROM run_command_on_all_nodes($$ + SELECT '"Text Search Requiring Quote''s".my_turkish_dict'::regdictionary; +$$); + +ALTER TEXT SEARCH DICTIONARY "Text Search Requiring Quote's".my_turkish_dict SET SCHEMA text_search; + +-- verify that we can drop the dictionary only with cascade option +DROP TEXT SEARCH DICTIONARY my_turkish_dict; +DROP TEXT SEARCH DICTIONARY my_turkish_dict CASCADE; + +-- verify that it is dropped now +SELECT result FROM run_command_on_all_nodes($$ + SELECT 'my_turkish_dict'::regdictionary; +$$); + + SET client_min_messages TO 'warning'; DROP SCHEMA text_search, text_search2, "Text Search Requiring Quote's" CASCADE; DROP ROLE text_search_owner;