diff --git a/contrib/babelfishpg_tsql/sql/fts_contains_pgconfig.sql b/contrib/babelfishpg_tsql/sql/fts_contains_pgconfig.sql index fc0a205f89..ce40c1ea4e 100644 --- a/contrib/babelfishpg_tsql/sql/fts_contains_pgconfig.sql +++ b/contrib/babelfishpg_tsql/sql/fts_contains_pgconfig.sql @@ -10,10 +10,11 @@ DECLARE joined_text text; word text; BEGIN - -- Prefix term (Examples: '"word1*"', '"word1 word2*"') if - -- (1) search term is surrounded by double quotes (Counter example: 'word1*', as it doesn't have double quotes) - -- (2) last word in the search term ends with a star (Counter example: '"word1* word2"', as last word doesn't end with star) - -- (3) last word is NOT a single star (Counter example: '"*"', '"word1 word2 *"', as last word is a single star) + /* 'Prefix term (Examples: '"word1*"', '"word1 word2*"') if + * (1) search term is surrounded by double quotes (Counter example: 'word1*', as it doesn't have double quotes) + * (2) last word in the search term ends with a star (Counter example: '"word1* word2"', as last word doesn't end with star) + * (3) last word is NOT a single star (Counter example: '"*"', '"word1 word2 *"', as last word is a single star) + */ IF (phrase COLLATE C) SIMILAR TO ('[ ]*"%\*"[ ]*' COLLATE C) AND (NOT (phrase COLLATE C) SIMILAR TO ('[ ]*"% \*"[ ]*' COLLATE C)) AND (NOT (phrase COLLATE C) SIMILAR TO ('[ ]*"\*"[ ]*' COLLATE C)) THEN RETURN 'simple'::regconfig; END IF; diff --git a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c index d3c48de791..9c9d49b0e5 100644 --- a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c +++ b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c @@ -1869,7 +1869,7 @@ tsql_pivot_select_transformation(List *target_list, List *from_clause, List *piv * for DESC index, default should be NULLS LAST. */ static void -tsql_index_nulls_order(List *indexParams) +tsql_index_nulls_order(List *indexParams, const char *accessMethod) { ListCell *lc; @@ -1887,6 +1887,10 @@ tsql_index_nulls_order(List *indexParams) if (indexElem->nulls_ordering != SORTBY_NULLS_DEFAULT) continue; + /* GIN indexes don't support NULLS FIRST/LAST options */ + if (strcmp(accessMethod, "gin") == 0) + return; + switch (indexElem->ordering) { case SORTBY_ASC: diff --git a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-prologue.y.h b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-prologue.y.h index 4596d0ba71..5f8256de59 100644 --- a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-prologue.y.h +++ b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-prologue.y.h @@ -88,4 +88,4 @@ static Node *tsql_update_output_into_cte_transformation(WithClause *opt_with_cla List *from_clause, Node *where_or_current_clause, core_yyscan_t yyscanner); static List *get_transformed_output_list(List *tsql_output_clause); static bool returning_list_has_column_name(List *existing_colnames, char *current_colname); -static void tsql_index_nulls_order(List *indexParams); +static void tsql_index_nulls_order(List *indexParams, const char *accessMethod); diff --git a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y index c01c63fd57..98bc9d16d6 100644 --- a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y +++ b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y @@ -3191,7 +3191,7 @@ tsql_IndexStmt: n->transformed = false; n->if_not_exists = false; - tsql_index_nulls_order(n->indexParams); + tsql_index_nulls_order(n->indexParams, n->accessMethod); $$ = (Node *)n; } ; diff --git a/contrib/babelfishpg_tsql/src/codegen.c b/contrib/babelfishpg_tsql/src/codegen.c index 211c49e92e..c25fb924e2 100644 --- a/contrib/babelfishpg_tsql/src/codegen.c +++ b/contrib/babelfishpg_tsql/src/codegen.c @@ -302,6 +302,7 @@ stmt_default_act(Walker_context *ctx, PLtsql_stmt *stmt) case PLTSQL_STMT_GRANTDB: case PLTSQL_STMT_CHANGE_DBOWNER: case PLTSQL_STMT_GRANTSCHEMA: + case PLTSQL_STMT_FULLTEXTINDEX: case PLTSQL_STMT_INSERT_BULK: case PLTSQL_STMT_DBCC: case PLTSQL_STMT_SET_EXPLAIN_MODE: diff --git a/contrib/babelfishpg_tsql/src/fts.c b/contrib/babelfishpg_tsql/src/fts.c index 424a5be9e1..389c40c9e5 100644 --- a/contrib/babelfishpg_tsql/src/fts.c +++ b/contrib/babelfishpg_tsql/src/fts.c @@ -3,6 +3,7 @@ #include "utils/builtins.h" #include "utils/memutils.h" #include "fts_data.h" +#include "guc.h" PG_FUNCTION_INFO_V1(babelfish_fts_rewrite); @@ -13,6 +14,13 @@ babelfish_fts_rewrite(PG_FUNCTION_ARGS) char* input_str = text_to_cstring(input_text); char* translated_query; text* result_text = NULL; // Initialize result_text to NULL + + if (!pltsql_allow_fulltext_parser) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Full Text Search is not yet supported."))); + } PG_TRY(); { diff --git a/contrib/babelfishpg_tsql/src/fts_parser.y b/contrib/babelfishpg_tsql/src/fts_parser.y index c9a3488552..a94d386718 100644 --- a/contrib/babelfishpg_tsql/src/fts_parser.y +++ b/contrib/babelfishpg_tsql/src/fts_parser.y @@ -74,13 +74,13 @@ simple_term: prefix_term: PREFIX_TERM_TOKEN { - fts_yyerror(NULL, "Prefix term not supported"); + fts_yyerror(NULL, "Prefix term is not currently supported in Babelfish"); } ; generation_term: FORMSOF_TOKEN O_PAREN_TOKEN generation_type COMMA_TOKEN simple_term_list C_PAREN_TOKEN { - fts_yyerror(NULL, "Generation term not supported"); + fts_yyerror(NULL, "Generation term is not currently supported in Babelfish"); } ; diff --git a/contrib/babelfishpg_tsql/src/fts_scan.l b/contrib/babelfishpg_tsql/src/fts_scan.l index 49dded6dec..0ee1a82376 100644 --- a/contrib/babelfishpg_tsql/src/fts_scan.l +++ b/contrib/babelfishpg_tsql/src/fts_scan.l @@ -21,9 +21,9 @@ static YY_BUFFER_STATE scanbufhandle; %% -"AND" { yylval = "AND"; return AND_TOKEN; } -"AND NOT" { yylval = "AND NOT"; return AND_NOT_TOKEN; } -"OR" { yylval = "OR"; return OR_TOKEN; } +"AND" { yylval = "AND"; yyerror(NULL, "Boolean operator is not currently supported in Babelfish"); return AND_TOKEN; } +"AND NOT" { yylval = "AND NOT"; yyerror(NULL, "Boolean operator is not currently supported in Babelfish"); return AND_NOT_TOKEN; } +"OR" { yylval = "OR"; yyerror(NULL, "Boolean operator is not currently supported in Babelfish"); return OR_TOKEN; } "INFLECTIONAL" { yylval = "INFLECTIONAL"; return INFLECTIONAL_TOKEN; } "THESAURUS" { yylval = "THESAURUS"; return THESAURUS_TOKEN; } "FORMSOF" { yylval = "FORMSOF"; return FORMSOF_TOKEN; } diff --git a/contrib/babelfishpg_tsql/src/guc.c b/contrib/babelfishpg_tsql/src/guc.c index 97de1d83fb..8e6e99dade 100644 --- a/contrib/babelfishpg_tsql/src/guc.c +++ b/contrib/babelfishpg_tsql/src/guc.c @@ -46,6 +46,7 @@ char *pltsql_psql_logical_babelfish_db_name = NULL; int pltsql_lock_timeout = -1; bool pltsql_enable_linked_servers = true; bool pltsql_allow_windows_login = true; +bool pltsql_allow_fulltext_parser = false; bool pltsql_xact_abort = false; bool pltsql_implicit_transactions = false; @@ -677,6 +678,16 @@ define_custom_variables(void) GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_AUTO_FILE, NULL, NULL, NULL); + /* GUC for enabling or disabling full text search features */ + DefineCustomBoolVariable("babelfishpg_tsql.allow_fulltext_parser", + gettext_noop("GUC for enabling or disabling full text search features"), + NULL, + &pltsql_allow_fulltext_parser, + false, + PGC_SUSET, + GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE | GUC_SUPERUSER_ONLY, + NULL, NULL, NULL); + /* ISO standard settings */ DefineCustomBoolVariable("babelfishpg_tsql.ansi_defaults", gettext_noop("Controls a group of settings that collectively specify some " @@ -1339,7 +1350,7 @@ define_escape_hatch_variables(void) /* fulltext */ DefineCustomEnumVariable("babelfishpg_tsql.escape_hatch_fulltext", - gettext_noop("escape hatch for fulltext"), + gettext_noop("escape hatch for fulltext search"), NULL, &escape_hatch_fulltext, EH_STRICT, diff --git a/contrib/babelfishpg_tsql/src/guc.h b/contrib/babelfishpg_tsql/src/guc.h index 19f2860a29..e3733e41b8 100644 --- a/contrib/babelfishpg_tsql/src/guc.h +++ b/contrib/babelfishpg_tsql/src/guc.h @@ -19,6 +19,7 @@ extern bool pltsql_fmtonly; extern bool pltsql_enable_create_alter_view_from_pg; extern bool pltsql_enable_linked_servers; extern bool pltsql_allow_windows_login; +extern bool pltsql_allow_fulltext_parser; extern char *pltsql_psql_logical_babelfish_db_name; extern int pltsql_isolation_level_repeatable_read; extern int pltsql_isolation_level_serializable; diff --git a/contrib/babelfishpg_tsql/src/iterative_exec.c b/contrib/babelfishpg_tsql/src/iterative_exec.c index 152bd5ae01..c5af008265 100644 --- a/contrib/babelfishpg_tsql/src/iterative_exec.c +++ b/contrib/babelfishpg_tsql/src/iterative_exec.c @@ -818,6 +818,15 @@ dispatch_stmt(PLtsql_execstate *estate, PLtsql_stmt *stmt) } exec_stmt_grantschema(estate, (PLtsql_stmt_grantschema *) stmt); break; + case PLTSQL_STMT_FULLTEXTINDEX: + if (pltsql_explain_only) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Showing Estimated Execution Plan for FULLTEXT INDEX statment is not yet supported"))); + } + exec_stmt_fulltextindex(estate, (PLtsql_stmt_fulltextindex *) stmt); + break; case PLTSQL_STMT_INSERT_BULK: if (pltsql_explain_only) { diff --git a/contrib/babelfishpg_tsql/src/pl_exec-2.c b/contrib/babelfishpg_tsql/src/pl_exec-2.c index ca90cfe7d4..3636c3ab7e 100644 --- a/contrib/babelfishpg_tsql/src/pl_exec-2.c +++ b/contrib/babelfishpg_tsql/src/pl_exec-2.c @@ -52,6 +52,7 @@ static int exec_stmt_usedb(PLtsql_execstate *estate, PLtsql_stmt_usedb *stmt); static int exec_stmt_usedb_explain(PLtsql_execstate *estate, PLtsql_stmt_usedb *stmt, bool shouldRestoreDb); static int exec_stmt_grantdb(PLtsql_execstate *estate, PLtsql_stmt_grantdb *stmt); static int exec_stmt_grantschema(PLtsql_execstate *estate, PLtsql_stmt_grantschema *stmt); +static int exec_stmt_fulltextindex(PLtsql_execstate *estate, PLtsql_stmt_fulltextindex *stmt); static int exec_stmt_insert_execute_select(PLtsql_execstate *estate, PLtsql_expr *expr); static int exec_stmt_insert_bulk(PLtsql_execstate *estate, PLtsql_stmt_insert_bulk *expr); static int exec_stmt_dbcc(PLtsql_execstate *estate, PLtsql_stmt_dbcc *stmt); @@ -87,6 +88,8 @@ extern void enable_sp_cursor_find_param_hook(void); extern void disable_sp_cursor_find_param_hook(void); extern void add_sp_cursor_param(char *name); extern void reset_sp_cursor_params(); +extern char *construct_unique_index_name(char *index_name, char *relation_name); +extern const char *gen_schema_name_for_fulltext_index(const char *schema_name); extern void pltsql_commit_not_required_impl_txn(PLtsql_execstate *estate); @@ -3822,3 +3825,101 @@ exec_stmt_change_dbowner(PLtsql_execstate *estate, PLtsql_stmt_change_dbowner *s update_db_owner(stmt->new_owner_name, stmt->db_name); return PLTSQL_RC_OK; } + +static int +exec_stmt_fulltextindex(PLtsql_execstate *estate, PLtsql_stmt_fulltextindex *stmt) +{ + char *table_name; + char *ft_index_name; + char *query_str; + char *old_ft_index_name; // existing fulltext index name + char *uniq_index_name; + const char *schema_name; + Oid schemaOid; + Oid relid; + List *column_name; + char *dbname = get_cur_db_name(); + char *login = GetUserNameFromId(GetSessionUserId(), false); + Oid datdba; + bool login_is_db_owner; + bool is_create; + + Assert(stmt->schema_name != NULL); + + /* + * If the login is not the db owner or the login is not the member of + * sysadmin or login is not the schema owner, then it doesn't have the permission to CREATE/DROP FULLTEXT INDEX. + */ + login_is_db_owner = 0 == strncmp(login, get_owner_of_db(dbname), NAMEDATALEN); + datdba = get_role_oid("sysadmin", false); + schema_name = gen_schema_name_for_fulltext_index((char *)stmt->schema_name); + schemaOid = LookupExplicitNamespace(schema_name, true); + table_name = stmt->table_name; + is_create = stmt->is_create; + + // Check if schema exists + if (!OidIsValid(schemaOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("schema \"%s\" does not exist", + stmt->schema_name))); + + // Check if the user has necessary permissions for CREATE/DROP FULLTEXT INDEX + if (!is_member_of_role(GetSessionUserId(), datdba) && !login_is_db_owner && !pg_namespace_ownercheck(schemaOid, GetUserId())) + { + const char *error_msg = is_create ? "A default full-text catalog does not exist in the database or user does not have permission to perform this action" : "Cannot drop the full-text index, because it does not exist or you do not have permission"; + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("%s", error_msg))); + } + + relid = get_relname_relid((const char *) table_name, schemaOid); + + // Check if table exists + if (!OidIsValid(relid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" does not exist", + table_name))); + + // Get the existing fulltext index name + old_ft_index_name = get_fulltext_index_name(relid, table_name); + + if (is_create) + { + uniq_index_name = construct_unique_index_name((char *) stmt->index_name, table_name); + if(is_unique_index(relid, (const char *) uniq_index_name)) + { + column_name = stmt->column_name; + ft_index_name = construct_unique_index_name("ft_index", table_name); + if (old_ft_index_name) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("A full-text index for table or indexed view \"%s\" has already been created.", + table_name))); + else + query_str = gen_createfulltextindex_cmds(table_name, schema_name, column_name, ft_index_name); + } + else + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("'\"%s\"' is not a valid index to enforce a full-text search key. A full-text search key must be a unique, non-nullable, single-column index which is not offline, is not defined on a non-deterministic or imprecise nonpersisted computed column, does not have a filter, and has maximum size of 900 bytes. Choose another index for the full-text key.", + stmt->index_name))); + } + else + { + if (old_ft_index_name) + query_str = gen_dropfulltextindex_cmds(old_ft_index_name, schema_name); + else + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("Table or indexed view \"%s\" does not have a full-text index or user does not have permission to perform this action.", + table_name))); + } + + /* The above query will be + * executed using ProcessUtility() + */ + exec_utility_cmd_helper(query_str); + return PLTSQL_RC_OK; +} \ No newline at end of file diff --git a/contrib/babelfishpg_tsql/src/pl_funcs-2.c b/contrib/babelfishpg_tsql/src/pl_funcs-2.c index b3cc3f9001..6ec9d93652 100644 --- a/contrib/babelfishpg_tsql/src/pl_funcs-2.c +++ b/contrib/babelfishpg_tsql/src/pl_funcs-2.c @@ -485,6 +485,7 @@ free_stmt2(PLtsql_stmt *stmt) case PLTSQL_STMT_GRANTDB: case PLTSQL_STMT_CHANGE_DBOWNER: case PLTSQL_STMT_GRANTSCHEMA: + case PLTSQL_STMT_FULLTEXTINDEX: case PLTSQL_STMT_SET_EXPLAIN_MODE: { /* Nothing to free */ diff --git a/contrib/babelfishpg_tsql/src/pltsql.h b/contrib/babelfishpg_tsql/src/pltsql.h index 051d243ef5..ae94b0ddf1 100644 --- a/contrib/babelfishpg_tsql/src/pltsql.h +++ b/contrib/babelfishpg_tsql/src/pltsql.h @@ -194,7 +194,8 @@ typedef enum PLtsql_stmt_type PLTSQL_STMT_GRANTDB, PLTSQL_STMT_CHANGE_DBOWNER, PLTSQL_STMT_DBCC, - PLTSQL_STMT_GRANTSCHEMA + PLTSQL_STMT_GRANTSCHEMA, + PLTSQL_STMT_FULLTEXTINDEX, } PLtsql_stmt_type; /* @@ -1065,6 +1066,20 @@ typedef struct PLtsql_stmt_grantschema char *schema_name; /* schema name */ } PLtsql_stmt_grantschema; +/* + * Fulltext Index stmt + */ +typedef struct PLtsql_stmt_fulltextindex +{ + PLtsql_stmt_type cmd_type; + int lineno; + char *table_name; /* table name */ + List *column_name; /* column name */ + char *index_name; /* index name */ + char *schema_name; /* schema name */ + bool is_create; /* flag for create index */ +} PLtsql_stmt_fulltextindex; + /* * ASSERT statement */ @@ -2034,6 +2049,12 @@ extern int pltsql_yyparse(void); /* functions in pltsql_utils.c */ extern List *gen_grantschema_subcmds(const char *schema, const char *db_user, bool is_grant, bool with_grant_option, const char *privilege); +extern char *gen_createfulltextindex_cmds(const char *table_name, const char *schema_name, const List *column_name, const char *index_name); +extern char *gen_dropfulltextindex_cmds(const char *index_name, const char *schema_name); +extern char *get_fulltext_index_name(Oid relid, const char *table_name); +extern const char *gen_schema_name_for_fulltext_index(const char *schema_name); +extern bool check_fulltext_exist(const char *schema_name, const char *table_name); +extern bool is_unique_index(Oid relid, const char *index_name); extern int TsqlUTF8LengthInUTF16(const void *vin, int len); extern void TsqlCheckUTF16Length_bpchar(const char *s, int32 len, int32 maxlen, int charlen, bool isExplicit); extern void TsqlCheckUTF16Length_varchar(const char *s, int32 len, int32 maxlen, bool isExplicit); diff --git a/contrib/babelfishpg_tsql/src/pltsql_utils.c b/contrib/babelfishpg_tsql/src/pltsql_utils.c index 23afd8d714..df3c57df71 100644 --- a/contrib/babelfishpg_tsql/src/pltsql_utils.c +++ b/contrib/babelfishpg_tsql/src/pltsql_utils.c @@ -43,6 +43,10 @@ pltsql_createFunction(ParseState *pstate, PlannedStmt *pstmt, const char *queryS ParamListInfo params); extern bool restore_tsql_tabletype; +extern char *get_cur_db_name(void); +extern char *construct_unique_index_name(char *index_name, char *relation_name); +extern char *get_physical_schema_name(char *db_name, const char *schema_name); +extern const char *get_dbo_schema_name(const char *dbname); /* To cache oid of sys.varchar */ static Oid sys_varcharoid = InvalidOid; @@ -1791,3 +1795,204 @@ List return stmt_list; } + +/* + * Generates the schema name for fulltext index statements + * depending on whether it's master schema or not + */ +const char * +gen_schema_name_for_fulltext_index(const char *schema_name) +{ + char *dbname = get_cur_db_name(); + if (strlen(schema_name) == 0) + return get_dbo_schema_name(dbname); + else + return get_physical_schema_name(dbname, schema_name); +} + +/* + * check_fulltext_exist + * Check if the fulltext index exist for the given table and schema + * during execution of CONTAINS() statement + */ +bool +check_fulltext_exist(const char *schema_name, const char *table_name) +{ + const char *gen_schema_name = gen_schema_name_for_fulltext_index((char *)schema_name); + char *ft_index_name; + Oid schemaOid; + Oid relid; + + schemaOid = LookupExplicitNamespace(gen_schema_name, true); + + // Check if schema exists + if (!OidIsValid(schemaOid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("schema \"%s\" does not exist", + schema_name))); + + relid = get_relname_relid((const char *) table_name, schemaOid); + + + // Check if table exists + if (!OidIsValid(relid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s\" does not exist", + table_name))); + + ft_index_name = get_fulltext_index_name(relid, table_name); + return ft_index_name != NULL; +} + +/* + * get_fulltext_index_name + * Get the fulltext index name of a relation specified by OID + */ +char +*get_fulltext_index_name(Oid relid, const char *table_name) +{ + Relation relation = RelationIdGetRelation(relid); + List *indexoidlist = RelationGetIndexList(relation); + ListCell *cell; + char *ft_index_name = NULL; + char *table_name_cpy = palloc(strlen(table_name) + 1); + char *temp_ft_index_name; + + strcpy(table_name_cpy, table_name); + temp_ft_index_name = construct_unique_index_name("ft_index", table_name_cpy); + foreach(cell, indexoidlist) + { + Oid indexOid = lfirst_oid(cell); + ft_index_name = get_rel_name(indexOid); + + if (strcmp(ft_index_name, temp_ft_index_name) == 0) + break; + + ft_index_name = NULL; + } + + RelationClose(relation); + list_free(indexoidlist); + pfree(table_name_cpy); + return ft_index_name; +} + +/* + * is_unique_index + * Check if given index is unique index of a relation specified by OID + */ +bool +is_unique_index(Oid relid, const char *index_name) +{ + Relation relation = RelationIdGetRelation(relid); + List *indexoidlist = RelationGetIndexList(relation); + ListCell *cell; + bool is_unique = false; + int unique_key_count = 0; + + foreach(cell, indexoidlist) + { + Oid indexOid = lfirst_oid(cell); + char *name = get_rel_name(indexOid); + + if (strcmp(name, index_name) == 0) + { + HeapTuple indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexOid)); + Form_pg_index indexForm = (Form_pg_index) GETSTRUCT(indexTuple); + + /* Check if the key column is unique and is of single-key */ + if (indexForm->indisunique && indexForm->indrelid == relid && indexForm->indnkeyatts == 1) + { + /* Check if the key column is non-nullable */ + for (int i = 0; i < indexForm->indnatts; i++) + { + AttrNumber attnum = indexForm->indkey.values[i]; + if (attnum != 0) + { + HeapTuple attTuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if(HeapTupleIsValid(attTuple)) + { + Form_pg_attribute attForm = (Form_pg_attribute) GETSTRUCT(attTuple); + + if (attForm->attnotnull) + { + unique_key_count++; + if (unique_key_count > 1) + break; + } + } + ReleaseSysCache(attTuple); + } + } + + if (unique_key_count == 1) + is_unique = true; + } + + ReleaseSysCache(indexTuple); + break; + } + } + + RelationClose(relation); + list_free(indexoidlist); + return is_unique; +} + +char +*gen_createfulltextindex_cmds(const char *table_name, const char *schema_name, const List *column_name, const char *index_name) +{ + StringInfoData query; + + initStringInfo(&query); + + /* + * We prepare the following query to create a fulltext index. + * + * CREATE INDEX ON + * USING gin(to_tsvector('fts_contains_simple', )); + * + */ + appendStringInfo(&query, "CREATE INDEX \"%s\" ON ", index_name); + if (schema_name == NULL || strlen(schema_name) == 0 || *schema_name == '\0') + appendStringInfo(&query, "\"%s\"", table_name); + else + appendStringInfo(&query, "\"%s\".\"%s\"", schema_name, table_name); + + appendStringInfo(&query, "USING GIN("); + for (int i = 0; i < list_length(column_name); i++) + { + char *col_name = (char *) list_nth(column_name, i); + // Add column name + appendStringInfo(&query, "to_tsvector('fts_contains_simple', \"%s\")", col_name); + if (i != list_length(column_name) - 1) + appendStringInfo(&query, ", "); + } + appendStringInfo(&query, ")"); + + return query.data; +} + +char +*gen_dropfulltextindex_cmds(const char *index_name, const char *schema_name) +{ + StringInfoData query; + initStringInfo(&query); + /* + * We prepare the following query to drop a fulltext index. + * + * DROP INDEX + * + */ + appendStringInfo(&query, "DROP INDEX "); + + if (schema_name == NULL || strlen(schema_name) == 0 || *schema_name == '\0') + appendStringInfo(&query, "\"%s\"", index_name); + else + appendStringInfo(&query, "\"%s\".\"%s\"", schema_name, index_name); + return query.data; +} diff --git a/contrib/babelfishpg_tsql/src/stmt_walker.c b/contrib/babelfishpg_tsql/src/stmt_walker.c index 459f582ce2..e41ec44e76 100644 --- a/contrib/babelfishpg_tsql/src/stmt_walker.c +++ b/contrib/babelfishpg_tsql/src/stmt_walker.c @@ -109,6 +109,7 @@ stmt_walker(PLtsql_stmt *stmt, WalkerFunc walker, void *context) case PLTSQL_STMT_GRANTDB: case PLTSQL_STMT_CHANGE_DBOWNER: case PLTSQL_STMT_GRANTSCHEMA: + case PLTSQL_STMT_FULLTEXTINDEX: case PLTSQL_STMT_DBCC: break; /* TSQL-only executable node */ @@ -211,6 +212,7 @@ general_walker_func(PLtsql_stmt *stmt, void *context) DISPATCH(CHANGE_DBOWNER, change_dbowner) DISPATCH(DBCC, dbcc) DISPATCH(GRANTSCHEMA, grantschema) + DISPATCH(FULLTEXTINDEX, fulltextindex) /* TSQL-only executable node */ DISPATCH(SAVE_CTX, save_ctx) diff --git a/contrib/babelfishpg_tsql/src/stmt_walker.h b/contrib/babelfishpg_tsql/src/stmt_walker.h index ba14c59133..96e58e89eb 100644 --- a/contrib/babelfishpg_tsql/src/stmt_walker.h +++ b/contrib/babelfishpg_tsql/src/stmt_walker.h @@ -90,6 +90,7 @@ typedef bool (*Stmt_grantdb_act) ACTION_SIGNITURE(grantdb); typedef bool (*Stmt_change_dbowner_act) ACTION_SIGNITURE(change_dbowner); typedef bool (*Stmt_dbcc_act) ACTION_SIGNITURE(dbcc); typedef bool (*Stmt_grantschema_act) ACTION_SIGNITURE(grantschema); +typedef bool (*Stmt_fulltextindex_act) ACTION_SIGNITURE(fulltextindex); /* TSQL-only executable node */ typedef bool (*Stmt_save_ctx) ACTION_SIGNITURE(save_ctx); @@ -143,6 +144,7 @@ typedef struct Walker_context Stmt_change_dbowner_act change_dbowner_act; Stmt_dbcc_act dbcc_act; Stmt_grantschema_act grantschema_act; + Stmt_fulltextindex_act fulltextindex_act; /* TSQL-only executable node */ Stmt_save_ctx save_ctx_act; diff --git a/contrib/babelfishpg_tsql/src/tsqlIface.cpp b/contrib/babelfishpg_tsql/src/tsqlIface.cpp index ca2c0cb501..695b28dc46 100644 --- a/contrib/babelfishpg_tsql/src/tsqlIface.cpp +++ b/contrib/babelfishpg_tsql/src/tsqlIface.cpp @@ -85,6 +85,7 @@ extern "C" extern size_t get_num_pg_reserved_keywords_to_be_delimited(); extern char * construct_unique_index_name(char *index_name, char *relation_name); extern bool enable_hint_mapping; + extern bool check_fulltext_exist(const char *schema_name, const char *table_name); extern int escape_hatch_showplan_all; } @@ -116,6 +117,9 @@ PLtsql_stmt *makeDbccCheckidentStatement(TSqlParser::Dbcc_statementContext *ctx) PLtsql_stmt *makeSetExplainModeStatement(TSqlParser::Set_statementContext *ctx, bool is_explain_only); PLtsql_expr *makeTsqlExpr(const std::string &fragment, bool addSelect); PLtsql_expr *makeTsqlExpr(ParserRuleContext *ctx, bool addSelect); +PLtsql_stmt *makeCreateFulltextIndexStmt(TSqlParser::Create_fulltext_indexContext *ctx); +PLtsql_stmt *makeDropFulltextIndexStmt(TSqlParser::Drop_fulltext_indexContext *ctx); +std::pair getTableNameAndSchemaName(TSqlParser::Table_nameContext* ctx); void * makeBlockStmt(ParserRuleContext *ctx, tsqlBuilder &builder); void replaceTokenStringFromQuery(PLtsql_expr* expr, TerminalNode* tokenNode, const char* repl, ParserRuleContext *baseCtx); void replaceCtxStringFromQuery(PLtsql_expr* expr, ParserRuleContext *ctx, const char *repl, ParserRuleContext *baseCtx); @@ -124,6 +128,7 @@ void removeCtxStringFromQuery(PLtsql_expr* expr, ParserRuleContext *ctx, ParserR void extractQueryHintsFromOptionClause(TSqlParser::Option_clauseContext *octx); void extractTableHints(TSqlParser::With_table_hintsContext *tctx, std::string table_name); std::string extractTableName(TSqlParser::Ddl_objectContext *ctx, TSqlParser::Table_source_itemContext *tctx); +std::string extractSchemaName(TSqlParser::Ddl_objectContext *ctx, TSqlParser::Table_source_itemContext *tctx); void extractTableHint(TSqlParser::Table_hintContext *table_hint, std::string table_name); void extractJoinHint(TSqlParser::Join_hintContext *join_hint, std::string table_name1, std::string table_names); void extractJoinHintFromOption(TSqlParser::OptionContext *option); @@ -138,9 +143,10 @@ static bool post_process_alter_table(TSqlParser::Alter_tableContext *ctx, PLtsql static bool post_process_create_index(TSqlParser::Create_indexContext *ctx, PLtsql_stmt_execsql *stmt, TSqlParser::Ddl_statementContext *baseCtx); static bool post_process_create_database(TSqlParser::Create_databaseContext *ctx, PLtsql_stmt_execsql *stmt, TSqlParser::Ddl_statementContext *baseCtx); static bool post_process_create_type(TSqlParser::Create_typeContext *ctx, PLtsql_stmt_execsql *stmt, TSqlParser::Ddl_statementContext *baseCtx); -static void post_process_table_source(TSqlParser::Table_source_itemContext *ctx, PLtsql_expr *expr, ParserRuleContext *baseCtx); +static void post_process_table_source(TSqlParser::Table_source_itemContext *ctx, PLtsql_expr *expr, ParserRuleContext *baseCtx, bool is_freetext_predicate = false); static void post_process_declare_cursor_statement(PLtsql_stmt_decl_cursor *stmt, TSqlParser::Declare_cursorContext *ctx, tsqlBuilder &builder); static void post_process_declare_table_statement(PLtsql_stmt_decl_table *stmt, TSqlParser::Table_type_definitionContext *ctx); +static bool check_freetext_predicate(TSqlParser::Search_conditionContext *ctx); static PLtsql_var *lookup_cursor_variable(const char *varname); static PLtsql_var *build_cursor_variable(const char *curname, int lineno); static int read_extended_cursor_option(TSqlParser::Declare_cursor_optionsContext *ctx, int current_cursor_option); @@ -1761,13 +1767,19 @@ class tsqlBuilder : public tsqlCommonMutator { stmt = makeChangeDbOwnerStatement(ctx->alter_authorization()); } - else + else if (ctx->create_fulltext_index()) + { + stmt = makeCreateFulltextIndexStmt(ctx->create_fulltext_index()); + } + else if (ctx->drop_fulltext_index()) + { + stmt = makeDropFulltextIndexStmt(ctx->drop_fulltext_index()); + } + else { stmt = makeSQL(ctx); } graft(stmt, peekContainer()); - - // clean up object_name positions maps before entering clear_rewritten_query_fragment(); } @@ -1779,6 +1791,16 @@ class tsqlBuilder : public tsqlCommonMutator return; } + if (ctx->create_fulltext_index()) + { + clear_rewritten_query_fragment(); + return; + } + if (ctx->drop_fulltext_index()) + { + clear_rewritten_query_fragment(); + return; + } PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) getPLtsql_fragment(ctx); Assert(stmt); // record that the stmt is ddl @@ -1802,13 +1824,11 @@ class tsqlBuilder : public tsqlCommonMutator nop = post_process_create_database(ctx->create_database(), stmt, ctx); else if (ctx->create_type()) nop = post_process_create_type(ctx->create_type(), stmt, ctx); - else if (ctx->create_fulltext_index() || - ctx->alter_fulltext_index() || - ctx->drop_fulltext_index()) + else if (ctx->alter_fulltext_index()) { ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("FULLTEXT related statements will be ignored."))); + errmsg("ALTER FULLTEXT INDEX statement will be ignored."))); nop = true; } @@ -2787,6 +2807,10 @@ static void process_query_specification( } } + bool is_freetext_predicate = false; + if(qctx->where) + is_freetext_predicate = check_freetext_predicate(qctx->where); + PLtsql_expr *expr = mutator->expr; ParserRuleContext* baseCtx = mutator->ctx; @@ -2794,7 +2818,7 @@ static void process_query_specification( if (qctx->table_sources()) { for (auto tctx : qctx->table_sources()->table_source_item()) // from-clause (to remove hints) - post_process_table_source(tctx, expr, baseCtx); + post_process_table_source(tctx, expr, baseCtx, is_freetext_predicate); } /* handle special alias syntax and quote alias */ @@ -4004,6 +4028,22 @@ void extractTableHints(TSqlParser::With_table_hintsContext *tctx, std::string ta } } +std::string extractSchemaName(TSqlParser::Ddl_objectContext *dctx, TSqlParser::Table_source_itemContext *tctx) +{ + std::string schema_name = ""; + if (dctx == nullptr) + { + if (tctx->full_object_name() && tctx->full_object_name()->schema) + schema_name = stripQuoteFromId(tctx->full_object_name()->schema); + } + else + { + if (dctx->full_object_name() && dctx->full_object_name()->schema) + schema_name = stripQuoteFromId(dctx->full_object_name()->schema); + } + return schema_name; +} + std::string extractTableName(TSqlParser::Ddl_objectContext *dctx, TSqlParser::Table_source_itemContext *tctx) { std::string table_name; @@ -6266,10 +6306,27 @@ void process_execsql_destination(TSqlParser::Dml_statementContext *ctx, PLtsql_s } } -static void post_process_table_source(TSqlParser::Table_source_itemContext *ctx, PLtsql_expr *expr, ParserRuleContext *baseCtx) +static bool check_freetext_predicate(TSqlParser::Search_conditionContext *ctx) +{ + if (ctx && ctx->predicate_br().size() > 0) + { + for (auto pred : ctx->predicate_br()) + { + if (pred && pred->predicate() && pred->predicate()->freetext_predicate()) + return true; + if (pred && pred->search_condition()) { + if (check_freetext_predicate(pred->search_condition())) + return true; + } + } + } + return false; +} + +static void post_process_table_source(TSqlParser::Table_source_itemContext *ctx, PLtsql_expr *expr, ParserRuleContext *baseCtx, bool is_freetext_predicate) { for (auto cctx : ctx->table_source_item()) - post_process_table_source(cctx, expr, baseCtx); + post_process_table_source(cctx, expr, baseCtx, is_freetext_predicate); std::string table_name = extractTableName(nullptr, ctx); @@ -6315,6 +6372,19 @@ static void post_process_table_source(TSqlParser::Table_source_itemContext *ctx, } removeCtxStringFromQuery(expr, ctx->join_hint(), baseCtx); } + + // check for freetext predicate CONTAINS() + if(is_freetext_predicate) + { + std::string schema_name = extractSchemaName(nullptr, ctx); + + const char * t_name = downcase_truncate_identifier(table_name.c_str(), table_name.length(), true); + const char * s_name = downcase_truncate_identifier(schema_name.c_str(), schema_name.length(), true); + + // check if full-text index exist for the table, if not throw error + if(!check_fulltext_exist(const_cast (s_name), const_cast (t_name))) + throw PGErrorWrapperException(ERROR, ERRCODE_RAISE_EXCEPTION, format_errmsg("Cannot use a CONTAINS or FREETEXT predicate on table or indexed view '%s' because it is not full-text indexed.", table_name.c_str()), getLineAndPos(ctx)); + } } void process_execsql_remove_unsupported_tokens(TSqlParser::Dml_statementContext *ctx, PLtsql_expr_query_mutator *exprMutator) @@ -6615,6 +6685,91 @@ post_process_alter_table(TSqlParser::Alter_tableContext *ctx, PLtsql_stmt_execsq return false; } +std::pair +getTableNameAndSchemaName(TSqlParser::Table_nameContext* ctx) +{ + std::string table_info = ::getFullText(ctx); + std::string table_name = ""; + std::string schema_name = ""; + size_t pos = table_info.find("."); + if (pos != std::string::npos) { + // Extract the schema name before the "." + schema_name = table_info.substr(0, pos); + // Extract the table name after the "." + table_name = table_info.substr(pos + 1); + } else { + // No "." character found, set first to the entire string + table_name = table_info; + } + return std::make_pair(downcase_truncate_identifier(table_name.c_str(), table_name.length(), true), + downcase_truncate_identifier(schema_name.c_str(), schema_name.length(), true)); +} + +PLtsql_stmt * +makeCreateFulltextIndexStmt(TSqlParser::Create_fulltext_indexContext *ctx) +{ + PLtsql_stmt_fulltextindex *stmt = (PLtsql_stmt_fulltextindex *) palloc0(sizeof(PLtsql_stmt_fulltextindex)); + stmt->cmd_type = PLTSQL_STMT_FULLTEXTINDEX; + stmt->lineno = getLineNo(ctx); + stmt->is_create = true; + + if (ctx->table_name()) + { + auto table_info = getTableNameAndSchemaName(ctx->table_name()); + stmt->table_name = pstrdup(table_info.first.c_str()); + stmt->schema_name = pstrdup(table_info.second.c_str()); + } + List *column_name_list = NIL; + if (ctx->fulltext_index_column().size() > 0) + { + for (auto column : ctx->fulltext_index_column()) + { + if (column->TYPE() && column->COLUMN()) + throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "'TYPE COLUMN' option is not currently supported in Babelfish", getLineAndPos(column->TYPE())); + else if (column->LANGUAGE()) + throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "'LANGUAGE' option is not currently supported in Babelfish", getLineAndPos(column->LANGUAGE())); + else if (column->STATISTICAL_SEMANTICS()) + throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "'STATISTICAL_SEMANTICS' option is not currently supported in Babelfish", getLineAndPos(column->STATISTICAL_SEMANTICS())); + else + { + std::string column_name_str = ::getFullText(column->full_column_name()); + char *column_name = pstrdup(downcase_truncate_identifier(column_name_str.c_str(), column_name_str.length(), true)); + column_name_list = lappend(column_name_list, column_name); + } + + } + stmt->column_name = column_name_list; + } + if (ctx->catalog_filegroup_option()) + throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "'CATALOG FILEGROUP OPTION' is not currently supported in Babelfish", getLineAndPos(ctx)); + if (ctx->fulltext_with_option().size() > 0) + throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "'WITH OPTION' is not currently supported in Babelfish", getLineAndPos(ctx)); + if (ctx->id()) + { + std::string index_name = ::getFullText(ctx->id()); + stmt->index_name = pstrdup(downcase_truncate_identifier(index_name.c_str(), index_name.length(), true)); + } + attachPLtsql_fragment(ctx, (PLtsql_stmt *) stmt); + return (PLtsql_stmt *) stmt; +} + +PLtsql_stmt * +makeDropFulltextIndexStmt(TSqlParser::Drop_fulltext_indexContext *ctx) +{ + PLtsql_stmt_fulltextindex *stmt = (PLtsql_stmt_fulltextindex *) palloc0(sizeof(PLtsql_stmt_fulltextindex)); + stmt->cmd_type = PLTSQL_STMT_FULLTEXTINDEX; + stmt->lineno = getLineNo(ctx); + stmt->is_create = false; + if (ctx->table_name()) + { + auto table_info = getTableNameAndSchemaName(ctx->table_name()); + stmt->table_name = pstrdup(table_info.first.c_str()); + stmt->schema_name = pstrdup(table_info.second.c_str()); + } + attachPLtsql_fragment(ctx, (PLtsql_stmt *) stmt); + return (PLtsql_stmt *) stmt; +} + static bool post_process_create_index(TSqlParser::Create_indexContext *ctx, PLtsql_stmt_execsql *stmt, TSqlParser::Ddl_statementContext *baseCtx) { diff --git a/contrib/babelfishpg_tsql/src/tsqlNodes.h b/contrib/babelfishpg_tsql/src/tsqlNodes.h index 7c35038083..27c6869442 100644 --- a/contrib/babelfishpg_tsql/src/tsqlNodes.h +++ b/contrib/babelfishpg_tsql/src/tsqlNodes.h @@ -52,6 +52,7 @@ typedef enum pltsql_stmt_type PLTSQL_STMT_CHANGE_DBOWNER, PLTSQL_STMT_DBCC, PLTSQL_STMT_GRANTSCHEMA + PLTSQL_STMT_FULLTEXTINDEX } PLtsql_stmt_type; typedef struct PLtsql_expr diff --git a/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp b/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp index 9ac12962b5..29cdc5ac32 100644 --- a/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp +++ b/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp @@ -1057,6 +1057,10 @@ antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitDdl_statement(TSqlParser:: if (create_user->WITHOUT()) handle(INSTR_UNSUPPORTED_TSQL_UNKNOWN_DDL, "CREATE USER WITHOUT LOGIN", getLineAndPos(ctx)); } + if(ctx->alter_fulltext_index()) + { + handle(INSTR_UNSUPPORTED_TSQL_UNKNOWN_DDL, "ALTER FULLTEXT INDEX", getLineAndPos(ctx)); + } /* * We have more than 100 DDLs but support a few of them. * manage the whitelist here. diff --git a/test/JDBC/expected/BABEL-UNSUPPORTED.out b/test/JDBC/expected/BABEL-UNSUPPORTED.out index aac7e87ad8..0f82d7b1e5 100644 --- a/test/JDBC/expected/BABEL-UNSUPPORTED.out +++ b/test/JDBC/expected/BABEL-UNSUPPORTED.out @@ -707,9 +707,15 @@ strict - -- escape hatch: fulltext -- 'strict' is default +CREATE DATABASE db_unsupported_ft WITH DEFAULT_FULLTEXT_LANGUAGE = English; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'DEFAULT_FULLTEXT_LANGUAGE' is not currently supported in Babelfish. please use babelfishpg_tsql.escape_hatch_fulltext to ignore)~~ + + CREATE TABLE t_unsupported_ft (a text); GO @@ -723,13 +729,6 @@ GO DROP TABLE t_unsupported_ft; GO -CREATE DATABASE db_unsupported_ft WITH DEFAULT_FULLTEXT_LANGUAGE = English; -GO -~~ERROR (Code: 33557097)~~ - -~~ERROR (Message: 'DEFAULT_FULLTEXT_LANGUAGE' is not currently supported in Babelfish. please use babelfishpg_tsql.escape_hatch_fulltext to ignore)~~ - - SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') GO ~~START~~ @@ -738,18 +737,46 @@ ignore ~~END~~ -CREATE TABLE t_unsupported_ft (a text); +CREATE DATABASE db_unsupported_ft WITH DEFAULT_FULLTEXT_LANGUAGE = English; GO -CREATE FULLTEXT INDEX ON t_unsupported_ft(a) KEY INDEX ix_unsupported_ft; +DROP DATABASE db_unsupported_ft; GO -DROP TABLE t_unsupported_ft; +CREATE SCHEMA t_unsupported_s_ft; GO -CREATE DATABASE db_unsupported_ft WITH DEFAULT_FULLTEXT_LANGUAGE = English; +CREATE TABLE t_unsupported_s_ft.t_unsupported_ft1 (id int not null, a text); GO -DROP DATABASE db_unsupported_ft; + +CREATE UNIQUE INDEX ix_unsupported_ft1 ON t_unsupported_s_ft.t_unsupported_ft1(id); +GO + +CREATE FULLTEXT INDEX ON t_unsupported_s_ft.t_unsupported_ft1(a) KEY INDEX ix_unsupported_ft1; +GO + +DROP FULLTEXT INDEX ON t_unsupported_s_ft.t_unsupported_ft1; +GO + +DROP TABLE t_unsupported_s_ft.t_unsupported_ft1; +GO + +DROP SCHEMA t_unsupported_s_ft; +GO + +CREATE TABLE t_unsupported_ft2 (id int not null, a text); +GO + +CREATE UNIQUE INDEX ix_unsupported_ft2 ON t_unsupported_ft2(id); +GO + +CREATE FULLTEXT INDEX ON t_unsupported_ft2(a) KEY INDEX ix_unsupported_ft2; +GO + +DROP FULLTEXT INDEX ON t_unsupported_ft2; +GO + +DROP TABLE t_unsupported_ft2; GO SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') @@ -2000,7 +2027,7 @@ babelfishpg_tsql.escape_hatch_checkpoint#!#ignore#!#escape hatch for CHECKPOINT babelfishpg_tsql.escape_hatch_constraint_name_for_default#!#ignore#!#escape hatch for DEFAULT option in alter table add constraint babelfishpg_tsql.escape_hatch_database_misc_options#!#ignore#!#escape hatch for misc options in CREATE/ALTER DATABASE babelfishpg_tsql.escape_hatch_for_replication#!#strict#!#escape hatch for (NOT) FOR REPLICATION option -babelfishpg_tsql.escape_hatch_fulltext#!#strict#!#escape hatch for fulltext +babelfishpg_tsql.escape_hatch_fulltext#!#strict#!#escape hatch for fulltext search babelfishpg_tsql.escape_hatch_ignore_dup_key#!#strict#!#escape hatch for ignore_dup_key=on option in CREATE/ALTER TABLE/INDEX babelfishpg_tsql.escape_hatch_index_clustering#!#ignore#!#escape hatch for CLUSTERED option in CREATE INDEX babelfishpg_tsql.escape_hatch_index_columnstore#!#strict#!#escape hatch for COLUMNSTORE option in CREATE INDEX @@ -2068,7 +2095,7 @@ babelfishpg_tsql.escape_hatch_checkpoint#!#ignore#!#escape hatch for CHECKPOINT babelfishpg_tsql.escape_hatch_constraint_name_for_default#!#ignore#!#escape hatch for DEFAULT option in alter table add constraint babelfishpg_tsql.escape_hatch_database_misc_options#!#ignore#!#escape hatch for misc options in CREATE/ALTER DATABASE babelfishpg_tsql.escape_hatch_for_replication#!#strict#!#escape hatch for (NOT) FOR REPLICATION option -babelfishpg_tsql.escape_hatch_fulltext#!#strict#!#escape hatch for fulltext +babelfishpg_tsql.escape_hatch_fulltext#!#strict#!#escape hatch for fulltext search babelfishpg_tsql.escape_hatch_ignore_dup_key#!#strict#!#escape hatch for ignore_dup_key=on option in CREATE/ALTER TABLE/INDEX babelfishpg_tsql.escape_hatch_index_clustering#!#ignore#!#escape hatch for CLUSTERED option in CREATE INDEX babelfishpg_tsql.escape_hatch_index_columnstore#!#strict#!#escape hatch for COLUMNSTORE option in CREATE INDEX @@ -2117,7 +2144,7 @@ babelfishpg_tsql.escape_hatch_checkpoint#!#ignore#!#escape hatch for CHECKPOINT babelfishpg_tsql.escape_hatch_constraint_name_for_default#!#ignore#!#escape hatch for DEFAULT option in alter table add constraint babelfishpg_tsql.escape_hatch_database_misc_options#!#ignore#!#escape hatch for misc options in CREATE/ALTER DATABASE babelfishpg_tsql.escape_hatch_for_replication#!#strict#!#escape hatch for (NOT) FOR REPLICATION option -babelfishpg_tsql.escape_hatch_fulltext#!#strict#!#escape hatch for fulltext +babelfishpg_tsql.escape_hatch_fulltext#!#strict#!#escape hatch for fulltext search babelfishpg_tsql.escape_hatch_ignore_dup_key#!#strict#!#escape hatch for ignore_dup_key=on option in CREATE/ALTER TABLE/INDEX babelfishpg_tsql.escape_hatch_index_clustering#!#ignore#!#escape hatch for CLUSTERED option in CREATE INDEX babelfishpg_tsql.escape_hatch_index_columnstore#!#strict#!#escape hatch for COLUMNSTORE option in CREATE INDEX diff --git a/test/JDBC/expected/FULLTEXT_INDEX-vu-cleanup.out b/test/JDBC/expected/FULLTEXT_INDEX-vu-cleanup.out new file mode 100644 index 0000000000..f5641a5aa1 --- /dev/null +++ b/test/JDBC/expected/FULLTEXT_INDEX-vu-cleanup.out @@ -0,0 +1,172 @@ +-- psql +-- enable CONTAINS +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + + +-- tsql user=testLogin password=abc +USE master; +GO + +-- enable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO +~~START~~ +text +ignore +~~END~~ + + +-- should throw error because login doesn't have sufficient permissions to DROP FULLTEXT INDEX +DROP FULLTEXT INDEX ON fti_table_t1; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Cannot drop the full-text index, because it does not exist or you do not have permission)~~ + + +-- tsql user=jdbc_user password=12345678 +-- enable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO +~~START~~ +text +ignore +~~END~~ + + +DROP FULLTEXT INDEX ON fti_table_t1; +GO + +DROP TABLE IF EXISTS fti_table_t1; +GO + +DROP FULLTEXT INDEX ON fti_table_t2; +GO + +DROP TABLE IF EXISTS fti_table_t2; +GO + +DROP FULLTEXT INDEX ON fti_table_t3; +GO + +DROP TABLE IF EXISTS fti_table_t3; +GO + +DROP FULLTEXT INDEX ON fti_table_t4; +GO + +DROP TABLE IF EXISTS fti_table_t4; +GO + +DROP FULLTEXT INDEX ON fti_table_t5; +GO + +DROP TABLE IF EXISTS fti_table_t5; +GO + +DROP FULLTEXT INDEX ON fti_table_t6; +GO + +DROP TABLE IF EXISTS fti_table_t6; +GO + +DROP FULLTEXT INDEX ON fti_table_t7; +GO + +DROP TABLE IF EXISTS fti_table_t7; +GO + +DROP FULLTEXT INDEX ON fti_schema_s1.fti_table_t8; +GO + +DROP TABLE IF EXISTS fti_schema_s1.fti_table_t8; +GO + +DROP SCHEMA IF EXISTS fti_schema_s1; +GO + +DROP FULLTEXT INDEX ON fti_schema_s2.fti_table_t8; +GO + +DROP TABLE IF EXISTS fti_schema_s2.fti_table_t8; +GO + +DROP SCHEMA IF EXISTS fti_schema_s2; +GO + +-- should throw error as there is no index in the table +DROP FULLTEXT INDEX ON fti_table_no_ix; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Table or indexed view "fti_table_no_ix" does not have a full-text index or user does not have permission to perform this action.)~~ + + +DROP TABLE IF EXISTS fti_table_no_ix; +GO + +DROP TABLE IF EXISTS fti_table_unsupported; +GO + +DROP VIEW IF EXISTS fti_prepare_v1; +GO + +DROP PROCEDURE IF EXISTS fti_prepare_p1; +GO + +DROP FUNCTION IF EXISTS fti_prepare_f1; +GO + +DROP TABLE IF EXISTS fti_schema_s3.fti_table_t9; +GO + +DROP SCHEMA IF EXISTS fti_schema_s3; +GO + +-- psql +-- Need to terminate active session before cleaning up the login +SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL) +WHERE sys.suser_name(usesysid) = 'testLogin' AND backend_type = 'client backend' AND usesysid IS NOT NULL; +GO +~~START~~ +bool +t +~~END~~ + +-- Wait to sync with another session +SELECT pg_sleep(1); +GO +~~START~~ +void + +~~END~~ + + +-- tsql +DROP LOGIN testLogin; +GO + +-- disable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') +GO +~~START~~ +text +strict +~~END~~ + + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + diff --git a/test/JDBC/expected/FULLTEXT_INDEX-vu-prepare.out b/test/JDBC/expected/FULLTEXT_INDEX-vu-prepare.out new file mode 100644 index 0000000000..10d0ed1fbb --- /dev/null +++ b/test/JDBC/expected/FULLTEXT_INDEX-vu-prepare.out @@ -0,0 +1,218 @@ +-- psql +-- enable CONTAINS +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + + +-- tsql user=jdbc_user password=12345678 +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO +~~START~~ +text +ignore +~~END~~ + + +CREATE LOGIN testLogin WITH PASSWORD = '123'; +GO + +USE master; +GO + +CREATE SCHEMA fti_schema_s3; +GO + +CREATE TABLE fti_schema_s3.fti_table_t9(id int NOT NULL, a text); +GO + +CREATE UNIQUE INDEX IX_t9_a ON fti_schema_s3.fti_table_t9(id); +GO + +-- tsql user=testLogin password=123 +-- Create new login with insufficient permissions +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO +~~START~~ +text +ignore +~~END~~ + + +-- should throw error as new login has insufficient permissions to CREATE FULLTEXT INDEX +CREATE FULLTEXT INDEX ON fti_schema_s3.fti_table_t9(a) KEY INDEX IX_t9_a; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: A default full-text catalog does not exist in the database or user does not have permission to perform this action)~~ + + +-- tsql user=jdbc_user password=12345678 +-- Reset to super user +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO +~~START~~ +text +ignore +~~END~~ + + +-- Index creation on different character data type columns +CREATE TABLE fti_table_t1(id int NOT NULL, a text); +GO + +-- should throw syntax error for NULL index name +CREATE FULLTEXT INDEX ON fti_table_t1(a) KEY INDEX NULL; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: syntax error near 'NULL' at line 2 and character position 51)~~ + + +-- should throw error for no unique index on the table +CREATE FULLTEXT INDEX ON fti_table_t1(a) KEY INDEX IX_t1_a; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: '"ix_t1_a"' is not a valid index to enforce a full-text search key. A full-text search key must be a unique, non-nullable, single-column index which is not offline, is not defined on a non-deterministic or imprecise nonpersisted computed column, does not have a filter, and has maximum size of 900 bytes. Choose another index for the full-text key.)~~ + + +CREATE UNIQUE INDEX IX_t1_a ON fti_table_t1(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t1(a) KEY INDEX IX_t1_a; +GO + +CREATE TABLE fti_table_t2(id int not null, b char(10)); +GO + +CREATE UNIQUE INDEX IX_t2_b ON fti_table_t2(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t2(b) KEY INDEX IX_t2_b; +GO + +CREATE TABLE fti_table_t3(id int not null, c varchar(10)); +GO + +CREATE UNIQUE INDEX IX_t3_c ON fti_table_t3(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t3(c) KEY INDEX IX_t3_c; +GO + +CREATE TABLE fti_table_t4(id int not null, d nvarchar(10)); +GO + +CREATE UNIQUE INDEX IX_t4_d ON fti_table_t4(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t4(d) KEY INDEX IX_t4_d; +GO + +CREATE TABLE fti_table_t5(id int not null, e nchar(10)); +GO + +CREATE UNIQUE INDEX IX_t5_e ON fti_table_t5(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t5(e) KEY INDEX IX_t5_e; +GO + +CREATE TABLE fti_table_t6(id int not null, f ntext); +GO + +CREATE UNIQUE INDEX IX_t6_f ON fti_table_t6(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t6(f) KEY INDEX IX_t6_f; +GO + +CREATE TABLE fti_table_t7(id int not null, a1 text, b1 char(10), c1 varchar(10)); +GO + +CREATE UNIQUE INDEX IX_t7_a1b1c1 ON fti_table_t7(id); +GO + +-- multi column index creation +CREATE FULLTEXT INDEX ON fti_table_t7(a1, b1, c1) KEY INDEX IX_t7_a1b1c1; +GO + +-- checking if the indexes are created correctly +CREATE VIEW fti_prepare_v1 AS (SELECT indexname FROM pg_indexes WHERE tablename='fti_table_t1' AND indexname LIKE 'ft_index%'); +GO + +CREATE PROCEDURE fti_prepare_p1 AS (SELECT indexname FROM pg_indexes WHERE tablename='fti_table_t2' AND indexname LIKE 'ft_index%'); +GO + + +CREATE FUNCTION fti_prepare_f1() +RETURNS NVARCHAR(MAX) AS +BEGIN + DECLARE @indexName NVARCHAR(MAX); + SELECT @indexName= indexname + FROM pg_indexes + WHERE tablename='fti_table_t3' AND indexname LIKE 'ft_index%'; + RETURN @indexName; +END +GO + +-- Creating index in a new schema +CREATE SCHEMA fti_schema_s1; +GO + +CREATE TABLE fti_schema_s1.fti_table_t8(id int not null, a text, b text); +GO + +CREATE UNIQUE INDEX IX_s1_t8_a ON fti_schema_s1.fti_table_t8(id); +GO + +CREATE FULLTEXT INDEX ON fti_schema_s1.fti_table_t8(a) KEY INDEX IX_s1_t8_a; +GO + +CREATE SCHEMA FTI_schema_s2; +GO + +CREATE TABLE FTI_schema_s2.FTI_table_t8(Id int not null, a text, b text); +GO + +CREATE UNIQUE INDEX IX_s2_t8_a ON FTI_schema_s2.FTI_table_t8(Id); +GO + +-- Case for same table name on different schemas +CREATE FULLTEXT INDEX ON FTI_schema_s2.FTI_table_t8(a) KEY INDEX IX_s2_t8_a; +GO + +-- Table for testing dropping non-existent index, should throw error on dropping +CREATE TABLE fti_table_no_ix(id int not null, a text) +GO + +-- Table for testing unsupported options +CREATE TABLE fti_table_unsupported(id int not null, a text) +GO + +CREATE UNIQUE INDEX ix_unsupported_fti ON fti_table_unsupported(id); +GO + +-- disable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') +GO +~~START~~ +text +strict +~~END~~ + + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + diff --git a/test/JDBC/expected/FULLTEXT_INDEX-vu-verify.out b/test/JDBC/expected/FULLTEXT_INDEX-vu-verify.out new file mode 100644 index 0000000000..f023ce14cd --- /dev/null +++ b/test/JDBC/expected/FULLTEXT_INDEX-vu-verify.out @@ -0,0 +1,192 @@ +-- psql +-- enable CONTAINS +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + + +-- tsql user=jdbc_user password=12345678 +-- enable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO +~~START~~ +text +ignore +~~END~~ + + +USE master; +GO + +-- Fetching index details to check if index is created correctly +SELECT * FROM fti_prepare_v1; +GO +~~START~~ +varchar +ft_indexfti_table_t114dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +EXEC fti_prepare_p1; +GO +~~START~~ +varchar +ft_indexfti_table_t214dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +SELECT fti_prepare_f1(); +GO +~~START~~ +nvarchar +ft_indexfti_table_t314dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t4' AND indexname LIKE 'ft_index%'; +GO +~~START~~ +varchar#!#varchar +fti_table_t4#!#ft_indexfti_table_t414dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t5' AND indexname LIKE 'ft_index%'; +GO +~~START~~ +varchar#!#varchar +fti_table_t5#!#ft_indexfti_table_t514dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t6' AND indexname LIKE 'ft_index%'; +GO +~~START~~ +varchar#!#varchar +fti_table_t6#!#ft_indexfti_table_t614dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t7' AND indexname LIKE 'ft_index%'; +GO +~~START~~ +varchar#!#varchar +fti_table_t7#!#ft_indexfti_table_t714dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t8' AND indexname LIKE 'ft_index%'; +GO +~~START~~ +varchar#!#varchar +fti_table_t8#!#ft_indexfti_table_t814dc211cf58dcb9fe2047eb8aa8a5bc0 +fti_table_t8#!#ft_indexfti_table_t814dc211cf58dcb9fe2047eb8aa8a5bc0 +~~END~~ + + +-- Creating more than 1 fulltext index in a table, should throw error +CREATE FULLTEXT INDEX ON fti_schema_s1.fti_table_t8(b) KEY INDEX IX_s1_t8_a; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: A full-text index for table or indexed view "fti_table_t8" has already been created.)~~ + + +-- Creating index in a non existent table of a schema, should throw error +CREATE FULLTEXT INDEX ON fti_schema_s1.fti_table_t9(a) KEY INDEX IX_s1_t9_a; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: relation "fti_table_t9" does not exist)~~ + + +-- Creating index in a table of a non existent schema, should throw error +CREATE FULLTEXT INDEX ON fti_schema_s4.fti_table_t8(a) KEY INDEX IX_s2_t8_a; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: schema "fti_schema_s4" does not exist)~~ + + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a TYPE COLUMN a) KEY INDEX ix_unsupported_fti; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'TYPE COLUMN' option is not currently supported in Babelfish)~~ + + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a LANGUAGE 1033) KEY INDEX ix_unsupported_fti; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'LANGUAGE' option is not currently supported in Babelfish)~~ + + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a STATISTICAL_SEMANTICS) KEY INDEX ix_unsupported_fti; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'STATISTICAL_SEMANTICS' option is not currently supported in Babelfish)~~ + + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti ON t_unsupported_catalog; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'CATALOG FILEGROUP OPTION' is not currently supported in Babelfish)~~ + + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti WITH CHANGE_TRACKING OFF; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'WITH OPTION' is not currently supported in Babelfish)~~ + + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti WITH STOPLIST = SYSTEM; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'WITH OPTION' is not currently supported in Babelfish)~~ + + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti WITH SEARCH PROPERTY LIST = DocumentPropertyList; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'WITH OPTION' is not currently supported in Babelfish)~~ + + +-- reset the login password +ALTER LOGIN testLogin WITH PASSWORD = 'abc'; +GO + +-- disable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') +GO +~~START~~ +text +strict +~~END~~ + + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + diff --git a/test/JDBC/expected/fts-contains-vu-cleanup.out b/test/JDBC/expected/fts-contains-vu-cleanup.out index eae3f62ab6..a3ad31a39a 100644 --- a/test/JDBC/expected/fts-contains-vu-cleanup.out +++ b/test/JDBC/expected/fts-contains-vu-cleanup.out @@ -1,3 +1,4 @@ +-- tsql user=jdbc_user password=12345678 -- enable CONTAINS SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') GO @@ -10,6 +11,9 @@ ignore DROP PROCEDURE fts_contains_vu_prepare_p1; GO +DROP FULLTEXT INDEX ON fts_contains_vu_t; +GO + DROP TABLE fts_contains_vu_t; GO @@ -24,3 +28,13 @@ text strict ~~END~~ + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + diff --git a/test/JDBC/expected/fts-contains-vu-prepare.out b/test/JDBC/expected/fts-contains-vu-prepare.out index 3bf1526733..1fefb90b7c 100644 --- a/test/JDBC/expected/fts-contains-vu-prepare.out +++ b/test/JDBC/expected/fts-contains-vu-prepare.out @@ -1,4 +1,15 @@ +-- psql -- enable CONTAINS +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + + +-- tsql user=jdbc_user password=12345678 SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') GO ~~START~~ @@ -8,7 +19,21 @@ ignore -- Create table for full text search CONTAINS predicate -CREATE TABLE fts_contains_vu_t (id int PRIMARY KEY, txt text) +CREATE TABLE fts_contains_vu_t (id int NOT NULL, txt text) +GO + +-- Should throw error because there exist no full-text index for the table +SELECT * FROM fts_contains_vu_t WHERE CONTAINS(txt, 'run'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Cannot use a CONTAINS or FREETEXT predicate on table or indexed view 'fts_contains_vu_t' because it is not full-text indexed.)~~ + + +CREATE UNIQUE INDEX fts_contains_vu_t_id_idx ON fts_contains_vu_t(id); +GO + +CREATE FULLTEXT INDEX ON fts_contains_vu_t(txt) KEY INDEX fts_contains_vu_t_id_idx; GO -- Full text search @query_string using CONTAINS diff --git a/test/JDBC/expected/fts-contains-vu-verify.out b/test/JDBC/expected/fts-contains-vu-verify.out index 9478cf0da0..5d88b8ad0a 100644 --- a/test/JDBC/expected/fts-contains-vu-verify.out +++ b/test/JDBC/expected/fts-contains-vu-verify.out @@ -1,3 +1,15 @@ +-- psql +-- enable CONTAINS +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO +~~START~~ +bool +t +~~END~~ + + +-- tsql user=jdbc_user password=12345678 -- enable CONTAINS SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') GO @@ -312,7 +324,7 @@ EXEC fts_contains_vu_prepare_p1 '"conf*"', 20 GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: Prefix term not supported)~~ +~~ERROR (Message: Prefix term is not currently supported in Babelfish)~~ -- Generation Term not supported @@ -320,5 +332,14 @@ EXEC fts_contains_vu_prepare_p1 'FORMSOF(THESAURUS, love)' GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: Generation term not supported)~~ +~~ERROR (Message: Generation term is not currently supported in Babelfish)~~ + + +-- disable CONTAINS +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') +GO +~~START~~ +text +strict +~~END~~ diff --git a/test/JDBC/input/BABEL-UNSUPPORTED.sql b/test/JDBC/input/BABEL-UNSUPPORTED.sql index 85db1a172e..ec93c5dd82 100644 --- a/test/JDBC/input/BABEL-UNSUPPORTED.sql +++ b/test/JDBC/input/BABEL-UNSUPPORTED.sql @@ -429,6 +429,8 @@ GO -- escape hatch: fulltext -- 'strict' is default +CREATE DATABASE db_unsupported_ft WITH DEFAULT_FULLTEXT_LANGUAGE = English; +GO CREATE TABLE t_unsupported_ft (a text); GO @@ -439,24 +441,49 @@ GO DROP TABLE t_unsupported_ft; GO +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO + CREATE DATABASE db_unsupported_ft WITH DEFAULT_FULLTEXT_LANGUAGE = English; GO -SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +DROP DATABASE db_unsupported_ft; GO -CREATE TABLE t_unsupported_ft (a text); +CREATE SCHEMA t_unsupported_s_ft; GO -CREATE FULLTEXT INDEX ON t_unsupported_ft(a) KEY INDEX ix_unsupported_ft; +CREATE TABLE t_unsupported_s_ft.t_unsupported_ft1 (id int not null, a text); GO -DROP TABLE t_unsupported_ft; +CREATE UNIQUE INDEX ix_unsupported_ft1 ON t_unsupported_s_ft.t_unsupported_ft1(id); GO -CREATE DATABASE db_unsupported_ft WITH DEFAULT_FULLTEXT_LANGUAGE = English; +CREATE FULLTEXT INDEX ON t_unsupported_s_ft.t_unsupported_ft1(a) KEY INDEX ix_unsupported_ft1; GO -DROP DATABASE db_unsupported_ft; + +DROP FULLTEXT INDEX ON t_unsupported_s_ft.t_unsupported_ft1; +GO + +DROP TABLE t_unsupported_s_ft.t_unsupported_ft1; +GO + +DROP SCHEMA t_unsupported_s_ft; +GO + +CREATE TABLE t_unsupported_ft2 (id int not null, a text); +GO + +CREATE UNIQUE INDEX ix_unsupported_ft2 ON t_unsupported_ft2(id); +GO + +CREATE FULLTEXT INDEX ON t_unsupported_ft2(a) KEY INDEX ix_unsupported_ft2; +GO + +DROP FULLTEXT INDEX ON t_unsupported_ft2; +GO + +DROP TABLE t_unsupported_ft2; GO SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') diff --git a/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-cleanup.mix b/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-cleanup.mix new file mode 100644 index 0000000000..9cf8a96841 --- /dev/null +++ b/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-cleanup.mix @@ -0,0 +1,129 @@ +-- enable CONTAINS +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO + +-- tsql user=testLogin password=abc +USE master; +GO + +-- enable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO + +-- should throw error because login doesn't have sufficient permissions to DROP FULLTEXT INDEX +DROP FULLTEXT INDEX ON fti_table_t1; +GO + +-- tsql user=jdbc_user password=12345678 +-- enable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO + +DROP FULLTEXT INDEX ON fti_table_t1; +GO + +DROP TABLE IF EXISTS fti_table_t1; +GO + +DROP FULLTEXT INDEX ON fti_table_t2; +GO + +DROP TABLE IF EXISTS fti_table_t2; +GO + +DROP FULLTEXT INDEX ON fti_table_t3; +GO + +DROP TABLE IF EXISTS fti_table_t3; +GO + +DROP FULLTEXT INDEX ON fti_table_t4; +GO + +DROP TABLE IF EXISTS fti_table_t4; +GO + +DROP FULLTEXT INDEX ON fti_table_t5; +GO + +DROP TABLE IF EXISTS fti_table_t5; +GO + +DROP FULLTEXT INDEX ON fti_table_t6; +GO + +DROP TABLE IF EXISTS fti_table_t6; +GO + +DROP FULLTEXT INDEX ON fti_table_t7; +GO + +DROP TABLE IF EXISTS fti_table_t7; +GO + +DROP FULLTEXT INDEX ON fti_schema_s1.fti_table_t8; +GO + +DROP TABLE IF EXISTS fti_schema_s1.fti_table_t8; +GO + +DROP SCHEMA IF EXISTS fti_schema_s1; +GO + +DROP FULLTEXT INDEX ON fti_schema_s2.fti_table_t8; +GO + +DROP TABLE IF EXISTS fti_schema_s2.fti_table_t8; +GO + +DROP SCHEMA IF EXISTS fti_schema_s2; +GO + +-- should throw error as there is no index in the table +DROP FULLTEXT INDEX ON fti_table_no_ix; +GO + +DROP TABLE IF EXISTS fti_table_no_ix; +GO + +DROP TABLE IF EXISTS fti_table_unsupported; +GO + +DROP VIEW IF EXISTS fti_prepare_v1; +GO + +DROP PROCEDURE IF EXISTS fti_prepare_p1; +GO + +DROP FUNCTION IF EXISTS fti_prepare_f1; +GO + +DROP TABLE IF EXISTS fti_schema_s3.fti_table_t9; +GO + +DROP SCHEMA IF EXISTS fti_schema_s3; +GO + +-- psql +-- Need to terminate active session before cleaning up the login +SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL) +WHERE sys.suser_name(usesysid) = 'testLogin' AND backend_type = 'client backend' AND usesysid IS NOT NULL; +GO +-- Wait to sync with another session +SELECT pg_sleep(1); +GO + +-- tsql +DROP LOGIN testLogin; +GO + +-- disable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') +GO + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO \ No newline at end of file diff --git a/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-prepare.mix b/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-prepare.mix new file mode 100644 index 0000000000..f84fea7745 --- /dev/null +++ b/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-prepare.mix @@ -0,0 +1,176 @@ +-- enable CONTAINS +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO + +-- tsql user=jdbc_user password=12345678 +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO + +CREATE LOGIN testLogin WITH PASSWORD = '123'; +GO + +USE master; +GO + +CREATE SCHEMA fti_schema_s3; +GO + +CREATE TABLE fti_schema_s3.fti_table_t9(id int NOT NULL, a text); +GO + +CREATE UNIQUE INDEX IX_t9_a ON fti_schema_s3.fti_table_t9(id); +GO + +-- tsql user=testLogin password=123 +-- Create new login with insufficient permissions +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO + +-- should throw error as new login has insufficient permissions to CREATE FULLTEXT INDEX +CREATE FULLTEXT INDEX ON fti_schema_s3.fti_table_t9(a) KEY INDEX IX_t9_a; +GO + +-- tsql user=jdbc_user password=12345678 +-- Reset to super user +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO + +-- Index creation on different character data type columns +CREATE TABLE fti_table_t1(id int NOT NULL, a text); +GO + +-- should throw syntax error for NULL index name +CREATE FULLTEXT INDEX ON fti_table_t1(a) KEY INDEX NULL; +GO + +-- should throw error for no unique index on the table +CREATE FULLTEXT INDEX ON fti_table_t1(a) KEY INDEX IX_t1_a; +GO + +CREATE UNIQUE INDEX IX_t1_a ON fti_table_t1(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t1(a) KEY INDEX IX_t1_a; +GO + +CREATE TABLE fti_table_t2(id int not null, b char(10)); +GO + +CREATE UNIQUE INDEX IX_t2_b ON fti_table_t2(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t2(b) KEY INDEX IX_t2_b; +GO + +CREATE TABLE fti_table_t3(id int not null, c varchar(10)); +GO + +CREATE UNIQUE INDEX IX_t3_c ON fti_table_t3(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t3(c) KEY INDEX IX_t3_c; +GO + +CREATE TABLE fti_table_t4(id int not null, d nvarchar(10)); +GO + +CREATE UNIQUE INDEX IX_t4_d ON fti_table_t4(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t4(d) KEY INDEX IX_t4_d; +GO + +CREATE TABLE fti_table_t5(id int not null, e nchar(10)); +GO + +CREATE UNIQUE INDEX IX_t5_e ON fti_table_t5(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t5(e) KEY INDEX IX_t5_e; +GO + +CREATE TABLE fti_table_t6(id int not null, f ntext); +GO + +CREATE UNIQUE INDEX IX_t6_f ON fti_table_t6(id); +GO + +CREATE FULLTEXT INDEX ON fti_table_t6(f) KEY INDEX IX_t6_f; +GO + +CREATE TABLE fti_table_t7(id int not null, a1 text, b1 char(10), c1 varchar(10)); +GO + +CREATE UNIQUE INDEX IX_t7_a1b1c1 ON fti_table_t7(id); +GO + +-- multi column index creation +CREATE FULLTEXT INDEX ON fti_table_t7(a1, b1, c1) KEY INDEX IX_t7_a1b1c1; +GO + +-- checking if the indexes are created correctly +CREATE VIEW fti_prepare_v1 AS (SELECT indexname FROM pg_indexes WHERE tablename='fti_table_t1' AND indexname LIKE 'ft_index%'); +GO + +CREATE PROCEDURE fti_prepare_p1 AS (SELECT indexname FROM pg_indexes WHERE tablename='fti_table_t2' AND indexname LIKE 'ft_index%'); +GO + +CREATE FUNCTION fti_prepare_f1() +RETURNS NVARCHAR(MAX) AS +BEGIN + DECLARE @indexName NVARCHAR(MAX); + SELECT @indexName= indexname + FROM pg_indexes + WHERE tablename='fti_table_t3' AND indexname LIKE 'ft_index%'; + + RETURN @indexName; +END +GO + +-- Creating index in a new schema +CREATE SCHEMA fti_schema_s1; +GO + +CREATE TABLE fti_schema_s1.fti_table_t8(id int not null, a text, b text); +GO + +CREATE UNIQUE INDEX IX_s1_t8_a ON fti_schema_s1.fti_table_t8(id); +GO + +CREATE FULLTEXT INDEX ON fti_schema_s1.fti_table_t8(a) KEY INDEX IX_s1_t8_a; +GO + +CREATE SCHEMA FTI_schema_s2; +GO + +CREATE TABLE FTI_schema_s2.FTI_table_t8(Id int not null, a text, b text); +GO + +CREATE UNIQUE INDEX IX_s2_t8_a ON FTI_schema_s2.FTI_table_t8(Id); +GO + +-- Case for same table name on different schemas +CREATE FULLTEXT INDEX ON FTI_schema_s2.FTI_table_t8(a) KEY INDEX IX_s2_t8_a; +GO + +-- Table for testing dropping non-existent index, should throw error on dropping +CREATE TABLE fti_table_no_ix(id int not null, a text) +GO + +-- Table for testing unsupported options +CREATE TABLE fti_table_unsupported(id int not null, a text) +GO + +CREATE UNIQUE INDEX ix_unsupported_fti ON fti_table_unsupported(id); +GO + +-- disable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') +GO + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO \ No newline at end of file diff --git a/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-verify.mix b/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-verify.mix new file mode 100644 index 0000000000..da6fb87e8f --- /dev/null +++ b/test/JDBC/input/full_text_search/FULLTEXT_INDEX-vu-verify.mix @@ -0,0 +1,91 @@ +-- enable CONTAINS +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO + +-- enable FULLTEXT +-- tsql user=jdbc_user password=12345678 +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') +GO + +USE master; +GO + +-- Fetching index details to check if index is created correctly +SELECT * FROM fti_prepare_v1; +GO + +EXEC fti_prepare_p1; +GO + +SELECT fti_prepare_f1(); +GO + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t4' AND indexname LIKE 'ft_index%'; +GO + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t5' AND indexname LIKE 'ft_index%'; +GO + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t6' AND indexname LIKE 'ft_index%'; +GO + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t7' AND indexname LIKE 'ft_index%'; +GO + +SELECT tablename, indexname FROM pg_indexes WHERE tablename='fti_table_t8' AND indexname LIKE 'ft_index%'; +GO + +-- Creating more than 1 fulltext index in a table, should throw error +CREATE FULLTEXT INDEX ON fti_schema_s1.fti_table_t8(b) KEY INDEX IX_s1_t8_a; +GO + +-- Creating index in a non existent table of a schema, should throw error +CREATE FULLTEXT INDEX ON fti_schema_s1.fti_table_t9(a) KEY INDEX IX_s1_t9_a; +GO + +-- Creating index in a table of a non existent schema, should throw error +CREATE FULLTEXT INDEX ON fti_schema_s4.fti_table_t8(a) KEY INDEX IX_s2_t8_a; +GO + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a TYPE COLUMN a) KEY INDEX ix_unsupported_fti; +GO + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a LANGUAGE 1033) KEY INDEX ix_unsupported_fti; +GO + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a STATISTICAL_SEMANTICS) KEY INDEX ix_unsupported_fti; +GO + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti ON t_unsupported_catalog; +GO + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti WITH CHANGE_TRACKING OFF; +GO + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti WITH STOPLIST = SYSTEM; +GO + +-- should throw unsupported error +CREATE FULLTEXT INDEX ON fti_table_unsupported(a) KEY INDEX ix_unsupported_fti WITH SEARCH PROPERTY LIST = DocumentPropertyList; +GO + +-- reset the login password +ALTER LOGIN testLogin WITH PASSWORD = 'abc'; +GO + +-- disable FULLTEXT +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') +GO + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO \ No newline at end of file diff --git a/test/JDBC/input/full_text_search/fts-contains-vu-cleanup.sql b/test/JDBC/input/full_text_search/fts-contains-vu-cleanup.mix similarity index 63% rename from test/JDBC/input/full_text_search/fts-contains-vu-cleanup.sql rename to test/JDBC/input/full_text_search/fts-contains-vu-cleanup.mix index 94b3a18f39..3a1fb68142 100644 --- a/test/JDBC/input/full_text_search/fts-contains-vu-cleanup.sql +++ b/test/JDBC/input/full_text_search/fts-contains-vu-cleanup.mix @@ -1,3 +1,4 @@ +-- tsql user=jdbc_user password=12345678 -- enable CONTAINS SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') GO @@ -5,6 +6,9 @@ GO DROP PROCEDURE fts_contains_vu_prepare_p1; GO +DROP FULLTEXT INDEX ON fts_contains_vu_t; +GO + DROP TABLE fts_contains_vu_t; GO @@ -14,3 +18,8 @@ GO -- disable CONTAINS SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') GO + +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = off; +SELECT pg_reload_conf(); +GO diff --git a/test/JDBC/input/full_text_search/fts-contains-vu-prepare.sql b/test/JDBC/input/full_text_search/fts-contains-vu-prepare.mix similarity index 99% rename from test/JDBC/input/full_text_search/fts-contains-vu-prepare.sql rename to test/JDBC/input/full_text_search/fts-contains-vu-prepare.mix index 99fb7751d6..797322949b 100644 --- a/test/JDBC/input/full_text_search/fts-contains-vu-prepare.sql +++ b/test/JDBC/input/full_text_search/fts-contains-vu-prepare.mix @@ -1,9 +1,25 @@ -- enable CONTAINS +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO + +-- tsql user=jdbc_user password=12345678 SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') GO -- Create table for full text search CONTAINS predicate -CREATE TABLE fts_contains_vu_t (id int PRIMARY KEY, txt text) +CREATE TABLE fts_contains_vu_t (id int NOT NULL, txt text) +GO + +-- Should throw error because there exist no full-text index for the table +SELECT * FROM fts_contains_vu_t WHERE CONTAINS(txt, 'run'); +GO + +CREATE UNIQUE INDEX fts_contains_vu_t_id_idx ON fts_contains_vu_t(id); +GO + +CREATE FULLTEXT INDEX ON fts_contains_vu_t(txt) KEY INDEX fts_contains_vu_t_id_idx; GO -- Full text search @query_string using CONTAINS diff --git a/test/JDBC/input/full_text_search/fts-contains-vu-verify.sql b/test/JDBC/input/full_text_search/fts-contains-vu-verify.mix similarity index 87% rename from test/JDBC/input/full_text_search/fts-contains-vu-verify.sql rename to test/JDBC/input/full_text_search/fts-contains-vu-verify.mix index eee6d5004f..1b4c97a090 100644 --- a/test/JDBC/input/full_text_search/fts-contains-vu-verify.sql +++ b/test/JDBC/input/full_text_search/fts-contains-vu-verify.mix @@ -1,4 +1,11 @@ -- enable CONTAINS +-- psql +ALTER SYSTEM SET babelfishpg_tsql.allow_fulltext_parser = on; +SELECT pg_reload_conf(); +GO + +-- tsql user=jdbc_user password=12345678 +-- enable CONTAINS SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'ignore', 'false') GO @@ -89,4 +96,8 @@ GO -- Generation Term not supported EXEC fts_contains_vu_prepare_p1 'FORMSOF(THESAURUS, love)' +GO + +-- disable CONTAINS +SELECT set_config('babelfishpg_tsql.escape_hatch_fulltext', 'strict', 'false') GO \ No newline at end of file diff --git a/test/JDBC/upgrade/latest/schedule b/test/JDBC/upgrade/latest/schedule index 7ea5127c17..7735536f0b 100644 --- a/test/JDBC/upgrade/latest/schedule +++ b/test/JDBC/upgrade/latest/schedule @@ -219,6 +219,7 @@ format-dep forxml forxml-subquery fulltextserviceproperty +FULLTEXT_INDEX fts-contains get_tds_id HAS_DBACCESS