diff --git a/.gitignore b/.gitignore index 2a395cf5c8c..5a401d734e2 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,11 @@ contrib/babelfishpg_tsql/src/pl_gram.c contrib/babelfishpg_tsql/src/pl_gram.h contrib/babelfishpg_tsql/src/pl_gram.output +# PLtsql nodes serialization - auto-generated by gen_pltsql_node_support.pl +# contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_nodetags.h +contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_outfuncs_gen.c +contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_outfuncs_switch.c +contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_readfuncs_gen.c +contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_readfuncs_switch.c +contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_equalfuncs_gen.c +contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_equalfuncs_switch.c diff --git a/contrib/babelfishpg_tsql/Makefile b/contrib/babelfishpg_tsql/Makefile index 4f64a02c4a5..05a16b13c54 100644 --- a/contrib/babelfishpg_tsql/Makefile +++ b/contrib/babelfishpg_tsql/Makefile @@ -86,6 +86,31 @@ export ANTLR4_RUNTIME_INCLUDE_DIR=/usr/local/include/antlr4-runtime export ANTLR4_RUNTIME_LIB_DIR=/usr/local/lib OBJS += src/pltsql_bulkcopy.o +OBJS += src/pltsql_serialize/pltsql_node_stubs.o + +# PLtsql serialization code generation (babelfishpg_tsql.enable_routine_parse_cache) +PLTSQL_SER_DIR = src/pltsql_serialize +PLTSQL_SER_HEADERS = $(PLTSQL_SER_DIR)/pltsql_serializable_1.h $(PLTSQL_SER_DIR)/pltsql_serializable_2.h +PLTSQL_GEN_SCRIPT = $(PLTSQL_SER_DIR)/gen_pltsql_node_support.pl + +$(PLTSQL_SER_DIR)/pltsql_nodetags.h $(PLTSQL_SER_DIR)/pltsql_outfuncs_gen.c $(PLTSQL_SER_DIR)/pltsql_readfuncs_gen.c $(PLTSQL_SER_DIR)/pltsql_outfuncs_switch.c $(PLTSQL_SER_DIR)/pltsql_readfuncs_switch.c $(PLTSQL_SER_DIR)/pltsql_equalfuncs_gen.c $(PLTSQL_SER_DIR)/pltsql_equalfuncs_switch.c: $(PLTSQL_SER_HEADERS) $(PLTSQL_GEN_SCRIPT) + $(PERL) $(PLTSQL_GEN_SCRIPT) --outdir $(PLTSQL_SER_DIR) $(PLTSQL_SER_HEADERS) + +# Wrapper .c files #include the generated .c files (mirroring engine pattern) +# so we compile the wrappers, not the gen files directly. +# pltsql_outfuncs and pltsql_readfuncs: generated code now included by pltsql_nodeio.c +OBJS += $(PLTSQL_SER_DIR)/pltsql_equalfuncs.o +OBJS += $(PLTSQL_SER_DIR)/pltsql_compare.o +OBJS += $(PLTSQL_SER_DIR)/pltsql_nodeio.o + +# pltsql_nodetags.h must be generated before any .o that includes pltsql.h +# The first OBJS entry (src/pl_gram.o) triggers this via implicit ordering, +# but we make it explicit for safety. +src/pl_gram.o: $(PLTSQL_SER_DIR)/pltsql_nodetags.h + +# Wrapper .o depends on generated .c files (included via #include) +$(PLTSQL_SER_DIR)/pltsql_nodeio.o: $(PLTSQL_SER_DIR)/pltsql_outfuncs_gen.c $(PLTSQL_SER_DIR)/pltsql_outfuncs_switch.c $(PLTSQL_SER_DIR)/pltsql_readfuncs_gen.c $(PLTSQL_SER_DIR)/pltsql_readfuncs_switch.c +$(PLTSQL_SER_DIR)/pltsql_equalfuncs.o: $(PLTSQL_SER_DIR)/pltsql_equalfuncs_gen.c $(PLTSQL_SER_DIR)/pltsql_equalfuncs_switch.c PG_CXXFLAGS += -g -Werror -Wfloat-conversion PG_CXXFLAGS += -Wno-deprecated -Wno-error=attributes -Wno-suggest-attribute=format # disable some warnings from ANTLR runtime header diff --git a/contrib/babelfishpg_tsql/sql/ownership.sql b/contrib/babelfishpg_tsql/sql/ownership.sql index ccdc2a0c266..e0f012e4e9b 100644 --- a/contrib/babelfishpg_tsql/sql/ownership.sql +++ b/contrib/babelfishpg_tsql/sql/ownership.sql @@ -41,6 +41,11 @@ CREATE TABLE sys.babelfish_function_ext ( create_date SYS.DATETIME NOT NULL, modify_date SYS.DATETIME NOT NULL, definition sys.NTEXT DEFAULT NULL, + antlr_parse_tree_text TEXT DEFAULT NULL, -- Native PG nodeToString() serialized parse tree + antlr_parse_tree_datums TEXT DEFAULT NULL, -- Native PG nodeToString() serialized datums array + antlr_parse_tree_modify_date SYS.DATETIME DEFAULT NULL, + antlr_parse_tree_bbf_version TEXT DEFAULT NULL, + antlr_cache_enabled BOOL DEFAULT false, PRIMARY KEY(funcname, nspname, funcsignature) ); GRANT SELECT ON sys.babelfish_function_ext TO PUBLIC; diff --git a/contrib/babelfishpg_tsql/sql/sys_functions.sql b/contrib/babelfishpg_tsql/sql/sys_functions.sql index bd18d9c795e..27fdfa78216 100644 --- a/contrib/babelfishpg_tsql/sql/sys_functions.sql +++ b/contrib/babelfishpg_tsql/sql/sys_functions.sql @@ -5312,3 +5312,10 @@ RETURNS table ( AS 'babelfishpg_tsql', 'openxml_simple' LANGUAGE C IMMUTABLE; + +-- Function-specific ANTLR parse tree cache GUC control +CREATE OR REPLACE FUNCTION sys.enable_routine_parse_cache( + IN func_identifier TEXT, + IN enable_flag BOOLEAN +) RETURNS BOOLEAN +AS 'babelfishpg_tsql', 'enable_routine_parse_cache' LANGUAGE C; diff --git a/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.5.0--5.6.0.sql b/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.5.0--5.6.0.sql index 2bb748ea0a6..8f805efd12b 100644 --- a/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.5.0--5.6.0.sql +++ b/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.5.0--5.6.0.sql @@ -231,6 +231,21 @@ $$ end; $$; +-- BABELFISH_FUNCTION_EXT +SET allow_system_table_mods = on; +ALTER TABLE sys.babelfish_function_ext ADD COLUMN IF NOT EXISTS antlr_parse_tree_text TEXT DEFAULT NULL; +ALTER TABLE sys.babelfish_function_ext ADD COLUMN IF NOT EXISTS antlr_parse_tree_datums TEXT DEFAULT NULL; +ALTER TABLE sys.babelfish_function_ext ADD COLUMN IF NOT EXISTS antlr_parse_tree_modify_date SYS.DATETIME DEFAULT NULL; +ALTER TABLE sys.babelfish_function_ext ADD COLUMN IF NOT EXISTS antlr_parse_tree_bbf_version TEXT DEFAULT NULL; +ALTER TABLE sys.babelfish_function_ext ADD COLUMN IF NOT EXISTS antlr_cache_enabled BOOL DEFAULT false; +RESET allow_system_table_mods; + +CREATE OR REPLACE FUNCTION sys.enable_routine_parse_cache( + IN func_identifier TEXT, + IN enable_flag BOOLEAN +) RETURNS BOOLEAN +AS 'babelfishpg_tsql', 'enable_routine_parse_cache' LANGUAGE C; + -- Please add your SQLs here -- Deprecate and drop old aggregates first (they depend on the function) diff --git a/contrib/babelfishpg_tsql/src/catalog.c b/contrib/babelfishpg_tsql/src/catalog.c index 5f5dfff8784..f7c0f0c6f24 100644 --- a/contrib/babelfishpg_tsql/src/catalog.c +++ b/contrib/babelfishpg_tsql/src/catalog.c @@ -3655,6 +3655,18 @@ rename_procfunc_update_bbf_catalog(RenameStmt *stmt) new_record_repl_func_ext[Anum_bbf_function_ext_orig_name - 1] = true; } + /* Invalidate cached parse tree — procedure name is embedded in the parse tree namespace stack */ + /* Alternatively, if guc enabled, explore if worth looking up session cache PLtsql_HashTable for + * updated parse tree result and serialize and re-populate babelfish_function_ext's antlr_parse_tree column */ + new_record_nulls_func_ext[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = true; + new_record_repl_func_ext[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = true; + new_record_nulls_func_ext[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + new_record_repl_func_ext[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + new_record_nulls_func_ext[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = true; + new_record_repl_func_ext[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = true; + new_record_nulls_func_ext[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = true; + new_record_repl_func_ext[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = true; + new_tuple = heap_modify_tuple(usertuple, bbf_func_ext_dsc, new_record_func_ext, @@ -6490,4 +6502,179 @@ bbf_check_member_has_direct_priv_to_grant_role(Oid member, Oid role) ReleaseSysCacheList(memlist); return false; -} \ No newline at end of file +} + +/* + * Helper: update antlr_cache_enabled flag and optionally clear cache columns + * in babelfish_function_ext for a given bbf function tuple. + */ +static void +update_bbf_function_cache_enabled(HeapTuple bbffunctuple, bool enable_flag) +{ + Relation rel; + TupleDesc dsc; + HeapTuple newtup; + Datum new_record[BBF_FUNCTION_EXT_NUM_COLS]; + bool new_record_nulls[BBF_FUNCTION_EXT_NUM_COLS]; + bool new_record_replaces[BBF_FUNCTION_EXT_NUM_COLS]; + + rel = table_open(get_bbf_function_ext_oid(), RowExclusiveLock); + dsc = RelationGetDescr(rel); + + MemSet(new_record, 0, sizeof(new_record)); + MemSet(new_record_nulls, false, sizeof(new_record_nulls)); + MemSet(new_record_replaces, false, sizeof(new_record_replaces)); + + /* Set antlr_cache_enabled */ + new_record[Anum_bbf_function_ext_antlr_cache_enabled - 1] = BoolGetDatum(enable_flag); + new_record_replaces[Anum_bbf_function_ext_antlr_cache_enabled - 1] = true; + + /* If disabling, NULL out the 4 cache columns for immediate invalidation */ + if (!enable_flag) + { + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = true; + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = true; + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = true; + } + + newtup = heap_modify_tuple(bbffunctuple, dsc, + new_record, new_record_nulls, new_record_replaces); + CatalogTupleUpdate(rel, &newtup->t_self, newtup); + + heap_freetuple(newtup); + table_close(rel, RowExclusiveLock); +} + +/* + * enable_routine_parse_cache + * + * SQL-callable: sys.enable_routine_parse_cache(func_identifier TEXT, enable_flag BOOLEAN) + * + * Enables or disables ANTLR parse result caching for a specific function. + * + * func_identifier formats: + * 'schema.funcname' — unique function (no overloads) + * 'funcname' — defaults to dbo schema + * 'schema.funcname(argtypes)' — disambiguate overloads, e.g. 'dbo.myProc("sys"."varchar")' + * + * Uses PROCNAMENSPSIGNATURE syscache (funcname, nspname, funcsignature). + * When no arg types given, uses 2-key list lookup; errors if ambiguous. + */ +PG_FUNCTION_INFO_V1(enable_routine_parse_cache); +Datum +enable_routine_parse_cache(PG_FUNCTION_ARGS) +{ + text *func_id_text = PG_GETARG_TEXT_PP(0); + bool enable_flag = PG_GETARG_BOOL(1); + char *func_id = text_to_cstring(func_id_text); + char *dot; + char *schema_name; + char *func_part; + char *funcname_str; + char *paren; + NameData nsp_name; + NameData funcname_data; + + /* Split schema from func_part on first dot */ + dot = strchr(func_id, '.'); + if (dot != NULL) + { + *dot = '\0'; + schema_name = func_id; + func_part = dot + 1; + } + else + { + schema_name = "dbo"; + func_part = func_id; + } + + /* Extract bare funcname (everything before '(' if present) */ + funcname_str = pstrdup(func_part); + paren = strchr(funcname_str, '('); + if (paren != NULL) + *paren = '\0'; + + /* Convert logical schema name (e.g. 'dbo') to physical (e.g. 'master_dbo') */ + { + char *cur_db = get_cur_db_name(); + char *physical_schema = get_physical_schema_name(cur_db, schema_name); + + namestrcpy(&nsp_name, physical_schema); + pfree(cur_db); + pfree(physical_schema); + } + namestrcpy(&funcname_data, funcname_str); + + if (strchr(func_part, '(') != NULL) + { + /* Full signature provided — exact 3-key lookup */ + HeapTuple bbffunctuple; + + bbffunctuple = SearchSysCache3(PROCNAMENSPSIGNATURE, + NameGetDatum(&funcname_data), + NameGetDatum(&nsp_name), + CStringGetTextDatum(func_part)); + + if (!HeapTupleIsValid(bbffunctuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("function \"%s\" not found in schema \"%s\"", + func_part, schema_name), + errhint("Use sys.babelfish_get_pltsql_function_signature(oid) to find the exact signature."))); + + { + HeapTuple copytup = heap_copytuple(bbffunctuple); + + ReleaseSysCache(bbffunctuple); + update_bbf_function_cache_enabled(copytup, enable_flag); + heap_freetuple(copytup); + } + } + else + { + /* No arg types — 2-key list lookup */ + CatCList *catlist; + + catlist = SearchSysCacheList2(PROCNAMENSPSIGNATURE, + NameGetDatum(&funcname_data), + NameGetDatum(&nsp_name)); + + if (catlist->n_members == 0) + { + ReleaseSysCacheList(catlist); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("function \"%s\" not found in schema \"%s\"", + funcname_str, schema_name))); + } + else if (catlist->n_members > 1) + { + ReleaseSysCacheList(catlist); + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_FUNCTION), + errmsg("multiple overloads of \"%s\" exist in schema \"%s\"", + funcname_str, schema_name), + errhint("Specify the full signature, e.g. 'dbo.%s(integer)'. " + "Use sys.babelfish_get_pltsql_function_signature(oid) to find the exact signature.", + funcname_str))); + } + else + { + HeapTuple copytup = heap_copytuple(&catlist->members[0]->tuple); + + ReleaseSysCacheList(catlist); + update_bbf_function_cache_enabled(copytup, enable_flag); + heap_freetuple(copytup); + } + } + + pfree(funcname_str); + pfree(func_id); + PG_RETURN_BOOL(enable_flag); +} diff --git a/contrib/babelfishpg_tsql/src/catalog.h b/contrib/babelfishpg_tsql/src/catalog.h index ecda2acd1c0..32ca192314a 100644 --- a/contrib/babelfishpg_tsql/src/catalog.h +++ b/contrib/babelfishpg_tsql/src/catalog.h @@ -277,7 +277,12 @@ typedef FormData_bbf_servers_def *Form_bbf_servers_def; #define Anum_bbf_function_ext_create_date 8 #define Anum_bbf_function_ext_modify_date 9 #define Anum_bbf_function_ext_definition 10 -#define BBF_FUNCTION_EXT_NUM_COLS 10 +#define Anum_bbf_function_ext_antlr_parse_tree_text 11 +#define Anum_bbf_function_ext_antlr_parse_tree_datums 12 +#define Anum_bbf_function_ext_antlr_parse_tree_modify_date 13 +#define Anum_bbf_function_ext_antlr_parse_tree_bbf_version 14 +#define Anum_bbf_function_ext_antlr_cache_enabled 15 +#define BBF_FUNCTION_EXT_NUM_COLS 15 #define FLAG_IS_ANSI_NULLS_ON (1<<0) #define FLAG_USES_QUOTED_IDENTIFIER (1<<1) #define FLAG_CREATED_WITH_RECOMPILE (1<<2) diff --git a/contrib/babelfishpg_tsql/src/codegen.c b/contrib/babelfishpg_tsql/src/codegen.c index a0d4f1e37ee..04f2110b1fd 100644 --- a/contrib/babelfishpg_tsql/src/codegen.c +++ b/contrib/babelfishpg_tsql/src/codegen.c @@ -147,7 +147,7 @@ create_goto(int lineno) { PLtsql_stmt_goto *stmt_goto; - stmt_goto = palloc(sizeof(PLtsql_stmt_goto)); + stmt_goto = makeNode(PLtsql_stmt_goto); stmt_goto->cmd_type = PLTSQL_STMT_GOTO; stmt_goto->lineno = lineno; stmt_goto->cond = NULL; /* unconditional goto */ @@ -159,7 +159,7 @@ create_goto(int lineno) static PLtsql_stmt_save_ctx * create_save_ctx(int lineno) { - PLtsql_stmt_save_ctx *save_ctx = palloc(sizeof(PLtsql_stmt_save_ctx)); + PLtsql_stmt_save_ctx *save_ctx = makeNode(PLtsql_stmt_save_ctx); save_ctx->cmd_type = PLTSQL_STMT_SAVE_CTX; save_ctx->lineno = lineno; @@ -171,7 +171,7 @@ create_save_ctx(int lineno) static PLtsql_stmt_restore_ctx_full * create_restore_ctx_full(int lineno) { - PLtsql_stmt_restore_ctx_full *restore_ctx = palloc(sizeof(PLtsql_stmt_restore_ctx_full)); + PLtsql_stmt_restore_ctx_full *restore_ctx = makeNode(PLtsql_stmt_restore_ctx_full); restore_ctx->cmd_type = PLTSQL_STMT_RESTORE_CTX_FULL; restore_ctx->lineno = lineno; @@ -181,7 +181,7 @@ create_restore_ctx_full(int lineno) static PLtsql_stmt_restore_ctx_partial * create_restore_ctx_partial(int lineno) { - PLtsql_stmt_restore_ctx_partial *restore_ctx = palloc(sizeof(PLtsql_stmt_restore_ctx_partial)); + PLtsql_stmt_restore_ctx_partial *restore_ctx = makeNode(PLtsql_stmt_restore_ctx_partial); restore_ctx->cmd_type = PLTSQL_STMT_RESTORE_CTX_PARTIAL; restore_ctx->lineno = lineno; diff --git a/contrib/babelfishpg_tsql/src/guc.c b/contrib/babelfishpg_tsql/src/guc.c index 6d8753a45fb..d33ac06c16a 100644 --- a/contrib/babelfishpg_tsql/src/guc.c +++ b/contrib/babelfishpg_tsql/src/guc.c @@ -72,6 +72,8 @@ char *pltsql_host_service_pack_level = NULL; bool pltsql_enable_create_alter_view_from_pg = false; bool pltsql_enable_alter_owner_from_pg = false; +bool pltsql_enable_routine_parse_cache = false; +bool pltsql_validate_parse_cache = false; static const struct config_enum_entry explain_format_options[] = { {"text", EXPLAIN_FORMAT_TEXT, false}, @@ -1140,6 +1142,31 @@ define_custom_variables(void) GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_DISALLOW_IN_AUTO_FILE, NULL, NULL, NULL); + /* + * Enable/disable procedure ANTLR parse result caching for cross-session (sys.babelfish_function_ext->antlr_parse_tree) performance optimization. + */ + DefineCustomBoolVariable("babelfishpg_tsql.enable_routine_parse_cache", + gettext_noop("Enables caching of ANTLR parse results for stored procedures across sessions"), + NULL, + &pltsql_enable_routine_parse_cache, + false, + PGC_USERSET, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_DISALLOW_IN_AUTO_FILE, + NULL, NULL, NULL); + + /* + * Debug GUC: when enabled, every cache-hit compilation also runs ANTLR + * and compares the two parse trees field-by-field. Logs PASS/FAIL. + */ + DefineCustomBoolVariable("babelfishpg_tsql.validate_parse_cache", + gettext_noop("When enabled, validates cached parse trees against fresh ANTLR compilation"), + NULL, + &pltsql_validate_parse_cache, + false, + PGC_USERSET, + GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_DISALLOW_IN_AUTO_FILE, + NULL, NULL, NULL); + /* Dump and Restore */ DefineCustomBoolVariable("babelfishpg_tsql.dump_restore", gettext_noop("Enable special handlings during dump and restore"), diff --git a/contrib/babelfishpg_tsql/src/guc.h b/contrib/babelfishpg_tsql/src/guc.h index dc90753fbc8..b11e23d99a5 100644 --- a/contrib/babelfishpg_tsql/src/guc.h +++ b/contrib/babelfishpg_tsql/src/guc.h @@ -18,6 +18,8 @@ typedef enum IsolationOptions extern bool pltsql_fmtonly; extern bool pltsql_enable_create_alter_view_from_pg; extern bool pltsql_enable_alter_owner_from_pg; +extern bool pltsql_enable_routine_parse_cache; +extern bool pltsql_validate_parse_cache; extern bool pltsql_enable_linked_servers; extern bool pltsql_enable_ownership_chaining; extern bool pltsql_allow_windows_login; diff --git a/contrib/babelfishpg_tsql/src/hooks.c b/contrib/babelfishpg_tsql/src/hooks.c index 02808baa66d..303d60281db 100644 --- a/contrib/babelfishpg_tsql/src/hooks.c +++ b/contrib/babelfishpg_tsql/src/hooks.c @@ -94,6 +94,7 @@ #include "pltsql_permissions.h" #include "pl_explain.h" #include "catalog.h" +#include "babelfish_version.h" #include "dbcmds.h" #include "rolecmds.h" #include "session.h" @@ -104,6 +105,10 @@ #include "extendedproperty.h" #include "utils/xml.h" +/* PLtsql serialization — extension-side dispatch (pltsql_nodeio.c) */ +extern char *pltsql_nodeToString(const void *obj); +extern void *pltsql_stringToNode(const char *str); + #ifdef USE_LIBXML #include #include @@ -4325,7 +4330,79 @@ pltsql_store_func_default_positions(ObjectAddress address, List *parameters, con new_record_nulls[Anum_bbf_function_ext_definition - 1] = true; new_record_replaces[Anum_bbf_function_ext_default_positions - 1] = true; - oldtup = get_bbf_function_tuple_from_proctuple(proctup); + /* + * Explicitly set antlr_cache_enabled to false for new INSERTs. + * For UPDATEs (ALTER), new_record_replaces is set to false below + * so the existing value is preserved by heap_modify_tuple. + */ + new_record[Anum_bbf_function_ext_antlr_cache_enabled - 1] = BoolGetDatum(false); + new_record_replaces[Anum_bbf_function_ext_antlr_cache_enabled - 1] = false; /* preserve on ALTER */ + + /* + * Store serialized ANTLR parse result for cross-session caching. + * Look up the compiled function and serialize its parse tree. + * + * Combined check: session GUC OR per-function antlr_cache_enabled flag. + * For INSERT (new function), antlr_cache_enabled defaults to false so we only + * serialize if session GUC is on. For UPDATE (ALTER), we also check the + * existing tuple's antlr_cache_enabled flag. + */ + { + PLtsql_func_hashkey hashkey; + PLtsql_function *function = NULL; + bool do_cache = pltsql_enable_routine_parse_cache; /* session GUC override */ + + /* + * Peek at existing tuple to check per-function flag (for ALTER case). + * We fetch oldtup early here; it will be re-used below for INSERT/UPDATE. + */ + oldtup = get_bbf_function_tuple_from_proctuple(proctup); + if (!do_cache && HeapTupleIsValid(oldtup)) + { + bool isnull; + Datum cache_flag = SysCacheGetAttr(PROCNAMENSPSIGNATURE, oldtup, + Anum_bbf_function_ext_antlr_cache_enabled, &isnull); + do_cache = (!isnull && DatumGetBool(cache_flag)); + } + + if (!do_cache) + { + elog(DEBUG1, "Parse result caching skipped for function %u: neither session GUC nor per-function flag enabled", + address.objectId); + pltsql_fill_cache_columns(NULL, (Datum) 0, + new_record, new_record_nulls, new_record_replaces); + } + else + { + /* + * Build hashkey to look up the function in pltsql_HashTable. + * Use inputCollation=0 to match CREATE PROC context (collation not + * evaluated during CREATE). (Event/)Triggers not supported for T-SQL procedures. + */ + MemSet(&hashkey, 0, sizeof(PLtsql_func_hashkey)); + hashkey.funcOid = address.objectId; + hashkey.isTrigger = false; + hashkey.isEventTrigger = false; + hashkey.trigOid = 0; + hashkey.inputCollation = 0; + + if (form_proctup->pronargs > 0) + { + /* Copy argument types from proc tuple */ + memcpy(hashkey.argtypes, form_proctup->proargtypes.values, + form_proctup->pronargs * sizeof(Oid)); + } + + /* Look up the compiled function in the hash table */ + function = pltsql_HashTableLookup(&hashkey); + + pltsql_fill_cache_columns(function, + new_record[Anum_bbf_function_ext_modify_date - 1], + new_record, new_record_nulls, new_record_replaces); + } + } + + /* oldtup was already fetched above for the antlr_cache_enabled check */ if (HeapTupleIsValid(oldtup)) { @@ -4362,6 +4439,464 @@ pltsql_store_func_default_positions(ObjectAddress address, List *parameters, con table_close(bbf_function_ext_rel, RowExclusiveLock); } +/* + * pltsql_fill_cache_columns + * Serialize a compiled function's ANTLR parse tree and datums into catalog record arrays (sys.babelfish_function_ext). + * + * This is a reusable helper called from both CREATE/ALTER (via pltsql_store_func_default_positions) + * and EXEC-time cache repopulation (via pltsql_update_func_cache_entry) for routines. It fills the 4 ANTLR + * cache columns: antlr_parse_tree_text, antlr_parse_tree_datums, antlr_parse_tree_modify_date, + * and antlr_parse_tree_bbf_version. + * + * Does no catalog I/O — only serializes nodes and populates the caller's Datum/nulls/replaces arrays. + * Uses PG_TRY/PG_CATCH around nodeToString to handle serialization failures gracefully. + * If either serialization fails, all 4 cache columns are NULLed to avoid partial entries. + * + * Pass function=NULL to explicitly clear all cache columns (e.g., when GUC is disabled). + * + * Parameters: + * function - Compiled PLtsql_function (NULL to clear cache columns) + * modify_date - Datum for antlr_parse_tree_modify_date (ignored if function is NULL) + * new_record - Caller's Datum array (BBF_FUNCTION_EXT_NUM_COLS) + * new_record_nulls - Caller's nulls array + * new_record_replaces - Caller's replaces array + */ +void +pltsql_fill_cache_columns(PLtsql_function *function, Datum modify_date, + Datum *new_record, bool *new_record_nulls, + bool *new_record_replaces) +{ + char *tree_str = NULL; + char *datums_str = NULL; + bool success = false; + + if (function == NULL || function->action == NULL) + goto null_out; + + /* Serialize parse tree */ + PG_TRY(); + { + tree_str = pltsql_nodeToString(function->action); + } + PG_CATCH(); + { + FlushErrorState(); + tree_str = NULL; + elog(LOG, "pltsql_fill_cache_columns: nodeToString failed for parse tree"); + } + PG_END_TRY(); + + if (tree_str == NULL) + goto null_out; + + /* Serialize datums */ + { + List *datum_list = NIL; + int i; + + for (i = 0; i < function->ndatums; i++) + { + if (function->datums[i] != NULL) + datum_list = lappend(datum_list, function->datums[i]); + } + + if (datum_list != NIL) + { + PG_TRY(); + { + datums_str = pltsql_nodeToString(datum_list); + } + PG_CATCH(); + { + FlushErrorState(); + datums_str = NULL; + elog(LOG, "pltsql_fill_cache_columns: nodeToString failed for datums"); + } + PG_END_TRY(); + + list_free(datum_list); + + /* If datums serialization failed, NULL out everything */ + if (datums_str == NULL) + { + pfree(tree_str); + goto null_out; + } + } + } + + /* Both serializations succeeded — fill the columns */ + + /* CREATE-time round-trip validation: deserialize and compare with original */ + if (pltsql_validate_parse_cache && function != NULL && function->action != NULL) + { + extern bool pltsql_compare_parse_trees(PLtsql_stmt_block *tree_a, + PLtsql_stmt_block *tree_b); + PLtsql_stmt_block *roundtrip = NULL; + + PG_TRY(); + { + roundtrip = (PLtsql_stmt_block *) pltsql_stringToNode(tree_str); + } + PG_CATCH(); + { + FlushErrorState(); + roundtrip = NULL; + } + PG_END_TRY(); + + if (roundtrip != NULL) + { + bool match = pltsql_compare_parse_trees(function->action, roundtrip); + elog(LOG, "pltsql_validate_parse_cache[%s]: %s ANTLR parse tree validation at CREATE/ALTER", + match ? "PASS" : "FAIL", function->fn_signature); + } + else + { + elog(WARNING, "pltsql_validate_parse_cache[FAIL]: %s ANTLR parse tree validation encountered deserialization error at CREATE/ALTER", + function->fn_signature); + } + } + + new_record[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = CStringGetTextDatum(tree_str); + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = false; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = true; + pfree(tree_str); + + if (datums_str != NULL) + { + new_record[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = CStringGetTextDatum(datums_str); + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = false; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + pfree(datums_str); + } + else + { + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + } + + new_record[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = modify_date; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = true; + + new_record[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = CStringGetTextDatum(BABELFISH_VERSION_STR); + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = false; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = true; + + success = true; + +null_out: + if (!success) + { + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_text - 1] = true; + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_datums - 1] = true; + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_modify_date - 1] = true; + new_record_nulls[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = true; + new_record_replaces[Anum_bbf_function_ext_antlr_parse_tree_bbf_version - 1] = true; + } +} + +/* + * pltsql_update_func_cache_entry + * Re-populate the ANTLR cache in babelfish_function_ext at execution time. + * + * Called from do_compile after a cache miss triggers ANTLR re-parsing (e.g., after + * MVU version mismatch, rename invalidation, or first exec with empty cache). + * Since pltsql_store_func_default_positions only runs during CREATE/ALTER, this + * function ensures the catalog is updated with the fresh parse result at EXEC time. + * + * Looks up the existing babelfish_function_ext tuple, reads its modify_date, + * and calls pltsql_fill_cache_columns to serialize and write the cache columns. + * Skips silently if GUC is disabled, during dump/restore, or if no catalog entry exists. + * + * Parameters: + * proctup - HeapTuple from pg_proc + * function - Compiled PLtsql_function + */ +void +pltsql_update_func_cache_entry(HeapTuple proctup, PLtsql_function *function) +{ + Relation rel; + TupleDesc dsc; + HeapTuple oldtup; + HeapTuple newtup; + Datum new_record[BBF_FUNCTION_EXT_NUM_COLS]; + bool new_record_nulls[BBF_FUNCTION_EXT_NUM_COLS]; + bool new_record_replaces[BBF_FUNCTION_EXT_NUM_COLS]; + Datum modify_date; + bool snapshot_pushed = false; + + if (babelfish_dump_restore) + return; + + if (!OidIsValid(get_bbf_function_ext_idx_oid())) + return; + + oldtup = get_bbf_function_tuple_from_proctuple(proctup); + if (!HeapTupleIsValid(oldtup)) + return; + + /* Combined check: session GUC OR per-function flag */ + { + bool isnull; + Datum cache_flag = SysCacheGetAttr(PROCNAMENSPSIGNATURE, oldtup, + Anum_bbf_function_ext_antlr_cache_enabled, &isnull); + bool func_cache_enabled = (!isnull && DatumGetBool(cache_flag)); + if (!pltsql_enable_routine_parse_cache && !func_cache_enabled) + { + heap_freetuple(oldtup); + return; + } + } + + /* + * CatalogTupleUpdate requires an active snapshot. During after-trigger + * execution the snapshot may already have been popped, so push one if + * needed. This mirrors the pattern at pltsql_store_func_default_positions. + */ + if (!ActiveSnapshotSet()) + { + PushActiveSnapshot(GetTransactionSnapshot()); + snapshot_pushed = true; + } + + rel = table_open(get_bbf_function_ext_oid(), RowExclusiveLock); + dsc = RelationGetDescr(rel); + + MemSet(new_record, 0, sizeof(new_record)); + MemSet(new_record_nulls, false, sizeof(new_record_nulls)); + MemSet(new_record_replaces, false, sizeof(new_record_replaces)); + + /* + * Use current timestamp for antlr_parse_tree_modify_date + * (when the cache was rewritten for antlr_parse_tree columns) + */ + /* [TODO]: Would it be more accurate to include Timezone as well GetSQLCurrentTimestamp)? */ + modify_date = TimestampGetDatum(GetSQLLocalTimestamp(3)); + + pltsql_fill_cache_columns(function, modify_date, + new_record, new_record_nulls, new_record_replaces); + + newtup = heap_modify_tuple(oldtup, dsc, + new_record, new_record_nulls, new_record_replaces); + CatalogTupleUpdate(rel, &newtup->t_self, newtup); + + heap_freetuple(oldtup); + heap_freetuple(newtup); + table_close(rel, RowExclusiveLock); + + if (snapshot_pushed) + PopActiveSnapshot(); +} + +/* + * pltsql_restore_func_parse_result + * Attempt to restore a cached ANTLR parse tree from babelfish_function_ext. + * + * Called from do_compile before ANTLR parsing. If a valid cache entry exists, + * returns the deserialized parse tree and datums, allowing the caller to skip + * expensive ANTLR parsing entirely for first time execution of routines in new sessions. + * + * Validation checks (all must pass for a cache hit): + * 1. Session GUC OR per-function antlr_cache_enabled flag is on + * 2. bbf_version matches current BABELFISH_VERSION_STR (detects MVU) + * 3. antlr_parse_tree_modify_date >= modify_date (detects guc-disabled ALTER after caching) + * 4. antlr_parse_tree_text is not NULL and deserializes successfully + * + * Parameters: + * proctup - HeapTuple from pg_proc for the function + * out_cache_enabled - output: whether antlr_cache_enabled is true for this function + * out_bbf_ext_xmin - output: xmin of the babelfish_function_ext tuple + * out_bbf_ext_tid - output: ctid of the babelfish_function_ext tuple + * + * Returns: + * PLtsql_cached_parse_result * - parse_tree + datums on cache hit + * NULL - on cache miss, any validation failure, deserialization error, or cache disabled + */ +PLtsql_cached_parse_result * +pltsql_restore_func_parse_result(HeapTuple proctup, + bool *out_cache_enabled, + TransactionId *out_bbf_ext_xmin, + ItemPointerData *out_bbf_ext_tid) +{ + HeapTuple bbffunctuple; + Datum attr; + bool isnull; + char *str; + PLtsql_cached_parse_result *result = NULL; + + /* Initialize output parameters */ + *out_cache_enabled = false; + *out_bbf_ext_xmin = InvalidTransactionId; + ItemPointerSetInvalid(out_bbf_ext_tid); + + /* Disallow during restore */ + if (babelfish_dump_restore) + return NULL; + + bbffunctuple = get_bbf_function_tuple_from_proctuple(proctup); + if (!HeapTupleIsValid(bbffunctuple)) + return NULL; + + /* Always capture xmin/tid for the caller (used for hash invalidation) */ + *out_bbf_ext_xmin = HeapTupleHeaderGetRawXmin(bbffunctuple->t_data); + *out_bbf_ext_tid = bbffunctuple->t_self; + + /* Read per-function cache flag */ + { + Datum cache_flag = SysCacheGetAttr(PROCNAMENSPSIGNATURE, bbffunctuple, + Anum_bbf_function_ext_antlr_cache_enabled, &isnull); + *out_cache_enabled = (!isnull && DatumGetBool(cache_flag)); + } + + /* Combined check: session GUC OR per-function flag */ + if (!pltsql_enable_routine_parse_cache && !(*out_cache_enabled)) + { + elog(DEBUG1, "Parse result cache retrieval skipped: neither session GUC nor per-function flag enabled"); + heap_freetuple(bbffunctuple); + return NULL; + } + + /* + * Check 1: bbf_version must match current BABELFISH_VERSION_STR. + * A NULL or mismatched version means the cache was produced by a different + * Babelfish release (e.g., pre-MVU) and the serialization format may differ. + */ + attr = SysCacheGetAttr(PROCNAMENSPSIGNATURE, bbffunctuple, + Anum_bbf_function_ext_antlr_parse_tree_bbf_version, &isnull); + if (isnull) + { + elog(DEBUG1, "pltsql_restore_func_parse_result: no bbf_version, skipping cache"); + heap_freetuple(bbffunctuple); + return NULL; + } + str = TextDatumGetCString(attr); + if (strcmp(str, BABELFISH_VERSION_STR) != 0) + { + elog(DEBUG1, "pltsql_restore_func_parse_result: version mismatch (stored=%s, current=%s)", + str, BABELFISH_VERSION_STR); + pfree(str); + heap_freetuple(bbffunctuple); + return NULL; + } + pfree(str); + + /* Validate antlr_parse_tree_modify_date is the same as or later than the modify_date representing function entry */ + { + Datum parse_tree_modify_datum; + Timestamp modify_ts; + Timestamp cache_modify_ts; + + attr = SysCacheGetAttrNotNull(PROCNAMENSPSIGNATURE, bbffunctuple, + Anum_bbf_function_ext_modify_date); + parse_tree_modify_datum = SysCacheGetAttr(PROCNAMENSPSIGNATURE, bbffunctuple, + Anum_bbf_function_ext_antlr_parse_tree_modify_date, &isnull); + + if (isnull) + { + elog(DEBUG1, "pltsql_restore_func_parse_result: missing antlr_parse_tree_modify_date, skipping cache"); + heap_freetuple(bbffunctuple); + return NULL; + } + + modify_ts = DatumGetTimestamp(attr); + cache_modify_ts = DatumGetTimestamp(parse_tree_modify_datum); + + if (modify_ts > cache_modify_ts) + { + elog(DEBUG1, "pltsql_restore_func_parse_result: modify_date newer than cache, skipping"); + heap_freetuple(bbffunctuple); + return NULL; + } + } + + /* Deserialize parse tree from antlr_parse_tree_text */ + attr = SysCacheGetAttr(PROCNAMENSPSIGNATURE, bbffunctuple, + Anum_bbf_function_ext_antlr_parse_tree_text, &isnull); + if (isnull) + { + /* No cached parse tree found */ + heap_freetuple(bbffunctuple); + return NULL; + } + + str = TextDatumGetCString(attr); + + { + PLtsql_stmt_block *block = NULL; + + PG_TRY(); + { + block = (PLtsql_stmt_block *) pltsql_stringToNode(str); + } + PG_CATCH(); + { + FlushErrorState(); + block = NULL; + elog(LOG, "pltsql_restore_func_parse_result: stringToNode failed for parse tree"); + } + PG_END_TRY(); + + if (block == NULL) + { + pfree(str); + heap_freetuple(bbffunctuple); + return NULL; + } + + result = (PLtsql_cached_parse_result *) palloc(sizeof(PLtsql_cached_parse_result)); + result->parse_tree = block; + result->ndatums = 0; + result->datums = NULL; + } + pfree(str); + + /* Deserialize datums from antlr_parse_tree_datums */ + attr = SysCacheGetAttr(PROCNAMENSPSIGNATURE, bbffunctuple, + Anum_bbf_function_ext_antlr_parse_tree_datums, &isnull); + if (!isnull) + { + char *datums_text = TextDatumGetCString(attr); + List *datum_list = NIL; + + PG_TRY(); + { + datum_list = (List *) pltsql_stringToNode(datums_text); + } + PG_CATCH(); + { + FlushErrorState(); + datum_list = NIL; + elog(LOG, "pltsql_restore_func_parse_result: stringToNode failed for datums"); + } + PG_END_TRY(); + + if (datum_list != NIL) + { + int ndatums = list_length(datum_list); + ListCell *lc; + int i = 0; + + result->ndatums = ndatums; + result->datums = (PLtsql_datum **) palloc(sizeof(PLtsql_datum *) * ndatums); + foreach(lc, datum_list) + { + result->datums[i++] = (PLtsql_datum *) lfirst(lc); + } + } + pfree(datums_text); + } + + elog(DEBUG1, "pltsql_restore_func_parse_result: deserialization succeeded (cmd_type=%d, lineno=%d, ndatums=%d)", + result->parse_tree->cmd_type, result->parse_tree->lineno, result->ndatums); + + heap_freetuple(bbffunctuple); + return result; +} + /* * Update 'function_args' in 'sys.babelfish_schema_permissions' */ diff --git a/contrib/babelfishpg_tsql/src/hooks.h b/contrib/babelfishpg_tsql/src/hooks.h index fa98f74e805..6f90f795276 100644 --- a/contrib/babelfishpg_tsql/src/hooks.h +++ b/contrib/babelfishpg_tsql/src/hooks.h @@ -6,6 +6,7 @@ #include "tcop/cmdtag.h" #include "utils/pg_locale.h" #include "utils/xml.h" +#include "pltsql.h" extern IsExtendedCatalogHookType PrevIsExtendedCatalogHook; extern IsToastRelationHookType PrevIsToastRelationHook; @@ -25,6 +26,26 @@ extern void pltsql_store_func_default_positions(ObjectAddress address, const char *queryString, int origname_location, bool with_recompile); + +/* + * Structure to hold cached parse result retrieved from catalog. + * Contains both the parse tree and datums array. + */ +typedef struct PLtsql_cached_parse_result +{ + PLtsql_stmt_block *parse_tree; /* Deserialized ANTLR parse tree */ + int ndatums; /* Number of datums */ + PLtsql_datum **datums; /* Array of datums (variables, etc.) */ +} PLtsql_cached_parse_result; + +extern PLtsql_cached_parse_result *pltsql_restore_func_parse_result(HeapTuple proctup, + bool *out_cache_enabled, + TransactionId *out_bbf_ext_xmin, + ItemPointerData *out_bbf_ext_tid); +extern void pltsql_fill_cache_columns(PLtsql_function *function, Datum modify_date, + Datum *new_record, bool *new_record_nulls, + bool *new_record_replaces); +extern void pltsql_update_func_cache_entry(HeapTuple proctup, PLtsql_function *function); extern void alter_bbf_schema_permissions_catalog(ObjectWithArgs *owa, List *parameters, int objtypeInt); diff --git a/contrib/babelfishpg_tsql/src/pl_comp.c b/contrib/babelfishpg_tsql/src/pl_comp.c index 5e9ebff06da..6ba8a233b98 100644 --- a/contrib/babelfishpg_tsql/src/pl_comp.c +++ b/contrib/babelfishpg_tsql/src/pl_comp.c @@ -41,6 +41,7 @@ #include "pltsql.h" #include "pltsql-2.h" +#include "hooks.h" #include "analyzer.h" #include "catalog.h" #include "codegen.h" @@ -137,7 +138,7 @@ static void pltsql_resolve_polymorphic_argtypes(int numargs, Oid *argtypes, char *argmodes, Node *call_expr, bool forValidator, const char *proname); -static PLtsql_function *pltsql_HashTableLookup(PLtsql_func_hashkey *func_key); +PLtsql_function *pltsql_HashTableLookup(PLtsql_func_hashkey *func_key); static void pltsql_HashTableInsert(PLtsql_function *function, PLtsql_func_hashkey *func_key); static void pltsql_HashTableDelete(PLtsql_function *function); @@ -200,7 +201,27 @@ pltsql_compile(FunctionCallInfo fcinfo, bool forValidator) if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) && ItemPointerEquals(&function->fn_tid, &procTup->t_self) && function->exec_codes_valid) - function_valid = true; + { + if (function->from_cache) + { + /* + * For functions loaded from persistent cache, also verify + * babelfish_function_ext hasn't changed (e.g., antlr_cache_enabled + * toggled via sys.enable_routine_parse_cache()). + */ + HeapTuple bbftup = get_bbf_function_tuple_from_proctuple(procTup); + if (HeapTupleIsValid(bbftup)) + { + if (function->bbf_ext_xmin == HeapTupleHeaderGetRawXmin(bbftup->t_data) && + ItemPointerEquals(&function->bbf_ext_tid, &bbftup->t_self)) + function_valid = true; + heap_freetuple(bbftup); + } + /* else: tuple gone or not found, invalidate */ + } + else + function_valid = true; + } else { /* @@ -251,7 +272,7 @@ pltsql_compile(FunctionCallInfo fcinfo, bool forValidator) forValidator); /* - * Do the hard part. + * Compile the function (will attempt to use cached parse result if available). */ function = do_compile(fcinfo, procTup, function, &hashkey, forValidator); @@ -328,6 +349,15 @@ do_compile(FunctionCallInfo fcinfo, char *tbl_typ = NULL; /* Name of the output table variable's type */ int *typmods = NULL; /* typmod of each argument if available */ CompileContext *cmpl_ctx = create_compile_context(); + bool cache_enabled_for_func = false; + TransactionId bbf_ext_xmin = InvalidTransactionId; + ItemPointerData bbf_ext_tid; + + /* saved for validate_parse_cache ANTLR parse tree comparison */ + PLtsql_stmt_block *validation_cached_tree = NULL; + PLtsql_datum **validation_cached_datums = NULL; + int validation_cached_ndatums = 0; + MemoryContext validation_cxt = NULL; /* * Setup the scanner input and error info. We assume that this function @@ -389,6 +419,8 @@ do_compile(FunctionCallInfo fcinfo, function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data); function->fn_tid = procTup->t_self; + function->bbf_ext_xmin = InvalidTransactionId; + ItemPointerSetInvalid(&function->bbf_ext_tid); function->fn_input_collation = fcinfo->fncollation; function->fn_cxt = func_cxt; function->out_param_varno = -1; /* set up for no OUT param */ @@ -907,7 +939,186 @@ do_compile(FunctionCallInfo fcinfo, * Now parse the function's text */ { - ANTLR_result result = antlr_parser_cpp(proc_source); + ANTLR_result result; + PLtsql_cached_parse_result *cached_result = NULL; + + ItemPointerSetInvalid(&bbf_ext_tid); + + /* + * Try to restore cached parse result from previous compilation. + * This allows us to skip expensive ANTLR parsing. + * Skip during validation (forValidator=true) as we're just checking syntax. + */ + if (!forValidator) + { + MemoryContext save_cxt = CurrentMemoryContext; + + /* + * When validation GUC is on, restore the cached tree into a + * separate memory context so ANTLR re-parsing (which reuses + * func_cxt) does not overwrite the cached node pointers. + */ + if (pltsql_validate_parse_cache) + { + validation_cxt = AllocSetContextCreate(func_cxt, + "PLtsql validation", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(validation_cxt); + } + + cached_result = pltsql_restore_func_parse_result(procTup, + &cache_enabled_for_func, + &bbf_ext_xmin, + &bbf_ext_tid); + + /* Restore original context */ + MemoryContextSwitchTo(save_cxt); + + if (cached_result) + { + int ci; + + elog(LOG, "pltsql_enable_routine_parse_cache[PASS]: %s (ndatums=%d), retrieved cached parse tree results, skipping ANTLR parsing", function->fn_signature, cached_result->ndatums); + pltsql_parse_result = cached_result->parse_tree; + + /* Populate global datums from cache - pltsql_finish_datums() will copy to function */ + pltsql_nDatums = cached_result->ndatums; + pltsql_Datums = cached_result->datums; + + /* Mark that this function was loaded from persistent cache */ + function->from_cache = true; + + /* + * Re-derive found_varno and fetch_status_varno from cached + * datums by scanning refnames. + * + * The cache was serialized from the validator-compiled function + * which may have a different datum layout than what do_compile + * built above (e.g., for ITVFs the validator serializes BEFORE + * pg_proc is updated with TABLE-mode columns, so the cache has + * fewer datums than the runtime parameter loop creates). + * Scanning by refname makes us layout-independent. + * + * in_arg_varnos is NOT re-derived here — the parameter loop + * above already set it correctly from pg_proc metadata, and + * scanning '@'-prefixed refnames would also match local + * variables, overflowing the pronargs-sized buffer. + */ + function->found_varno = -1; + function->fetch_status_varno = -1; + + for (ci = 0; ci < cached_result->ndatums; ci++) + { + PLtsql_datum *d = cached_result->datums[ci]; + + if (d->dtype == PLTSQL_DTYPE_VAR) + { + PLtsql_var *v = (PLtsql_var *) d; + + if (v->refname && strcmp(v->refname, "found") == 0) + function->found_varno = d->dno; + else if (v->refname && strcmp(v->refname, "@@fetch_status") == 0) + function->fetch_status_varno = d->dno; + } + } + + /* + * Do NOT re-derive in_arg_varnos from cached datums. + * The parameter loop above already populated in_arg_varnos + * correctly from pg_proc metadata. Re-scanning cached datums + * for '@'-prefixed refnames would also match local variables + * (e.g. @sql, @precision), overflowing the in_arg_varnos + * buffer which is sized for pronargs only. + */ + + Assert(function->found_varno >= 0); + Assert(function->fetch_status_varno >= 0); + + /* + * Re-derive out_param_varno from cached datums. + * + * The validator already built the OUT row (PLtsql_row) and it was + * serialized into the cache. The cached parse tree's RETURN nodes + * reference it by dno. We just need to find it and set + * function->out_param_varno so the runtime can use it. + * + * For multiple OUT args (or procedure with any OUT): find the + * PLtsql_row datum in the cache, then rebuild its rowtupdesc + * (which is not serialized — marked read_write_ignore). + * For single OUT arg (non-procedure function): point directly + * to the matching cached datum using the dno from the parameter + * loop (which assigned the same sequential dnos as the validator). + */ + if (num_out_args > 1 || + (num_out_args == 1 && function->fn_prokind == PROKIND_PROCEDURE)) + { + for (ci = 0; ci < cached_result->ndatums; ci++) + { + if (cached_result->datums[ci]->dtype == PLTSQL_DTYPE_ROW) + { + PLtsql_row *row = (PLtsql_row *) cached_result->datums[ci]; + int fi; + + function->out_param_varno = row->dno; + + /* + * Rebuild rowtupdesc — it is NULL after deserialization + * (TupleDesc is not serializable). Walk the ROW's varnos + * to find each member VAR's type info, same as + * build_row_from_vars does. + */ + row->rowtupdesc = CreateTemplateTupleDesc(row->nfields); + for (fi = 0; fi < row->nfields; fi++) + { + PLtsql_var *fvar = (PLtsql_var *) cached_result->datums[row->varnos[fi]]; + + TupleDescInitEntry(row->rowtupdesc, fi + 1, + row->fieldnames[fi], + fvar->datatype->typoid, + fvar->datatype->atttypmod, + 0); + TupleDescInitEntryCollation(row->rowtupdesc, fi + 1, + fvar->datatype->collation); + } + break; + } + } + } + else if (num_out_args == 1) + function->out_param_varno = out_arg_variables[0]->dno; + + /* Free the wrapper structure (but not the data we transferred) */ + pfree(cached_result); + + parse_rc = 0; + + /* + * Debug mode: save the cached tree and fall through to + * ANTLR so both paths get identical post-processing. Compare + * after function hash table insert. + */ + if (pltsql_validate_parse_cache) + { + validation_cached_tree = pltsql_parse_result; + validation_cached_datums = pltsql_Datums; + validation_cached_ndatums = pltsql_nDatums; + function->from_cache = false; + elog(LOG, "pltsql_validate_parse_cache[INFO]: %s validating cached tree against ANTLR at EXEC", + function->fn_signature); + /* fall through to normal ANTLR parsing below */ + } + else + { + /* Debug GUC is OFF, use cached results and skip ANTLR parsing */ + goto skip_antlr_parsing; + } + } + } + + /* No cache hit - do ANTLR parsing */ + /* Mark that this function was NOT loaded from persistent cache */ + function->from_cache = false; + result = antlr_parser_cpp(proc_source); if (result.success) { @@ -919,7 +1130,8 @@ do_compile(FunctionCallInfo fcinfo, parse_rc = 1; /* invalid input */ } } - +skip_antlr_parsing: + /* Continue with normal flow */ if (parse_rc != 0) elog(ERROR, "pltsql parser returned %d", parse_rc); function->action = pltsql_parse_result; @@ -930,8 +1142,9 @@ do_compile(FunctionCallInfo fcinfo, /* * Multi-Statement Table-Valued Function: 1) Add a declare table statement * to the beginning 2) Add a return table statement to the end + * Skip when restoring from cache — the cached parse tree already contains these. */ - if (function->is_mstvf) + if (function->is_mstvf && !function->from_cache) { /* * ANTLR parser would return a stmt list like INIT->BLOCK, where BLOCK @@ -948,9 +1161,11 @@ do_compile(FunctionCallInfo fcinfo, * control to fall off the end without an explicit RETURN statement. The * easiest way to implement this is to add a RETURN statement to the end * of the statement list during parsing. + * Skip when restoring from cache — the cached parse tree already has the dummy return. */ - if (num_out_args > 0 || function->fn_rettype == VOIDOID || - function->fn_retset) + if (!function->from_cache && + (num_out_args > 0 || function->fn_rettype == VOIDOID || + function->fn_retset)) add_dummy_return(function); /* @@ -974,6 +1189,31 @@ do_compile(FunctionCallInfo fcinfo, pltsql_finish_datums(function); + /* + * Re-populate the cross-session cache in babelfish_function_ext if we + * fresh ANTLR parse (not restored from cache) so future sessions can skip parsing. + * This covers: + * - MVU: version mismatch rejected old cache, fresh parse needs storing + * - Rename: cache was NULLed, fresh parse needs storing + * - First exec with empty cache (e.g., created with GUC off, now on) + * Skip during CREATE/ALTER (forValidator path) — pltsql_store_func_default_positions + * handles cache writes for DDL. Calling both in the same transaction causes + * "tuple already updated by self" errors. + */ + if (!forValidator && !function->from_cache && + (pltsql_enable_routine_parse_cache || cache_enabled_for_func)) + pltsql_update_func_cache_entry(procTup, function); + + /* + * Store babelfish_function_ext xmin/tid for cross-session invalidation. + * This was captured by pltsql_restore_func_parse_result (even on cache miss). + */ + if (TransactionIdIsValid(bbf_ext_xmin)) + { + function->bbf_ext_xmin = bbf_ext_xmin; + function->bbf_ext_tid = bbf_ext_tid; + } + /* Debug dump for completed functions */ if (pltsql_DumpExecTree || pltsql_trace_tree) pltsql_dumptree(function); @@ -983,6 +1223,79 @@ do_compile(FunctionCallInfo fcinfo, */ pltsql_HashTableInsert(function, hashkey); + /* + * Parse cache validation: compare cached (deserialized) tree against + * fresh ANTLR compilation. Both trees have been through identical + * post-processing at this point. Gated behind validate_parse_cache GUC. + */ + if (pltsql_validate_parse_cache && validation_cached_tree != NULL) + { + extern bool pltsql_compare_parse_trees(PLtsql_stmt_block *tree_a, + PLtsql_stmt_block *tree_b); + extern bool pltsql_equal_node(const void *a, const void *b); + + bool trees_match = pltsql_compare_parse_trees(validation_cached_tree, + function->action); + + elog(LOG, "pltsql_validate_parse_cache[%s]: %s ANTLR parse tree comparison at EXEC", + trees_match ? "PASS" : "FAIL", function->fn_signature); + + /* + * Datum array comparison: cached vs ANTLR-compiled. + * + * The runtime do_compile parameter loop may create an extra $N + * placeholder datum for text-type parameters (inputCollId handling), + * causing the ANTLR datum array to have 1 more entry than the cached + * (validator-compiled) array. This offset is benign: the cache-hit + * path re-derives found_varno/fetch_status_varno by refname scan. + * We compare the overlapping datums using the generated equality + * functions (which skip lineno/dno/varno/itemno fields). + */ + { + int antlr_ndatums = function->ndatums; + PLtsql_datum **antlr_datums = function->datums; + int max_d = Max(validation_cached_ndatums, antlr_ndatums); + int datum_index; + int datum_mismatches = 0; + + for (datum_index = 0; datum_index < max_d; datum_index++) + { + PLtsql_datum *dc = (datum_index < validation_cached_ndatums) ? validation_cached_datums[datum_index] : NULL; + PLtsql_datum *da = (datum_index < antlr_ndatums) ? antlr_datums[datum_index] : NULL; + + if (dc == NULL && da == NULL) + continue; + if (dc == NULL || da == NULL || !pltsql_equal_node(dc, da)) + { + datum_mismatches++; + elog(LOG, "pltsql_validate_parse_cache[DIFF]: %s has mismatched datum[%d] %s at EXEC (cached_tag=%d, antlr_tag=%d)", + function->fn_signature, datum_index, + (dc == NULL) ? "extra in antlr" : (da == NULL) ? "extra in cached" : "mismatch", + dc ? (int) nodeTag(dc) : -1, + da ? (int) nodeTag(da) : -1); + } + } + + elog(LOG, "pltsql_validate_parse_cache[%s]: %s PLtsql Datums comparison at EXEC (cached=%d, antlr=%d, mismatches=%d)", + (datum_mismatches == 0) ? "PASS" : "DIFF", + function->fn_signature, + validation_cached_ndatums, antlr_ndatums, datum_mismatches); + } + } + + /* Free the validation memory context (cached tree data no longer needed) */ + // if (validation_cxt != NULL) + // { + // MemoryContextDelete(validation_cxt); + // validation_cxt = NULL; + // } + /* + * Keep validation_cxt alive — it is a child of func_cxt and will be + * cleaned up when the function is evicted from the hash table. + * Deleting it here would risk freeing memory that the ANTLR tree + * might reference (e.g., shared interned strings). + */ + /* * Pop the error context stack */ @@ -1446,7 +1759,7 @@ add_dummy_return(PLtsql_function *function) { PLtsql_stmt_block *new; - new = palloc0(sizeof(PLtsql_stmt_block)); + new = makeNode(PLtsql_stmt_block); new->cmd_type = PLTSQL_STMT_BLOCK; new->body = list_make1(function->action); @@ -1457,7 +1770,7 @@ add_dummy_return(PLtsql_function *function) { PLtsql_stmt_return *new; - new = palloc0(sizeof(PLtsql_stmt_return)); + new = makeNode(PLtsql_stmt_return); new->cmd_type = PLTSQL_STMT_RETURN; new->expr = NULL; new->retvarno = function->out_param_varno; @@ -1474,7 +1787,7 @@ add_decl_table(PLtsql_function *function, int tbl_dno, char *tbl_typ) { PLtsql_stmt_decl_table *new; - new = palloc0(sizeof(PLtsql_stmt_decl_table)); + new = makeNode(PLtsql_stmt_decl_table); new->cmd_type = PLTSQL_STMT_DECL_TABLE; new->dno = tbl_dno; new->tbltypname = tbl_typ; @@ -2363,7 +2676,7 @@ pltsql_build_variable(const char *refname, int lineno, PLtsql_type *dtype, /* Ordinary scalar datatype */ PLtsql_var *var; - var = palloc0(sizeof(PLtsql_var)); + var = makeNode(PLtsql_var); var->dtype = PLTSQL_DTYPE_VAR; var->refname = pstrdup(refname); var->lineno = lineno; @@ -2446,7 +2759,7 @@ pltsql_build_record(const char *refname, int lineno, { PLtsql_rec *rec; - rec = palloc0(sizeof(PLtsql_rec)); + rec = makeNode(PLtsql_rec); rec->dtype = PLTSQL_DTYPE_REC; rec->refname = pstrdup(refname); rec->lineno = lineno; @@ -2472,7 +2785,7 @@ pltsql_build_table(const char *refname, int lineno, { PLtsql_tbl *tbl; - tbl = palloc0(sizeof(PLtsql_tbl)); + tbl = makeNode(PLtsql_tbl); tbl->dtype = PLTSQL_DTYPE_TBL; tbl->refname = pstrdup(refname); tbl->lineno = lineno; @@ -2498,7 +2811,7 @@ build_row_from_vars(PLtsql_variable **vars, int numvars) PLtsql_row *row; int i; - row = palloc0(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = "(unnamed row)"; row->lineno = -1; @@ -2579,7 +2892,7 @@ pltsql_build_recfield(PLtsql_rec *rec, const char *fldname) } /* nope, so make a new one */ - recfield = palloc0(sizeof(PLtsql_recfield)); + recfield = makeNode(PLtsql_recfield); recfield->dtype = PLTSQL_DTYPE_RECFIELD; recfield->fieldname = pstrdup(fldname); recfield->recparentno = rec->dno; @@ -2645,7 +2958,7 @@ build_datatype(HeapTuple typeTup, int32 typmod, errmsg("type \"%s\" is only a shell", NameStr(typeStruct->typname)))); - typ = (PLtsql_type *) palloc(sizeof(PLtsql_type)); + typ = (PLtsql_type *) makeNode(PLtsql_type); typ->typname = pstrdup(NameStr(typeStruct->typname)); typ->typoid = typeStruct->oid; @@ -2749,7 +3062,7 @@ pltsql_build_table_datatype_coldef(const char *coldef) { PLtsql_type *typ; - typ = (PLtsql_type *) palloc(sizeof(PLtsql_type)); + typ = (PLtsql_type *) makeNode(PLtsql_type); typ->typname = NULL; typ->typoid = InvalidOid; typ->ttype = PLTSQL_TTYPE_TBL; @@ -2828,7 +3141,7 @@ pltsql_parse_err_condition(char *condname) */ if (strcmp(condname, "others") == 0) { - new = palloc(sizeof(PLtsql_condition)); + new = makeNode(PLtsql_condition); new->sqlerrstate = 0; new->condname = condname; new->next = NULL; @@ -2840,7 +3153,7 @@ pltsql_parse_err_condition(char *condname) { if (strcmp(condname, exception_label_map[i].label) == 0) { - new = palloc(sizeof(PLtsql_condition)); + new = makeNode(PLtsql_condition); new->sqlerrstate = exception_label_map[i].sqlerrstate; new->condname = condname; new->next = prev; @@ -3148,7 +3461,7 @@ pltsql_HashTableInit(void) HASH_ELEM | HASH_BLOBS); } -static PLtsql_function * +PLtsql_function * pltsql_HashTableLookup(PLtsql_func_hashkey *func_key) { pltsql_HashEnt *hentry; @@ -3158,7 +3471,16 @@ pltsql_HashTableLookup(PLtsql_func_hashkey *func_key) HASH_FIND, NULL); if (hentry) + { + /* If GUC is disabled and function was loaded from cache, return NULL to force re-parse */ + if (!pltsql_enable_routine_parse_cache && hentry->function->from_cache) + { + elog(DEBUG1, "pltsql_HashTableLookup: GUC disabled, invalidating cached function %u", (unsigned int) func_key->funcOid); + return NULL; + } + return hentry->function; + } else return NULL; } diff --git a/contrib/babelfishpg_tsql/src/pl_exec-2.c b/contrib/babelfishpg_tsql/src/pl_exec-2.c index ad8d6559d39..4d545c6be7d 100644 --- a/contrib/babelfishpg_tsql/src/pl_exec-2.c +++ b/contrib/babelfishpg_tsql/src/pl_exec-2.c @@ -1029,7 +1029,7 @@ exec_stmt_exec(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt) */ oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt); - row = (PLtsql_row *) palloc0(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = "(unnamed row)"; row->lineno = -1; @@ -1449,7 +1449,7 @@ exec_stmt_return_table(PLtsql_execstate *estate, PLtsql_stmt_return_query *stmt) */ oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt); - expr = palloc0(sizeof(PLtsql_expr)); + expr = makeNode(PLtsql_expr); /* * Add delimiters for valid T-SQL variable names like @@var or @var# @@ -1613,7 +1613,7 @@ execute_batch(PLtsql_execstate *estate, char *batch, InlineCodeBlockArgs *args, * 3. Read parameter values, insert OUT parameter info in the row * Datum. */ - row = (PLtsql_row *) palloc0(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = "(unnamed row)"; row->lineno = -1; diff --git a/contrib/babelfishpg_tsql/src/pl_exec.c b/contrib/babelfishpg_tsql/src/pl_exec.c index f7f880d110c..bf5dea31938 100644 --- a/contrib/babelfishpg_tsql/src/pl_exec.c +++ b/contrib/babelfishpg_tsql/src/pl_exec.c @@ -2517,7 +2517,7 @@ exec_stmt_call(PLtsql_execstate *estate, PLtsql_stmt_call *stmt) */ oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt); - row = (PLtsql_row *) palloc0(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = "(unnamed row)"; row->lineno = -1; @@ -3474,7 +3474,7 @@ exec_stmt_return(PLtsql_execstate *estate, PLtsql_stmt_return *stmt) { PLtsql_stmt_return_query *return_table; - return_table = (PLtsql_stmt_return_query *) palloc0(sizeof(PLtsql_stmt_return_query)); + return_table = (PLtsql_stmt_return_query *) makeNode(PLtsql_stmt_return_query); return_table->cmd_type = PLTSQL_STMT_RETURN_TABLE; return_table->query = NULL; return_table->dynquery = NULL; @@ -4717,7 +4717,7 @@ setup_procedure_output_target_for_insert_exec(PLtsql_execstate *estate, PLtsql_s */ oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt); - row = (PLtsql_row *) palloc0(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = "(unnamed row)"; row->lineno = -1; @@ -5376,7 +5376,7 @@ exec_fmtonly(PLtsql_execstate *estate, PLtsql_var *return_code; Query *query; - estmt = (PLtsql_stmt_exec *) palloc0(sizeof(*estmt)); + estmt = (PLtsql_stmt_exec *) makeNode(PLtsql_stmt_exec); estmt->cmd_type = PLTSQL_STMT_EXEC; estmt->lineno = stmt->lineno; estmt->is_call = true; @@ -5387,7 +5387,7 @@ exec_fmtonly(PLtsql_execstate *estate, appendStringInfo(&ss, "EXEC sp_describe_first_result_set N'"); appendStringInfoString(&ss, expr->query); appendStringInfo(&ss, "', null, 0;"); - estmt->expr = (PLtsql_expr *) palloc0(sizeof(estmt->expr)); + estmt->expr = makeNode(PLtsql_expr); estmt->expr->query = strdup(ss.data); estmt->expr->plan = NULL; estmt->expr->paramnos = NULL; @@ -5436,7 +5436,7 @@ exec_fmtonly(PLtsql_execstate *estate, */ oldcontext = MemoryContextSwitchTo(estate->func->fn_cxt); - row = (PLtsql_row *) palloc0(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = "(unnamed row)"; row->lineno = -1; diff --git a/contrib/babelfishpg_tsql/src/pl_funcs.c b/contrib/babelfishpg_tsql/src/pl_funcs.c index aae396ecc19..358200cf5ec 100644 --- a/contrib/babelfishpg_tsql/src/pl_funcs.c +++ b/contrib/babelfishpg_tsql/src/pl_funcs.c @@ -102,6 +102,7 @@ pltsql_ns_additem(PLtsql_nsitem_type itemtype, int itemno, const char *name) Assert(ns_top != NULL || itemtype == PLTSQL_NSTYPE_LABEL); nse = palloc0(offsetof(PLtsql_nsitem, name) + strlen(name) + 1); + NodeSetTag(nse, T_PLtsql_nsitem); nse->itemtype = itemtype; nse->itemno = itemno; nse->prev = ns_top; diff --git a/contrib/babelfishpg_tsql/src/pl_gram.y b/contrib/babelfishpg_tsql/src/pl_gram.y index a144f1d3617..8aeba5b67e7 100644 --- a/contrib/babelfishpg_tsql/src/pl_gram.y +++ b/contrib/babelfishpg_tsql/src/pl_gram.y @@ -553,7 +553,7 @@ pl_function : comp_options proc_sect { if ($2 == NIL) { - PLtsql_stmt_block *block = palloc0(sizeof(PLtsql_stmt_block)); + PLtsql_stmt_block *block = makeNode(PLtsql_stmt_block); block->cmd_type = PLTSQL_STMT_BLOCK; block->lineno = 0; @@ -577,7 +577,7 @@ pl_function : comp_options proc_sect pltsql_parse_result = (PLtsql_stmt_block *) first; else { - PLtsql_stmt_block *block = palloc0(sizeof(PLtsql_stmt_block)); + PLtsql_stmt_block *block = makeNode(PLtsql_stmt_block); block->cmd_type = PLTSQL_STMT_BLOCK; block->lineno = pltsql_location_to_lineno(@2); @@ -628,7 +628,7 @@ pl_block : opt_block_label K_BEGIN proc_sect exception_sect K_END int tok1; int tok2; - new = palloc0(sizeof(PLtsql_stmt_block)); + new = makeNode(PLtsql_stmt_block); new->cmd_type = PLTSQL_STMT_BLOCK; new->lineno = pltsql_location_to_lineno(@3); @@ -662,7 +662,7 @@ try_catch_block : opt_block_label try_block catch_block { PLtsql_stmt_try_catch *new; - new = palloc0(sizeof(PLtsql_stmt_try_catch)); + new = makeNode(PLtsql_stmt_try_catch); TSQLInstrumentation(INSTR_TSQL_TRY_CATCH_BLOCK); @@ -686,7 +686,7 @@ try_block : K_BEGIN K_TRY opt_semi proc_sect K_END_TRY else { PLtsql_stmt_block *new; - new = palloc0(sizeof(PLtsql_stmt_block)); + new = makeNode(PLtsql_stmt_block); new->cmd_type = PLTSQL_STMT_BLOCK; new->lineno = pltsql_location_to_lineno(@1); @@ -709,7 +709,7 @@ catch_block: K_BEGIN K_CATCH opt_semi proc_sect K_END_CATCH opt_semi else { PLtsql_stmt_block *new; - new = palloc0(sizeof(PLtsql_stmt_block)); + new = makeNode(PLtsql_stmt_block); new->cmd_type = PLTSQL_STMT_BLOCK; new->lineno = pltsql_location_to_lineno(@1); @@ -724,7 +724,7 @@ catch_block: K_BEGIN K_CATCH opt_semi proc_sect K_END_CATCH opt_semi stmt_goto : K_GOTO T_WORD opt_semi { - PLtsql_stmt_goto * stmt_goto = palloc0(sizeof(PLtsql_stmt_goto)); + PLtsql_stmt_goto * stmt_goto = makeNode(PLtsql_stmt_goto); TSQLInstrumentation(INSTR_TSQL_GOTO_STMT); stmt_goto->cmd_type = PLTSQL_STMT_GOTO; stmt_goto->lineno = pltsql_location_to_lineno(@1); @@ -737,7 +737,7 @@ stmt_goto : K_GOTO T_WORD opt_semi stmt_label : TSQL_LABEL { - PLtsql_stmt_label *label = palloc0(sizeof(PLtsql_stmt_label)); + PLtsql_stmt_label *label = makeNode(PLtsql_stmt_label); label->cmd_type = PLTSQL_STMT_LABEL; label->lineno = pltsql_location_to_lineno(@1); label->label = pnstrdup($1, strlen($1) - 1 ); // exclude last : @@ -752,7 +752,7 @@ stmt_raiserror : K_RAISERROR '(' int term; PLtsql_expr *expr; - new = palloc(sizeof(PLtsql_stmt_raiserror)); + new = makeNode(PLtsql_stmt_raiserror); new->cmd_type = PLTSQL_STMT_RAISERROR; new->lineno = pltsql_location_to_lineno(@1); @@ -837,7 +837,7 @@ stmt_throw : K_THROW int term; PLtsql_expr *expr; - new = palloc(sizeof(PLtsql_stmt_throw)); + new = makeNode(PLtsql_stmt_throw); new->cmd_type = PLTSQL_STMT_THROW; new->lineno = pltsql_location_to_lineno(@1); @@ -872,7 +872,7 @@ stmt_throw : K_THROW stmt_use_db : K_USE T_WORD opt_semi { - PLtsql_stmt_usedb *use_db = palloc0(sizeof(PLtsql_stmt_usedb)); + PLtsql_stmt_usedb *use_db = makeNode(PLtsql_stmt_usedb); use_db->cmd_type = PLTSQL_STMT_USEDB; use_db->lineno = pltsql_location_to_lineno(@1); use_db->db_name = pstrdup($2.ident); @@ -941,7 +941,7 @@ decl_statement : decl_varname opt_as decl_const decl_datatype decl_collate decl_ if ($7 != NULL) { - PLtsql_stmt_assign *init = palloc0(sizeof(*init)); + PLtsql_stmt_assign *init = makeNode(PLtsql_stmt_assign); init->cmd_type = PLTSQL_STMT_ASSIGN; init->lineno = pltsql_location_to_lineno(@7); @@ -953,7 +953,7 @@ decl_statement : decl_varname opt_as decl_const decl_datatype decl_collate decl_ { if (var->dtype == PLTSQL_DTYPE_TBL) { - PLtsql_stmt_decl_table *decl = palloc0(sizeof(*decl)); + PLtsql_stmt_decl_table *decl = makeNode(PLtsql_stmt_decl_table); decl->cmd_type = PLTSQL_STMT_DECL_TABLE; decl->lineno = pltsql_location_to_lineno(@1); decl->dno = var->dno; @@ -1028,7 +1028,7 @@ decl_statement : decl_varname opt_as decl_const decl_datatype decl_collate decl_ NULL), true); - curname_def = palloc0(sizeof(PLtsql_expr)); + curname_def = makeNode(PLtsql_expr); #if 0 curname_def->dtype = PLTSQL_DTYPE_EXPR; #endif @@ -1066,7 +1066,7 @@ decl_statement : decl_varname opt_as decl_const decl_datatype decl_collate decl_ if (query != NULL) new->isconst = true; - new_stmt = palloc0(sizeof(PLtsql_stmt_decl_cursor)); + new_stmt = makeNode(PLtsql_stmt_decl_cursor); new_stmt->cmd_type = PLTSQL_STMT_DECL_CURSOR; new_stmt->lineno = pltsql_location_to_lineno(@1); new_stmt->curvar = new->dno; @@ -1122,7 +1122,7 @@ decl_statement : decl_varname opt_as decl_const decl_datatype decl_collate decl_ yyerror("syntax error, expected \"FOR\""); } - new = palloc0(sizeof(PLtsql_stmt_decl_cursor)); + new = makeNode(PLtsql_stmt_decl_cursor); new->cmd_type = PLTSQL_STMT_DECL_CURSOR; new->lineno = pltsql_location_to_lineno(@1); new->curvar = $1.datum->dno; @@ -1164,7 +1164,7 @@ decl_cursor_args : int i; ListCell *l; - new = palloc0(sizeof(PLtsql_row)); + new = makeNode(PLtsql_row); new->dtype = PLTSQL_DTYPE_ROW; new->lineno = pltsql_location_to_lineno(@1); new->rowtupdesc = NULL; @@ -1345,7 +1345,7 @@ stmt_declare : K_DECLARE decl_list * At execution time, we execute each of these assignment * statements, in order. */ - PLtsql_stmt_init *new = palloc0(sizeof *new); + PLtsql_stmt_init *new = makeNode(PLtsql_stmt_init); new->cmd_type = PLTSQL_STMT_INIT; new->lineno = pltsql_location_to_lineno(@1); @@ -1469,7 +1469,7 @@ stmt_perform : K_PERFORM expr_until_semi_or_bos { PLtsql_stmt_perform *new; - new = palloc0(sizeof(PLtsql_stmt_perform)); + new = makeNode(PLtsql_stmt_perform); new->cmd_type = PLTSQL_STMT_PERFORM; new->lineno = pltsql_location_to_lineno(@1); new->expr = $2; @@ -1490,7 +1490,7 @@ stmt_exec : exec_keyword { PLtsql_stmt_exec_batch *new_batch; - new_batch = palloc0(sizeof(PLtsql_stmt_exec_batch)); + new_batch = makeNode(PLtsql_stmt_exec_batch); new_batch->cmd_type = PLTSQL_STMT_EXEC_BATCH; new_batch->lineno = pltsql_location_to_lineno(@1); new_batch->expr = read_sql_expression(')', ")"); @@ -1540,7 +1540,7 @@ stmt_exec : exec_keyword { PLtsql_stmt_exec *new; - new = palloc0(sizeof(PLtsql_stmt_exec)); + new = makeNode(PLtsql_stmt_exec); new->cmd_type = PLTSQL_STMT_EXEC; new->lineno = pltsql_location_to_lineno(@1); new->expr = read_sql_stmt_bos("EXEC "); @@ -1629,7 +1629,7 @@ stmt_assign : K_SET assign_var '=' /* Start to build anonymous constant cursor. similar with DECLARE CURSOR */ /* Generate cursor name based on pointer of PLtsql_stmt_assign since it is unique until procedure is dropped */ - new = palloc0(sizeof(PLtsql_stmt_assign)); + new = makeNode(PLtsql_stmt_assign); snprintf(varname, NAMEDATALEN, "%s##sys_gen##%p", ((PLtsql_var *) $2)->refname, (void *) new); new_curvar = (PLtsql_var *) @@ -1637,7 +1637,7 @@ stmt_assign : K_SET assign_var '=' pltsql_build_datatype(REFCURSOROID, -1, InvalidOid, NULL), true); - curname_def = palloc0(sizeof(PLtsql_expr)); + curname_def = makeNode(PLtsql_expr); #if 0 curname_def->dtype = PLTSQL_DTYPE_EXPR; #endif @@ -1668,7 +1668,7 @@ stmt_assign : K_SET assign_var '=' /* Start of assignment part */ - new_curvar_expr = palloc0(sizeof(PLtsql_expr)); + new_curvar_expr = makeNode(PLtsql_expr); snprintf(buf, 1024, "SELECT \"%s\"", varname); new_curvar_expr->query = pstrdup(buf); new_curvar_expr->ns = pltsql_ns_top(); @@ -1684,7 +1684,7 @@ stmt_assign : K_SET assign_var '=' { pltsql_push_back_token(tok); - new = palloc0(sizeof(PLtsql_stmt_assign)); + new = makeNode(PLtsql_stmt_assign); new->cmd_type = PLTSQL_STMT_ASSIGN; new->lineno = pltsql_location_to_lineno(@2); new->varno = $2->dno; @@ -1746,7 +1746,7 @@ stmt_assign : K_SET assign_var '=' } } - new = palloc0(sizeof(PLtsql_stmt_assign)); + new = makeNode(PLtsql_stmt_assign); new->cmd_type = PLTSQL_STMT_ASSIGN; new->lineno = pltsql_location_to_lineno(@2); new->varno = $2->dno; @@ -1794,8 +1794,8 @@ stmt_assign : K_SET assign_var '=' * execute time (just like an INSERT or DELETE or * other SQL command) */ - PLtsql_stmt_execsql *stmt = palloc(sizeof(*stmt)); - PLtsql_expr *expr = palloc(sizeof(*expr)); + PLtsql_stmt_execsql *stmt = makeNode(PLtsql_stmt_execsql); + PLtsql_expr *expr = makeNode(PLtsql_expr); StringInfoData cmd; initStringInfo(&cmd); @@ -1827,7 +1827,7 @@ stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list opt_semi PLtsql_stmt_getdiag *new; ListCell *lc; - new = palloc0(sizeof(PLtsql_stmt_getdiag)); + new = makeNode(PLtsql_stmt_getdiag); new->cmd_type = PLTSQL_STMT_GETDIAG; new->lineno = pltsql_location_to_lineno(@1); new->is_stacked = $2; @@ -1903,7 +1903,7 @@ getdiag_list_item : getdiag_target assign_operator getdiag_item { PLtsql_diag_item *new; - new = palloc(sizeof(PLtsql_diag_item)); + new = makeNode(PLtsql_diag_item); new->target = $1; new->kind = $3; @@ -1972,7 +1972,7 @@ assign_var : T_DATUM { PLtsql_arrayelem *new; - new = palloc0(sizeof(PLtsql_arrayelem)); + new = makeNode(PLtsql_arrayelem); new->dtype = PLTSQL_DTYPE_ARRAYELEM; new->subscript = $3; new->arrayparentno = $1->dno; @@ -1989,7 +1989,7 @@ stmt_if : K_IF expr_until_bos proc_stmt %prec LOWER_THAN_ELSE { PLtsql_stmt_if *new; - new = palloc0(sizeof(PLtsql_stmt_if)); + new = makeNode(PLtsql_stmt_if); new->cmd_type = PLTSQL_STMT_IF; new->lineno = pltsql_location_to_lineno(@1); new->cond = $2; @@ -2001,7 +2001,7 @@ stmt_if : K_IF expr_until_bos proc_stmt %prec LOWER_THAN_ELSE { PLtsql_stmt_if *new; - new = palloc0(sizeof(PLtsql_stmt_if)); + new = makeNode(PLtsql_stmt_if); new->cmd_type = PLTSQL_STMT_IF; new->lineno = pltsql_location_to_lineno(@1); new->cond = $2; @@ -2016,7 +2016,7 @@ stmt_loop : opt_block_label K_LOOP loop_body { PLtsql_stmt_loop *new; - new = palloc0(sizeof(PLtsql_stmt_loop)); + new = makeNode(PLtsql_stmt_loop); new->cmd_type = PLTSQL_STMT_LOOP; new->lineno = pltsql_location_to_lineno(@2); new->label = $1; @@ -2033,7 +2033,7 @@ stmt_while : opt_block_label K_WHILE expr_until_bos K_LOOP loop_body { PLtsql_stmt_while *new; - new = palloc0(sizeof(PLtsql_stmt_while)); + new = makeNode(PLtsql_stmt_while); new->cmd_type = PLTSQL_STMT_WHILE; new->lineno = pltsql_location_to_lineno(@2); new->label = $1; @@ -2049,7 +2049,7 @@ stmt_while : opt_block_label K_WHILE expr_until_bos K_LOOP loop_body { PLtsql_stmt_while *new; - new = palloc0(sizeof(PLtsql_stmt_while)); + new = makeNode(PLtsql_stmt_while); new->cmd_type = PLTSQL_STMT_WHILE; new->lineno = pltsql_location_to_lineno(@2); new->label = $1; @@ -2114,7 +2114,7 @@ for_control : for_variable K_IN "LOOP or USING", &term); - new = palloc0(sizeof(PLtsql_stmt_dynfors)); + new = makeNode(PLtsql_stmt_dynfors); new->cmd_type = PLTSQL_STMT_DYNFORS; if ($1.row) @@ -2160,7 +2160,7 @@ for_control : for_variable K_IN PLtsql_stmt_forc *new; PLtsql_var *cursor = (PLtsql_var *) yylval.wdatum.datum; - new = (PLtsql_stmt_forc *) palloc0(sizeof(PLtsql_stmt_forc)); + new = (PLtsql_stmt_forc *) makeNode(PLtsql_stmt_forc); new->cmd_type = PLTSQL_STMT_FORC; new->curvar = cursor->dno; @@ -2275,7 +2275,7 @@ for_control : for_variable K_IN NULL), true); - new = palloc0(sizeof(PLtsql_stmt_fori)); + new = makeNode(PLtsql_stmt_fori); new->cmd_type = PLTSQL_STMT_FORI; new->var = fvar; new->reverse = reverse; @@ -2309,7 +2309,7 @@ for_control : for_variable K_IN check_sql_expr(expr1->query, expr1loc, 0); - new = palloc0(sizeof(PLtsql_stmt_fors)); + new = makeNode(PLtsql_stmt_fors); new->cmd_type = PLTSQL_STMT_FORS; if ($1.row) { @@ -2407,7 +2407,7 @@ stmt_foreach_a : opt_block_label K_FOREACH for_variable foreach_slice K_IN K_ARR { PLtsql_stmt_foreach_a *new; - new = palloc0(sizeof(PLtsql_stmt_foreach_a)); + new = makeNode(PLtsql_stmt_foreach_a); new->cmd_type = PLTSQL_STMT_FOREACH_A; new->lineno = pltsql_location_to_lineno(@2); new->label = $1; @@ -2454,7 +2454,7 @@ stmt_exit : exit_type opt_semi { PLtsql_stmt_exit *new; - new = palloc0(sizeof(PLtsql_stmt_exit)); + new = makeNode(PLtsql_stmt_exit); new->cmd_type = PLTSQL_STMT_EXIT; new->is_exit = $1; new->lineno = pltsql_location_to_lineno(@1); @@ -2539,7 +2539,7 @@ stmt_raise : K_RAISE PLtsql_stmt_raise *new; int tok; - new = palloc(sizeof(PLtsql_stmt_raise)); + new = makeNode(PLtsql_stmt_raise); new->cmd_type = PLTSQL_STMT_RAISE; new->lineno = pltsql_location_to_lineno(@1); @@ -2677,7 +2677,7 @@ stmt_print : K_PRINT expr_until_semi_or_bos { PLtsql_stmt_print *new; - new = palloc(sizeof(*new)); + new = makeNode(PLtsql_stmt_print); new->cmd_type = PLTSQL_STMT_PRINT; new->lineno = pltsql_location_to_lineno(@1); @@ -2779,7 +2779,7 @@ stmt_open : K_OPEN opt_global_or_local cursor_variable opt_semi parser_errposition(@1))); } - new = palloc0(sizeof(PLtsql_stmt_open)); + new = makeNode(PLtsql_stmt_open); new->cmd_type = PLTSQL_STMT_OPEN; new->lineno = pltsql_location_to_lineno(@1); new->curvar = $3->dno; @@ -2851,7 +2851,7 @@ stmt_close : K_CLOSE opt_global_or_local cursor_variable opt_semi parser_errposition(@1))); } - new = palloc(sizeof(PLtsql_stmt_close)); + new = makeNode(PLtsql_stmt_close); new->cmd_type = PLTSQL_STMT_CLOSE; new->lineno = pltsql_location_to_lineno(@1); new->curvar = $3->dno; @@ -2879,7 +2879,7 @@ stmt_deallocate : K_DEALLOCATE opt_global_or_local cursor_variable opt_semi parser_errposition(@1))); } - new = palloc0(sizeof(PLtsql_stmt_deallocate)); + new = makeNode(PLtsql_stmt_deallocate); new->cmd_type = PLTSQL_STMT_DEALLOCATE; new->lineno = pltsql_location_to_lineno(@1); new->curvar = $3->dno; @@ -2928,7 +2928,7 @@ exception_sect : * current block. */ int lineno = pltsql_location_to_lineno(@1); - PLtsql_exception_block *new = palloc(sizeof(PLtsql_exception_block)); + PLtsql_exception_block *new = makeNode(PLtsql_exception_block); PLtsql_variable *var; var = pltsql_build_variable("sqlstate", lineno, @@ -2974,7 +2974,7 @@ proc_exception : K_WHEN proc_conditions K_THEN proc_sect { PLtsql_exception *new; - new = palloc0(sizeof(PLtsql_exception)); + new = makeNode(PLtsql_exception); new->lineno = pltsql_location_to_lineno(@1); new->conditions = $2; new->action = $4; @@ -3019,7 +3019,7 @@ proc_condition : any_identifier if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5) yyerror("invalid SQLSTATE code"); - new = palloc(sizeof(PLtsql_condition)); + new = makeNode(PLtsql_condition); new->sqlerrstate = MAKE_SQLSTATE(sqlstatestr[0], sqlstatestr[1], @@ -3554,7 +3554,7 @@ read_sql_bos(int until, ds.data[--ds.len] = '\0'; } - expr = palloc0(sizeof(PLtsql_expr)); + expr = makeNode(PLtsql_expr); #if 0 expr->dtype = PLTSQL_DTYPE_EXPR; #endif @@ -4407,7 +4407,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr * appendStringInfoString(&query, quote_tsql_identifiers(&ctx.ds, ctx.tsql_idents)); - ctx.expr = palloc0(sizeof(PLtsql_expr)); + ctx.expr = makeNode(PLtsql_expr); #if 0 ctx.expr->dtype = PLTSQL_DTYPE_EXPR; #endif @@ -4428,7 +4428,7 @@ make_execsql_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr * check_sql_expr(ctx.expr->query, ctx.location, (ctx.have_temptbl ? strlen(TEMPOBJ_QUALIFIER) : 0)); - execsql = palloc(sizeof(PLtsql_stmt_execsql)); + execsql = makeNode(PLtsql_stmt_execsql); execsql->cmd_type = PLTSQL_STMT_EXECSQL; execsql->lineno = pltsql_location_to_lineno(location); execsql->sqlstmt = ctx.expr; @@ -4588,7 +4588,7 @@ read_query_targets2() if (tok->token == '=' && caselevel == 0 && parenlevel == 0) { query_target *target2 = palloc(sizeof(*target2)); - PLtsql_expr *target2_expr = palloc0(sizeof(*target2_expr)); + PLtsql_expr *target2_expr = makeNode(PLtsql_expr); target2->dno = -1; target2->operator = -1; target2_expr->plan = NULL; @@ -4641,7 +4641,7 @@ read_query_targets() static PLtsql_expr * make_target_expr(List *fields, int location) { - PLtsql_expr *result = palloc0(sizeof(*result)); + PLtsql_expr *result = makeNode(PLtsql_expr); const char *separator = ""; StringInfoData src; ListCell *it; @@ -4670,7 +4670,7 @@ make_target_expr(List *fields, int location) static PLtsql_row * make_target_row(List *fields, int location) { - PLtsql_row *result = palloc0(sizeof(*result)); + PLtsql_row *result = makeNode(PLtsql_row); ListCell *it; int i; @@ -5035,7 +5035,7 @@ make_create_stmt(int firsttoken, int location, PLword *firstword) pltsql_append_source_text(&ctx.ds, ctx.location, yylloc); - ctx.expr = palloc0(sizeof(PLtsql_expr)); + ctx.expr = makeNode(PLtsql_expr); ctx.expr->query = pstrdup(quote_tsql_identifiers(&ctx.ds, ctx.tsql_idents)); ctx.expr->plan = NULL; ctx.expr->paramnos = NULL; @@ -5043,7 +5043,7 @@ make_create_stmt(int firsttoken, int location, PLword *firstword) ctx.expr->ns = pltsql_ns_top(); pfree(ctx.ds.data); - execsql = palloc(sizeof(PLtsql_stmt_execsql)); + execsql = makeNode(PLtsql_stmt_execsql); execsql->cmd_type = PLTSQL_STMT_EXECSQL; execsql->lineno = pltsql_location_to_lineno(location); execsql->sqlstmt = ctx.expr; @@ -5236,7 +5236,7 @@ make_select_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr *w * need to create execsql stmt instead of select set stmt for it. */ if (ctx.select_into_table_name != NULL) { - PLtsql_stmt_execsql *result = palloc(sizeof(*result)); + PLtsql_stmt_execsql *result = makeNode(PLtsql_stmt_execsql); result->cmd_type = PLTSQL_STMT_EXECSQL; result->lineno = pltsql_location_to_lineno(location); result->sqlstmt = ctx.expr; @@ -5250,7 +5250,7 @@ make_select_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr *w } else { - PLtsql_stmt_query_set *result = palloc(sizeof(*result)); + PLtsql_stmt_query_set *result = makeNode(PLtsql_stmt_query_set); result->cmd_type = PLTSQL_STMT_QUERY_SET; result->lineno = pltsql_location_to_lineno(location); @@ -5274,7 +5274,7 @@ make_select_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr *w * 2) T-SQL does not require the caller to declare the record format */ - PLtsql_stmt_push_result *result = palloc(sizeof(*result)); + PLtsql_stmt_push_result *result = makeNode(PLtsql_stmt_push_result); result->cmd_type = PLTSQL_STMT_PUSH_RESULT; result->lineno = pltsql_location_to_lineno(location); @@ -5369,7 +5369,7 @@ parse_and_build_select_expr(execsql_ctx *ctx, PLtsql_expr *with_clauses, PLtsql_ YYDPRINTF((stderr, "*** final query\n %s\n", query.data)); - ctx->expr = palloc0(sizeof(PLtsql_expr)); + ctx->expr = makeNode(PLtsql_expr); #if 0 ctx->expr->dtype = PLTSQL_DTYPE_EXPR; #endif @@ -5457,7 +5457,7 @@ make_update_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr *w YYDPRINTF((stderr, "*** final query\n %s\n", query.data)); - ctx.expr = palloc0(sizeof(PLtsql_expr)); + ctx.expr = makeNode(PLtsql_expr); #if 0 ctx.expr->dtype = PLTSQL_DTYPE_EXPR; #endif @@ -5483,7 +5483,7 @@ make_update_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr *w /* * UPDATE with variables will use query_set */ - PLtsql_stmt_query_set *result = palloc(sizeof(*result)); + PLtsql_stmt_query_set *result = makeNode(PLtsql_stmt_query_set); result->cmd_type = PLTSQL_STMT_QUERY_SET; result->lineno = pltsql_location_to_lineno(location); @@ -5497,7 +5497,7 @@ make_update_stmt(int firsttoken, int location, PLword *firstword, PLtsql_expr *w /* * UPDATE with no variables */ - PLtsql_stmt_execsql *result = palloc(sizeof(*result)); + PLtsql_stmt_execsql *result = makeNode(PLtsql_stmt_execsql); result->cmd_type = PLTSQL_STMT_EXECSQL; result->lineno = pltsql_location_to_lineno(location); @@ -5527,7 +5527,7 @@ read_fetch_direction(void) * We create the PLtsql_stmt_fetch struct here, but only fill in * the fields arising from the optional direction clause */ - fetch = (PLtsql_stmt_fetch *) palloc0(sizeof(PLtsql_stmt_fetch)); + fetch = (PLtsql_stmt_fetch *) makeNode(PLtsql_stmt_fetch); fetch->cmd_type = PLTSQL_STMT_FETCH; /* set direction defaults: */ fetch->direction = FETCH_FORWARD; @@ -5679,7 +5679,7 @@ make_return_stmt(int location) int tok = 0; PLtsql_stmt_return *new; - new = palloc0(sizeof(PLtsql_stmt_return)); + new = makeNode(PLtsql_stmt_return); new->cmd_type = PLTSQL_STMT_RETURN; new->lineno = pltsql_location_to_lineno(location); new->expr = NULL; @@ -5814,7 +5814,7 @@ make_return_next_stmt(int location) errmsg("cannot use RETURN NEXT in a non-SETOF function"), parser_errposition(location))); - new = palloc0(sizeof(PLtsql_stmt_return_next)); + new = makeNode(PLtsql_stmt_return_next); new->cmd_type = PLTSQL_STMT_RETURN_NEXT; new->lineno = pltsql_location_to_lineno(location); new->expr = NULL; @@ -5875,7 +5875,7 @@ make_return_query_stmt(int location, PLtsql_expr *with_clauses) errmsg("cannot use RETURN QUERY in a non-SETOF function"), parser_errposition(location))); - new = palloc0(sizeof(PLtsql_stmt_return_query)); + new = makeNode(PLtsql_stmt_return_query); new->cmd_type = PLTSQL_STMT_RETURN_QUERY; new->lineno = pltsql_location_to_lineno(location); @@ -6173,7 +6173,7 @@ read_into_scalar_list(char *initial_name, */ pltsql_push_back_token(tok); - row = palloc(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = pstrdup("*internal*"); row->lineno = pltsql_location_to_lineno(initial_location); @@ -6208,7 +6208,7 @@ make_scalar_list1(char *initial_name, check_assignable(initial_datum, location); - row = palloc(sizeof(PLtsql_row)); + row = makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = pstrdup("*internal*"); row->lineno = lineno; @@ -7457,7 +7457,7 @@ read_cursor_args(PLtsql_var *cursor, int until, const char *expected) } appendStringInfoChar(&ds, ';'); - expr = palloc0(sizeof(PLtsql_expr)); + expr = makeNode(PLtsql_expr); #if 0 expr->dtype = PLTSQL_DTYPE_EXPR; #endif @@ -7609,7 +7609,7 @@ read_raise_options(void) if ((tok = yylex()) == 0) yyerror("unexpected end of function definition"); - opt = (PLtsql_raise_option *) palloc(sizeof(PLtsql_raise_option)); + opt = (PLtsql_raise_option *) makeNode(PLtsql_raise_option); if (tok_is_keyword(tok, &yylval, K_ERRCODE, "errcode")) @@ -7656,7 +7656,7 @@ parse_sp_proc_param(int *endtoken, bool *flag) int tok; int term; - p = palloc0(sizeof(tsql_exec_param)); + p = makeNode(tsql_exec_param); /* Initialize the param with the default setting */ p->name = NULL; @@ -7800,7 +7800,7 @@ parse_sp_proc(int tok, int lineno, int return_dno) PLtsql_stmt_exec_sp *new_sp; StringInfoData buffer; - new_sp = palloc0(sizeof(PLtsql_stmt_exec_sp)); + new_sp = makeNode(PLtsql_stmt_exec_sp); new_sp->cmd_type = PLTSQL_STMT_EXEC_SP; new_sp->lineno = lineno; new_sp->return_code_dno = return_dno; diff --git a/contrib/babelfishpg_tsql/src/pltsql-2.h b/contrib/babelfishpg_tsql/src/pltsql-2.h index 0e6d3fd5b93..0ff7bddfa32 100644 --- a/contrib/babelfishpg_tsql/src/pltsql-2.h +++ b/contrib/babelfishpg_tsql/src/pltsql-2.h @@ -2,11 +2,19 @@ #define PLTSQL_2_H #include "pg_config_manual.h" +/* + * IMPORTANT: Any struct changes here (adding/removing fields, reordering) + * must also be reflected in pltsql_serializable_2.h in + * postgresql_modified_for_babelfish/src/include/pltsql/ to keep the + * ANTLR parse tree serialization in sync. + */ + /* * PRINT statement */ typedef struct { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -18,6 +26,7 @@ typedef struct */ typedef struct { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -29,6 +38,7 @@ typedef struct */ typedef struct { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -40,6 +50,7 @@ typedef struct */ typedef struct PLtsql_stmt_try_catch { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -55,15 +66,17 @@ typedef struct PLtsql_stmt_try_catch */ typedef struct PLtsql_stmt_query_set { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; - unsigned int stmtid; + uint32 stmtid; PLtsql_expr *sqlstmt; PLtsql_variable *target; /* INTO target (record or row) */ } PLtsql_stmt_query_set; typedef struct PLtsql_stmt_push_result { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -75,6 +88,7 @@ typedef struct PLtsql_stmt_push_result */ typedef struct PLtsql_stmt_exec { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *expr; @@ -96,6 +110,7 @@ typedef struct PLtsql_stmt_exec typedef struct { + NodeTag type; const char *name; PLtsql_expr *expr; char mode; @@ -126,6 +141,7 @@ typedef enum PLtsql_exec_sp_type_code typedef struct PLtsql_stmt_exec_sp { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; @@ -152,6 +168,7 @@ typedef struct PLtsql_stmt_exec_sp */ typedef struct PLtsql_stmt_decl_table { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int dno; /* dno of the table variable */ @@ -162,6 +179,7 @@ typedef struct PLtsql_stmt_decl_table typedef struct PLtsql_stmt_exec_batch { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *expr; @@ -169,6 +187,7 @@ typedef struct PLtsql_stmt_exec_batch typedef struct PLtsql_stmt_raiserror { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; List *params; @@ -180,6 +199,7 @@ typedef struct PLtsql_stmt_raiserror typedef struct PLtsql_stmt_throw { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; List *params; @@ -219,6 +239,7 @@ typedef struct PLtsql_stmt_throw */ typedef struct PLtsql_stmt_deallocate { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int curvar; @@ -229,6 +250,7 @@ typedef struct PLtsql_stmt_deallocate */ typedef struct PLtsql_stmt_decl_cursor { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int curvar; @@ -243,6 +265,7 @@ extern bool is_cursor_datatype(Oid oid); */ typedef struct PLtsql_stmt_goto { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -257,6 +280,7 @@ typedef struct PLtsql_stmt_goto #define INTERNAL_LABEL_FORMAT "LABEL-0x%lX" typedef struct PLtsql_stmt_label { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -267,6 +291,7 @@ typedef struct PLtsql_stmt_label */ typedef struct PLtsql_stmt_usedb { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *db_name; @@ -277,6 +302,7 @@ typedef struct PLtsql_stmt_usedb */ typedef struct PLtsql_stmt_save_ctx { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int32_t target_pc; @@ -288,6 +314,7 @@ typedef struct PLtsql_stmt_save_ctx */ typedef struct PLtsql_stmt_restore_ctx_full { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; } PLtsql_stmt_restore_ctx_full; @@ -297,6 +324,7 @@ typedef struct PLtsql_stmt_restore_ctx_full */ typedef struct PLtsql_stmt_restore_ctx_partial { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; } PLtsql_stmt_restore_ctx_partial; diff --git a/contrib/babelfishpg_tsql/src/pltsql.h b/contrib/babelfishpg_tsql/src/pltsql.h index 5eaaeae4af5..39f204dcc07 100644 --- a/contrib/babelfishpg_tsql/src/pltsql.h +++ b/contrib/babelfishpg_tsql/src/pltsql.h @@ -3,6 +3,11 @@ * pltsql.h - Definitions for the PL/tsql * procedural language * + * IMPORTANT: Any struct changes here (adding/removing fields, reordering) + * must also be reflected in pltsql_serializable_1.h in + * src/pltsql_serialize/ to keep the ANTLR parse tree serialization for + * procedure caching in sync (babelfishpg_tsql.enable_routine_parse_cache). + * * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * @@ -28,6 +33,9 @@ #include "executor/spi.h" #include "libpq/libpq-be.h" #include "optimizer/planner.h" + +/* PLtsql NodeTag values — generated by gen_pltsql_node_support.pl */ +#include "pltsql_serialize/pltsql_nodetags.h" #include "utils/expandedrecord.h" #include "utils/plancache.h" #include "utils/portal.h" @@ -290,6 +298,7 @@ typedef enum PLtsql_schema_mapping */ typedef struct PLtsql_type { + NodeTag type; char *typname; /* (simple) name of the type */ Oid typoid; /* OID of the data type */ PLtsql_type_type ttype; /* PLTSQL_TTYPE_ code */ @@ -320,6 +329,7 @@ typedef struct PLtsql_type */ typedef struct PLtsql_expr { + NodeTag type; char *query; SPIPlanPtr plan; Bitmapset *paramnos; /* all dnos referenced by this query */ @@ -361,6 +371,7 @@ typedef struct PLtsql_expr */ typedef struct PLtsql_datum { + NodeTag type; PLtsql_datum_type dtype; int dno; } PLtsql_datum; @@ -373,6 +384,7 @@ typedef struct PLtsql_datum */ typedef struct PLtsql_variable { + NodeTag type; PLtsql_datum_type dtype; int dno; char *refname; @@ -395,6 +407,7 @@ typedef struct PLtsql_variable */ typedef struct PLtsql_var { + NodeTag type; PLtsql_datum_type dtype; int dno; char *refname; @@ -452,6 +465,7 @@ typedef struct PLtsql_var */ typedef struct PLtsql_row { + NodeTag type; PLtsql_datum_type dtype; int dno; char *refname; @@ -478,6 +492,7 @@ typedef struct PLtsql_row */ typedef struct PLtsql_rec { + NodeTag type; PLtsql_datum_type dtype; int dno; char *refname; @@ -509,6 +524,7 @@ typedef struct PLtsql_rec */ typedef struct PLtsql_tbl { + NodeTag type; PLtsql_datum_type dtype; int dno; char *refname; @@ -536,6 +552,7 @@ typedef struct PLtsql_tbl */ typedef struct PLtsql_recfield { + NodeTag type; PLtsql_datum_type dtype; int dno; /* end of PLtsql_datum fields */ @@ -553,6 +570,7 @@ typedef struct PLtsql_recfield */ typedef struct PLtsql_arrayelem { + NodeTag type; PLtsql_datum_type dtype; int dno; /* end of PLtsql_datum fields */ @@ -577,6 +595,7 @@ typedef struct PLtsql_arrayelem */ typedef struct PLtsql_nsitem { + NodeTag type; PLtsql_nsitem_type itemtype; /* @@ -600,6 +619,7 @@ typedef enum PLtsql_impl_txn_type */ typedef struct PLtsql_stmt { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; } PLtsql_stmt; @@ -609,6 +629,7 @@ typedef struct PLtsql_stmt */ typedef struct PLtsql_condition { + NodeTag type; int sqlerrstate; /* SQLSTATE code */ char *condname; /* condition name (for debugging) */ struct PLtsql_condition *next; @@ -619,6 +640,7 @@ typedef struct PLtsql_condition */ typedef struct PLtsql_exception_block { + NodeTag type; int sqlstate_varno; int sqlerrm_varno; List *exc_list; /* List of WHEN clauses */ @@ -629,6 +651,7 @@ typedef struct PLtsql_exception_block */ typedef struct PLtsql_exception { + NodeTag type; int lineno; PLtsql_condition *conditions; List *action; /* List of statements */ @@ -639,6 +662,7 @@ typedef struct PLtsql_exception */ typedef struct PLtsql_stmt_block { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -653,6 +677,7 @@ typedef struct PLtsql_stmt_block */ typedef struct PLtsql_stmt_assign { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int varno; @@ -664,6 +689,7 @@ typedef struct PLtsql_stmt_assign */ typedef struct PLtsql_stmt_perform { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *expr; @@ -674,6 +700,7 @@ typedef struct PLtsql_stmt_perform */ typedef struct PLtsql_stmt_call { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *expr; @@ -686,6 +713,7 @@ typedef struct PLtsql_stmt_call */ typedef struct PLtsql_stmt_commit { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; } PLtsql_stmt_commit; @@ -695,6 +723,7 @@ typedef struct PLtsql_stmt_commit */ typedef struct PLtsql_stmt_rollback { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; } PLtsql_stmt_rollback; @@ -704,6 +733,7 @@ typedef struct PLtsql_stmt_rollback */ typedef struct PLtsql_stmt_set { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *expr; @@ -714,6 +744,7 @@ typedef struct PLtsql_stmt_set */ typedef struct PLtsql_diag_item { + NodeTag type; PLtsql_getdiag_kind kind; /* id for diagnostic value desired */ int target; /* where to assign it */ } PLtsql_diag_item; @@ -723,6 +754,7 @@ typedef struct PLtsql_diag_item */ typedef struct PLtsql_stmt_getdiag { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; bool is_stacked; /* STACKED or CURRENT diagnostics area? */ @@ -734,6 +766,7 @@ typedef struct PLtsql_stmt_getdiag */ typedef struct PLtsql_stmt_if { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *cond; /* boolean expression for THEN */ @@ -747,6 +780,7 @@ typedef struct PLtsql_stmt_if */ typedef struct PLtsql_if_elsif { + NodeTag type; int lineno; PLtsql_expr *cond; /* boolean expression for this case */ List *stmts; /* List of statements */ @@ -757,6 +791,7 @@ typedef struct PLtsql_if_elsif */ typedef struct PLtsql_stmt_case { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *t_expr; /* test expression, or NULL if none */ @@ -771,6 +806,7 @@ typedef struct PLtsql_stmt_case */ typedef struct PLtsql_case_when { + NodeTag type; int lineno; PLtsql_expr *expr; /* boolean expression for this case */ List *stmts; /* List of statements */ @@ -781,6 +817,7 @@ typedef struct PLtsql_case_when */ typedef struct PLtsql_stmt_loop { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -792,6 +829,7 @@ typedef struct PLtsql_stmt_loop */ typedef struct PLtsql_stmt_while { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -804,6 +842,7 @@ typedef struct PLtsql_stmt_while */ typedef struct PLtsql_stmt_fori { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -822,6 +861,7 @@ typedef struct PLtsql_stmt_fori */ typedef struct PLtsql_stmt_forq { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -834,6 +874,7 @@ typedef struct PLtsql_stmt_forq */ typedef struct PLtsql_stmt_fors { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -848,6 +889,7 @@ typedef struct PLtsql_stmt_fors */ typedef struct PLtsql_stmt_forc { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -863,6 +905,7 @@ typedef struct PLtsql_stmt_forc */ typedef struct PLtsql_stmt_dynfors { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -878,6 +921,7 @@ typedef struct PLtsql_stmt_dynfors */ typedef struct PLtsql_stmt_foreach_a { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *label; @@ -892,6 +936,7 @@ typedef struct PLtsql_stmt_foreach_a */ typedef struct PLtsql_stmt_open { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int curvar; @@ -907,6 +952,7 @@ typedef struct PLtsql_stmt_open */ typedef struct PLtsql_stmt_fetch { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_variable *target; /* target (record or row) */ @@ -923,6 +969,7 @@ typedef struct PLtsql_stmt_fetch */ typedef struct PLtsql_stmt_close { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int curvar; @@ -933,6 +980,7 @@ typedef struct PLtsql_stmt_close */ typedef struct PLtsql_stmt_exit { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; bool is_exit; /* Is this an exit or a continue? */ @@ -945,6 +993,7 @@ typedef struct PLtsql_stmt_exit */ typedef struct PLtsql_stmt_insert_bulk { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *table_name; @@ -966,6 +1015,7 @@ typedef union PLtsql_dbcc_stmt_data { struct dbcc_checkident { + // TODO: NodeTag type; char *db_name; char *schema_name; char *table_name; @@ -981,6 +1031,7 @@ typedef union PLtsql_dbcc_stmt_data */ typedef struct PLtsql_stmt_dbcc { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_dbcc_stmt_type dbcc_stmt_type; @@ -992,6 +1043,7 @@ typedef struct PLtsql_stmt_dbcc */ typedef struct PLtsql_stmt_return { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *expr; @@ -1003,6 +1055,7 @@ typedef struct PLtsql_stmt_return */ typedef struct PLtsql_stmt_return_next { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *expr; @@ -1014,6 +1067,7 @@ typedef struct PLtsql_stmt_return_next */ typedef struct PLtsql_stmt_return_query { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *query; /* if static query */ @@ -1026,6 +1080,7 @@ typedef struct PLtsql_stmt_return_query */ typedef struct PLtsql_stmt_raise { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; int elog_level; @@ -1040,6 +1095,7 @@ typedef struct PLtsql_stmt_raise */ typedef struct PLtsql_raise_option { + NodeTag type; PLtsql_raise_option_type opt_type; PLtsql_expr *expr; } PLtsql_raise_option; @@ -1049,6 +1105,7 @@ typedef struct PLtsql_raise_option */ typedef struct PLtsql_stmt_grantdb { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; bool is_grant; @@ -1060,6 +1117,7 @@ typedef struct PLtsql_stmt_grantdb */ typedef struct PLtsql_stmt_change_dbowner { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *db_name; @@ -1068,6 +1126,7 @@ typedef struct PLtsql_stmt_change_dbowner typedef struct PLtsql_stmt_alter_db { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *old_db_name; @@ -1079,6 +1138,7 @@ typedef struct PLtsql_stmt_alter_db */ typedef struct PLtsql_stmt_fulltextindex { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *table_name; /* table name */ @@ -1094,6 +1154,7 @@ typedef struct PLtsql_stmt_fulltextindex */ typedef struct PLtsql_stmt_grantschema { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; bool is_grant; @@ -1109,6 +1170,7 @@ typedef struct PLtsql_stmt_grantschema */ typedef struct PLtsql_stmt_partition_function { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *function_name; @@ -1124,6 +1186,7 @@ typedef struct PLtsql_stmt_partition_function */ typedef struct PLtsql_stmt_partition_scheme { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *scheme_name; @@ -1137,6 +1200,7 @@ typedef struct PLtsql_stmt_partition_scheme */ typedef struct PLtsql_stmt_assert { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *cond; @@ -1145,6 +1209,7 @@ typedef struct PLtsql_stmt_assert typedef struct PLtsql_txn_data { + NodeTag type; TransactionStmtKind stmt_kind; /* Commit or rollback */ char *txn_name; /* Transaction name */ PLtsql_expr *txn_name_expr; /* Transaction name variable */ @@ -1155,6 +1220,7 @@ typedef struct PLtsql_txn_data */ typedef struct PLtsql_stmt_execsql { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *sqlstmt; @@ -1189,6 +1255,7 @@ typedef struct PLtsql_stmt_execsql */ typedef struct PLtsql_stmt_set_explain_mode { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; char *query; @@ -1202,6 +1269,7 @@ typedef struct PLtsql_stmt_set_explain_mode */ typedef struct PLtsql_stmt_dynexecute { + NodeTag type; PLtsql_stmt_type cmd_type; int lineno; PLtsql_expr *query; /* string expression */ @@ -1268,6 +1336,7 @@ typedef enum PLtsql_trigtype typedef struct InlineCodeBlockArgs { + NodeTag type; int numargs; Oid *argtypes; int32 *argtypmods; @@ -1286,10 +1355,13 @@ typedef struct InlineCodeBlockArgs */ typedef struct PLtsql_function { + NodeTag type; char *fn_signature; Oid fn_oid; TransactionId fn_xmin; ItemPointerData fn_tid; + TransactionId bbf_ext_xmin; /* xmin of babelfish_function_ext tuple at compile time */ + ItemPointerData bbf_ext_tid; /* ctid of babelfish_function_ext tuple at compile time */ PLtsql_trigtype fn_is_trigger; Oid fn_input_collation; PLtsql_func_hashkey *fn_hashkey; /* back-link to hashtable key */ @@ -1338,6 +1410,9 @@ typedef struct PLtsql_function /* function body parsetree */ PLtsql_stmt_block *action; + /* Track if this function was loaded from ANTLR parse result cache */ + bool from_cache; + /* these fields change when the function is used */ struct PLtsql_execstate *cur_estate; unsigned long use_count; @@ -2085,6 +2160,7 @@ extern PLtsql_condition *pltsql_parse_err_condition(char *condname); extern void pltsql_adddatum(PLtsql_datum *newdatum); extern int pltsql_add_initdatums(int **varnos); extern void pltsql_HashTableInit(void); +extern PLtsql_function *pltsql_HashTableLookup(PLtsql_func_hashkey *func_key); extern void reset_cache(void); /* diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/gen_pltsql_node_support.pl b/contrib/babelfishpg_tsql/src/pltsql_serialize/gen_pltsql_node_support.pl new file mode 100644 index 00000000000..0a9fbb2ef5d --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/gen_pltsql_node_support.pl @@ -0,0 +1,1236 @@ +#!/usr/bin/perl +#---------------------------------------------------------------------- +# +# gen_pltsql_node_support.pl +# Generate PLtsql serialization/deserialization code for the extension. +# +# Stripped-down version of PostgreSQL's gen_node_support.pl. +# Only generates outfuncs and readfuncs for PLtsql node types. +# Does NOT generate nodetags.h, copyfuncs, equalfuncs, or queryjumblefuncs +# (the engine handles those). +# +# Input: pltsql_serializable_1.h, pltsql_serializable_2.h +# Output: pltsql_outfuncs_gen.c, pltsql_readfuncs_gen.c +# +# Invoked by the extension Makefile during build. +# +# +# src/backend/nodes/gen_pltsql_node_support.pl +# +#---------------------------------------------------------------------- + +# Changes to gen_pltsql_node_support.pl (from the engine original): + +# - Replaced use FindBin / use Catalog with inline RenameTempFile sub +# - @all_input_files — only pltsql_serializable_1.h and pltsql_serializable_2.h +# - @nodetag_only_files — empty (none of our files are nodetag-only) +# - $last_nodetag / $last_nodetag_no — set to undef (engine handles ABI check) +# - @extra_tags — empty (PG-specific) +# - $infile path stripping — uses basename() instead of s!.*src/include/!! +# - Added skips for #include, extern, // lines during parsing (serializable headers have these) +# - PLtsql enum types moved from @scalar_types to @enum_types (outfuncs needs WRITE_ENUM_FIELD) +# - Added PG enums FetchDirection, LockClauseStrength to @enum_types +# - Removed nodetags.h generation section +# - $node_includes — hardcoded to pltsql.h and pltsql-2.h +# - Removed copyfuncs/equalfuncs generation section +# - Renamed output files: outfuncs.funcs.c → pltsql_outfuncs_gen.c, etc. +# - Added fallback for unknown Capitalized* pointer types → WRITE_NODE_FIELD/READ_NODE_FIELD (handles PG node types like TypeName* without pre-seeding) +# - Removed queryjumblefuncs generation section +# - Catalog::RenameTempFile → RenameTempFile (our inline version) +# - Header comment references gen_pltsql_node_support.pl +# - All original arrays (@custom_copy_equal, @custom_query_jumble, %manual_nodetag_number, etc.) and their elsif branches kept intact + + +use strict; +use warnings FATAL => 'all'; + +use File::Basename; +use Getopt::Long; + +# Inline replacement for Catalog::RenameTempFile (avoids engine dependency) +sub RenameTempFile +{ + my ($path, $tmpext) = @_; + my $tmpfile = "$path$tmpext"; + rename($tmpfile, $path) or die "could not rename $tmpfile to $path: $!"; +} + +my $output_path = '.'; + +GetOptions('outdir:s' => \$output_path) + or die "$0: wrong arguments"; + + +# Test whether first argument is element of the list in the second +# argument +sub elem +{ + my $x = shift; + return grep { $_ eq $x } @_; +} + +# This list defines the canonical set of header files to be read by this +# script, and the order they are to be processed in. We must have a stable +# processing order, else the NodeTag enum's order will vary, with catastrophic +# consequences for ABI stability across different builds. +# +# Currently, the various build systems also have copies of this list, +# so that they can do dependency checking properly. In future we may be +# able to make this list the only copy. For now, we just check that +# it matches the list of files passed on the command line. +# +# The two PLtsql serializable headers — order matters for struct processing. +my @all_input_files = qw( + pltsql_serializable_1.h + pltsql_serializable_2.h +); + +# Nodes from these input files are automatically treated as nodetag_only. +# (empty for PLtsql — none of our files are nodetag-only-by-file, unlike in engine) +my @nodetag_only_files; + +# ABI STABILITY CHECK: +# +# In stable branches, set $last_nodetag to the name of the last PLtsql node type +# that should receive an auto-generated nodetag number, and $last_nodetag_no +# to its number. (Find these values in the last line of the current +# pltsql_nodetags.h file.) The script will then complain if those values don't +# match reality, providing a cross-check that we haven't broken ABI by +# adding or removing nodetags. +# In HEAD, these variables should be left undef, since we don't promise +# ABI stability during development. + +my $last_nodetag = 'PLtsql_stmt_restore_ctx_partial'; +my $last_nodetag_no = 1079; + +# output file names +my @output_files; + +# collect node names +my @node_types = qw(Node); +# collect info for each node type +my %node_type_info; + +# node types we don't want copy support for +my @no_copy; +# node types we don't want equal support for +my @no_equal; +# node types we don't want query jumble support for +my @no_query_jumble; +# node types we don't want read support for +my @no_read; +# node types we don't want read/write support for +my @no_read_write; +# node types that have handmade read/write support +my @special_read_write; +# node types we don't want any support functions for, just node tags +my @nodetag_only; + +# types that are copied by straight assignment +my @scalar_types = qw( + bits32 bool char double int int8 int16 int32 int64 long uint8 uint16 uint32 uint64 + AclMode AttrNumber Cardinality Cost Index Oid RelFileNumber Selectivity Size StrategyNumber SubTransactionId TimeLineID XLogRecPtr +); + +# collect enum types +my @enum_types; + +# collect types that are abstract (hence no node tag, no support functions) +my @abstract_types = qw(Node); + +# Special cases that either don't have their own struct or the struct +# is not in a header file. We generate node tags for them, but +# they otherwise don't participate in node support. +# (empty for PLtsql — these are PG-specific) + +my @extra_tags; + +# This is a regular node, but we skip parsing it from its header file +# since we won't use its internal structure here anyway. +push @node_types, qw(List); +# Lists are specially treated in all five support files, too. +# (Ideally we'd mark List as "special copy/equal" not "no copy/equal". +# But until there's other use-cases for that, just hot-wire the tests +# that would need to distinguish.) +push @no_copy, qw(List); +push @no_equal, qw(List); +push @no_query_jumble, qw(List); +push @special_read_write, qw(List); + +# Nodes with custom copy/equal implementations are skipped from +# .funcs.c but need case statements in .switch.c. +my @custom_copy_equal; + +# node types with custom read/write implementations (in pltsql_node_stubs.c) +my @custom_read_write; + +# Similarly for custom query jumble implementation. +my @custom_query_jumble; + +# Track node types with manually assigned NodeTag numbers. +my %manual_nodetag_number; + +# This is a struct, so we can copy it by assignment. Equal support is +# currently not required. +push @scalar_types, qw(QualCost); + +# PLtsql-specific enum types (defined in pltsql.h/pltsql-2.h). +# The engine discovers these by parsing typedef enum lines in headers; +# we must pre-declare them since we only parse PLtsql serializable headers. +push @enum_types, qw( + PLtsql_stmt_type PLtsql_datum_type PLtsql_nsitem_type + PLtsql_promise_type PLtsql_type_type PLtsql_dbcc_stmt_type + PLtsql_exec_sp_type_code PLtsql_sp_type_code TransactionStmtKind +); + +# PLtsql extension: PG enum types referenced by PLtsql struct fields. +push @enum_types, qw(FetchDirection LockClauseStrength); + + +## check that we have the expected number of files on the command line +die "wrong number of input files, expected:\n@all_input_files\ngot:\n@ARGV\n" + if ($#ARGV != $#all_input_files); + +## read input + +my $next_input_file = 0; +foreach my $infile (@ARGV) +{ + my $in_struct; + my $subline; + my $is_node_struct; + my $supertype; + my $supertype_field; + + my $node_attrs = ''; + my $node_attrs_lineno; + my @my_fields; + my %my_field_types; + my %my_field_attrs; + + # open file with name from command line, which may have a path prefix + open my $ifh, '<', $infile or die "could not open \"$infile\": $!"; + + # now shorten filename for use below + # $infile =~ s!.*src/include/!!; + # PLtsql extension: files aren't under src/include/, use basename + $infile = basename($infile); + + # check it against next member of @all_input_files + die "wrong input file ordering, expected @all_input_files\n" + if ($infile ne $all_input_files[$next_input_file]); + $next_input_file++; + + my $raw_file_content = do { local $/; <$ifh> }; + + # strip C comments, preserving newlines so we can count lines correctly + my $file_content = ''; + while ($raw_file_content =~ m{^(.*?)(/\*.*?\*/)(.*)$}s) + { + $file_content .= $1; + my $comment = $2; + $raw_file_content = $3; + $comment =~ tr/\n//cd; + $file_content .= $comment; + } + $file_content .= $raw_file_content; + + my $lineno = 0; + my $prevline = ''; + foreach my $line (split /\n/, $file_content) + { + # per-physical-line processing + $lineno++; + chomp $line; + $line =~ s/\s*$//; + next if $line eq ''; + next if $line =~ /^#(define|ifdef|endif)/; + # PLtsql extension: serializable headers have #include, extern, and + # C++ comment lines that PG node headers don't — skip them. + next if $line =~ /^#include\b/; + next if $line =~ /^extern\b/; + next if $line =~ /^\/\//; + + # within a struct, don't process until we have whole logical line + if ($in_struct && $subline > 0) + { + if ($line =~ /;$/) + { + # found the end, re-attach any previous line(s) + $line = $prevline . $line; + $prevline = ''; + } + elsif ($prevline eq '' + && $line =~ /^\s*pg_node_attr\(([\w(), ]*)\)$/) + { + # special case: node-attributes line doesn't end with semi + } + else + { + # set it aside for a moment + $prevline .= $line . ' '; + next; + } + } + + # we are analyzing a struct definition + if ($in_struct) + { + $subline++; + + # first line should have opening brace + if ($subline == 1) + { + $is_node_struct = 0; + $supertype = undef; + next if $line eq '{'; + die "$infile:$lineno: expected opening brace\n"; + } + # second line could be node attributes + elsif ($subline == 2 + && $line =~ /^\s*pg_node_attr\(([\w(), ]*)\)$/) + { + $node_attrs = $1; + $node_attrs_lineno = $lineno; + # hack: don't count the line + $subline--; + next; + } + # next line should have node tag or supertype + elsif ($subline == 2) + { + if ($line =~ /^\s*NodeTag\s+type;/) + { + $is_node_struct = 1; + next; + } + elsif ($line =~ /\s*(\w+)\s+(\w+);/ and elem $1, @node_types) + { + $is_node_struct = 1; + $supertype = $1; + $supertype_field = $2; + next; + } + } + + # end of struct + if ($line =~ /^\}\s*(?:\Q$in_struct\E\s*)?;$/) + { + if ($is_node_struct) + { + # This is the end of a node struct definition. + # Save everything we have collected. + + foreach my $attr (split /,\s*/, $node_attrs) + { + if ($attr eq 'abstract') + { + push @abstract_types, $in_struct; + } + elsif ($attr eq 'custom_copy_equal') + { + push @custom_copy_equal, $in_struct; + } + elsif ($attr eq 'custom_read_write') + { + push @custom_read_write, $in_struct; + } + elsif ($attr eq 'custom_query_jumble') + { + push @custom_query_jumble, $in_struct; + } + elsif ($attr eq 'no_copy') + { + push @no_copy, $in_struct; + } + elsif ($attr eq 'no_equal') + { + push @no_equal, $in_struct; + } + elsif ($attr eq 'no_copy_equal') + { + push @no_copy, $in_struct; + push @no_equal, $in_struct; + } + elsif ($attr eq 'no_query_jumble') + { + push @no_query_jumble, $in_struct; + } + elsif ($attr eq 'no_read') + { + push @no_read, $in_struct; + } + elsif ($attr eq 'nodetag_only') + { + push @nodetag_only, $in_struct; + } + elsif ($attr eq 'special_read_write') + { + push @special_read_write, $in_struct; + } + elsif ($attr =~ /^nodetag_number\((\d+)\)$/) + { + $manual_nodetag_number{$in_struct} = $1; + } + else + { + die + "$infile:$node_attrs_lineno: unrecognized attribute \"$attr\"\n"; + } + } + + # node name + push @node_types, $in_struct; + + # field names, types, attributes + my @f = @my_fields; + my %ft = %my_field_types; + my %fa = %my_field_attrs; + + # If there is a supertype, add those fields, too. + if ($supertype) + { + my @superfields; + foreach + my $sf (@{ $node_type_info{$supertype}->{fields} }) + { + my $fn = "${supertype_field}.$sf"; + push @superfields, $fn; + $ft{$fn} = + $node_type_info{$supertype}->{field_types}{$sf}; + if ($node_type_info{$supertype} + ->{field_attrs}{$sf}) + { + # Copy any attributes, adjusting array_size field references + my @newa = @{ $node_type_info{$supertype} + ->{field_attrs}{$sf} }; + foreach my $a (@newa) + { + $a =~ + s/array_size\((\w+)\)/array_size(${supertype_field}.$1)/; + } + $fa{$fn} = \@newa; + } + } + unshift @f, @superfields; + } + # save in global info structure + $node_type_info{$in_struct}->{fields} = \@f; + $node_type_info{$in_struct}->{field_types} = \%ft; + $node_type_info{$in_struct}->{field_attrs} = \%fa; + + # Propagate nodetag_only marking from files to nodes + push @nodetag_only, $in_struct + if (elem $infile, @nodetag_only_files); + + # Propagate some node attributes from supertypes + if ($supertype) + { + push @no_copy, $in_struct + if elem $supertype, @no_copy; + push @no_equal, $in_struct + if elem $supertype, @no_equal; + push @no_read, $in_struct + if elem $supertype, @no_read; + push @no_query_jumble, $in_struct + if elem $supertype, @no_query_jumble; + } + } + + # start new cycle + $in_struct = undef; + $node_attrs = ''; + @my_fields = (); + %my_field_types = (); + %my_field_attrs = (); + } + # normal struct field + elsif ($line =~ + /^\s*(.+)\s*\b(\w+)(\[[\w\s+]+\])?\s*(?:pg_node_attr\(([\w(), ]*)\))?;/ + ) + { + if ($is_node_struct) + { + my $type = $1; + my $name = $2; + my $array_size = $3; + my $attrs = $4; + + # strip "const" + $type =~ s/^const\s*//; + # strip trailing space + $type =~ s/\s*$//; + # strip space between type and "*" (pointer) */ + $type =~ s/\s+\*$/*/; + # strip space between type and "**" (array of pointers) */ + $type =~ s/\s+\*\*$/**/; + + die + "$infile:$lineno: cannot parse data type in \"$line\"\n" + if $type eq ''; + + my @attrs; + if ($attrs) + { + @attrs = split /,\s*/, $attrs; + foreach my $attr (@attrs) + { + if ( $attr !~ /^array_size\(\w+\)$/ + && $attr !~ /^copy_as\(\w+\)$/ + && $attr !~ /^read_as\(\w+\)$/ + && !elem $attr, + qw(copy_as_scalar + equal_as_scalar + equal_ignore + equal_ignore_if_zero + query_jumble_ignore + query_jumble_location + read_write_ignore + write_only_relids + write_only_nondefault_pathtarget + write_only_req_outer)) + { + die + "$infile:$lineno: unrecognized attribute \"$attr\"\n"; + } + } + } + + $type = $type . $array_size if $array_size; + push @my_fields, $name; + $my_field_types{$name} = $type; + $my_field_attrs{$name} = \@attrs; + } + } + # function pointer field + elsif ($line =~ + /^\s*([\w\s*]+)\s*\(\*(\w+)\)\s*\((.*)\)\s*(?:pg_node_attr\(([\w(), ]*)\))?;/ + ) + { + if ($is_node_struct) + { + my $type = $1; + my $name = $2; + my $args = $3; + my $attrs = $4; + + my @attrs; + if ($attrs) + { + @attrs = split /,\s*/, $attrs; + foreach my $attr (@attrs) + { + if ( $attr !~ /^copy_as\(\w+\)$/ + && $attr !~ /^read_as\(\w+\)$/ + && !elem $attr, + qw(equal_ignore read_write_ignore)) + { + die + "$infile:$lineno: unrecognized attribute \"$attr\"\n"; + } + } + } + + push @my_fields, $name; + $my_field_types{$name} = 'function pointer'; + $my_field_attrs{$name} = \@attrs; + } + } + else + { + # We're not too picky about what's outside structs, + # but we'd better understand everything inside. + die "$infile:$lineno: could not parse \"$line\"\n"; + } + } + # not in a struct + else + { + # start of a struct? + if ($line =~ /^(?:typedef )?struct (\w+)$/ && $1 ne 'Node') + { + $in_struct = $1; + $subline = 0; + } + # one node type typedef'ed directly from another + elsif ($line =~ /^typedef (\w+) (\w+);$/ and elem $1, @node_types) + { + my $alias_of = $1; + my $n = $2; + + # copy everything over + push @node_types, $n; + my @f = @{ $node_type_info{$alias_of}->{fields} }; + my %ft = %{ $node_type_info{$alias_of}->{field_types} }; + my %fa = %{ $node_type_info{$alias_of}->{field_attrs} }; + $node_type_info{$n}->{fields} = \@f; + $node_type_info{$n}->{field_types} = \%ft; + $node_type_info{$n}->{field_attrs} = \%fa; + } + # collect enum names + elsif ($line =~ /^typedef enum (\w+)(\s*\/\*.*)?$/) + { + push @enum_types, $1; + } + } + } + + if ($in_struct) + { + die "runaway \"$in_struct\" in file \"$infile\"\n"; + } + + close $ifh; +} # for each file + + +## write output + +my $tmpext = ".tmp$$"; + +# opening boilerplate for output files +my $header_comment = + '/*------------------------------------------------------------------------- + * + * %s + * Generated node infrastructure code + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * ****************************** + * *** DO NOT EDIT THIS FILE! *** + * ****************************** + * + * It has been GENERATED by gen_pltsql_node_support.pl + * + *------------------------------------------------------------------------- + */ +'; + + +# PLtsql extension: generate pltsql_nodetags.h with T_PLtsql_* defines. +# These are defined as macros (not enum members) starting at offset 1000 +# to avoid collision with the engine's NodeTag enum (which ends at ~475). +# This allows the extension to define its own NodeTags without any engine changes. + +my $pltsql_nodetag_start = 1000; + +push @output_files, 'pltsql_nodetags.h'; +open my $nth, '>', "$output_path/pltsql_nodetags.h$tmpext" or die $!; + +printf $nth $header_comment, 'pltsql_nodetags.h'; +print $nth "#ifndef PLTSQL_NODETAGS_H\n#define PLTSQL_NODETAGS_H\n\n"; +print $nth "/*\n"; +print $nth " * PLtsql NodeTag values, auto-generated by gen_pltsql_node_support.pl.\n"; +print $nth " * Offset from $pltsql_nodetag_start to avoid collision with engine NodeTags.\n"; +print $nth " */\n\n"; + +my $tagno = $pltsql_nodetag_start; +my $last_tag = undef; +foreach my $n (@node_types) +{ + next if $n eq 'Node'; + next if $n eq 'List'; + next if elem $n, @abstract_types; + print $nth "#define T_${n} ((NodeTag) $tagno)\n"; + $last_tag = $n; + $tagno++; +} + +# ABI stability cross-check +if (defined $last_nodetag) +{ + my $expected_no = $tagno - 1; # last assigned number + if ($last_tag ne $last_nodetag) + { + die "ABI stability check failed: last PLtsql nodetag is T_${last_tag}, expected T_${last_nodetag}\n" + . "If you added or removed a node type, update \$last_nodetag and \$last_nodetag_no.\n"; + } + if ($expected_no != $last_nodetag_no) + { + die "ABI stability check failed: T_${last_tag} = ${expected_no}, expected ${last_nodetag_no}\n" + . "If you added or removed a node type, update \$last_nodetag and \$last_nodetag_no.\n"; + } +} + +print $nth "\n#endif /* PLTSQL_NODETAGS_H */\n"; +close $nth; + +# make #include lines necessary to pull in all the struct definitions +# PLtsql extension: hardcode includes for extension headers instead of +# auto-generating from input file paths. +# my $node_includes = qq{#include "src/pltsql.h"\n#include "src/pltsql-2.h"\n}; +my $node_includes = qq{#include "pltsql_serialize_macros.h"\n}; + + +# copyfuncs.c, equalfuncs.c +# PLtsql extension: we only generate equalfuncs (no copyfuncs needed). +# Used for parse tree validation (comparing ANTLR-compiled tree vs deserialized tree). +# (PoC) NOTE: We intentionally ignore @no_equal here because all PLtsql nodes are marked +# no_copy_equal (to prevent the engine from generating copy/equal for them). +# We still want our own extension-side equality for validation purposes. + +push @output_files, 'pltsql_equalfuncs_gen.c'; +open my $eff, '>', "$output_path/pltsql_equalfuncs_gen.c$tmpext" or die $!; +push @output_files, 'pltsql_equalfuncs_switch.c'; +open my $efs, '>', "$output_path/pltsql_equalfuncs_switch.c$tmpext" or die $!; + +printf $eff $header_comment, 'pltsql_equalfuncs_gen.c'; +printf $efs $header_comment, 'pltsql_equalfuncs_switch.c'; + +print $eff $node_includes; + +foreach my $n (@node_types) +{ + next if elem $n, @abstract_types; + next if elem $n, @nodetag_only; + # (PoC) Intentionally NOT checking @no_equal — see comment above. + + print $efs "\t\tcase T_${n}:\n" + . "\t\t\tretval = _equal${n}(a, b);\n" + . "\t\t\tif (!retval)\n" + . "\t\t\t\telog(WARNING, \"pltsql_equal_node: mismatch in ${n}\");\n" + . "\t\t\tbreak;\n"; + + next if elem $n, @custom_copy_equal; + next if elem $n, @custom_read_write; + next if elem $n, @special_read_write; + + print $eff " +static bool +_equal${n}(const $n *a, const $n *b) +{ +"; + + my %previous_fields; + + foreach my $f (@{ $node_type_info{$n}->{fields} }) + { + my $t = $node_type_info{$n}->{field_types}{$f}; + my @a = @{ $node_type_info{$n}->{field_attrs}{$f} }; + my $equal_ignore = 0; + + my $array_size_field; + my $equal_as_scalar = 0; + foreach my $a (@a) + { + if ($a =~ /^array_size\(([\w.]+)\)$/) + { + $array_size_field = $1; + } + elsif ($a eq 'equal_as_scalar') + { + $equal_as_scalar = 1; + } + elsif ($a eq 'equal_ignore' || $a eq 'read_write_ignore') + { + $equal_ignore = 1; + } + } + + next if $equal_ignore; + + # Skip lineno — line numbers differ between cached (CREATE-time) + # and ANTLR (EXEC-time) compilation due to source offset differences. + next if $f eq 'lineno'; + + # Skip varno/dno/itemno and named dno-reference fields — datum + # numbering differs between validator (CREATE) and runtime (EXEC) + # compilation contexts. Fields like curvar, cursor_handleno, + # prepared_handleno, and return_code_dno store datum numbers under + # different names but have the same dno-offset issue. + next if $f eq 'dno'; + next if $f eq 'varno'; + next if $f eq 'itemno'; + next if $f eq 'retvarno'; + next if $f eq 'curvar'; + next if $f eq 'cursor_handleno'; + next if $f eq 'prepared_handleno'; + next if $f eq 'return_code_dno'; + + if ($equal_as_scalar) + { + print $eff "\tCOMPARE_SCALAR_FIELD_LOG($f, \"$n\");\n"; + $previous_fields{$f} = 1; + next; + } + + if ($t eq 'char*') + { + print $eff "\tCOMPARE_STRING_FIELD_LOG($f, \"$n\");\n"; + } + elsif ($t eq 'Bitmapset*' || $t eq 'Relids') + { + print $eff "\tCOMPARE_BITMAPSET_FIELD($f);\n"; + } + elsif ($t eq 'ParseLoc') + { + print $eff "\tCOMPARE_LOCATION_FIELD($f);\n"; + } + elsif (elem $t, @scalar_types or elem $t, @enum_types) + { + print $eff "\tCOMPARE_SCALAR_FIELD_LOG($f, \"$n\");\n"; + } + elsif ($t =~ /^(\w+)\*$/ and elem $1, @scalar_types) + { + my $tt = $1; + if (!defined $array_size_field) + { + die "no array size defined for $n.$f of type $t\n"; + } + if ($node_type_info{$n}->{field_types}{$array_size_field} eq + 'List*') + { + print $eff + "\tCOMPARE_POINTER_FIELD($f, list_length(a->$array_size_field) * sizeof($tt));\n"; + } + else + { + print $eff + "\tCOMPARE_POINTER_FIELD($f, a->$array_size_field * sizeof($tt));\n"; + } + } + elsif ($t eq 'function pointer') + { + print $eff "\tCOMPARE_SCALAR_FIELD_LOG($f, \"$n\");\n"; + } + elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/) + and elem $1, @node_types) + { + print $eff "\tCOMPARE_NODE_FIELD_LOG($f, \"$n\");\n"; + } + elsif ($t =~ /^([A-Z]\w+)\*$/) + { + print $eff "\tCOMPARE_NODE_FIELD_LOG($f, \"$n\");\n"; + } + elsif ($t =~ /^\w+\[\w+\]$/) + { + print $eff "\tCOMPARE_ARRAY_FIELD($f);\n"; + } + elsif (($t =~ /^(\w+)\*\*$/ or $t =~ /^struct\s+(\w+)\*\*$/) + and elem($1, @node_types)) + { + print $eff "\t/* skip node array field $f */\n"; + } + elsif ($t eq 'void*') + { + print $eff "\tCOMPARE_SCALAR_FIELD_LOG($f, \"$n\");\n"; + } + else + { + die + "could not handle type \"$t\" in struct \"$n\" field \"$f\"\n"; + } + + $previous_fields{$f} = 1; + } + + print $eff " +\treturn true; +} +"; +} + +close $eff; +close $efs; + + +# outfuncs.c, readfuncs.c +# PLtsql extension: rename output files to pltsql_* names + +push @output_files, 'pltsql_outfuncs_gen.c'; +open my $off, '>', "$output_path/pltsql_outfuncs_gen.c$tmpext" or die $!; +push @output_files, 'pltsql_readfuncs_gen.c'; +open my $rff, '>', "$output_path/pltsql_readfuncs_gen.c$tmpext" or die $!; +push @output_files, 'pltsql_outfuncs_switch.c'; +open my $ofs, '>', "$output_path/pltsql_outfuncs_switch.c$tmpext" or die $!; +push @output_files, 'pltsql_readfuncs_switch.c'; +open my $rfs, '>', "$output_path/pltsql_readfuncs_switch.c$tmpext" or die $!; + +printf $off $header_comment, 'pltsql_outfuncs_gen.c'; +printf $rff $header_comment, 'pltsql_readfuncs_gen.c'; +printf $ofs $header_comment, 'pltsql_outfuncs_switch.c'; +printf $rfs $header_comment, 'pltsql_readfuncs_switch.c'; + +print $off $node_includes; +print $rff $node_includes; + +foreach my $n (@node_types) +{ + next if elem $n, @abstract_types; + next if elem $n, @nodetag_only; + next if elem $n, @no_read_write; + next if elem $n, @special_read_write; + + my $no_read = (elem $n, @no_read); + + # output format starts with upper case node type name + my $N = uc $n; + + print $ofs "\t\t\tcase T_${n}:\n" + . "\t\t\t\t_out${n}(str, obj);\n" + . "\t\t\t\tbreak;\n"; + + print $rfs "\tif (MATCH(\"$N\", " + . length($N) . "))\n" + . "\t\treturn (Node *) _read${n}();\n" + unless $no_read; + + next if elem $n, @custom_read_write; + + print $off " +static void +_out${n}(StringInfo str, const $n *node) +{ +\tWRITE_NODE_TYPE(\"$N\"); + +"; + + if (!$no_read) + { + my $macro = + (@{ $node_type_info{$n}->{fields} } > 0) + ? 'READ_LOCALS' + : 'READ_LOCALS_NO_FIELDS'; + print $rff " +static $n * +_read${n}(void) +{ +\t$macro($n); + +"; + } + + # track already-processed fields to support field order checks + # (this isn't quite redundant with the previous loop, since + # we may be considering structs that lack copy/equal support) + my %previous_fields; + + # print instructions for each field + foreach my $f (@{ $node_type_info{$n}->{fields} }) + { + my $t = $node_type_info{$n}->{field_types}{$f}; + my @a = @{ $node_type_info{$n}->{field_attrs}{$f} }; + + # extract per-field attributes + my $array_size_field; + my $read_as_field; + my $read_write_ignore = 0; + foreach my $a (@a) + { + if ($a =~ /^array_size\(([\w.]+)\)$/) + { + $array_size_field = $1; + # insist that we read the array size first! + die + "array size field $array_size_field for field $n.$f must precede $f\n" + if (!$previous_fields{$array_size_field} && !$no_read); + } + elsif ($a =~ /^read_as\(([\w.]+)\)$/) + { + $read_as_field = $1; + } + elsif ($a eq 'read_write_ignore') + { + $read_write_ignore = 1; + } + } + + if ($read_write_ignore) + { + # nothing to do if no_read + next if $no_read; + # for read_write_ignore with read_as(), emit the appropriate + # assignment on the read side and move on. + if (defined $read_as_field) + { + print $rff "\tlocal_node->$f = $read_as_field;\n"; + next; + } + # else, bad specification + die "$n.$f must not be marked read_write_ignore\n"; + } + + # select instructions by field type + if ($t eq 'bool') + { + print $off "\tWRITE_BOOL_FIELD($f);\n"; + print $rff "\tREAD_BOOL_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'ParseLoc') + { + print $off "\tWRITE_LOCATION_FIELD($f);\n"; + print $rff "\tREAD_LOCATION_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'int' + || $t eq 'int16' + || $t eq 'int32' + || $t eq 'AttrNumber' + || $t eq 'StrategyNumber') + { + print $off "\tWRITE_INT_FIELD($f);\n"; + print $rff "\tREAD_INT_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'uint32' + || $t eq 'bits32' + || $t eq 'BlockNumber' + || $t eq 'Index' + || $t eq 'SubTransactionId') + { + print $off "\tWRITE_UINT_FIELD($f);\n"; + print $rff "\tREAD_UINT_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'uint64' + || $t eq 'AclMode') + { + print $off "\tWRITE_UINT64_FIELD($f);\n"; + print $rff "\tREAD_UINT64_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'Oid' || $t eq 'RelFileNumber') + { + print $off "\tWRITE_OID_FIELD($f);\n"; + print $rff "\tREAD_OID_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'long') + { + print $off "\tWRITE_LONG_FIELD($f);\n"; + print $rff "\tREAD_LONG_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'char') + { + print $off "\tWRITE_CHAR_FIELD($f);\n"; + print $rff "\tREAD_CHAR_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'double') + { + print $off "\tWRITE_FLOAT_FIELD($f);\n"; + print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'Cardinality') + { + print $off "\tWRITE_FLOAT_FIELD($f);\n"; + print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'Cost') + { + print $off "\tWRITE_FLOAT_FIELD($f);\n"; + print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'QualCost') + { + print $off "\tWRITE_FLOAT_FIELD($f.startup);\n"; + print $off "\tWRITE_FLOAT_FIELD($f.per_tuple);\n"; + print $rff "\tREAD_FLOAT_FIELD($f.startup);\n" unless $no_read; + print $rff "\tREAD_FLOAT_FIELD($f.per_tuple);\n" unless $no_read; + } + elsif ($t eq 'Selectivity') + { + print $off "\tWRITE_FLOAT_FIELD($f);\n"; + print $rff "\tREAD_FLOAT_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'char*') + { + print $off "\tWRITE_STRING_FIELD($f);\n"; + print $rff "\tREAD_STRING_FIELD($f);\n" unless $no_read; + } + elsif ($t eq 'Bitmapset*' || $t eq 'Relids') + { + print $off "\tWRITE_BITMAPSET_FIELD($f);\n"; + print $rff "\tREAD_BITMAPSET_FIELD($f);\n" unless $no_read; + } + elsif (elem $t, @enum_types) + { + print $off "\tWRITE_ENUM_FIELD($f, $t);\n"; + print $rff "\tREAD_ENUM_FIELD($f, $t);\n" unless $no_read; + } + # arrays of scalar types + elsif ($t =~ /^(\w+)(\*|\[\w+\])$/ and elem $1, @scalar_types) + { + my $tt = uc $1; + if (!defined $array_size_field) + { + die "no array size defined for $n.$f of type $t\n"; + } + if ($node_type_info{$n}->{field_types}{$array_size_field} eq + 'List*') + { + print $off + "\tWRITE_${tt}_ARRAY($f, list_length(node->$array_size_field));\n"; + print $rff + "\tREAD_${tt}_ARRAY($f, list_length(local_node->$array_size_field));\n" + unless $no_read; + } + else + { + print $off + "\tWRITE_${tt}_ARRAY($f, node->$array_size_field);\n"; + print $rff + "\tREAD_${tt}_ARRAY($f, local_node->$array_size_field);\n" + unless $no_read; + } + } + elsif ($t eq 'function pointer') + { + # We don't print these, and we can't read them either + die "cannot read function pointer in struct \"$n\" field \"$f\"\n" + unless $no_read; + } + # Special treatments of several Path node fields + elsif ($t eq 'RelOptInfo*' && elem 'write_only_relids', @a) + { + print $off + "\tappendStringInfoString(str, \" :parent_relids \");\n" + . "\toutBitmapset(str, node->$f->relids);\n"; + } + elsif ($t eq 'PathTarget*' && elem 'write_only_nondefault_pathtarget', + @a) + { + (my $f2 = $f) =~ s/pathtarget/parent/; + print $off "\tif (node->$f != node->$f2->reltarget)\n" + . "\t\tWRITE_NODE_FIELD($f);\n"; + } + elsif ($t eq 'ParamPathInfo*' && elem 'write_only_req_outer', @a) + { + print $off + "\tappendStringInfoString(str, \" :required_outer \");\n" + . "\tif (node->$f)\n" + . "\t\toutBitmapset(str, node->$f->ppi_req_outer);\n" + . "\telse\n" + . "\t\toutBitmapset(str, NULL);\n"; + } + # node type + elsif (($t =~ /^(\w+)\*$/ or $t =~ /^struct\s+(\w+)\*$/) + and elem $1, @node_types) + { + die + "node type \"$1\" lacks write support, which is required for struct \"$n\" field \"$f\"\n" + if (elem $1, @no_read_write or elem $1, @nodetag_only); + die + "node type \"$1\" lacks read support, which is required for struct \"$n\" field \"$f\"\n" + if (elem $1, @no_read or elem $1, @nodetag_only) + and !$no_read; + + print $off "\tWRITE_NODE_FIELD($f);\n"; + print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read; + } + # arrays of node pointers (currently supported for write only) + elsif (($t =~ /^(\w+)\*\*$/ or $t =~ /^struct\s+(\w+)\*\*$/) + and elem($1, @node_types)) + { + if (!defined $array_size_field) + { + die "no array size defined for $n.$f of type $t\n"; + } + if ($node_type_info{$n}->{field_types}{$array_size_field} eq + 'List*') + { + print $off + "\tWRITE_NODE_ARRAY($f, list_length(node->$array_size_field));\n"; + print $rff + "\tREAD_NODE_ARRAY($f, list_length(local_node->$array_size_field));\n" + unless $no_read; + } + else + { + print $off + "\tWRITE_NODE_ARRAY($f, node->$array_size_field);\n"; + print $rff + "\tREAD_NODE_ARRAY($f, local_node->$array_size_field);\n" + unless $no_read; + } + } + elsif ($t eq 'struct CustomPathMethods*' + || $t eq 'struct CustomScanMethods*') + { + print $off q{ + /* CustomName is a key to lookup CustomScanMethods */ + appendStringInfoString(str, " :methods "); + outToken(str, node->methods->CustomName); +}; + print $rff q! + { + /* Lookup CustomScanMethods by CustomName */ + char *custom_name; + const CustomScanMethods *methods; + token = pg_strtok(&length); /* skip methods: */ + token = pg_strtok(&length); /* CustomName */ + custom_name = nullable_string(token, length); + methods = GetCustomScanMethods(custom_name, false); + local_node->methods = methods; + } +! unless $no_read; + } + elsif ($t eq 'void*' && ($f eq 'retdesc' || $f eq 'dest') && $n eq 'CallStmt') + { + # Babelfish-specific type override + # do nothing + } + # PLtsql extension: pointer to a PG node type not in our input files + # (e.g. TypeName*, Query*). Assume any Capitalized* pointer is a PG + # node and emit WRITE_NODE_FIELD / READ_NODE_FIELD — the engine's + # nodeToString/stringToNode already knows how to handle them. + elsif ($t =~ /^([A-Z]\w+)\*$/) + { + print $off "\tWRITE_NODE_FIELD($f);\n"; + print $rff "\tREAD_NODE_FIELD($f);\n" unless $no_read; + } + else + { + die + "could not handle type \"$t\" in struct \"$n\" field \"$f\"\n"; + } + + # for read_as() without read_write_ignore, we have to read the value + # that outfuncs.c wrote and then overwrite it. + if (defined $read_as_field) + { + print $rff "\tlocal_node->$f = $read_as_field;\n" unless $no_read; + } + + $previous_fields{$f} = 1; + } + + print $off "} +"; + print $rff " +\tREAD_DONE(); +} +" unless $no_read; +} + +close $off; +close $rff; +close $ofs; +close $rfs; + + +# queryjumblefuncs.c +# PLtsql extension: we don't generate queryjumblefuncs +# (engine handles query jumble for PLtsql nodes via no_query_jumble attribute). + + +# now rename the temporary files to their final names +foreach my $file (@output_files) +{ + RenameTempFile("$output_path/$file", $tmpext); +} + + +# Automatically clean up any temp files if the script fails. +END +{ + # take care not to change the script's exit value + my $exit_code = $?; + + if ($exit_code != 0) + { + foreach my $file (@output_files) + { + unlink("$output_path/$file$tmpext"); + } + } + + $? = $exit_code; +} diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_compare.c b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_compare.c new file mode 100644 index 00000000000..c48de6e8333 --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_compare.c @@ -0,0 +1,49 @@ +/*------------------------------------------------------------------------- + * + * pltsql_compare.c + * Parse tree comparison harness for validation testing. + * + * Provides pltsql_compare_parse_trees() which compares two PLtsql_stmt_block + * trees field-by-field using the generated equality functions. + * + * Gated behind babelfishpg_tsql.validate_parse_cache GUC. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/nodes.h" +#include "lib/stringinfo.h" +#include "utils/builtins.h" + +#include "src/pltsql.h" +#include "src/pltsql-2.h" + +/* From pltsql_equalfuncs.c */ +extern bool pltsql_equal_node(const void *a, const void *b); + +/* Forward declaration */ +extern bool pltsql_compare_parse_trees(PLtsql_stmt_block *tree_a, + PLtsql_stmt_block *tree_b); + +/* + * pltsql_compare_parse_trees + * Compare two PLtsql_stmt_block trees for structural equality. + * + * Uses generated equality functions. On mismatch, the generated _equal* + * functions log which node type and field differ at DEBUG1 level. + */ +bool +pltsql_compare_parse_trees(PLtsql_stmt_block *tree_a, + PLtsql_stmt_block *tree_b) +{ + if (tree_a == NULL && tree_b == NULL) + return true; + if (tree_a == NULL || tree_b == NULL) + { + elog(DEBUG1, "pltsql_validate_parse_cache[FAIL]: pltsql_compare_parse_trees: one tree is NULL"); + return false; + } + + return pltsql_equal_node(tree_a, tree_b); +} diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_equalfuncs.c b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_equalfuncs.c new file mode 100644 index 00000000000..6b8c9950614 --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_equalfuncs.c @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- + * + * pltsql_equalfuncs.c + * Wrapper for PLtsql node equality comparison (equalfuncs). + * + * Mirrors the engine's equalfuncs.c pattern: includes the generated + * static _equal* functions (pltsql_equalfuncs_gen.c) and the switch + * dispatch fragment (pltsql_equalfuncs_switch.c). + * + * Provides pltsql_equal_node() as the public entry point for comparing + * PLtsql node types field-by-field. + * + * Used for parse tree validation (PoC testing): comparing an ANTLR-compiled + * tree against a serialized-then-deserialized tree to verify round-trip + * correctness. + * + *------------------------------------------------------------------------- + */ +#include "pltsql_serialize_macros.h" + +/* Forward declaration */ +extern bool pltsql_equal_node(const void *a, const void *b); + +/* + * Stub equality functions for custom_read_write / special_read_write nodes. + * These are referenced by the generated switch but skipped from gen code. + * For PoC validation, compare via serialized string representation. + */ +static bool +_equalList(const List *a, const List *b) +{ + return pltsql_equal_nodes_or_equal(a, b); +} + +static bool +_equalPLtsql_expr(const PLtsql_expr *a, const PLtsql_expr *b) +{ + if (a == NULL && b == NULL) return true; + if (a == NULL || b == NULL) return false; + if (a->query == NULL && b->query == NULL) return true; + if (a->query == NULL || b->query == NULL) return false; + + /* + * Skip query, paramnos, rwparam, and ns comparison. + * + * query: contains embedded dno values (e.g. "pltsql_assign_var(3, ...)") + * that differ between cached (CREATE-time) and ANTLR (EXEC-time) trees + * due to datum numbering offsets. The query text is derived from the + * same source code so it's semantically identical. + * + * paramnos: Bitmapset of datum numbers referenced by the expression. + * Since dno numbering differs between CREATE and EXEC contexts, the + * bitmapset values differ even though they reference equivalent datums. + * + * rwparam: dno of the read-write parameter, same dno offset issue. + * + * ns: PLtsql_nsitem namespace chain. Each nsitem contains an itemno + * field (which is a dno reference). Although _equalPLtsql_nsitem skips + * itemno, the EXEC-time do_compile creates additional namespace entries + * for extra $N placeholder datums, making the chain lengths differ. + * + * itvf_query: safe to compare — contains the rewritten ITVF query string + * which does not embed dno values. + */ + COMPARE_STRING_FIELD(itvf_query); + + return true; +} + +static bool +_equalPLtsql_row(const PLtsql_row *a, const PLtsql_row *b) +{ + if (a == NULL && b == NULL) return true; + if (a == NULL || b == NULL) return false; + COMPARE_SCALAR_FIELD(dtype); + /* skip dno — datum numbering differs between CREATE/EXEC contexts */ + /*COMPARE_SCALAR_FIELD(dno);*/ + COMPARE_STRING_FIELD(refname); + /* skip lineno */ + /*COMPARE_SCALAR_FIELD(lineno);*/ + COMPARE_SCALAR_FIELD(nfields); + return true; +} + +static bool +_equalPLtsql_recfield(const PLtsql_recfield *a, const PLtsql_recfield *b) +{ + if (a == NULL && b == NULL) return true; + if (a == NULL || b == NULL) return false; + COMPARE_SCALAR_FIELD(dtype); + /* skip dno */ + /*COMPARE_SCALAR_FIELD(dno);*/ + COMPARE_STRING_FIELD(fieldname); + /* skip recparentno — same reason as dno */ + /*COMPARE_SCALAR_FIELD(recparentno);*/ + return true; +} + +static bool +_equalPLtsql_nsitem(const PLtsql_nsitem *a, const PLtsql_nsitem *b) +{ + if (a == NULL && b == NULL) return true; + if (a == NULL || b == NULL) return false; + COMPARE_SCALAR_FIELD(itemtype); + /* skip itemno — datum numbering differs between CREATE/EXEC contexts */ + /*COMPARE_SCALAR_FIELD(itemno);*/ + COMPARE_NODE_FIELD(prev); + COMPARE_STRING_FIELD(name); + return true; +} + +/* Pull in the generated static _equal* functions */ +#include "pltsql_equalfuncs_gen.c" + +/* + * pltsql_equal_node - compare two PLtsql nodes for structural equality. + * + * Returns true if both nodes are field-by-field equal. + * Returns false on any mismatch, NULL difference, or unknown node type. + */ +bool +pltsql_equal_node(const void *a, const void *b) +{ + bool retval; + + if (a == b) + return true; + if (a == NULL || b == NULL) + return false; + if (nodeTag(a) != nodeTag(b)) + return false; + + switch ((int) nodeTag(a)) + { +#include "pltsql_equalfuncs_switch.c" + + default: + /* Unknown PLtsql node type — cannot compare */ + retval = false; + elog(DEBUG1, "pltsql_equal_node: unknown PLtsql node type: %d", (int) nodeTag(a)); + break; + } + + return retval; +} diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_node_stubs.c b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_node_stubs.c new file mode 100644 index 00000000000..8e0e9640ce6 --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_node_stubs.c @@ -0,0 +1,329 @@ +/*------------------------------------------------------------------------- + * + * pltsql_node_stubs.c + * Custom read/write implementations for PLtsql custom_read_write nodes. + * + * These functions implement serialization/deserialization for PLtsql node + * types that are marked pg_node_attr(custom_read_write) in + * pltsql_serializable.h: + * - PLtsql_expr: has runtime-only fields that must be skipped + * - PLtsql_nsitem: has FLEXIBLE_ARRAY_MEMBER requiring special allocation + * - PLtsql_row: has string/int arrays requiring custom handling + * + * These are called by the generated outfuncs.switch.c / readfuncs.switch.c + * dispatch code when nodeToString/stringToNode encounters these types. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/nodes.h" +#include "nodes/bitmapset.h" +#include "nodes/readfuncs.h" +#include "lib/stringinfo.h" +/* + * On the extension side we include pltsql.h and pltsql-2.h directly — they have + * the real struct definitions that the serializable headers mirror. + */ +#include "src/pltsql.h" +#include "src/pltsql-2.h" +#include "nodes/parsenodes.h" /* for TypeName */ +#include "nodes/execnodes.h" /* for ExprState */ + +#include "pltsql_serialize_macros.h" + +/* + * Forward declarations for custom_read_write functions. + * Required by -Werror=missing-prototypes. + */ +extern void _outPLtsql_nsitem(StringInfo str, const PLtsql_nsitem *node); +extern PLtsql_nsitem *_readPLtsql_nsitem(void); +extern void _outPLtsql_expr(StringInfo str, const PLtsql_expr *node); +extern PLtsql_expr *_readPLtsql_expr(void); +extern void _outPLtsql_row(StringInfo str, const PLtsql_row *node); +extern PLtsql_row *_readPLtsql_row(void); +extern void _outPLtsql_recfield(StringInfo str, const PLtsql_recfield *node); +extern PLtsql_recfield *_readPLtsql_recfield(void); + +/* ---------------------------------------------------------------- + * PLtsql_nsitem (custom_read_write) + * + * Has FLEXIBLE_ARRAY_MEMBER for name[], so cannot use makeNode. + * The prev pointer forms a linked list (namespace chain). + * ---------------------------------------------------------------- + */ +void +_outPLtsql_nsitem(StringInfo str, const PLtsql_nsitem *node) +{ + if (node == NULL) + { + appendStringInfoString(str, "<>"); + return; + } + + /* Note: outNode() already writes the opening '{' before calling us */ + WRITE_NODE_TYPE("PLTSQL_NSITEM"); + WRITE_ENUM_FIELD(itemtype, PLtsql_nsitem_type); + WRITE_INT_FIELD(itemno); + + /* Serialize prev as a nested node (recursive, via pltsql_outNode for proper bracing) */ + appendStringInfoString(str, " :prev "); + pltsql_outNode(str, node->prev); + + /* Serialize flexible array member as a string */ + appendStringInfoString(str, " :name "); + outToken(str, node->name); + + /* Note: outNode() writes the closing '}' after we return */ +} + +PLtsql_nsitem * +_readPLtsql_nsitem(void) +{ + const char *token; + int length; + int itemtype; + int itemno; + PLtsql_nsitem *prev; + char *name_str; + PLtsql_nsitem *result; + + /* Read itemtype */ + token = pg_strtok(&length); /* skip :itemtype */ + token = pg_strtok(&length); /* get value */ + itemtype = atoi(token); + + /* Read itemno */ + token = pg_strtok(&length); /* skip :itemno */ + token = pg_strtok(&length); /* get value */ + itemno = atoi(token); + + /* Read prev (recursive) */ + token = pg_strtok(&length); /* skip :prev */ + prev = (PLtsql_nsitem *) pltsql_nodeRead(NULL, 0); + + /* Read name */ + token = pg_strtok(&length); /* skip :name */ + token = pg_strtok(&length); /* get value */ + name_str = pltsql_nullable_string(token, length); + + /* Allocate with extra space for flexible array member */ + { + int name_len = name_str ? strlen(name_str) : 0; + + result = (PLtsql_nsitem *) palloc0(offsetof(PLtsql_nsitem, name) + name_len + 1); + NodeSetTag(result, T_PLtsql_nsitem); + result->itemtype = (PLtsql_nsitem_type) itemtype; + result->itemno = itemno; + result->prev = prev; + if (name_str) + { + memcpy(result->name, name_str, name_len + 1); + pfree(name_str); + } + else + result->name[0] = '\0'; + } + + return result; +} + +/* ---------------------------------------------------------------- + * PLtsql_expr (custom_read_write) + * + * Has many read_write_ignore fields (runtime-only: plan, func, + * expr_simple_*). Only serializes: query, paramnos, rwparam, ns, + * itvf_query. + * ---------------------------------------------------------------- + */ +void +_outPLtsql_expr(StringInfo str, const PLtsql_expr *node) +{ + if (node == NULL) + { + appendStringInfoString(str, "<>"); + return; + } + + /* Note: outNode() already writes the opening '{' before calling us */ + WRITE_NODE_TYPE("PLTSQL_EXPR"); + WRITE_STRING_FIELD(query); + /* plan: read_write_ignore */ + WRITE_BITMAPSET_FIELD(paramnos); + WRITE_INT_FIELD(rwparam); + /* func: read_write_ignore */ + + /* ns: serialize the namespace chain */ + appendStringInfoString(str, " :ns "); + pltsql_outNode(str, node->ns); + + /* expr_simple_*: all read_write_ignore */ + WRITE_STRING_FIELD(itvf_query); + /* Note: outNode() writes the closing '}' after we return */ +} + +PLtsql_expr * +_readPLtsql_expr(void) +{ + READ_LOCALS(PLtsql_expr); + + READ_STRING_FIELD(query); + /* plan: read_write_ignore, init to NULL (palloc0 via makeNode) */ + READ_BITMAPSET_FIELD(paramnos); + READ_INT_FIELD(rwparam); + /* func: read_write_ignore, already NULL from makeNode */ + + /* ns: read the namespace chain */ + token = pg_strtok(&length); /* skip :ns */ + (void) token; + local_node->ns = (PLtsql_nsitem *) pltsql_nodeRead(NULL, 0); + + /* expr_simple_*: all read_write_ignore, already zeroed by makeNode */ + READ_STRING_FIELD(itvf_query); + + READ_DONE(); +} + +/* ---------------------------------------------------------------- + * PLtsql_row (custom_read_write) + * + * Has string array (fieldnames) and int array (varnos) with + * array_size(nfields). rowtupdesc is read_write_ignore. + * ---------------------------------------------------------------- + */ +void +_outPLtsql_row(StringInfo str, const PLtsql_row *node) +{ + int i; + + if (node == NULL) + { + appendStringInfoString(str, "<>"); + return; + } + + /* Note: outNode() already writes the opening '{' before calling us */ + WRITE_NODE_TYPE("PLTSQL_ROW"); + WRITE_ENUM_FIELD(dtype, PLtsql_datum_type); + WRITE_INT_FIELD(dno); + WRITE_STRING_FIELD(refname); + WRITE_INT_FIELD(lineno); + WRITE_BOOL_FIELD(isconst); + WRITE_BOOL_FIELD(notnull); + + /* default_val is a PLtsql_expr pointer */ + appendStringInfoString(str, " :default_val "); + pltsql_outNode(str, node->default_val); + + /* rowtupdesc: read_write_ignore */ + WRITE_INT_FIELD(nfields); + + /* fieldnames: string array of size nfields */ + appendStringInfoString(str, " :fieldnames"); + for (i = 0; i < node->nfields; i++) + { + appendStringInfoChar(str, ' '); + outToken(str, node->fieldnames[i]); + } + + /* varnos: int array of size nfields */ + appendStringInfoString(str, " :varnos"); + for (i = 0; i < node->nfields; i++) + appendStringInfo(str, " %d", node->varnos[i]); + + /* Note: outNode() writes the closing '}' after we return */ +} + +PLtsql_row * +_readPLtsql_row(void) +{ + int i; + + READ_LOCALS(PLtsql_row); + + READ_ENUM_FIELD(dtype, PLtsql_datum_type); + READ_INT_FIELD(dno); + READ_STRING_FIELD(refname); + READ_INT_FIELD(lineno); + READ_BOOL_FIELD(isconst); + READ_BOOL_FIELD(notnull); + + /* default_val: PLtsql_expr pointer */ + token = pg_strtok(&length); /* skip :default_val */ + (void) token; + local_node->default_val = (PLtsql_expr *) pltsql_nodeRead(NULL, 0); + + /* rowtupdesc: read_write_ignore, already NULL from makeNode */ + READ_INT_FIELD(nfields); + + /* fieldnames: string array */ + token = pg_strtok(&length); /* skip :fieldnames */ + if (local_node->nfields > 0) + { + local_node->fieldnames = (char **) palloc(local_node->nfields * sizeof(char *)); + for (i = 0; i < local_node->nfields; i++) + { + token = pg_strtok(&length); + local_node->fieldnames[i] = pltsql_nullable_string(token, length); + } + } + + /* varnos: int array */ + token = pg_strtok(&length); /* skip :varnos */ + if (local_node->nfields > 0) + { + local_node->varnos = (int *) palloc(local_node->nfields * sizeof(int)); + for (i = 0; i < local_node->nfields; i++) + { + token = pg_strtok(&length); + local_node->varnos[i] = atoi(token); + } + } + + READ_DONE(); +} + +/* ---------------------------------------------------------------- + * PLtsql_recfield (custom_read_write) + * + * Has ExpandedRecordFieldInfo finfo (inline struct) which + * gen_node_support.pl cannot handle with read_as(). + * Only serialize: dtype, dno, fieldname, recparentno, nextfield. + * rectupledescid and finfo are runtime cache — zeroed by makeNode. + * ---------------------------------------------------------------- + */ +void +_outPLtsql_recfield(StringInfo str, const PLtsql_recfield *node) +{ + if (node == NULL) + { + appendStringInfoString(str, "<>"); + return; + } + + WRITE_NODE_TYPE("PLTSQL_RECFIELD"); + WRITE_ENUM_FIELD(dtype, PLtsql_datum_type); + WRITE_INT_FIELD(dno); + WRITE_STRING_FIELD(fieldname); + WRITE_INT_FIELD(recparentno); + WRITE_INT_FIELD(nextfield); + /* rectupledescid: runtime cache, skip */ + /* finfo: runtime cache, skip */ +} + +PLtsql_recfield * +_readPLtsql_recfield(void) +{ + READ_LOCALS(PLtsql_recfield); + + READ_ENUM_FIELD(dtype, PLtsql_datum_type); + READ_INT_FIELD(dno); + READ_STRING_FIELD(fieldname); + READ_INT_FIELD(recparentno); + READ_INT_FIELD(nextfield); + /* rectupledescid: zeroed by makeNode */ + /* finfo: zeroed by makeNode */ + + READ_DONE(); +} + diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_nodeio.c b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_nodeio.c new file mode 100644 index 00000000000..74044e208c3 --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_nodeio.c @@ -0,0 +1,463 @@ +/*------------------------------------------------------------------------- + * + * pltsql_nodeio.c + * Extension-side node serialization/deserialization dispatch. + * + * Provides pltsql_nodeToString() and pltsql_stringToNode() as the public + * API for serializing/deserializing PLtsql parse trees. + * + * These functions handle PLtsql node types directly via the generated + * _out/_read switch files, and delegate standard PG node types to the + * engine public outNode() / nodeRead() functions. + * + * Adapted from PG outfuncs.c, read.c, and readfuncs.c dispatch logic. + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "nodes/nodes.h" +#include "nodes/bitmapset.h" +#include "nodes/value.h" +#include "nodes/readfuncs.h" +#include "lib/stringinfo.h" + +#include "src/pltsql.h" +#include "src/pltsql-2.h" + +/* + * Forward declarations for functions defined in this file. + * pltsql_nodeToString and pltsql_stringToNode are the public API. + * The rest are static helpers. + */ +extern char *pltsql_nodeToString(const void *obj); +extern void *pltsql_stringToNode(const char *str); +extern void pltsql_outNode(StringInfo str, const void *obj); + +static void pltsql_outList(StringInfo str, const List *node); +extern void *pltsql_nodeRead(const char *token, int tok_len); +static Node *pltsql_parseNodeString(void); + +/* + * Forward declarations for generated/hand-written _out* functions. + * These are defined in pltsql_outfuncs.c (which #includes the generated + * pltsql_outfuncs_gen.c and pltsql_outfuncs_switch.c). + */ +extern void _outPLtsql_nsitem(StringInfo str, const PLtsql_nsitem *node); +extern void _outPLtsql_expr(StringInfo str, const PLtsql_expr *node); +extern void _outPLtsql_row(StringInfo str, const PLtsql_row *node); +extern void _outPLtsql_recfield(StringInfo str, const PLtsql_recfield *node); + +/* Forward declarations for hand-written _read* functions (pltsql_node_stubs.c) */ +extern PLtsql_nsitem *_readPLtsql_nsitem(void); +extern PLtsql_expr *_readPLtsql_expr(void); +extern PLtsql_row *_readPLtsql_row(void); +extern PLtsql_recfield *_readPLtsql_recfield(void); + +/* + * Pull in the generated static _out* and _read* functions. + * These must be in the same compilation unit as the switch that calls them. + */ +#include "pltsql_serialize_macros.h" +#include "pltsql_outfuncs_gen.c" +#include "pltsql_readfuncs_gen.c" + + +/* ---------------------------------------------------------------- + * Helper: check if a NodeTag is a PLtsql extension type. + * Uses the generated defines from pltsql_nodetags.h. + * ---------------------------------------------------------------- + */ +static inline bool +is_pltsql_node(const void *obj) +{ + int tag = (int) nodeTag(obj); + + return (tag >= (int) T_PLtsql_type && + tag <= (int) T_PLtsql_stmt_restore_ctx_partial); +} + + +/* ================================================================ + * SERIALIZE (WRITE) SIDE + * ================================================================ + */ + +/* + * pltsql_outList - serialize a List to StringInfo. + * + * Adapted from PG's _outList() in outfuncs.c. + * The only change: calls pltsql_outNode() per element instead of + * PG's outNode(), so PLtsql nodes inside Lists are handled by us. + */ +static void +pltsql_outList(StringInfo str, const List *node) +{ + const ListCell *lc; + + appendStringInfoChar(str, '('); + + if (IsA(node, IntList)) + appendStringInfoChar(str, 'i'); + else if (IsA(node, OidList)) + appendStringInfoChar(str, 'o'); + else if (IsA(node, XidList)) + appendStringInfoChar(str, 'x'); + + foreach(lc, node) + { + if (IsA(node, List)) + { + pltsql_outNode(str, lfirst(lc)); + if (lnext(node, lc)) + appendStringInfoChar(str, ' '); + } + else if (IsA(node, IntList)) + appendStringInfo(str, " %d", lfirst_int(lc)); + else if (IsA(node, OidList)) + appendStringInfo(str, " %u", lfirst_oid(lc)); + else if (IsA(node, XidList)) + appendStringInfo(str, " %u", lfirst_xid(lc)); + else + elog(ERROR, "unrecognized list node type: %d", + (int) node->type); + } + + appendStringInfoChar(str, ')'); +} + +/* + * pltsql_outNode - serialize a node to StringInfo. + * + * Adapted from PG's outNode() in outfuncs.c. + * Routes PLtsql nodes to our generated _out* functions, Lists to + * pltsql_outList(), and everything else to PG's outNode(). + */ +void +pltsql_outNode(StringInfo str, const void *obj) +{ + check_stack_depth(); + + if (obj == NULL) + { + appendStringInfoString(str, "<>"); + return; + } + + /* Lists: use our walker so PLtsql elements are dispatched correctly */ + if (IsA(obj, List) || IsA(obj, IntList) || IsA(obj, OidList) || + IsA(obj, XidList)) + { + pltsql_outList(str, obj); + return; + } + + /* PLtsql nodes: dispatch to our generated/hand-written _out* functions */ + if (is_pltsql_node(obj)) + { + appendStringInfoChar(str, '{'); + switch ((int) nodeTag(obj)) + { +#include "pltsql_outfuncs_switch.c" + + default: + elog(ERROR, "pltsql_outNode: unrecognized PLtsql node type: %d", + (int) nodeTag(obj)); + break; + } + appendStringInfoChar(str, '}'); + return; + } + + /* + * PG nodes (TypeName, Integer, Float, Boolean, String, BitString, + * Bitmapset, etc.): delegate to PG's public outNode(). + */ + outNode(str, obj); +} + +/* + * pltsql_nodeToString - serialize a PLtsql node tree to a palloc'd string. + * + * Public entry point. Call this instead of nodeToString() when serializing + * PLtsql parse trees (function->action, datum lists, etc.). + */ +char * +pltsql_nodeToString(const void *obj) +{ + StringInfoData str; + + initStringInfo(&str); + pltsql_outNode(&str, obj); + return str.data; +} + +/* End of write-side functions */ + + +/* ================================================================ + * DESERIALIZE (READ) SIDE + * ================================================================ + */ + +/* + * Token type constants (from PG read.c, not in any public header). + * Used by pltsql_tokenType() below. + */ +#define PLTSQL_RIGHT_PAREN (1000000 + 1) +#define PLTSQL_LEFT_PAREN (1000000 + 2) +#define PLTSQL_LEFT_BRACE (1000000 + 3) +#define PLTSQL_OTHER_TOKEN (1000000 + 4) + +/* + * pltsql_tokenType - classify a token from pg_strtok. + * + * Replica of PG static nodeTokenType() in read.c. + * Returns T_Integer, T_Float, T_Boolean, T_String, T_BitString, + * or one of the PLTSQL_LEFT_BRACE/PLTSQL_LEFT_PAREN/etc constants. + */ +static NodeTag +pltsql_tokenType(const char *token, int length) +{ + const char *numptr; + int numlen; + + numptr = token; + numlen = length; + if (*numptr == '+' || *numptr == '-') + numptr++, numlen--; + if ((numlen > 0 && isdigit((unsigned char) *numptr)) || + (numlen > 1 && *numptr == '.' && isdigit((unsigned char) numptr[1]))) + { + char *endptr; + + errno = 0; + (void) strtol(token, &endptr, 10); + if (endptr != token + length || errno == ERANGE) + return T_Float; + return T_Integer; + } + else if (*token == '(') + return (NodeTag) PLTSQL_LEFT_PAREN; + else if (*token == ')') + return (NodeTag) PLTSQL_RIGHT_PAREN; + else if (*token == '{') + return (NodeTag) PLTSQL_LEFT_BRACE; + else if ((length == 4 && strncmp(token, "true", 4) == 0) || + (length == 5 && strncmp(token, "false", 5) == 0)) + return T_Boolean; + else if (*token == '"' && length > 1 && token[length - 1] == '"') + return T_String; + else if (*token == 'b' || *token == 'x') + return T_BitString; + else if (*token == '0' && length > 1 && (token[1] == 'x' || token[1] == 'X')) + return T_TSQL_HexString; + else + return (NodeTag) PLTSQL_OTHER_TOKEN; +} + +/* + * pltsql_parseNodeString - read a node type name and dispatch. + * + * Adapted from PG parseNodeString() in readfuncs.c. + * Tries PLtsql node names first (via generated switch), then falls + * back to PG parseNodeString() for standard PG types. + */ +static Node * +pltsql_parseNodeString(void) +{ + READ_TEMP_LOCALS(); + + check_stack_depth(); + + token = pg_strtok(&length); + + /* Try PLtsql node types first */ +#define MATCH(tokname, namelen) \ + (length == namelen && memcmp(token, tokname, namelen) == 0) + +#include "pltsql_readfuncs_switch.c" + +#undef MATCH + + /* Not a PLtsql node - delegate to PG parseNodeString(). + * Push back the token by resetting pg_strtok to the token position. */ + if (token != NULL) + pg_strtok_init(token); + + return parseNodeString(); +} + +/* + * pltsql_nodeRead - read a serialized node from pg_strtok state. + * + * Adapted from PG nodeRead() in read.c. + * Routes '{' tokens through pltsql_parseNodeString() (which handles + * both PLtsql and PG types), and '(' list tokens through our own + * list reader. Everything else delegates to PG nodeRead(). + */ +void * +pltsql_nodeRead(const char *token, int tok_len) +{ + Node *result; + NodeTag type; + + if (token == NULL) + { + token = pg_strtok(&tok_len); + if (token == NULL) + return NULL; + } + + type = pltsql_tokenType(token, tok_len); + + switch ((int) type) + { + case PLTSQL_LEFT_BRACE: + result = pltsql_parseNodeString(); + token = pg_strtok(&tok_len); + if (token == NULL || token[0] != '}') + elog(ERROR, "did not find '}' at end of input node"); + break; + + case PLTSQL_LEFT_PAREN: + { + List *l = NIL; + + token = pg_strtok(&tok_len); + if (token == NULL) + elog(ERROR, "unterminated List structure"); + + if (tok_len == 1 && token[0] == 'i') + { + for (;;) + { + int val; + char *endptr; + token = pg_strtok(&tok_len); + if (token == NULL) + elog(ERROR, "unterminated List structure"); + if (token[0] == ')') + break; + val = (int) strtol(token, &endptr, 10); + if (endptr != token + tok_len) + elog(ERROR, "unrecognized integer: \"%.*s\"", tok_len, token); + l = lappend_int(l, val); + } + result = (Node *) l; + } + else if (tok_len == 1 && token[0] == 'o') + { + for (;;) + { + Oid val; + char *endptr; + token = pg_strtok(&tok_len); + if (token == NULL) + elog(ERROR, "unterminated List structure"); + if (token[0] == ')') + break; + val = (Oid) strtoul(token, &endptr, 10); + if (endptr != token + tok_len) + elog(ERROR, "unrecognized OID: \"%.*s\"", tok_len, token); + l = lappend_oid(l, val); + } + result = (Node *) l; + } + else if (tok_len == 1 && token[0] == 'x') + { + for (;;) + { + TransactionId val; + char *endptr; + token = pg_strtok(&tok_len); + if (token == NULL) + elog(ERROR, "unterminated List structure"); + if (token[0] == ')') + break; + val = (TransactionId) strtoul(token, &endptr, 10); + if (endptr != token + tok_len) + elog(ERROR, "unrecognized Xid: \"%.*s\"", tok_len, token); + l = lappend_xid(l, val); + } + result = (Node *) l; + } + else if (tok_len == 1 && token[0] == 'b') + { + Bitmapset *bms = NULL; + for (;;) + { + int val; + char *endptr; + token = pg_strtok(&tok_len); + if (token == NULL) + elog(ERROR, "unterminated Bitmapset structure"); + if (tok_len == 1 && token[0] == ')') + break; + val = (int) strtol(token, &endptr, 10); + if (endptr != token + tok_len) + elog(ERROR, "unrecognized integer: \"%.*s\"", tok_len, token); + bms = bms_add_member(bms, val); + } + result = (Node *) bms; + } + else + { + /* Node list: walk elements through our reader */ + for (;;) + { + if (token[0] == ')') + break; + l = lappend(l, pltsql_nodeRead(token, tok_len)); + token = pg_strtok(&tok_len); + if (token == NULL) + elog(ERROR, "unterminated List structure"); + } + result = (Node *) l; + } + break; + } + + case PLTSQL_RIGHT_PAREN: + elog(ERROR, "unexpected right parenthesis"); + result = NULL; + break; + + case PLTSQL_OTHER_TOKEN: + if (tok_len == 0) + result = NULL; /* "<>" = null pointer */ + else + { + elog(ERROR, "unrecognized token: \"%.*s\"", tok_len, token); + result = NULL; + } + break; + + default: + /* Value types (Integer, Float, Boolean, String, etc.) + * - delegate to PG nodeRead() */ + result = (Node *) nodeRead(token, tok_len); + break; + } + + return (void *) result; +} + +/* + * pltsql_stringToNode - deserialize a PLtsql node tree from a string. + * + * Public entry point. Sets up pg_strtok state via pg_strtok_init(), + * then calls pltsql_nodeRead() to parse the tree. + */ +void * +pltsql_stringToNode(const char *str) +{ + void *retval; + + /* Point tokenizer at our string */ + pg_strtok_init(str); + + retval = pltsql_nodeRead(NULL, 0); + + return retval; +} diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_nodetags.h b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_nodetags.h new file mode 100644 index 00000000000..58ad6ed07de --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_nodetags.h @@ -0,0 +1,107 @@ +/*------------------------------------------------------------------------- + * + * pltsql_nodetags.h + * Generated node infrastructure code + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * NOTES + * ****************************** + * *** DO NOT EDIT THIS FILE! *** + * ****************************** + * + * It has been GENERATED by gen_pltsql_node_support.pl + * + *------------------------------------------------------------------------- + */ +#ifndef PLTSQL_NODETAGS_H +#define PLTSQL_NODETAGS_H + +/* + * PLtsql NodeTag values, auto-generated by gen_pltsql_node_support.pl. + * Offset from 1000 to avoid collision with engine NodeTags. + */ + +#define T_PLtsql_type ((NodeTag) 1000) +#define T_PLtsql_expr ((NodeTag) 1001) +#define T_PLtsql_datum ((NodeTag) 1002) +#define T_PLtsql_variable ((NodeTag) 1003) +#define T_PLtsql_var ((NodeTag) 1004) +#define T_PLtsql_row ((NodeTag) 1005) +#define T_PLtsql_rec ((NodeTag) 1006) +#define T_PLtsql_tbl ((NodeTag) 1007) +#define T_PLtsql_recfield ((NodeTag) 1008) +#define T_PLtsql_arrayelem ((NodeTag) 1009) +#define T_PLtsql_nsitem ((NodeTag) 1010) +#define T_PLtsql_stmt ((NodeTag) 1011) +#define T_PLtsql_condition ((NodeTag) 1012) +#define T_PLtsql_exception_block ((NodeTag) 1013) +#define T_PLtsql_exception ((NodeTag) 1014) +#define T_PLtsql_stmt_block ((NodeTag) 1015) +#define T_PLtsql_stmt_assign ((NodeTag) 1016) +#define T_PLtsql_stmt_perform ((NodeTag) 1017) +#define T_PLtsql_stmt_call ((NodeTag) 1018) +#define T_PLtsql_stmt_commit ((NodeTag) 1019) +#define T_PLtsql_stmt_rollback ((NodeTag) 1020) +#define T_PLtsql_stmt_set ((NodeTag) 1021) +#define T_PLtsql_diag_item ((NodeTag) 1022) +#define T_PLtsql_stmt_getdiag ((NodeTag) 1023) +#define T_PLtsql_stmt_if ((NodeTag) 1024) +#define T_PLtsql_if_elsif ((NodeTag) 1025) +#define T_PLtsql_stmt_case ((NodeTag) 1026) +#define T_PLtsql_case_when ((NodeTag) 1027) +#define T_PLtsql_stmt_loop ((NodeTag) 1028) +#define T_PLtsql_stmt_while ((NodeTag) 1029) +#define T_PLtsql_stmt_fori ((NodeTag) 1030) +#define T_PLtsql_stmt_forq ((NodeTag) 1031) +#define T_PLtsql_stmt_fors ((NodeTag) 1032) +#define T_PLtsql_stmt_forc ((NodeTag) 1033) +#define T_PLtsql_stmt_dynfors ((NodeTag) 1034) +#define T_PLtsql_stmt_foreach_a ((NodeTag) 1035) +#define T_PLtsql_stmt_open ((NodeTag) 1036) +#define T_PLtsql_stmt_fetch ((NodeTag) 1037) +#define T_PLtsql_stmt_close ((NodeTag) 1038) +#define T_PLtsql_stmt_exit ((NodeTag) 1039) +#define T_PLtsql_stmt_insert_bulk ((NodeTag) 1040) +#define T_PLtsql_stmt_dbcc ((NodeTag) 1041) +#define T_PLtsql_stmt_return ((NodeTag) 1042) +#define T_PLtsql_stmt_return_next ((NodeTag) 1043) +#define T_PLtsql_stmt_return_query ((NodeTag) 1044) +#define T_PLtsql_stmt_raise ((NodeTag) 1045) +#define T_PLtsql_raise_option ((NodeTag) 1046) +#define T_PLtsql_stmt_grantdb ((NodeTag) 1047) +#define T_PLtsql_stmt_change_dbowner ((NodeTag) 1048) +#define T_PLtsql_stmt_alter_db ((NodeTag) 1049) +#define T_PLtsql_stmt_fulltextindex ((NodeTag) 1050) +#define T_PLtsql_stmt_grantschema ((NodeTag) 1051) +#define T_PLtsql_stmt_partition_function ((NodeTag) 1052) +#define T_PLtsql_stmt_partition_scheme ((NodeTag) 1053) +#define T_PLtsql_stmt_assert ((NodeTag) 1054) +#define T_PLtsql_txn_data ((NodeTag) 1055) +#define T_PLtsql_stmt_execsql ((NodeTag) 1056) +#define T_PLtsql_stmt_set_explain_mode ((NodeTag) 1057) +#define T_PLtsql_stmt_dynexecute ((NodeTag) 1058) +#define T_PLtsql_stmt_print ((NodeTag) 1059) +#define T_PLtsql_stmt_kill ((NodeTag) 1060) +#define T_PLtsql_stmt_init ((NodeTag) 1061) +#define T_PLtsql_stmt_try_catch ((NodeTag) 1062) +#define T_PLtsql_stmt_query_set ((NodeTag) 1063) +#define T_PLtsql_stmt_push_result ((NodeTag) 1064) +#define T_PLtsql_stmt_exec ((NodeTag) 1065) +#define T_tsql_exec_param ((NodeTag) 1066) +#define T_PLtsql_stmt_exec_sp ((NodeTag) 1067) +#define T_PLtsql_stmt_decl_table ((NodeTag) 1068) +#define T_PLtsql_stmt_exec_batch ((NodeTag) 1069) +#define T_PLtsql_stmt_raiserror ((NodeTag) 1070) +#define T_PLtsql_stmt_throw ((NodeTag) 1071) +#define T_PLtsql_stmt_deallocate ((NodeTag) 1072) +#define T_PLtsql_stmt_decl_cursor ((NodeTag) 1073) +#define T_PLtsql_stmt_goto ((NodeTag) 1074) +#define T_PLtsql_stmt_label ((NodeTag) 1075) +#define T_PLtsql_stmt_usedb ((NodeTag) 1076) +#define T_PLtsql_stmt_save_ctx ((NodeTag) 1077) +#define T_PLtsql_stmt_restore_ctx_full ((NodeTag) 1078) +#define T_PLtsql_stmt_restore_ctx_partial ((NodeTag) 1079) + +#endif /* PLTSQL_NODETAGS_H */ diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serializable_1.h b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serializable_1.h new file mode 100644 index 00000000000..642af61ec25 --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serializable_1.h @@ -0,0 +1,1499 @@ +/*------------------------------------------------------------------------- + * + * pltsql_serializable.h + * Annotated PLtsql node definitions for code generation + * + * This file contains PLtsql node struct definitions from pltsql.h with + * additional pg_node_attr() annotations for use with gen_pltsql_support.pl to + * generate serialization and deserialization code for caching ANTLR parse tree. + * + * NOTES: + * - This file is used by gen_pltsql_support.pl to generate: + * * pltsql_serialize_gen.c + * * pltsql_deserialize_gen.c + * - Annotations follow PostgreSQL's gen_node_support.pl pattern + * - Start with 5 nodes that already have manual serialization: + * BLOCK, IF, WHILE, LOOP, TRY_CATCH + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/pl/pltsql/src/pltsql_serializable_!.h + * + *------------------------------------------------------------------------- + */ + +/* + * NOTE: This file is NOT compiled by the C compiler. + * It is read as text input by gen_pltsql_support.pl, which parses the + * struct definitions and pg_node_attr() annotations to generate + * pltsql_outfuncs_gen.c and pltsql_readfuncs_gen.c. + * + * The generated .c files will #include "pltsql.h" and "pltsql-2.h" + * and operate on the real runtime struct types directly. + * + * The struct definitions here MUST match pltsql.h exactly + * (same field names, same types, same order) — the only additions are + * pg_node_attr() annotations which compile to nothing in C. + */ + +/* + * Annotation Guide: + * + * Struct-level attributes (placed after opening brace): + * - custom_read_write: Struct has custom serialization/deserialization logic + * - no_copy: Don't generate copy support + * - no_equal: Don't generate equal support + * - special_read_write: Special handling for read/write + * + * Field-level attributes (placed after field declaration): + * - read_write_ignore: Skip this field during serialization/deserialization + * - array_size(field): Specifies the field that contains array size + * - copy_as(expr): Use custom expression for copying + * - read_as(expr): Use custom expression for reading + * - equal_ignore: Skip this field during equality comparison + * + * Special handling notes: + * - PLtsql_variable* references: Store dno (datum number) instead of pointer + * - PLtsql_expr*: Serialize query string, paramnos, and other metadata + * - List*: Serialize list length and elements + * - Flexible arrays: Use array_size() annotation + */ + +/* Note: + * Most nodes in this file are annotated with pg_node_attr(no_copy_equal, no_query_jumble) + * + * Reason: + * pg_node_attr(no_copy_equal, no_query_jumble) tells the generator: don't generate copy, equal, or jumble functions for this struct. Only generate _out and _read. + * PLtsql nodes are never used in PG's query planner/optimizer, so _copy and _equal are never called on them by PG internals + * Query jumbling is for PG's prepared statement cache, not relevant to PLtsql nodes + * Generating copy/equal for these structs would require handling all the complex fields (linked lists, flexible arrays, runtime pointers) which would be a lot of work for no benefit + * It also avoids compile errors — the generated copy/equal code might choke on fields like PLtsql_variable * or PLtsql_txn_data * that have complex types +*/ + +/* + * Forward declarations for PLtsql types referenced before their definition. + */ +// typedef struct PLtsql_expr PLtsql_expr; +// typedef struct PLtsql_exception_block PLtsql_exception_block; +// typedef struct PLtsql_condition PLtsql_condition; +// typedef struct PLtsql_type PLtsql_type; +// typedef struct PLtsql_variable PLtsql_variable; +// typedef struct PLtsql_txn_data PLtsql_txn_data; + +/* + * Forward declarations for external PG types referenced in struct fields. + * These are all pointer fields, so the compiler only needs to know they exist. + */ +typedef struct SPIPlanData *SPIPlanPtr; +typedef struct ExpandedRecordHeader ExpandedRecordHeader; +#include "utils/expandedrecord.h" /* needed for ExpandedRecordFieldInfo (inline struct in PLtsql_recfield) */ +struct TypeCacheEntry; +typedef struct TypeCacheEntry TypeCacheEntry; +typedef struct TupleDescData *TupleDesc; +typedef struct ExprState ExprState; /* from nodes/execnodes.h - too heavy to include */ + +/* + * Prototypes for custom_read_write functions in pltsql_node_stubs.c. + * These are called by the generated outfuncs.switch.c / readfuncs.switch.c. + */ +struct PLtsql_expr; +struct PLtsql_nsitem; +struct PLtsql_row; +struct PLtsql_recfield; + +extern void _outPLtsql_expr(StringInfo str, const struct PLtsql_expr *node); +extern struct PLtsql_expr *_readPLtsql_expr(void); +extern void _outPLtsql_nsitem(StringInfo str, const struct PLtsql_nsitem *node); +extern struct PLtsql_nsitem *_readPLtsql_nsitem(void); +extern void _outPLtsql_row(StringInfo str, const struct PLtsql_row *node); +extern struct PLtsql_row *_readPLtsql_row(void); +extern void _outPLtsql_recfield(StringInfo str, const struct PLtsql_recfield *node); +extern struct PLtsql_recfield *_readPLtsql_recfield(void); +#include "nodes/lockoptions.h" /* for LockClauseStrength etc */ +#include "nodes/parsenodes.h" /* for FetchDirection, TransactionStmtKind, TypeName */ + + +/* + * Compiler's namespace item types + */ +typedef enum PLtsql_nsitem_type +{ + PLTSQL_NSTYPE_LABEL, /* block label */ + PLTSQL_NSTYPE_VAR, /* scalar variable */ + PLTSQL_NSTYPE_REC, /* composite variable */ + PLTSQL_NSTYPE_TBL /* table variable */ +} PLtsql_nsitem_type; + +/* + * A PLTSQL_NSTYPE_LABEL stack entry must be one of these types + */ +typedef enum PLtsql_label_type +{ + PLTSQL_LABEL_BLOCK, /* DECLARE/BEGIN block */ + PLTSQL_LABEL_LOOP, /* looping construct */ + PLTSQL_LABEL_OTHER /* anything else */ +} PLtsql_label_type; + +/* + * Datum array node types + */ +typedef enum PLtsql_datum_type +{ + PLTSQL_DTYPE_VAR, + PLTSQL_DTYPE_ROW, + PLTSQL_DTYPE_REC, + PLTSQL_DTYPE_TBL, + PLTSQL_DTYPE_RECFIELD, + PLTSQL_DTYPE_ARRAYELEM, + PLTSQL_DTYPE_PROMISE +} PLtsql_datum_type; + +/* + * DTYPE_PROMISE datums have these possible ways of computing the promise + */ +typedef enum PLtsql_promise_type +{ + PLTSQL_PROMISE_NONE = 0, /* not a promise, or promise satisfied */ + PLTSQL_PROMISE_TG_NAME, + PLTSQL_PROMISE_TG_WHEN, + PLTSQL_PROMISE_TG_LEVEL, + PLTSQL_PROMISE_TG_OP, + PLTSQL_PROMISE_TG_RELID, + PLTSQL_PROMISE_TG_TABLE_NAME, + PLTSQL_PROMISE_TG_TABLE_SCHEMA, + PLTSQL_PROMISE_TG_NARGS, + PLTSQL_PROMISE_TG_ARGV, + PLTSQL_PROMISE_TG_EVENT, + PLTSQL_PROMISE_TG_TAG +} PLtsql_promise_type; + + +typedef enum PLtsql_dbcc_stmt_type +{ + PLTSQL_DBCC_CHECKIDENT +} PLtsql_dbcc_stmt_type; + +/* + * Variants distinguished in PLtsql_type structs + */ +typedef enum PLtsql_type_type +{ + PLTSQL_TTYPE_SCALAR, /* scalar types and domains */ + PLTSQL_TTYPE_REC, /* composite types, including RECORD */ + PLTSQL_TTYPE_PSEUDO, /* pseudotypes */ + PLTSQL_TTYPE_TBL /* table types */ +} PLtsql_type_type; + +/* + * Execution tree node types + */ +typedef enum PLtsql_stmt_type +{ + PLTSQL_STMT_BLOCK, + PLTSQL_STMT_ASSIGN, + PLTSQL_STMT_IF, + PLTSQL_STMT_CASE, /* PLPGSQL */ + PLTSQL_STMT_LOOP, /* PLPGSQL */ + PLTSQL_STMT_WHILE, + PLTSQL_STMT_FORI, /* PLPGSQL */ + PLTSQL_STMT_FORS, /* PLPGSQL */ + PLTSQL_STMT_FORC, /* PLPGSQL */ + PLTSQL_STMT_FOREACH_A, /* PLPGSQL */ + PLTSQL_STMT_EXIT, + PLTSQL_STMT_RETURN, + PLTSQL_STMT_RETURN_NEXT, /* PLPGSQL */ + PLTSQL_STMT_RETURN_QUERY, /* PLPGSQL */ + PLTSQL_STMT_RAISE, /* PLPGSQL */ + PLTSQL_STMT_ASSERT, /* PLPGSQL */ + PLTSQL_STMT_EXECSQL, + PLTSQL_STMT_DYNEXECUTE, /* PLPGSQL */ + PLTSQL_STMT_DYNFORS, /* PLPGSQL */ + PLTSQL_STMT_GETDIAG, /* PLPGSQL */ + PLTSQL_STMT_OPEN, + PLTSQL_STMT_FETCH, + PLTSQL_STMT_CLOSE, + PLTSQL_STMT_PERFORM, /* PLPGSQL */ + PLTSQL_STMT_CALL, /* PLPGSQL */ + PLTSQL_STMT_COMMIT, + PLTSQL_STMT_ROLLBACK, + PLTSQL_STMT_SET, /* PLPGSQL */ + /* TSQL-only statement types follow */ + PLTSQL_STMT_GOTO, + PLTSQL_STMT_PRINT, + PLTSQL_STMT_INIT, + PLTSQL_STMT_QUERY_SET, + PLTSQL_STMT_TRY_CATCH, + PLTSQL_STMT_PUSH_RESULT, + PLTSQL_STMT_EXEC, + PLTSQL_STMT_EXEC_BATCH, + PLTSQL_STMT_EXEC_SP, + PLTSQL_STMT_DECL_TABLE, + PLTSQL_STMT_RETURN_TABLE, + PLTSQL_STMT_DEALLOCATE, + PLTSQL_STMT_DECL_CURSOR, + PLTSQL_STMT_LABEL, + PLTSQL_STMT_RAISERROR, + PLTSQL_STMT_THROW, + PLTSQL_STMT_USEDB, + PLTSQL_STMT_SET_EXPLAIN_MODE, + PLTSQL_STMT_KILL, + /* TSQL-only executable node */ + PLTSQL_STMT_SAVE_CTX, + PLTSQL_STMT_RESTORE_CTX_FULL, + PLTSQL_STMT_RESTORE_CTX_PARTIAL, + PLTSQL_STMT_INSERT_BULK, + PLTSQL_STMT_GRANTDB, + PLTSQL_STMT_CHANGE_DBOWNER, + PLTSQL_STMT_DBCC, + PLTSQL_STMT_ALTER_DB, + PLTSQL_STMT_FULLTEXTINDEX, + PLTSQL_STMT_GRANTSCHEMA, + PLTSQL_STMT_PARTITION_FUNCTION, + PLTSQL_STMT_PARTITION_SCHEME +} PLtsql_stmt_type; + +/* + * Execution node return codes + */ +enum +{ + PLTSQL_RC_OK, + PLTSQL_RC_EXIT, + PLTSQL_RC_RETURN, + PLTSQL_RC_CONTINUE +}; + +/* + * GET DIAGNOSTICS information items + */ +typedef enum PLtsql_getdiag_kind +{ + PLTSQL_GETDIAG_ROW_COUNT, + PLTSQL_GETDIAG_RESULT_OID, + PLTSQL_GETDIAG_CONTEXT, + PLTSQL_GETDIAG_ERROR_CONTEXT, + PLTSQL_GETDIAG_ERROR_DETAIL, + PLTSQL_GETDIAG_ERROR_HINT, + PLTSQL_GETDIAG_RETURNED_SQLSTATE, + PLTSQL_GETDIAG_COLUMN_NAME, + PLTSQL_GETDIAG_CONSTRAINT_NAME, + PLTSQL_GETDIAG_DATATYPE_NAME, + PLTSQL_GETDIAG_MESSAGE_TEXT, + PLTSQL_GETDIAG_TABLE_NAME, + PLTSQL_GETDIAG_SCHEMA_NAME +} PLtsql_getdiag_kind; + +/* + * RAISE statement options + */ +typedef enum PLtsql_raise_option_type +{ + PLTSQL_RAISEOPTION_ERRCODE, + PLTSQL_RAISEOPTION_MESSAGE, + PLTSQL_RAISEOPTION_DETAIL, + PLTSQL_RAISEOPTION_HINT, + PLTSQL_RAISEOPTION_COLUMN, + PLTSQL_RAISEOPTION_CONSTRAINT, + PLTSQL_RAISEOPTION_DATATYPE, + PLTSQL_RAISEOPTION_TABLE, + PLTSQL_RAISEOPTION_SCHEMA +} PLtsql_raise_option_type; + +/* + * Behavioral modes for pltsql variable resolution + */ +typedef enum PLtsql_resolve_option +{ + PLTSQL_RESOLVE_ERROR, /* throw error if ambiguous */ + PLTSQL_RESOLVE_VARIABLE, /* prefer pltsql var to table column */ + PLTSQL_RESOLVE_COLUMN /* prefer table column to pltsql var */ +} PLtsql_resolve_option; + +/* + * Schema mapping for pltsql databases + */ +typedef enum PLtsql_schema_mapping +{ + PLTSQL_DB_SCHEMA, + PLTSQL_DB, + PLTSQL_SCHEMA +} PLtsql_schema_mapping; + +#define TSQL_TRIGGER_STARTED 0x1 +#define TSQL_TRAN_STARTED 0x2 + +/********************************************************************** + * Node and structure definitions + **********************************************************************/ + +/* + * Postgres data type + */ +typedef struct PLtsql_type +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + char *typname; /* (simple) name of the type */ + Oid typoid; /* OID of the data type */ + PLtsql_type_type ttype; /* PLTSQL_TTYPE_ code */ + int16 typlen; /* stuff copied from its pg_type entry */ + bool typbyval; + char typtype; + Oid collation; /* from pg_type, but can be overridden */ + bool typisarray; /* is "true" array, or domain over one */ + int32 atttypmod; /* typmod (taken from someplace else) */ + + /* + * This field is only used when a table variable does not have a + * pre-defined type, e.g. DECLARE @tableVar TABLE (a int, b int) + */ + char *coldef; + + /* + * Remaining fields are used only for named composite types (not RECORD) + * and table types + */ + TypeName *origtypname; /* type name as written by user */ + TypeCacheEntry *tcache pg_node_attr(read_write_ignore, read_as(NULL)); /* typcache entry for composite type */ + uint64 tupdesc_id pg_node_attr(read_write_ignore, read_as(0)); /* last-seen tupdesc identifier */ +} PLtsql_type; + +/* + * SQL Query to plan and execute + */ +typedef struct PLtsql_expr +{ + pg_node_attr(custom_read_write, no_copy_equal, no_query_jumble) + NodeTag type; + char *query; + SPIPlanPtr plan pg_node_attr(read_write_ignore, read_as(NULL)); + Bitmapset *paramnos; /* all dnos referenced by this query */ + int rwparam; /* dno of read/write param, or -1 if none */ + + /* function containing this expr (not set until we first parse query) */ + struct PLtsql_function *func pg_node_attr(read_write_ignore, read_as(NULL)); + + /* namespace chain visible to this expr */ + struct PLtsql_nsitem *ns; + + /* fields for "simple expression" fast-path execution: */ + Expr *expr_simple_expr; /* NULL means not a simple expr */ + int expr_simple_generation; /* plancache generation we checked */ + Oid expr_simple_type; /* result type Oid, if simple */ + int32 expr_simple_typmod; /* result typmod, if simple */ + bool expr_simple_mutable; /* true if simple expr is mutable */ + + /* + * if expr is simple AND prepared in current transaction, + * expr_simple_state and expr_simple_in_use are valid. Test validity by + * seeing if expr_simple_lxid matches current LXID. (If not, + * expr_simple_state probably points at garbage!) + */ + ExprState *expr_simple_state; /* eval tree for expr_simple_expr */ + bool expr_simple_in_use; /* true if eval tree is active */ + LocalTransactionId expr_simple_lxid; + + /* here for itvf? queries with all idents replaced with NULLs */ + char *itvf_query; + /* make sure always set to NULL */ +} PLtsql_expr; + +/* + * Generic datum array item + * + * PLtsql_datum is the common supertype for PLtsql_var, PLtsql_row, + * PLtsql_rec, PLtsql_recfield, and PLtsql_arrayelem. + */ +typedef struct PLtsql_datum +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; +} PLtsql_datum; + +/* + * Scalar or composite variable + * + * The variants PLtsql_var, PLtsql_row, and PLtsql_rec share these + * fields. + */ +typedef struct PLtsql_variable +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; + char *refname; + int lineno; + bool isconst; + bool notnull; + PLtsql_expr *default_val; +} PLtsql_variable; + +/* + * Scalar variable + * + * DTYPE_VAR and DTYPE_PROMISE datums both use this struct type. + * A PROMISE datum works exactly like a VAR datum for most purposes, + * but if it is read without having previously been assigned to, then + * a special "promised" value is computed and assigned to the datum + * before the read is performed. This technique avoids the overhead of + * computing the variable's value in cases where we expect that many + * functions will never read it. + */ +typedef struct PLtsql_var +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; + char *refname; + int lineno; + bool isconst; + bool notnull; + PLtsql_expr *default_val; + /* end of PLtsql_variable fields */ + + PLtsql_type *datatype; + + /* + * Variables declared as CURSOR FOR are mostly like ordinary + * scalar variables of type refcursor, but they have these additional + * properties: + */ + PLtsql_expr *cursor_explicit_expr; + int cursor_explicit_argrow; + int cursor_options; + + /* to identify if variable is getting used for babelfish GUC */ + bool is_babelfish_guc; + + /* Fields below here can change at runtime */ + + Datum value pg_node_attr(read_write_ignore, read_as(0)); + bool isnull pg_node_attr(read_write_ignore, read_as(true)); + bool freeval pg_node_attr(read_write_ignore, read_as(false)); + + /* + * The promise field records which "promised" value to assign if the + * promise must be honored. If it's a normal variable, or the promise has + * been fulfilled, this is PLTSQL_PROMISE_NONE. + */ + PLtsql_promise_type promise; +} PLtsql_var; + +/* + * Row variable - this represents one or more variables that are listed in an + * INTO clause, FOR-loop targetlist, cursor argument list, etc. We also use + * a row to represent a function's OUT parameters when there's more than one. + * + * Note that there's no way to name the row as such from PL/tsql code, + * so many functions don't need to support these. + * + * That also means that there's no real name for the row variable, so we + * conventionally set refname to "(unnamed row)". We could leave it NULL, + * but it's too convenient to be able to assume that refname is valid in + * all variants of PLtsql_variable. + * + * isconst, notnull, and default_val are unsupported (and hence + * always zero/null) for a row. The member variables of a row should have + * been checked to be writable at compile time, so isconst is correctly set + * to false. notnull and default_val aren't applicable. + */ +typedef struct PLtsql_row +{ + pg_node_attr(custom_read_write, no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; + char *refname; + int lineno; + bool isconst; + bool notnull; + PLtsql_expr *default_val; + /* end of PLtsql_variable fields */ + + /* + * rowtupdesc is only set up if we might need to convert the row into a + * composite datum, which currently only happens for OUT parameters. + * Otherwise it is NULL. + */ + TupleDesc rowtupdesc pg_node_attr(read_write_ignore, read_as(NULL)); + + int nfields; + char **fieldnames pg_node_attr(array_size(nfields)); + int *varnos pg_node_attr(array_size(nfields)); +} PLtsql_row; + +/* + * Record variable (any composite type, including RECORD) + */ +typedef struct PLtsql_rec +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; + char *refname; + int lineno; + bool isconst; + bool notnull; + PLtsql_expr *default_val; + /* end of PLtsql_variable fields */ + + /* + * Note: for non-RECORD cases, we may from time to time re-look-up the + * composite type, using datatype->origtypname. That can result in + * changing rectypeid. + */ + + PLtsql_type *datatype; /* can be NULL, if rectypeid is RECORDOID */ + Oid rectypeid; /* declared type of variable */ + /* RECFIELDs for this record are chained together for easy access */ + int firstfield; /* dno of first RECFIELD, or -1 if none */ + + /* Fields below here can change at runtime */ + + /* We always store record variables as "expanded" records */ + ExpandedRecordHeader *erh pg_node_attr(read_write_ignore, read_as(NULL)); +} PLtsql_rec; + +/* + * Table variable + */ +typedef struct PLtsql_tbl +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; + char *refname; + int lineno; + bool isconst; + bool notnull; + PLtsql_expr *default_val; + /* end of PLtsql_variable fields */ + + PLtsql_type *datatype; + Oid tbltypeid; /* declared type of variable */ + char *tblname; /* name of the underlying table */ + + /* + * If a table variable is declared inside a function, then we need to drop + * its underlying table at the end of execution. If a table variable is + * passed in as a table-valued parameter, then we don't need to drop its + * underlying table - it's the caller's responsibility. + */ + bool need_drop; +} PLtsql_tbl; + +/* + * Field in record + */ +typedef struct PLtsql_recfield +{ + pg_node_attr(custom_read_write, no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; + /* end of PLtsql_datum fields */ + + char *fieldname; /* name of field */ + int recparentno; /* dno of parent record */ + int nextfield; /* dno of next child, or -1 if none */ + uint64 rectupledescid; /* record's tupledesc ID as of last lookup */ + ExpandedRecordFieldInfo finfo; /* field's attnum and type info */ + /* if rectupledescid == INVALID_TUPLEDESC_IDENTIFIER, finfo isn't valid */ +} PLtsql_recfield; + +/* + * Element of array variable + */ +typedef struct PLtsql_arrayelem +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_datum_type dtype; + int dno; + /* end of PLtsql_datum fields */ + + PLtsql_expr *subscript; + int arrayparentno; /* dno of parent array variable */ + + /* Remaining fields are cached info about the array variable's type */ + Oid parenttypoid; /* type of array variable; 0 if not yet set */ + int32 parenttypmod; /* typmod of array variable */ + Oid arraytypoid; /* OID of actual array type */ + int32 arraytypmod; /* typmod of array (and its elements too) */ + int16 arraytyplen; /* typlen of array type */ + Oid elemtypoid; /* OID of array element type */ + int16 elemtyplen; /* typlen of element type */ + bool elemtypbyval; /* element type is pass-by-value? */ + char elemtypalign; /* typalign of element type */ +} PLtsql_arrayelem; + +/* + * Item in the compilers namespace tree + */ +typedef struct PLtsql_nsitem +{ + pg_node_attr(custom_read_write, no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_nsitem_type itemtype; + + /* + * For labels, itemno is a value of enum PLtsql_label_type. For other + * itemtypes, itemno is the associated PLtsql_datum's dno. + */ + int itemno; + struct PLtsql_nsitem *prev; + char name[FLEXIBLE_ARRAY_MEMBER]; /* nul-terminated string */ +} PLtsql_nsitem; + +typedef enum PLtsql_impl_txn_type +{ + PLTSQL_IMPL_TRAN_OFF, + PLTSQL_IMPL_TRAN_ON, + PLTSQL_IMPL_TRAN_START +} PLtsql_impl_txn_type; + +/* + * Generic execution node + */ +typedef struct PLtsql_stmt +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; +} PLtsql_stmt; + +/* + * One EXCEPTION condition name + */ +typedef struct PLtsql_condition +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + int sqlerrstate; /* SQLSTATE code */ + char *condname; /* condition name (for debugging) */ + struct PLtsql_condition *next; +} PLtsql_condition; + +/* + * EXCEPTION block + */ +typedef struct PLtsql_exception_block +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + int sqlstate_varno; + int sqlerrm_varno; + List *exc_list; /* List of WHEN clauses */ +} PLtsql_exception_block; + +/* + * One EXCEPTION ... WHEN clause + */ +typedef struct PLtsql_exception +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + int lineno; + PLtsql_condition *conditions; + List *action; /* List of statements */ +} PLtsql_exception; + +/* + * Block of statements + */ +typedef struct PLtsql_stmt_block +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + List *body; /* List of statements */ + int n_initvars; /* Length of initvarnos[] */ + int *initvarnos pg_node_attr(array_size(n_initvars)); /* dnos of variables declared in this block */ + PLtsql_exception_block *exceptions; +} PLtsql_stmt_block; + +/* + * Assign statement + */ +typedef struct PLtsql_stmt_assign +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int varno; + PLtsql_expr *expr; +} PLtsql_stmt_assign; + +/* + * PERFORM statement + */ +typedef struct PLtsql_stmt_perform +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *expr; +} PLtsql_stmt_perform; + +/* + * CALL statement + */ +typedef struct PLtsql_stmt_call +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *expr; + bool is_call; + PLtsql_variable *target; +} PLtsql_stmt_call; + +/* + * COMMIT statement + */ +typedef struct PLtsql_stmt_commit +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; +} PLtsql_stmt_commit; + +/* + * ROLLBACK statement + */ +typedef struct PLtsql_stmt_rollback +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; +} PLtsql_stmt_rollback; + +/* + * SET statement + */ +typedef struct PLtsql_stmt_set +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *expr; +} PLtsql_stmt_set; + +/* + * GET DIAGNOSTICS item + */ +typedef struct PLtsql_diag_item +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_getdiag_kind kind; /* id for diagnostic value desired */ + int target; /* where to assign it */ +} PLtsql_diag_item; + +/* + * GET DIAGNOSTICS statement + */ +typedef struct PLtsql_stmt_getdiag +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + bool is_stacked; /* STACKED or CURRENT diagnostics area? */ + List *diag_items; /* List of PLtsql_diag_item */ +} PLtsql_stmt_getdiag; + +/* + * IF statement + */ +typedef struct PLtsql_stmt_if +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *cond; /* boolean expression for THEN */ + PLtsql_stmt *then_body; /* List of statements */ + List *elsif_list; /* List of PLtsql_if_elsif structs */ + PLtsql_stmt *else_body; /* List of statements */ +} PLtsql_stmt_if; + +/* + * one ELSIF arm of IF statement + */ +typedef struct PLtsql_if_elsif +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + int lineno; + PLtsql_expr *cond; /* boolean expression for this case */ + List *stmts; /* List of statements */ +} PLtsql_if_elsif; + +/* + * CASE statement + */ +typedef struct PLtsql_stmt_case +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *t_expr; /* test expression, or NULL if none */ + int t_varno; /* var to store test expression value into */ + List *case_when_list; /* List of PLtsql_case_when structs */ + bool have_else; /* flag needed because list could be empty */ + List *else_stmts; /* List of statements */ +} PLtsql_stmt_case; + +/* + * one arm of CASE statement + */ +typedef struct PLtsql_case_when +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + int lineno; + PLtsql_expr *expr; /* boolean expression for this case */ + List *stmts; /* List of statements */ +} PLtsql_case_when; + +/* + * Unconditional LOOP statement + */ +typedef struct PLtsql_stmt_loop +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + List *body; /* List of statements */ +} PLtsql_stmt_loop; + +/* + * WHILE cond LOOP statement + */ +typedef struct PLtsql_stmt_while +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_expr *cond; + List *body; /* List of statements */ +} PLtsql_stmt_while; + +/* + * FOR statement with integer loopvar + */ +typedef struct PLtsql_stmt_fori +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_var *var; + PLtsql_expr *lower; + PLtsql_expr *upper; + PLtsql_expr *step; /* NULL means default (ie, BY 1) */ + int reverse; + List *body; /* List of statements */ +} PLtsql_stmt_fori; + +/* + * PLtsql_stmt_forq represents a FOR statement running over a SQL query. + * It is the common supertype of PLtsql_stmt_fors, PLtsql_stmt_forc + * and PLtsql_dynfors. + */ +typedef struct PLtsql_stmt_forq +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_variable *var; /* Loop variable (record or row) */ + List *body; /* List of statements */ +} PLtsql_stmt_forq; + +/* + * FOR statement running over SELECT + */ +typedef struct PLtsql_stmt_fors +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_variable *var; /* Loop variable (record or row) */ + List *body; /* List of statements */ + /* end of fields that must match PLtsql_stmt_forq */ + PLtsql_expr *query; +} PLtsql_stmt_fors; + +/* + * FOR statement running over cursor + */ +typedef struct PLtsql_stmt_forc +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_variable *var; /* Loop variable (record or row) */ + List *body; /* List of statements */ + /* end of fields that must match PLtsql_stmt_forq */ + int curvar; + PLtsql_expr *argquery; /* cursor arguments if any */ +} PLtsql_stmt_forc; + +/* + * FOR statement running over EXECUTE + */ +typedef struct PLtsql_stmt_dynfors +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_variable *var; /* Loop variable (record or row) */ + List *body; /* List of statements */ + /* end of fields that must match PLtsql_stmt_forq */ + PLtsql_expr *query; + List *params; /* USING expressions */ +} PLtsql_stmt_dynfors; + +/* + * FOREACH item in array loop + */ +typedef struct PLtsql_stmt_foreach_a +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + int varno; /* loop target variable */ + int slice; /* slice dimension, or 0 */ + PLtsql_expr *expr; /* array expression */ + List *body; /* List of statements */ +} PLtsql_stmt_foreach_a; + +/* + * OPEN a curvar + */ +typedef struct PLtsql_stmt_open +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int curvar; + int cursor_options; + PLtsql_expr *argquery; + PLtsql_expr *query; + PLtsql_expr *dynquery; + List *params; /* USING expressions */ +} PLtsql_stmt_open; + +/* + * FETCH or MOVE statement + */ +typedef struct PLtsql_stmt_fetch +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_variable *target; /* target (record or row) */ + int curvar; /* cursor variable to fetch from */ + FetchDirection direction; /* fetch direction */ + long how_many; /* count, if constant (expr is NULL) */ + PLtsql_expr *expr; /* count, if expression */ + bool is_move; /* is this a fetch or move? */ + bool returns_multiple_rows; /* can return more than one row? */ +} PLtsql_stmt_fetch; + +/* + * CLOSE curvar + */ +typedef struct PLtsql_stmt_close +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int curvar; +} PLtsql_stmt_close; + +/* + * EXIT or CONTINUE statement + */ +typedef struct PLtsql_stmt_exit +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + bool is_exit; /* Is this an exit or a continue? */ + char *label; /* NULL if it's an unlabelled EXIT/CONTINUE */ + PLtsql_expr *cond; +} PLtsql_stmt_exit; + +/* + * INSERT BULK statement + */ +typedef struct PLtsql_stmt_insert_bulk +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *table_name; + char *schema_name; + char *db_name; + List *column_refs; + + /* Insert Bulk Options. */ + char *kilobytes_per_batch; + char *rows_per_batch; + bool keep_nulls; + bool check_constraints; +} PLtsql_stmt_insert_bulk; + +/* + * DBCC statement — nodetag_only because PLtsql_dbcc_stmt_data is a union + * that gen_node_support.pl cannot parse. Only the NodeTag enum is generated. + * Procedures containing DBCC will fall back to ANTLR parse. + */ +typedef struct PLtsql_stmt_dbcc +{ + pg_node_attr(nodetag_only) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_dbcc_stmt_type dbcc_stmt_type; +} PLtsql_stmt_dbcc; + +/* + * RETURN statement + */ +typedef struct PLtsql_stmt_return +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *expr; + int retvarno; +} PLtsql_stmt_return; + +/* + * RETURN NEXT statement + */ +typedef struct PLtsql_stmt_return_next +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *expr; + int retvarno; +} PLtsql_stmt_return_next; + +/* + * RETURN QUERY statement + */ +typedef struct PLtsql_stmt_return_query +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *query; /* if static query */ + PLtsql_expr *dynquery; /* if dynamic query (RETURN QUERY EXECUTE) */ + List *params; /* USING arguments for dynamic query */ +} PLtsql_stmt_return_query; + +/* + * RAISE statement + */ +typedef struct PLtsql_stmt_raise +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int elog_level; + char *condname; /* condition name, SQLSTATE, or NULL */ + char *message; /* old-style message format literal, or NULL */ + List *params; /* list of expressions for old-style message */ + List *options; /* list of PLtsql_raise_option */ +} PLtsql_stmt_raise; + +/* + * RAISE statement option + */ +typedef struct PLtsql_raise_option +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_raise_option_type opt_type; + PLtsql_expr *expr; +} PLtsql_raise_option; + +/* + * Grant Connect stmt + */ +typedef struct PLtsql_stmt_grantdb +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + bool is_grant; + List *grantees; /* list of users */ +} PLtsql_stmt_grantdb; + +/* + * ALTER AUTHORIZATION ON DATABASE:: TO + */ +typedef struct PLtsql_stmt_change_dbowner +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *db_name; + char *new_owner_name; /* Login name for new owner */ +} PLtsql_stmt_change_dbowner; + +typedef struct PLtsql_stmt_alter_db +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *old_db_name; + char *new_db_name; +} PLtsql_stmt_alter_db; + +/* + * Fulltext Index stmt + */ +typedef struct PLtsql_stmt_fulltextindex +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + 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 */ + char *db_name; /* database name */ + bool is_create; /* flag for create index */ +} PLtsql_stmt_fulltextindex; + +/* + * Grant on schema stmt + */ +typedef struct PLtsql_stmt_grantschema +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + bool is_grant; + int privileges; + List *grantees; /* list of users */ + bool with_grant_option; + char *schema_name; /* schema name */ +} PLtsql_stmt_grantschema; + + +/* + * Partition Function + */ +typedef struct PLtsql_stmt_partition_function +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *function_name; + bool is_create; + bool is_right; + PLtsql_type *datatype; + List *args; /* the arguments (list of exprs) */ + char *collation; +} PLtsql_stmt_partition_function; + +/* + * Partition Scheme + */ +typedef struct PLtsql_stmt_partition_scheme +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *scheme_name; + bool is_create; + char *function_name; + int filegroups; /* filegroups count, -1 indicates ALL is specified */ +} PLtsql_stmt_partition_scheme; + +/* + * ASSERT statement + */ +typedef struct PLtsql_stmt_assert +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *cond; + PLtsql_expr *message; +} PLtsql_stmt_assert; + +typedef struct PLtsql_txn_data +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + TransactionStmtKind stmt_kind; /* Commit or rollback */ /*TODO: is it exec only?*/ + char *txn_name; /* Transaction name */ + PLtsql_expr *txn_name_expr; /* Transaction name variable */ +} PLtsql_txn_data; + +/* + * Generic SQL statement to execute + */ +typedef struct PLtsql_stmt_execsql +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *sqlstmt; + bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE? Note: + * mod_stmt is set when we plan the query */ + bool into; /* INTO supplied? */ + bool strict; /* INTO STRICT flag */ + PLtsql_txn_data *txn_data; /* Transaction data */ + PLtsql_variable *target; /* INTO target (record or row) */ + bool mod_stmt_tablevar; /* is the stmt INSERT/UPDATE/DELETE on a + * table variable? Note: + * mod_stmt_tablevar is set when we plan + * the query */ + bool need_to_push_result; /* push result to client */ + bool is_tsql_select_assign_stmt; /* T-SQL SELECT-assign (i.e. + * SELECT @a=1) */ + bool insert_exec; /* INSERT-EXEC stmt? */ + bool is_cross_db; /* cross database reference */ + bool is_ddl; /* DDL statement? */ + char *schema_name; /* Schema specified */ + char *db_name; /* db_name: only for cross db query */ + bool is_create_view; /* CREATE VIEW? */ + bool is_set_tran_isolation; /* SET TRANSACTION ISOLATION? */ + char *original_query; /* Only for batch level statement. */ + bool is_schemabinding; /* Is schema binding? */ +} PLtsql_stmt_execsql; + +/* + * SET statement to change EXPLAIN MODE + * The main reason for this PLtsql statement is + * to turn off EXPLAIN ONLY MODE while it is on. + */ +typedef struct PLtsql_stmt_set_explain_mode +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *query; + bool is_explain_only; + bool is_explain_analyze; + bool val; +} PLtsql_stmt_set_explain_mode; + +/* + * Dynamic SQL string to execute + */ +typedef struct PLtsql_stmt_dynexecute +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *query; /* string expression */ + bool into; /* INTO supplied? */ + bool strict; /* INTO STRICT flag */ + PLtsql_variable *target; /* INTO target (record or row) */ + List *params; /* USING expressions */ +} PLtsql_stmt_dynexecute; + +// /* +// * Hash lookup key for functions +// */ +// typedef struct PLtsql_func_hashkey +// { +// /* +// * lower 32bit for stored procedure's OID, upper 32bit for prepared +// * batch's handle +// */ +// uint64_t funcOid; + +// bool isTrigger; /* true if called as a DML trigger */ +// bool isEventTrigger; /* true if called as an event trigger */ + +// /* be careful that pad bytes in this struct get zeroed! */ + +// /* +// * For a trigger function, the OID of the trigger is part of the hash key +// * --- we want to compile the trigger function separately for each trigger +// * it is used with, in case the rowtype or transition table names are +// * different. Zero if not called as a DML trigger. +// */ +// Oid trigOid; + +// /* +// * We must include the input collation as part of the hash key too, +// * because we have to generate different plans (with different Param +// * collations) for different collation settings. +// */ +// Oid inputCollation; + +// /* +// * We include actual argument types in the hash key to support polymorphic +// * Pltsql functions. Be careful that extra positions are zeroed! +// */ +// Oid argtypes[FUNC_MAX_ARGS]; +// } PLtsql_func_hashkey; + +// /* +// * Trigger type +// */ +// typedef enum PLtsql_trigtype +// { +// PLTSQL_DML_TRIGGER, +// PLTSQL_EVENT_TRIGGER, +// PLTSQL_NOT_TRIGGER +// } PLtsql_trigtype; + +// #define BATCH_OPTION_CACHE_PLAN 0x1 +// #define BATCH_OPTION_PREPARE_PLAN 0x2 +// #define BATCH_OPTION_SEND_METADATA 0x4 +// #define BATCH_OPTION_NO_EXEC 0x8 +// #define BATCH_OPTION_EXEC_CACHED_PLAN 0x10 +// #define BATCH_OPTION_NO_FREE 0x20 + +// typedef struct InlineCodeBlockArgs +// { +// NodeTag type; +// int numargs; +// Oid *argtypes; +// int32 *argtypmods; +// char **argnames; +// char *argmodes; +// int *varnos; +// unsigned long options; +// int handle; +// } InlineCodeBlockArgs; + +// /* +// * Complete compiled function +// */ +// typedef struct PLtsql_function +// { +// NodeTag type; +// char *fn_signature; +// Oid fn_oid; +// TransactionId fn_xmin; +// ItemPointerData fn_tid; +// PLtsql_trigtype fn_is_trigger; +// Oid fn_input_collation; +// PLtsql_func_hashkey *fn_hashkey; /* back-link to hashtable key */ +// MemoryContext fn_cxt; + +// Oid fn_rettype; +// int fn_rettyplen; +// bool fn_retbyval; +// bool fn_retistuple; +// bool fn_retisdomain; +// bool fn_retset; +// bool fn_readonly; +// char fn_prokind; + +// int fn_nargs; +// int fn_argvarnos[PREPARE_STMT_MAX_ARGS]; +// int out_param_varno; +// int found_varno; +// int fetch_status_varno; +// int new_varno; +// int old_varno; +// int16 fn_dbid; /* logical db which contains the function */ +// char *fn_search_path; + +// TupleDesc fn_tupdesc; /* tuple descriptor for return info */ + +// /* table variables */ +// List *table_varnos; + +// bool is_itvf; +// bool is_mstvf; + +// PLtsql_resolve_option resolve_option; + +// bool print_strict_params; + +// /* extra checks */ +// int extra_warnings; +// int extra_errors; + +// /* the datums representing the function's local variables */ +// int ndatums; +// PLtsql_datum **datums; +// Size copiable_size; /* space for locally instantiated datums */ + +// /* function body parsetree */ +// PLtsql_stmt_block *action; + +// /* Track if this function was loaded from ANTLR parse result cache */ +// bool from_cache; + +// /* these fields change when the function is used */ +// struct PLtsql_execstate *cur_estate; +// unsigned long use_count; + +// /* execution codes for new executor */ +// struct ExecCodes *exec_codes; +// bool exec_codes_valid; + +// /* arguments for inline code block */ +// InlineCodeBlockArgs *inline_args; +// } PLtsql_function; + diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serializable_2.h b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serializable_2.h new file mode 100644 index 00000000000..57192e9509c --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serializable_2.h @@ -0,0 +1,377 @@ + +/*------------------------------------------------------------------------- + * + * pltsql_serializable_2.h + * Annotated PLtsql node definitions for code generation + * + * This file contains PLtsql node struct definitions from pltsql-2.h with + * additional pg_node_attr() annotations for use with gen_pltsql_support.pl to + * generate serialization and deserialization code for caching ANTLR parse tree. + * + * NOTES: + * - This file is used by gen_pltsql_support.pl to generate: + * * pltsql_serialize_gen.c + * * pltsql_deserialize_gen.c + * - Annotations follow PostgreSQL's gen_node_support.pl pattern + * + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/pl/pltsql/src/pltsql_serializable_2.h + * + *------------------------------------------------------------------------- + */ + +/* + * NOTE: This file is NOT compiled by the C compiler. + * It is read as text input by gen_pltsql_support.pl, which parses the + * struct definitions and pg_node_attr() annotations to generate + * pltsql_outfuncs_gen.c and pltsql_readfuncs_gen.c. + * + * The generated .c files will #include "pltsql.h" and "pltsql-2.h" + * and operate on the real runtime struct types directly. + * + * The struct definitions here MUST match pltsql-2.h exactly + * (same field names, same types, same order) — the only additions are + * pg_node_attr() annotations which compile to nothing in C. + */ + +/* + * Annotation Guide: + * + * Struct-level attributes (placed after opening brace): + * - custom_read_write: Struct has custom serialization/deserialization logic + * - no_copy: Don't generate copy support + * - no_equal: Don't generate equal support + * - special_read_write: Special handling for read/write + * + * Field-level attributes (placed after field declaration): + * - read_write_ignore: Skip this field during serialization/deserialization + * - array_size(field): Specifies the field that contains array size + * - copy_as(expr): Use custom expression for copying + * - read_as(expr): Use custom expression for reading + * - equal_ignore: Skip this field during equality comparison + * + * Special handling notes: + * - PLtsql_variable* references: Store dno (datum number) instead of pointer + * - PLtsql_expr*: Serialize query string, paramnos, and other metadata + * - List*: Serialize list length and elements + * - Flexible arrays: Use array_size() annotation + */ + +/* + * PRINT statement + */ +typedef struct PLtsql_stmt_print +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + List *exprs; +} PLtsql_stmt_print; + +/* + * KILL statement + */ +typedef struct PLtsql_stmt_kill +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + int spid; +} PLtsql_stmt_kill; + +/* + * init statement + */ +typedef struct PLtsql_stmt_init +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + List *inits; +} PLtsql_stmt_init; + +/* + * BEGIN TRY...END TRY BEGIN CATCH...END CATCH block + */ +typedef struct PLtsql_stmt_try_catch +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_stmt *body; /* List of statements */ + PLtsql_stmt *handler; +} PLtsql_stmt_try_catch; + +/* + * SELECT-SET statement (this represents a SELECT + * statement that assignes variables to a set of + * target variables, such as: + * SELECT @balance = cust_balance FROM customer ... + */ +typedef struct PLtsql_stmt_query_set +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + uint32 stmtid; + PLtsql_expr *sqlstmt; + PLtsql_variable *target; /* INTO target (record or row) */ +} PLtsql_stmt_query_set; + +typedef struct PLtsql_stmt_push_result +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_expr *query; +} PLtsql_stmt_push_result; + +/* + * EXEC statement + */ +typedef struct PLtsql_stmt_exec +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *expr; + bool is_call; + PLtsql_variable *target; + int return_code_dno; + int paramno; + List *params; + + /* indicates whether we're executing a scalar UDF using EXEC keyword */ + bool is_scalar_func; + bool is_cross_db; /* cross database reference */ + char *db_name; + char *proc_name; + char *schema_name; + + bool exec_with_recompile; /* forced recompile through EXECUTE */ +} PLtsql_stmt_exec; + +typedef struct tsql_exec_param +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + const char *name; + PLtsql_expr *expr; + char mode; + int varno; /* dno of the output variable */ +} tsql_exec_param; + +/* + * T-SQL provides variadic system procedures which are used for RPC. + * We cannot use "CREATE PROCEUDRE" to define those procedures since they can be variadic. + * PLtsql_stmt_exec_sp is a general stmt wrapper to call those procedures. + * TODO: integrate with other system procedures such as sp_executesql + */ +/* + * NOTE: In pltsql-2.h the enum tag is PLtsql_exec_sp_type_code but the typedef + * name is PLtsql_sp_type_code. gen_node_support.pl captures the tag name, so we + * use the typedef name as the tag here so the generator recognizes it. + */ +typedef enum PLtsql_sp_type_code +{ + PLTSQL_EXEC_SP_CURSOR, + PLTSQL_EXEC_SP_CURSOROPEN, + PLTSQL_EXEC_SP_CURSORPREPARE, + PLTSQL_EXEC_SP_CURSOREXECUTE, + PLTSQL_EXEC_SP_CURSORPREPEXEC, + PLTSQL_EXEC_SP_CURSORUNPREPARE, + PLTSQL_EXEC_SP_CURSORFETCH, + PLTSQL_EXEC_SP_CURSOROPTION, + PLTSQL_EXEC_SP_CURSORCLOSE, + PLTSQL_EXEC_SP_EXECUTESQL, + PLTSQL_EXEC_SP_EXECUTE, + PLTSQL_EXEC_SP_PREPEXEC +} PLtsql_sp_type_code; + +typedef struct PLtsql_stmt_exec_sp +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + + PLtsql_sp_type_code sp_type_code; + int prepared_handleno; + int cursor_handleno; + int return_code_dno; + + PLtsql_expr *handle; + + PLtsql_expr *query; /* stmt */ + int paramno; + PLtsql_expr *param_def; + List *params; + + PLtsql_expr *opt1; + PLtsql_expr *opt2; + PLtsql_expr *opt3; + List *stropt; +} PLtsql_stmt_exec_sp; + +/* + * DECLARE table variable statement + */ +typedef struct PLtsql_stmt_decl_table +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int dno; /* dno of the table variable */ + /* One and only one of the remaining two fields should be used */ + char *tbltypname; /* name of the table type */ + char *coldef; /* column definition list */ +} PLtsql_stmt_decl_table; + +typedef struct PLtsql_stmt_exec_batch +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + PLtsql_expr *expr; +} PLtsql_stmt_exec_batch; + +typedef struct PLtsql_stmt_raiserror +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + List *params; + int paramno; + bool log; + bool nowait; + bool seterror; +} PLtsql_stmt_raiserror; + +typedef struct PLtsql_stmt_throw +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + List *params; +} PLtsql_stmt_throw; + +/* + * DEALLOCATE curvar + */ +typedef struct PLtsql_stmt_deallocate +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int curvar; +} PLtsql_stmt_deallocate; + +/* + * (re)DECLARE cur CURSOR ... + */ +typedef struct PLtsql_stmt_decl_cursor +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int curvar; + PLtsql_expr *cursor_explicit_expr; + int cursor_options; +} PLtsql_stmt_decl_cursor; + +extern bool is_cursor_datatype(Oid oid); + +/* + * GOTO statement + */ +typedef struct PLtsql_stmt_goto +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; + PLtsql_expr *cond; /* conditional GOTO */ + int32 target_pc; /* int32_t in pltsql-2.h */ + char *target_label; +} PLtsql_stmt_goto; + +/* + * Label + */ +typedef struct PLtsql_stmt_label +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *label; +} PLtsql_stmt_label; + +/* + * Use DB statement + */ +typedef struct PLtsql_stmt_usedb +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + char *db_name; +} PLtsql_stmt_usedb; + +/* + * Save error handling context + */ +typedef struct PLtsql_stmt_save_ctx +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; + int32 target_pc; /* int32_t in pltsql-2.h */ + char *target_label; +} PLtsql_stmt_save_ctx; + +/* + * Delete exception handling context + */ +typedef struct PLtsql_stmt_restore_ctx_full +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; +} PLtsql_stmt_restore_ctx_full; + +/* + * Post-exception handling block + */ +typedef struct PLtsql_stmt_restore_ctx_partial +{ + pg_node_attr(no_copy_equal, no_query_jumble) + NodeTag type; + PLtsql_stmt_type cmd_type; + int lineno; +} PLtsql_stmt_restore_ctx_partial; diff --git a/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serialize_macros.h b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serialize_macros.h new file mode 100644 index 00000000000..cab7aa8e43e --- /dev/null +++ b/contrib/babelfishpg_tsql/src/pltsql_serialize/pltsql_serialize_macros.h @@ -0,0 +1,360 @@ +/*------------------------------------------------------------------------- + * + * pltsql_serialize_macros.h + * Shared WRITE_/READ_ macro definitions for PLtsql serialization. + * + * These macros are defined inside PostgreSQL's outfuncs.c / readfuncs.c + * and are NOT exposed in any public header. We replicate them here so + * that both pltsql_node_stubs.c (hand-written) and the generated + * pltsql_outfuncs_gen.c / pltsql_readfuncs_gen.c can share a single + * definition. + * + * This file also provides pltsql_nullable_string() and helper macros + * (booltostr, atoui, strtobool) that match readfuncs.c internals. + * + *------------------------------------------------------------------------- + */ +#ifndef PLTSQL_SERIALIZE_MACROS_H +#define PLTSQL_SERIALIZE_MACROS_H + +#include "postgres.h" + +#include "miscadmin.h" +#include "nodes/nodes.h" +#include "nodes/bitmapset.h" +#include "nodes/readfuncs.h" +#include "lib/stringinfo.h" +#include "nodes/parsenodes.h" +#include "nodes/execnodes.h" + +#include "src/pltsql.h" +#include "src/pltsql-2.h" + +/* ---------------------------------------------------------------- + * Helper macros (from outfuncs.c / readfuncs.c / equalfuncs.c internals) + * ---------------------------------------------------------------- + */ + +/* + * outfuncs.c macros are not in a header, so we redefine the ones we need. + * These match the definitions in outfuncs.c exactly. + */ +#define WRITE_NODE_TYPE(nodelabel) \ + appendStringInfoString(str, nodelabel) + +#define WRITE_INT_FIELD(fldname) \ + appendStringInfo(str, " :" CppAsString(fldname) " %d", node->fldname) + +#define WRITE_UINT_FIELD(fldname) \ + appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname) + +#define WRITE_BOOL_FIELD(fldname) \ + appendStringInfo(str, " :" CppAsString(fldname) " %s", \ + booltostr(node->fldname)) + +#define WRITE_STRING_FIELD(fldname) \ + (appendStringInfoString(str, " :" CppAsString(fldname) " "), \ + outToken(str, node->fldname)) + +#define WRITE_ENUM_FIELD(fldname, enumtype) \ + appendStringInfo(str, " :" CppAsString(fldname) " %d", \ + (int) node->fldname) + +/* + * WRITE_NODE_FIELD — serialize a child node field. + * Routes through pltsql_outNode() so PLtsql nodes inside Lists and + * nested fields are handled by our extension dispatcher, not PG's outNode(). + */ +extern void pltsql_outNode(StringInfo str, const void *obj); + +#define WRITE_NODE_FIELD(fldname) \ + (appendStringInfoString(str, " :" CppAsString(fldname) " "), \ + pltsql_outNode(str, node->fldname)) + +#define WRITE_BITMAPSET_FIELD(fldname) \ + (appendStringInfoString(str, " :" CppAsString(fldname) " "), \ + outBitmapset(str, node->fldname)) + +/* + * readfuncs.c macros - redefined here since they're not in a header. + * Note: READ_LOCALS cannot be used for PLtsql_nsitem (flexible array member), + * so we handle that case manually. + */ +#define READ_TEMP_LOCALS() \ + const char *token; \ + int length + +#define READ_LOCALS_NO_FIELDS(nodeTypeName) \ + nodeTypeName *local_node = makeNode(nodeTypeName) + +#define READ_LOCALS(nodeTypeName) \ + READ_LOCALS_NO_FIELDS(nodeTypeName); \ + READ_TEMP_LOCALS() + +#define READ_INT_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = atoi(token) + +#define READ_UINT_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = atoui(token) + +#define READ_BOOL_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = strtobool(token) + +#define READ_STRING_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = pltsql_nullable_string(token, length) + +#define READ_ENUM_FIELD(fldname, enumtype) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = (enumtype) atoi(token) + +/* + * READ_NODE_FIELD - read a child node field. + * Routes through pltsql_nodeRead() so PLtsql nodes inside Lists and + * nested fields are handled by our extension dispatcher. + */ +extern void *pltsql_nodeRead(const char *token, int tok_len); + +#define READ_NODE_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + (void) token; \ + local_node->fldname = pltsql_nodeRead(NULL, 0) + +#define READ_BITMAPSET_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + (void) token; \ + local_node->fldname = readBitmapset() + +#define READ_DONE() \ + return local_node + +/* Helper macros matching readfuncs.c internals */ +#define atoui(x) ((unsigned int) strtoul((x), NULL, 10)) +#define strtobool(x) ((*(x) == 't') ? true : false) +#define booltostr(x) ((x) ? "true" : "false") + +/* + * pltsql_nullable_string - local version of nullable_string from readfuncs.c + * + * outToken emits <> for NULL, and pg_strtok makes that an empty string. + */ +static inline char * +pltsql_nullable_string(const char *token, int length) +{ + if (length == 0) + return NULL; + else if (length == 2 && token[0] == '<' && token[1] == '>') + return NULL; + else + return debackslash(token, length); +} + +/* + * Additional macros needed by generated code (not used by pltsql_node_stubs.c + * but required by pltsql_outfuncs_gen.c / pltsql_readfuncs_gen.c). + */ +#define WRITE_OID_FIELD(fldname) \ + appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname) + +#define WRITE_LONG_FIELD(fldname) \ + appendStringInfo(str, " :" CppAsString(fldname) " %ld", node->fldname) + +#define WRITE_CHAR_FIELD(fldname) \ + do { \ + char _c = node->fldname; \ + if (_c == '\0') \ + appendStringInfo(str, " :" CppAsString(fldname) " <>"); \ + else { \ + char _in[2]; _in[0] = _c; _in[1] = '\0'; \ + appendStringInfoString(str, " :" CppAsString(fldname) " "); \ + outToken(str, _in); \ + } \ + } while (0) + +#define WRITE_INT_ARRAY(fldname, len) \ + do { \ + appendStringInfoString(str, " :" CppAsString(fldname) " "); \ + if (node->fldname) { \ + int _i; \ + appendStringInfoChar(str, '('); \ + for (_i = 0; _i < (len); _i++) \ + appendStringInfo(str, " %d", node->fldname[_i]); \ + appendStringInfoString(str, " )"); \ + } else { \ + appendStringInfoString(str, "<>"); \ + } \ + } while (0) + +#define READ_OID_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = atooid(token) + +#define READ_LONG_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = atol(token) + +#define READ_CHAR_FIELD(fldname) \ + token = pg_strtok(&length); /* skip :fldname */ \ + token = pg_strtok(&length); /* get field value */ \ + local_node->fldname = (length == 0) ? '\0' : (token[0] == '\\' ? token[1] : token[0]) + +#define READ_INT_ARRAY(fldname, len) \ + token = pg_strtok(&length); /* skip :fldname */ \ + local_node->fldname = readIntCols(len) + +/* MATCH macro for readfuncs switch dispatch */ +#define MATCH(tokname, namelen) \ + (length == namelen && memcmp(token, tokname, namelen) == 0) + + +/* + * equalfuncs.c macros are not in a header, so we redefine the ones we need. + * These match the definitions in postgresql_modified_for_babelfish/src/backend/nodes/equalfuncs.c exactly. + */ + + +/* Compare a simple scalar field (int, float, bool, enum, etc) */ +#define COMPARE_SCALAR_FIELD(fldname) \ + do { \ + if (a->fldname != b->fldname) \ + return false; \ + } while (0) + +/* Compare a field that is a pointer to some kind of Node or Node tree. + * Uses pltsql_equal_nodes_or_equal() to handle both PLtsql and PG node types. */ +extern bool pltsql_equal_node(const void *a, const void *b); + +static inline bool +pltsql_equal_nodes_or_equal(const void *a, const void *b) +{ + if (a == b) + return true; + if (a == NULL || b == NULL) + return (a == b); + + if (nodeTag(a) != nodeTag(b)) + return false; + + check_stack_depth(); + + /* PLtsql node tags are in range 1000-1079 */ + if ((int) nodeTag(a) >= 1000 && (int) nodeTag(a) <= 1079) + return pltsql_equal_node(a, b); + + /* Lists (all variants): walk elements and check each */ + if (IsA(a, List) || IsA(a, IntList) || IsA(a, OidList) || IsA(a, XidList)) + { + if(IsA(b, List) || IsA(b, IntList) || IsA(b, OidList) || IsA(b, XidList)) + { + const List *la = (const List *) a; + const List *lb = (const List *) b; + const ListCell *lca, *lcb; + + if (list_length(la) != list_length(lb)) + return false; + + forboth(lca, la, lcb, lb) + { + if (!pltsql_equal_nodes_or_equal(lfirst(lca), lfirst(lcb))) + return false; + } + return true; + } + else + return false; + } + + /* Fall back to PG's equal() for standard node types */ + return equal(a, b); +} + +#define COMPARE_NODE_FIELD(fldname) \ + do { \ + if (!pltsql_equal_nodes_or_equal(a->fldname, b->fldname)) \ + return false; \ + } while (0) + +/* Compare a field that is a pointer to a Bitmapset */ +#define COMPARE_BITMAPSET_FIELD(fldname) \ + do { \ + if (!bms_equal(a->fldname, b->fldname)) \ + return false; \ + } while (0) + +/* Compare a field that is a pointer to a C string, or perhaps NULL */ +#define COMPARE_STRING_FIELD(fldname) \ + do { \ + if (!equalstr(a->fldname, b->fldname)) \ + return false; \ + } while (0) + +/* Macro for comparing string fields that might be NULL */ +#define equalstr(a, b) \ + (((a) != NULL && (b) != NULL) ? (strcmp(a, b) == 0) : (a) == (b)) + +/* Compare a field that is an inline array */ +#define COMPARE_ARRAY_FIELD(fldname) \ + do { \ + if (memcmp(a->fldname, b->fldname, sizeof(a->fldname)) != 0) \ + return false; \ + } while (0) + +/* Compare a field that is a pointer to a simple palloc'd object of size sz */ +#define COMPARE_POINTER_FIELD(fldname, sz) \ + do { \ + if (memcmp(a->fldname, b->fldname, (sz)) != 0) \ + return false; \ + } while (0) + +/* Compare a parse location field (this is a no-op, per note above) */ +#define COMPARE_LOCATION_FIELD(fldname) \ + ((void) 0) + +/* Compare a CoercionForm field (also a no-op, per comment in primnodes.h) */ +#define COMPARE_COERCIONFORM_FIELD(fldname) \ + ((void) 0) + +/* + * Logging COMPARE macros for equalfuncs — log field name and node type on mismatch. + * Used by generated pltsql_equalfuncs_gen.c for detailed diff reporting. + */ +#define COMPARE_SCALAR_FIELD_LOG(fldname, nodename) \ + do { \ + if (a->fldname != b->fldname) { \ + elog(LOG, "pltsql_equal: %s.%s differs (a=%d, b=%d)", \ + nodename, CppAsString(fldname), (int)(a->fldname), (int)(b->fldname)); \ + return false; \ + } \ + } while (0) + +#define COMPARE_STRING_FIELD_LOG(fldname, nodename) \ + do { \ + if (!equalstr(a->fldname, b->fldname)) { \ + elog(LOG, "pltsql_equal: %s.%s differs (a=%s, b=%s)", \ + nodename, CppAsString(fldname), \ + a->fldname ? a->fldname : "", \ + b->fldname ? b->fldname : ""); \ + return false; \ + } \ + } while (0) + +#define COMPARE_NODE_FIELD_LOG(fldname, nodename) \ + do { \ + if (!pltsql_equal_nodes_or_equal(a->fldname, b->fldname)) { \ + elog(LOG, "pltsql_equal: %s.%s (node field) differs", \ + nodename, CppAsString(fldname)); \ + return false; \ + } \ + } while (0) + +#endif /* PLTSQL_SERIALIZE_MACROS_H */ diff --git a/contrib/babelfishpg_tsql/src/tsqlIface.cpp b/contrib/babelfishpg_tsql/src/tsqlIface.cpp index 65c67f0252d..f900031dcef 100644 --- a/contrib/babelfishpg_tsql/src/tsqlIface.cpp +++ b/contrib/babelfishpg_tsql/src/tsqlIface.cpp @@ -4467,7 +4467,7 @@ handleBatchLevelStatement(TSqlParser::Batch_level_statementContext *ctx, tsqlSel // batch-level statment can be inputted in SQL batch only (by inline_handler) or has empty body. getLineNo() will not be affected by uninitialized pltsql_curr_compile_body_lineno. Assert(pltsql_curr_compile->fn_oid == InvalidOid || ctx->SEMI()); - PLtsql_stmt_block *result = (PLtsql_stmt_block *) palloc0(sizeof(*result)); + PLtsql_stmt_block *result = makeNode(PLtsql_stmt_block); result->cmd_type = PLTSQL_STMT_BLOCK; result->lineno = getLineNo(ctx); result->label = NULL; @@ -4476,7 +4476,7 @@ handleBatchLevelStatement(TSqlParser::Batch_level_statementContext *ctx, tsqlSel result->initvarnos = nullptr; result->exceptions = nullptr; - PLtsql_stmt_init *init = (PLtsql_stmt_init *) palloc0(sizeof(*init)); + PLtsql_stmt_init *init = (PLtsql_stmt_init *) makeNode(PLtsql_stmt_init); init->cmd_type = PLTSQL_STMT_INIT; init->lineno = getLineNo(ctx); init->label = NULL; @@ -4525,7 +4525,7 @@ handleBatchLevelStatement(TSqlParser::Batch_level_statementContext *ctx, tsqlSel bool handleITVFBody(TSqlParser::Func_body_return_select_bodyContext *ctx) { - PLtsql_stmt_block *result = (PLtsql_stmt_block *) palloc0(sizeof(*result)); + PLtsql_stmt_block *result = makeNode(PLtsql_stmt_block); result->cmd_type = PLTSQL_STMT_BLOCK; result->lineno = getLineNo(ctx); result->label = NULL; @@ -4941,7 +4941,7 @@ toDotRecursive(ParseTree *t, const std::vector &ruleNames, const st PLtsql_stmt * makeExecSql(ParserRuleContext *ctx) { - PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) palloc0(sizeof(*stmt)); + PLtsql_stmt_execsql *stmt = makeNode(PLtsql_stmt_execsql); stmt->cmd_type = PLTSQL_STMT_EXECSQL; stmt->lineno = getLineNo(ctx); @@ -4959,7 +4959,7 @@ makeExecSql(ParserRuleContext *ctx) PLtsql_expr * makeTsqlExpr(const std::string &fragment, bool addSelect) { - PLtsql_expr *result = (PLtsql_expr *) palloc0(sizeof(*result)); + PLtsql_expr *result = makeNode(PLtsql_expr); if (addSelect) result->query = pstrdup((fragment_SELECT_prefix + delimitIfAtAtUserVarName(fragment)).c_str()); @@ -5195,14 +5195,14 @@ makeBatch(TSqlParser::Block_statementContext *ctx, tsqlBuilder &tsql) { breakHere(); - PLtsql_stmt_block *result = (PLtsql_stmt_block *) palloc0(sizeof(*result)); + PLtsql_stmt_block *result = makeNode(PLtsql_stmt_block); result->cmd_type = PLTSQL_STMT_BLOCK; result->lineno = getLineNo(ctx); result->label = NULL; result->body = NIL; - PLtsql_stmt_init *init = (PLtsql_stmt_init *) palloc0(sizeof(*init)); + PLtsql_stmt_init *init = (PLtsql_stmt_init *) makeNode(PLtsql_stmt_init); init->cmd_type = PLTSQL_STMT_INIT; init->lineno = getLineNo(ctx); @@ -5233,14 +5233,14 @@ makeBatch(TSqlParser::Tsql_fileContext *ctx, tsqlBuilder &builder) { breakHere(); - PLtsql_stmt_block *result = (PLtsql_stmt_block *) palloc0(sizeof(*result)); + PLtsql_stmt_block *result = makeNode(PLtsql_stmt_block); result->cmd_type = PLTSQL_STMT_BLOCK; result->lineno = getLineNo(ctx); result->label = NULL; result->body = NIL; - PLtsql_stmt_init *init = (PLtsql_stmt_init *) palloc0(sizeof(*init)); + PLtsql_stmt_init *init = (PLtsql_stmt_init *) makeNode(PLtsql_stmt_init); init->cmd_type = PLTSQL_STMT_INIT; init->lineno = getLineNo(ctx); @@ -5286,7 +5286,7 @@ makeBatch(TSqlParser::Tsql_fileContext *ctx, tsqlBuilder &builder) void * makeBlockStmt(ParserRuleContext *ctx, tsqlBuilder &builder) { - PLtsql_stmt_block *result = (PLtsql_stmt_block *) palloc0(sizeof(*result)); + PLtsql_stmt_block *result = makeNode(PLtsql_stmt_block); result->cmd_type = PLTSQL_STMT_BLOCK; result->lineno = getLineNo(ctx); @@ -5302,7 +5302,7 @@ makeBlockStmt(ParserRuleContext *ctx, tsqlBuilder &builder) PLtsql_stmt_block * makeEmptyBlockStmt(int lineno) { - PLtsql_stmt_block *result = (PLtsql_stmt_block *) palloc0(sizeof(*result)); + PLtsql_stmt_block *result = makeNode(PLtsql_stmt_block); result->cmd_type = PLTSQL_STMT_BLOCK; result->lineno = lineno; @@ -5320,7 +5320,7 @@ makeBreakStmt(TSqlParser::Break_statementContext *ctx) { PLtsql_stmt_exit *result; - result = (PLtsql_stmt_exit *) palloc0(sizeof(*result)); + result = makeNode(PLtsql_stmt_exit); result->cmd_type = PLTSQL_STMT_EXIT; result->is_exit = true; @@ -5336,7 +5336,7 @@ makeContinueStmt(TSqlParser::Continue_statementContext *ctx) { PLtsql_stmt_exit *result; - result = (PLtsql_stmt_exit *) palloc0(sizeof(*result)); + result = makeNode(PLtsql_stmt_exit); result->cmd_type = PLTSQL_STMT_EXIT; result->is_exit = false; @@ -5362,7 +5362,7 @@ makeGotoStmt(TSqlParser::Goto_statementContext *ctx) if (ctx->GOTO() == nullptr) { // This is a statement label - PLtsql_stmt_label *result = (PLtsql_stmt_label *) palloc0(sizeof(*result)); + PLtsql_stmt_label *result = makeNode(PLtsql_stmt_label); result->cmd_type = PLTSQL_STMT_LABEL; result->lineno = getLineNo(ctx); @@ -5374,7 +5374,7 @@ makeGotoStmt(TSqlParser::Goto_statementContext *ctx) else { // This is a GOTO statement - PLtsql_stmt_goto *result = (PLtsql_stmt_goto *) palloc0(sizeof(*result)); + PLtsql_stmt_goto *result = makeNode(PLtsql_stmt_goto); result->cmd_type = PLTSQL_STMT_GOTO; result->lineno = getLineNo(ctx); @@ -5392,7 +5392,7 @@ makeIfStmt(TSqlParser::If_statementContext *ctx) { // IF search_condition sql_clauses (ELSE sql_clauses)? ';'? - PLtsql_stmt_if *result = (PLtsql_stmt_if *) palloc0(sizeof(*result)); + PLtsql_stmt_if *result = makeNode(PLtsql_stmt_if); result->cmd_type = PLTSQL_STMT_IF; result->lineno = getLineNo(ctx); @@ -5410,7 +5410,7 @@ makeIfStmt(TSqlParser::If_statementContext *ctx) void * makeReturnStmt(TSqlParser::Return_statementContext *ctx) { - PLtsql_stmt_return *result = (PLtsql_stmt_return *) palloc0(sizeof(*result)); + PLtsql_stmt_return *result = makeNode(PLtsql_stmt_return); result->cmd_type = PLTSQL_STMT_RETURN; result->lineno = getLineNo(ctx); @@ -5448,7 +5448,7 @@ makeReturnStmt(TSqlParser::Return_statementContext *ctx) void * makeReturnQueryStmt(TSqlParser::Select_statement_standaloneContext *ctx, bool itvf) { - PLtsql_stmt_return_query *result = (PLtsql_stmt_return_query *) palloc0(sizeof(*result)); + PLtsql_stmt_return_query *result = (PLtsql_stmt_return_query *) makeNode(PLtsql_stmt_return_query); result->cmd_type = PLTSQL_STMT_RETURN_QUERY; result->lineno =getLineNo(ctx); @@ -5519,7 +5519,7 @@ makeReturnQueryStmt(TSqlParser::Select_statement_standaloneContext *ctx, bool it void * makeThrowStmt(TSqlParser::Throw_statementContext *ctx) { - PLtsql_stmt_throw *result = (PLtsql_stmt_throw *) palloc0(sizeof(*result)); + PLtsql_stmt_throw *result = (PLtsql_stmt_throw *) makeNode(PLtsql_stmt_throw); result->cmd_type = PLTSQL_STMT_THROW; result->lineno = getLineNo(ctx); @@ -5538,7 +5538,7 @@ makeThrowStmt(TSqlParser::Throw_statementContext *ctx) void * makeTryCatchStmt(TSqlParser::Try_catch_statementContext *ctx) { - PLtsql_stmt_try_catch *result = (PLtsql_stmt_try_catch *) palloc0(sizeof(*result)); + PLtsql_stmt_try_catch *result = makeNode(PLtsql_stmt_try_catch); result->cmd_type = PLTSQL_STMT_TRY_CATCH; result->lineno = getLineNo(ctx); @@ -5558,7 +5558,7 @@ makeWaitForStmt(TSqlParser::Waitfor_statementContext *ctx) void * makeWhileStmt(TSqlParser::While_statementContext *ctx) { - PLtsql_stmt_while *result = (PLtsql_stmt_while *) palloc0(sizeof(*result)); + PLtsql_stmt_while *result = makeNode(PLtsql_stmt_while); result->cmd_type = PLTSQL_STMT_WHILE; /* We will populate result->cond during exitSearch_condition() */ @@ -5569,7 +5569,7 @@ makeWhileStmt(TSqlParser::While_statementContext *ctx) void * makePrintStmt(TSqlParser::Print_statementContext *ctx) { - PLtsql_stmt_print *result = (PLtsql_stmt_print *) palloc0(sizeof(*result)); + PLtsql_stmt_print *result = makeNode(PLtsql_stmt_print); result->cmd_type = PLTSQL_STMT_PRINT; result->exprs = list_make1(makeTsqlExpr(ctx->expression(), true)); @@ -5581,7 +5581,7 @@ makePrintStmt(TSqlParser::Print_statementContext *ctx) void * makeRaiseErrorStmt(TSqlParser::Raiseerror_statementContext *ctx) { - PLtsql_stmt_raiserror *result = (PLtsql_stmt_raiserror *) palloc0(sizeof(*result)); + PLtsql_stmt_raiserror *result = (PLtsql_stmt_raiserror *) makeNode(PLtsql_stmt_raiserror); result->cmd_type = PLTSQL_STMT_RAISERROR; result->lineno = getLineNo(ctx); @@ -5640,7 +5640,7 @@ makeRaiseErrorStmt(TSqlParser::Raiseerror_statementContext *ctx) PLtsql_stmt * makeInitializer(int varno, int lineno, TSqlParser::ExpressionContext *val) { - PLtsql_stmt_assign *result = (PLtsql_stmt_assign *) palloc0(sizeof(*result)); + PLtsql_stmt_assign *result = makeNode(PLtsql_stmt_assign); result->cmd_type = PLTSQL_STMT_ASSIGN; result->lineno = lineno; @@ -5743,7 +5743,7 @@ makeDeclTableStmt(PLtsql_variable *var, PLtsql_type *type, int lineno) { Assert(var->dtype == PLTSQL_DTYPE_TBL); - PLtsql_stmt_decl_table *result = (PLtsql_stmt_decl_table *) palloc0(sizeof(*result)); + PLtsql_stmt_decl_table *result = (PLtsql_stmt_decl_table *) makeNode(PLtsql_stmt_decl_table); result->cmd_type = PLTSQL_STMT_DECL_TABLE; result->lineno = lineno; result->dno = var->dno; @@ -5930,7 +5930,7 @@ makeSetStatement(TSqlParser::Set_statementContext *ctx, tsqlBuilder &builder) } else if (ctx->CURSOR()) { - PLtsql_stmt_assign *result = (PLtsql_stmt_assign *) palloc0(sizeof(*result)); + PLtsql_stmt_assign *result = makeNode(PLtsql_stmt_assign); result->cmd_type = PLTSQL_STMT_ASSIGN; result->lineno = getLineNo(ctx); @@ -5981,7 +5981,7 @@ makeSetStatement(TSqlParser::Set_statementContext *ctx, tsqlBuilder &builder) if (set_special_ctx->set_on_off_option().size() > 1) { - PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) palloc0(sizeof(PLtsql_stmt_execsql)); + PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) makeNode(PLtsql_stmt_execsql); std::string query; for (auto option : set_special_ctx->set_on_off_option()) { @@ -6055,7 +6055,7 @@ makeSetStatement(TSqlParser::Set_statementContext *ctx, tsqlBuilder &builder) if (pg_strncasecmp(param.c_str(), "NULL", 4) == 0 || param.length() == 0 || (pg_strncasecmp(param.c_str(), "0x", 2) == 0 && param.length() - 2 > 256)) throw PGErrorWrapperException(ERROR, ERRCODE_INVALID_PARAMETER_VALUE, "SET CONTEXT_INFO option requires varbinary (128) NOT NULL parameter.", getLineAndPos(set_special_ctx->constant_LOCAL_ID())); - PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) palloc0(sizeof(PLtsql_stmt_execsql)); + PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) makeNode(PLtsql_stmt_execsql); std::string query; query += "CALL sys.bbf_set_context_info(convert(varbinary(128), "; query += param; @@ -6132,7 +6132,7 @@ makeSetStatement(TSqlParser::Set_statementContext *ctx, tsqlBuilder &builder) /* build target variable for this GUC, so that in backend we can identify that target is GUC */ PLtsql_var *target_var = build_babelfish_guc_variable(guc_ctx); /* assign expression to target */ - PLtsql_stmt_assign *result = (PLtsql_stmt_assign *) palloc0(sizeof(*result)); + PLtsql_stmt_assign *result = makeNode(PLtsql_stmt_assign); result->cmd_type = PLTSQL_STMT_ASSIGN; result->lineno = getLineNo(ctx); result->varno = target_var->dno; @@ -6160,7 +6160,7 @@ makeSetExplainModeStatement(TSqlParser::Set_statementContext *ctx, bool is_expla if (!set_special_ctx) return nullptr; - stmt = (PLtsql_stmt_set_explain_mode *) palloc0(sizeof(PLtsql_stmt_set_explain_mode)); + stmt = (PLtsql_stmt_set_explain_mode *) makeNode(PLtsql_stmt_set_explain_mode); on_off = getFullText(set_special_ctx->on_off()); len = on_off.length(); @@ -6192,7 +6192,7 @@ makeSetExplainModeStatement(TSqlParser::Set_statementContext *ctx, bool is_expla PLtsql_stmt * makeInsertBulkStatement(TSqlParser::Dml_statementContext *ctx) { - PLtsql_stmt_insert_bulk *stmt = (PLtsql_stmt_insert_bulk *) palloc0(sizeof(*stmt)); + PLtsql_stmt_insert_bulk *stmt = (PLtsql_stmt_insert_bulk *) makeNode(PLtsql_stmt_insert_bulk); TSqlParser::Bulk_insert_statementContext *bulk_ctx = ctx->bulk_insert_statement(); std::vector column_list = bulk_ctx->insert_bulk_column_definition(); std::vector option_list = bulk_ctx->bulk_insert_option(); @@ -6309,7 +6309,7 @@ makeExecuteStatement(TSqlParser::Execute_statementContext *ctx) if (body->LR_BRACKET()) /* execute a character string */ { - PLtsql_stmt_exec_batch *result = (PLtsql_stmt_exec_batch *) palloc0(sizeof(*result)); + PLtsql_stmt_exec_batch *result = (PLtsql_stmt_exec_batch *) makeNode(PLtsql_stmt_exec_batch); result->cmd_type = PLTSQL_STMT_EXEC_BATCH; result->lineno = getLineNo(ctx); @@ -6397,7 +6397,7 @@ makeDeclareCursorStatement(TSqlParser::Declare_cursorContext *ctx) curvar->isconst = true; } - PLtsql_stmt_decl_cursor *result = (PLtsql_stmt_decl_cursor *) palloc0(sizeof(PLtsql_stmt_decl_cursor)); + PLtsql_stmt_decl_cursor *result = (PLtsql_stmt_decl_cursor *) makeNode(PLtsql_stmt_decl_cursor); result->cmd_type = PLTSQL_STMT_DECL_CURSOR; result->lineno = getLineNo(ctx); result->curvar = curvar->dno; @@ -6415,7 +6415,7 @@ makeOpenCursorStatement(TSqlParser::Cursor_statementContext *ctx) if (ctx->GLOBAL()) throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "GLOBAL CURSOR is not supported yet", getLineAndPos(ctx->GLOBAL())); - PLtsql_stmt_open *result = (PLtsql_stmt_open *) palloc0(sizeof(PLtsql_stmt_open)); + PLtsql_stmt_open *result = (PLtsql_stmt_open *) makeNode(PLtsql_stmt_open); result->cmd_type = PLTSQL_STMT_OPEN; result->lineno = getLineNo(ctx); result->curvar = -1; @@ -6430,7 +6430,7 @@ makeOpenCursorStatement(TSqlParser::Cursor_statementContext *ctx) PLtsql_stmt * makeFetchCursorStatement(TSqlParser::Fetch_cursorContext *ctx) { - PLtsql_stmt_fetch *result = (PLtsql_stmt_fetch *) palloc(sizeof(PLtsql_stmt_fetch)); + PLtsql_stmt_fetch *result = (PLtsql_stmt_fetch *) makeNode(PLtsql_stmt_fetch); result->cmd_type = PLTSQL_STMT_FETCH; result->lineno = getLineNo(ctx); result->target = NULL; @@ -6481,7 +6481,7 @@ makeFetchCursorStatement(TSqlParser::Fetch_cursorContext *ctx) if (localIDs.size() > 1024) throw PGErrorWrapperException(ERROR, ERRCODE_PROGRAM_LIMIT_EXCEEDED, "too many INTO variables specified", getLineAndPos(ctx->LOCAL_ID()[0])); - PLtsql_row *row = (PLtsql_row *) palloc(sizeof(PLtsql_row)); + PLtsql_row *row = (PLtsql_row *) makeNode(PLtsql_row); row->dtype = PLTSQL_DTYPE_ROW; row->refname = pstrdup("*internal*"); row->lineno = getLineNo(ctx); @@ -6530,7 +6530,7 @@ makeCloseCursorStatement(TSqlParser::Cursor_statementContext *ctx) if (ctx->GLOBAL()) throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "GLOBAL CURSOR is not supported yet", getLineAndPos(ctx->GLOBAL())); - PLtsql_stmt_close *result = (PLtsql_stmt_close *) palloc0(sizeof(PLtsql_stmt_close)); + PLtsql_stmt_close *result = (PLtsql_stmt_close *) makeNode(PLtsql_stmt_close); result->cmd_type = PLTSQL_STMT_CLOSE; result->lineno = getLineNo(ctx); result->curvar = -1; @@ -6547,7 +6547,7 @@ makeDeallocateCursorStatement(TSqlParser::Cursor_statementContext *ctx) if (ctx->GLOBAL()) throw PGErrorWrapperException(ERROR, ERRCODE_FEATURE_NOT_SUPPORTED, "GLOBAL CURSOR is not supported yet", getLineAndPos(ctx->GLOBAL())); - PLtsql_stmt_deallocate *result = (PLtsql_stmt_deallocate *) palloc0(sizeof(PLtsql_stmt_deallocate)); + PLtsql_stmt_deallocate *result = makeNode(PLtsql_stmt_deallocate); result->cmd_type = PLTSQL_STMT_DEALLOCATE; result->lineno = getLineNo(ctx);; result->curvar = -1; @@ -6579,7 +6579,7 @@ makeCursorStatement(TSqlParser::Cursor_statementContext *ctx) PLtsql_stmt * makeUseStatement(TSqlParser::Use_statementContext *ctx) { - PLtsql_stmt_usedb *result = (PLtsql_stmt_usedb *) palloc0(sizeof(PLtsql_stmt_usedb)); + PLtsql_stmt_usedb *result = (PLtsql_stmt_usedb *) makeNode(PLtsql_stmt_usedb); result->cmd_type = PLTSQL_STMT_USEDB; result->lineno = getLineNo(ctx); @@ -6599,7 +6599,7 @@ makeUseStatement(TSqlParser::Use_statementContext *ctx) PLtsql_stmt * makeKillStatement(TSqlParser::Kill_statementContext *ctx) { - PLtsql_stmt_kill *result = (PLtsql_stmt_kill *) palloc0(sizeof(*result)); + PLtsql_stmt_kill *result = (PLtsql_stmt_kill *) makeNode(PLtsql_stmt_kill); result->cmd_type = PLTSQL_STMT_KILL; result->lineno = getLineNo(ctx); @@ -6630,7 +6630,7 @@ makeGrantdbStatement(TSqlParser::Security_statementContext *ctx) auto single_perm = perm->single_permission(); if (single_perm->CONNECT()) { - PLtsql_stmt_grantdb *result = (PLtsql_stmt_grantdb *) palloc0(sizeof(PLtsql_stmt_grantdb)); + PLtsql_stmt_grantdb *result = makeNode(PLtsql_stmt_grantdb); result->cmd_type = PLTSQL_STMT_GRANTDB; result->lineno = getLineNo(grant); result->is_grant = true; @@ -6658,7 +6658,7 @@ makeGrantdbStatement(TSqlParser::Security_statementContext *ctx) { if (grant->principals() && grant->permissions()) { - PLtsql_stmt_grantschema *result = (PLtsql_stmt_grantschema *) palloc0(sizeof(PLtsql_stmt_grantschema)); + PLtsql_stmt_grantschema *result = makeNode(PLtsql_stmt_grantschema); result->cmd_type = PLTSQL_STMT_GRANTSCHEMA; result->lineno = getLineNo(grant); result->is_grant = true; @@ -6723,7 +6723,7 @@ makeGrantdbStatement(TSqlParser::Security_statementContext *ctx) auto single_perm = perm->single_permission(); if (single_perm->CONNECT()) { - PLtsql_stmt_grantdb *result = (PLtsql_stmt_grantdb *) palloc0(sizeof(PLtsql_stmt_grantdb)); + PLtsql_stmt_grantdb *result = makeNode(PLtsql_stmt_grantdb); result->cmd_type = PLTSQL_STMT_GRANTDB; result->lineno = getLineNo(revoke); result->is_grant = false; @@ -6753,7 +6753,7 @@ makeGrantdbStatement(TSqlParser::Security_statementContext *ctx) { if (revoke->principals() && revoke->permissions()) { - PLtsql_stmt_grantschema *result = (PLtsql_stmt_grantschema *) palloc0(sizeof(PLtsql_stmt_grantschema)); + PLtsql_stmt_grantschema *result = makeNode(PLtsql_stmt_grantschema); result->cmd_type = PLTSQL_STMT_GRANTSCHEMA; result->lineno = getLineNo(revoke); result->is_grant = false; @@ -6819,7 +6819,7 @@ makeTransactionStatement(TSqlParser::Transaction_statementContext *ctx) PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) result; - stmt->txn_data = (PLtsql_txn_data *) palloc0(sizeof(PLtsql_txn_data)); + stmt->txn_data = (PLtsql_txn_data *) makeNode(PLtsql_txn_data); auto *localID = ctx->local_id(); if (localID) { @@ -7032,7 +7032,7 @@ makeExecuteProcedure(ParserRuleContext *ctx, std::string call_type) } // Build the statement - PLtsql_stmt_exec *result = (PLtsql_stmt_exec *) palloc0(sizeof(*result)); + PLtsql_stmt_exec *result = (PLtsql_stmt_exec *) makeNode(PLtsql_stmt_exec); result->cmd_type = PLTSQL_STMT_EXEC; result->lineno = lineno; result->is_call = true; @@ -7137,7 +7137,7 @@ makeExecuteProcedure(ParserRuleContext *ctx, std::string call_type) PLtsql_stmt* makeDbccCheckidentStatement(TSqlParser::Dbcc_statementContext *ctx) { - PLtsql_stmt_dbcc *stmt = (PLtsql_stmt_dbcc *) palloc0(sizeof(*stmt)); + PLtsql_stmt_dbcc *stmt = makeNode(PLtsql_stmt_dbcc); std::string new_reseed_value; std::string input_str; @@ -7259,7 +7259,7 @@ PLtsql_row * create_select_target_row(const char *refname, size_t nfields, int lineno) { /* prepare target if it is not ready */ - PLtsql_row *target = (PLtsql_row *) palloc0(sizeof(*target)); + PLtsql_row *target = makeNode(PLtsql_row); target->dtype = PLTSQL_DTYPE_ROW; target->refname = (char *) refname; target->lineno = lineno; @@ -7971,7 +7971,7 @@ getDatabaseSchemaAndTableName(TSqlParser::Table_nameContext* tctx) PLtsql_stmt * makeCreatePartitionFunction(TSqlParser::Create_partition_functionContext *ctx) { - PLtsql_stmt_partition_function *stmt = (PLtsql_stmt_partition_function *) palloc(sizeof(PLtsql_stmt_partition_function)); + PLtsql_stmt_partition_function *stmt = (PLtsql_stmt_partition_function *) makeNode(PLtsql_stmt_partition_function); std::string typeStr = ::getFullText(ctx->data_type()); PLtsql_type *type = parse_datatype(typeStr.c_str(), 0); @@ -8005,7 +8005,7 @@ return (PLtsql_stmt *) stmt; PLtsql_stmt * makeDropPartitionFunction(TSqlParser::Drop_partition_functionContext *ctx) { - PLtsql_stmt_partition_function *stmt = (PLtsql_stmt_partition_function *) palloc(sizeof(PLtsql_stmt_partition_function)); + PLtsql_stmt_partition_function *stmt = (PLtsql_stmt_partition_function *) makeNode(PLtsql_stmt_partition_function); stmt->function_name = pstrdup(stripQuoteFromId(ctx->id()).c_str()); stmt->lineno = getLineNo(ctx); stmt->cmd_type = PLTSQL_STMT_PARTITION_FUNCTION; @@ -8018,7 +8018,7 @@ makeDropPartitionFunction(TSqlParser::Drop_partition_functionContext *ctx) PLtsql_stmt * makeCreatePartitionScheme(TSqlParser::Create_partition_schemeContext *ctx) { - PLtsql_stmt_partition_scheme *stmt = (PLtsql_stmt_partition_scheme *) palloc(sizeof(PLtsql_stmt_partition_scheme)); + PLtsql_stmt_partition_scheme *stmt = (PLtsql_stmt_partition_scheme *) makeNode(PLtsql_stmt_partition_scheme); stmt->scheme_name = pstrdup(stripQuoteFromId(ctx->id()[0]).c_str()); stmt->function_name = pstrdup(stripQuoteFromId(ctx->id()[1]).c_str()); stmt->is_create = true; @@ -8037,7 +8037,7 @@ makeCreatePartitionScheme(TSqlParser::Create_partition_schemeContext *ctx) PLtsql_stmt * makeDropPartitionScheme(TSqlParser::Drop_partition_schemeContext *ctx) { - PLtsql_stmt_partition_scheme *stmt = (PLtsql_stmt_partition_scheme *) palloc(sizeof(PLtsql_stmt_partition_scheme)); + PLtsql_stmt_partition_scheme *stmt = (PLtsql_stmt_partition_scheme *) makeNode(PLtsql_stmt_partition_scheme); stmt->is_create = false; stmt->scheme_name = pstrdup(stripQuoteFromId(ctx->id()).c_str()); stmt->lineno = getLineNo(ctx); @@ -8050,7 +8050,7 @@ makeDropPartitionScheme(TSqlParser::Drop_partition_schemeContext *ctx) PLtsql_stmt * makeCreateFulltextIndexStmt(TSqlParser::Create_fulltext_indexContext *ctx) { - PLtsql_stmt_fulltextindex *stmt = (PLtsql_stmt_fulltextindex *) palloc0(sizeof(PLtsql_stmt_fulltextindex)); + PLtsql_stmt_fulltextindex *stmt = (PLtsql_stmt_fulltextindex *) makeNode(PLtsql_stmt_fulltextindex); stmt->cmd_type = PLTSQL_STMT_FULLTEXTINDEX; stmt->lineno = getLineNo(ctx); stmt->is_create = true; @@ -8098,7 +8098,7 @@ makeCreateFulltextIndexStmt(TSqlParser::Create_fulltext_indexContext *ctx) PLtsql_stmt * makeDropFulltextIndexStmt(TSqlParser::Drop_fulltext_indexContext *ctx) { - PLtsql_stmt_fulltextindex *stmt = (PLtsql_stmt_fulltextindex *) palloc0(sizeof(PLtsql_stmt_fulltextindex)); + PLtsql_stmt_fulltextindex *stmt = (PLtsql_stmt_fulltextindex *) makeNode(PLtsql_stmt_fulltextindex); stmt->cmd_type = PLTSQL_STMT_FULLTEXTINDEX; stmt->lineno = getLineNo(ctx); stmt->is_create = false; @@ -8318,7 +8318,7 @@ build_cursor_variable(const char *curname, int lineno) StringInfoData ds; initStringInfo(&ds); char *cp1; - PLtsql_expr *curname_def = (PLtsql_expr *) palloc0(sizeof(PLtsql_expr)); + PLtsql_expr *curname_def = makeNode(PLtsql_expr); appendStringInfo(&ds, "SELECT "); cp1 = curvar->refname; /* @@ -8406,7 +8406,7 @@ makeSpStatement(const std::string& name_str, TSqlParser::Execute_statement_argCo std::vector params; - PLtsql_stmt_exec_sp *result = (PLtsql_stmt_exec_sp *) palloc0(sizeof(*result)); + PLtsql_stmt_exec_sp *result = (PLtsql_stmt_exec_sp *) makeNode(PLtsql_stmt_exec_sp); result->cmd_type = PLTSQL_STMT_EXEC_SP; result->lineno = lineno; result->return_code_dno = return_code_dno; @@ -8621,7 +8621,7 @@ makeSpParam(TSqlParser::Execute_statement_arg_namedContext *ctx) TSqlParser::Execute_parameterContext *exec_param = ctx->execute_parameter(); Assert(exec_param && ctx->local_id()); - tsql_exec_param *p = (tsql_exec_param *) palloc0(sizeof(*p)); + tsql_exec_param *p = (tsql_exec_param *) makeNode(tsql_exec_param); std::string targetText = ::getFullText(ctx->local_id()); p->name = pstrdup(targetText.c_str()); p->varno = -1; @@ -8648,7 +8648,7 @@ makeSpParam(TSqlParser::Execute_statement_arg_unnamedContext *ctx) TSqlParser::Execute_parameterContext *exec_param = ctx->execute_parameter(); Assert(exec_param); - tsql_exec_param *p = (tsql_exec_param *) palloc0(sizeof(*p)); + tsql_exec_param *p = (tsql_exec_param *) makeNode(tsql_exec_param); p->name = NULL; p->varno = -1; p->mode = FUNC_PARAM_IN; @@ -9716,7 +9716,7 @@ escapeDoubleQuotes(const std::string strWithDoubleQuote) PLtsql_stmt * makeChangeDbOwnerStatement(TSqlParser::Alter_authorizationContext *ctx) { - PLtsql_stmt_change_dbowner *result = (PLtsql_stmt_change_dbowner *) palloc0(sizeof(*result)); + PLtsql_stmt_change_dbowner *result = makeNode(PLtsql_stmt_change_dbowner); result->cmd_type = PLTSQL_STMT_CHANGE_DBOWNER; result->lineno = getLineNo(ctx); @@ -9735,7 +9735,7 @@ makeChangeDbOwnerStatement(TSqlParser::Alter_authorizationContext *ctx) static PLtsql_stmt * makeAlterDatabaseStatement(TSqlParser::Alter_databaseContext *ctx) { - PLtsql_stmt_alter_db *result = (PLtsql_stmt_alter_db *) palloc0(sizeof(*result)); + PLtsql_stmt_alter_db *result = makeNode(PLtsql_stmt_alter_db); result->cmd_type = PLTSQL_STMT_ALTER_DB; result->lineno = getLineNo(ctx); diff --git a/contrib/babelfishpg_tsql/src/tsqlNodes.c b/contrib/babelfishpg_tsql/src/tsqlNodes.c index 385a20d7256..1bcf5b83987 100644 --- a/contrib/babelfishpg_tsql/src/tsqlNodes.c +++ b/contrib/babelfishpg_tsql/src/tsqlNodes.c @@ -5,7 +5,7 @@ PLtsql_expr * makeTsqlExpr(const char *fragment) { - PLtsql_expr *result = (PLtsql_expr *) palloc0(sizeof(*result)); + PLtsql_expr *result = makeNode(PLtsql_expr); result ->query = pstrdup(fragment); result ->plan = NULL; @@ -20,7 +20,7 @@ makeTsqlExpr(const char *fragment) PLtsql_stmt_while * makeWhileStmt(PLtsql_expr *cond) { - PLtsql_stmt_while *result = (PLtsql_stmt_while *) palloc0(sizeof(*result)); + PLtsql_stmt_while *result = (PLtsql_stmt_while *) makeNode(PLtsql_stmt_while); result ->cmd_type = PLTSQL_STMT_WHILE; result ->cond = cond; @@ -32,7 +32,7 @@ PLtsql_stmt_print * makePrintStmt(PLtsql_expr *expr) { - PLtsql_stmt_print *result = (PLtsql_stmt_print *) palloc0(sizeof(*result)); + PLtsql_stmt_print *result = (PLtsql_stmt_print *) makeNode(PLtsql_stmt_print); result ->cmd_type = PLTSQL_STMT_PRINT; result ->exprs = list_make1(expr); diff --git a/test/JDBC/expected/BABEL-6037-vu-cleanup.out b/test/JDBC/expected/BABEL-6037-vu-cleanup.out new file mode 100644 index 00000000000..70033db2428 --- /dev/null +++ b/test/JDBC/expected/BABEL-6037-vu-cleanup.out @@ -0,0 +1,93 @@ + +-- BABEL-6037 POC Test - Cleanup +-- Drops all test procedures created during testing +PRINT 'Starting cleanup of BABEL-6037 POC test procedures...'; +GO + +-- Drop Test 1: Simple procedure without arguments +DROP PROCEDURE IF EXISTS dbo.small_proc; +PRINT 'Dropped: dbo.small_proc'; +GO + +-- Drop Test 2: Simple procedure with arguments +DROP PROCEDURE IF EXISTS dbo.small_proc_param; +PRINT 'Dropped: dbo.small_proc_param'; +GO + +-- Drop Test 3: Procedure with unsupported node type +DROP PROCEDURE IF EXISTS dbo.proc_param_supported; +PRINT 'Dropped: dbo.proc_with_unsupported'; +GO + +-- Drop Test 4: Procedure with unsupported node type +DROP PROCEDURE IF EXISTS dbo.proc_param_unsupported; +PRINT 'Dropped: dbo.proc_with_unsupported'; +GO + +-- Drop Test 5: Complex procedure +DROP PROCEDURE IF EXISTS dbo.complex_proc; +PRINT 'Dropped: dbo.complex_proc'; +GO + +-- Drop Test 6: Same-session procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.samesession_proc; +PRINT 'Dropped: dbo.samesession_proc'; +GO + +-- Drop Test 7: Old-session procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.oldsession_proc; +PRINT 'Dropped: dbo.newsession_proc'; +GO + +-- Drop Test 8: renamed_cache_proc procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.rename_cache_proc; +GO +DROP PROCEDURE IF EXISTS dbo.renamed_cache_proc; +GO + +-- Drop Test 9: alter_guc_proc procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.alter_guc_proc; +GO + +-- Drop Test 10: dep_table_proc procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.dep_table_proc; +GO +DROP TABLE IF EXISTS dbo.dep_test_table; +GO + +-- Drop Test 11: Procedure with single OUT parameter +DROP PROCEDURE IF EXISTS dbo.babel_6037_out_single; +GO + +-- Drop Test 12: Procedure with multiple OUT parameters +DROP PROCEDURE IF EXISTS dbo.babel_6037_out_multi; +GO + +-- Drop Test 13: MSTVF +DROP FUNCTION IF EXISTS dbo.babel_6037_mstvf; +GO + +-- Per-function cache control test procedures +DROP PROCEDURE IF EXISTS dbo.perfunc_cache_sig; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_cache_name; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_guc_override; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_alter_test; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_drop_test; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_default_test; +GO +DROP PROCEDURE IF EXISTS test_cache_schema.perfunc_custom_schema; +GO +DROP SCHEMA IF EXISTS test_cache_schema; +GO +DROP PROCEDURE IF EXISTS dbo.validate_cache_proc; +GO +DROP PROCEDURE IF EXISTS dbo.nocache_create_proc; +GO + +PRINT 'Cleanup completed successfully'; +GO diff --git a/test/JDBC/expected/BABEL-6037-vu-prepare.out b/test/JDBC/expected/BABEL-6037-vu-prepare.out new file mode 100644 index 00000000000..f0f0ad09e10 --- /dev/null +++ b/test/JDBC/expected/BABEL-6037-vu-prepare.out @@ -0,0 +1,409 @@ + +-- BABEL-6037 +-- Creates test procedures for ANTLR parse tree serialization/deserialization +-- 1. SESSION-LEVEL GUC `babelfishpg_tsql.enable_routine_parse_cache` +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +-- Test 1: Simple procedure without arguments +-- This tests basic serialization with minimal complexity +CREATE PROCEDURE dbo.small_proc +AS +BEGIN + DECLARE @var1 INT = 1; + SELECT @var1; +END; +GO + +-- Test 2: Simple procedure with arguments (from focused revision doc) +-- This tests parameter handling and variable serialization +CREATE PROCEDURE dbo.small_proc_param + @param1 INT, + @param2 VARCHAR(50) +AS +BEGIN + DECLARE @var1 INT = 1; + SELECT @var1, @param1, @param2; + CREATE TABLE #test(id int); +END; +GO + +-- Test 3: Procedure with supported node types (PRINT, WHILE, GOTO, TRY-CATCH, CASE, BREAK, CONTINUE) +-- This tests various statement types within a BLOCK statement +CREATE PROCEDURE dbo.proc_param_supported +@input_val INT = 10 +AS +BEGIN + DECLARE @counter INT = 0; + DECLARE @result VARCHAR(100) = ''; + DECLARE @status INT = 0; + + -- Test PRINT statement + PRINT 'Starting test procedure'; + + -- Test WHILE loop with BREAK and CONTINUE + WHILE @counter < 3 + BEGIN + SET @counter = @counter + 1; + + -- Test IF with EXECSQL + IF @counter = 2 + BEGIN + SELECT @result = 'Found two'; + END + END + + -- Test WHILE with BREAK and CONTINUE + SET @counter = 0; + WHILE 1 = 1 + BEGIN + SET @counter = @counter + 1; + + -- Test BREAK statement + IF @counter > 5 + BREAK; + + -- Test CONTINUE statement + IF @counter = 3 + CONTINUE; + + SET @result = @result + CAST(@counter AS VARCHAR); + END + + -- Test CASE statement + SET @status = CASE @input_val + WHEN 10 THEN 1 + WHEN 20 THEN 2 + WHEN 30 THEN 3 + ELSE 0 + END; + + -- Test GOTO and LABEL + IF @status = 0 + GOTO skip_section; + + skip_section: + PRINT 'Skipped or continued'; + + -- Test TRY-CATCH block + BEGIN TRY + -- Test EXECSQL with potential error + DECLARE @test_val INT; + SELECT @test_val = 100 / @input_val; + + -- Test ASSIGN statement + SET @result = 'Success: ' + CAST(@test_val AS VARCHAR); + END TRY + BEGIN CATCH + -- Error handling + SET @result = 'Error handled'; + END CATCH + + -- Test RETURN statement + IF @input_val < 0 + RETURN; + + -- Final output + SELECT @result AS FinalResult, @status AS Status, @counter AS Counter; +END; +GO + + +-- Test 4: Procedure with unsupported node type (EXEC_SP / sp_executesql) +-- PLTSQL_STMT_EXEC_SP is not in pltsql_is_serializable; triggers PLTSQL_SERIAL_UNSUPPORTED +-- Each new session falls back to full ANTLR recompile instead of using persistent cache +CREATE PROCEDURE dbo.proc_param_unsupported + @max INT +AS +BEGIN + DECLARE @sql NVARCHAR(100) = N'SELECT ' + CAST(@max AS NVARCHAR); + EXEC sp_executesql @sql; +END; +GO + + + +-- Test 5: Complex procedure that may lead to serialize error +-- Tests nested blocks, multiple statement types, and IF conditions +CREATE PROCEDURE dbo.complex_proc + @input INT, + @flag BIT +AS +BEGIN + DECLARE @result INT; + DECLARE @temp VARCHAR(100); + + -- Test ASSIGN statement + SET @result = @input * 2; + + -- Test IF statement with nested block + IF @flag = 1 + BEGIN + SET @temp = 'Flag is true'; + SELECT @result, @temp; + + -- Nested temp table operations + CREATE TABLE #temp1(id INT, value VARCHAR(50)); + INSERT INTO #temp1 VALUES (@result, @temp); + SELECT * FROM #temp1; + END + ELSE + BEGIN + SET @temp = 'Flag is false'; + SELECT @result, @temp; + END; + + -- Test EXECSQL with UNPIVOT (complex SQL expression within serializable proc) + SELECT col, val + FROM (SELECT @input AS input_val, @result AS doubled_val) AS src + UNPIVOT (val FOR col IN (input_val, doubled_val)) AS unpvt; + -- Test RETURN statement + RETURN @result; +END; +GO + + +-- Test 6: Procedure for same-session testing (procedure created in verify file) +-- Test 7: Procedure for new-session testing +-- This will be used to test ALTER and DROP in new session +CREATE PROCEDURE dbo.oldsession_proc + @value VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@value AS INT); + SELECT @num AS original_value; +END; +GO + +-- Test 8: Procedure for rename + GUC off/on testing +CREATE PROCEDURE dbo.rename_cache_proc + @val INT +AS +BEGIN + SELECT @val * 10 AS result; +END; +GO + +-- Test 9: Procedure for ALTER with GUC off then EXEC with GUC on +CREATE PROCEDURE dbo.alter_guc_proc + @val INT +AS +BEGIN + SELECT @val + 1 AS incremented; +END; +GO + +-- Test 10: Table + procedure for altered dependency testing +-- Procedure references a table; dropping/recreating the table tests cache staleness +CREATE TABLE dbo.dep_test_table (id INT, name VARCHAR(50)); +GO +INSERT INTO dbo.dep_test_table VALUES (1, 'Alice'), (2, 'Bob'); +GO +~~ROW COUNT: 2~~ + + +CREATE PROCEDURE dbo.dep_table_proc +AS +BEGIN + SELECT * FROM dbo.dep_test_table; +END; +GO + +-- Test 11: Procedure with single OUT parameter +-- Tests out_param_varno re-derivation for single OUT arg on a procedure +-- (validator builds PLtsql_row, serialized into cache). +CREATE PROCEDURE dbo.babel_6037_out_single + @in_val INT, + @out_val INT OUT +AS +BEGIN + SET @out_val = @in_val * 10; +END; +GO + +-- Test 12: Procedure with multiple OUT parameters +-- For procedures with >1 OUT args, the validator builds a PLtsql_row datum +-- that gets serialized into the cache. The cache-hit path must find it +-- and set function->out_param_varno. +CREATE PROCEDURE dbo.babel_6037_out_multi + @in_val INT, + @out_val INT OUT, + @out_msg VARCHAR(100) OUT +AS +BEGIN + SET @out_val = @in_val * 2; + SET @out_msg = 'Result: ' + CAST(@out_val AS VARCHAR); +END; +GO + +-- Test 13: Multi-statement table-valued function (MSTVF) +-- MSTVFs use out_param_varno at runtime (pl_exec-2.c:1445) to build the +-- result table. The cache must preserve the ROW datum so the runtime can +-- find it. +CREATE FUNCTION dbo.babel_6037_mstvf(@in_val INT) +RETURNS @result TABLE (id INT, val VARCHAR(50)) +AS +BEGIN + INSERT INTO @result VALUES (@in_val, 'row1'); + INSERT INTO @result VALUES (@in_val + 1, 'row2'); + RETURN; +END; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + + +-- 2. Function-specific guc function `sys.enable_routine_parse_cache(, );` +-- Test 14: Per-function cache enable/disable with full signature +CREATE PROCEDURE dbo.perfunc_cache_sig + @val INT +AS +BEGIN + SELECT @val * 5 AS result; +END; +GO + +-- Test 15: Per-function cache enable/disable with simple name (no arg types) +CREATE PROCEDURE dbo.perfunc_cache_name + @val INT +AS +BEGIN + SELECT @val + 10 AS result; +END; +GO + +-- Test 16: Global GUC off + per-function enable interaction +CREATE PROCEDURE dbo.perfunc_guc_override + @val INT +AS +BEGIN + SELECT @val * 3 AS result; +END; +GO + +-- Test 17: ALTER preserves per-function flag +CREATE PROCEDURE dbo.perfunc_alter_test + @val INT +AS +BEGIN + SELECT @val + 1 AS original_result; +END; +GO + +-- Test 18: DROP removes per-function flag +CREATE PROCEDURE dbo.perfunc_drop_test + @val INT +AS +BEGIN + SELECT @val * 2 AS result; +END; +GO + +-- Test 19: Default behavior (antlr_cache_enabled = false by default) +CREATE PROCEDURE dbo.perfunc_default_test + @val INT +AS +BEGIN + SELECT @val + 100 AS result; +END; +GO + +-- Test 20: Custom schema test +CREATE SCHEMA test_cache_schema; +GO + +CREATE PROCEDURE test_cache_schema.perfunc_custom_schema + @val INT +AS +BEGIN + SELECT @val + 50 AS result; +END; +GO + +-- Test 22: Validation procedure with complex statement types +-- Used with pltsql_validate_parse_cache GUC to compare cached vs ANTLR trees +SELECT set_config('babelfishpg_tsql.validate_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + + + + + + + + +CREATE PROCEDURE dbo.validate_cache_proc + @input INT, + @name VARCHAR(50) +AS +BEGIN + DECLARE @counter INT; + DECLARE @result VARCHAR(100); + DECLARE @flag BIT; + SET @counter = 0; + SET @flag = 1; + -- IF/ELSE + IF @input > 10 + SET @result = 'large'; + ELSE + SET @result = 'small'; + -- WHILE loop + WHILE @counter < @input + BEGIN + SET @counter = @counter + 1; + IF @counter = 5 + BREAK; + END; + -- TRY/CATCH + BEGIN TRY + SELECT @counter / @input AS division_result; + END TRY + BEGIN CATCH + PRINT 'Error caught'; + END CATCH; + -- GOTO + IF @flag = 0 + GOTO skip_section; + SELECT @result AS final_result, @counter AS final_counter, @name AS input_name; + skip_section: + PRINT 'validate_cache_proc completed'; +END; +GO + +PRINT 'All test procedures created successfully'; +GO + +-- Test 23: Procedure created with cache OFF, executed with cache ON in verify +-- Tests that pltsql_update_func_cache_entry populates cache at EXEC time +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +CREATE PROCEDURE dbo.nocache_create_proc + @val TEXT +AS +BEGIN + SELECT @val AS result; +END; +GO diff --git a/test/JDBC/expected/BABEL-6037-vu-verify.out b/test/JDBC/expected/BABEL-6037-vu-verify.out new file mode 100644 index 00000000000..9fe0b338262 --- /dev/null +++ b/test/JDBC/expected/BABEL-6037-vu-verify.out @@ -0,0 +1,1246 @@ + + +-- BABEL-6037 POC Test - Verify +-- Tests procedure execution with parse tree caching +-- +-- Cache layers (see BABEL-6037 design): +-- CREATE/ALTER: compiles the procedure, serializes parse tree to babelfish_func_ext (persistent cache) +-- 1st exec in session: pltsql hash table miss -> deserializes from babelfish_func_ext, populates hash table +-- 2nd exec in session: pltsql hash table hit +-- SET BABELFISH_STATISTICS PROFILE ON; +-- GO +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +PRINT '=== Test 1: Simple procedure without arguments ==='; +GO +-- First call (deserializes ANTLR parse tree cached at CREATE time) +PRINT 'exec 1: small_proc'; +GO +EXEC dbo.small_proc; +GO +~~START~~ +int +1 +~~END~~ + +-- Second call (uses in-memory hash table cache) +PRINT 'exec 2: small_proc'; +GO +EXEC dbo.small_proc; +GO +~~START~~ +int +1 +~~END~~ + + +PRINT '=== Test 2: Simple procedure with arguments ==='; +GO +PRINT 'exec 1: small_proc_param'; +GO +EXEC dbo.small_proc_param @param1 = 10, @param2 = 'Hello'; +GO +~~START~~ +int#!#int#!#varchar +1#!#10#!#Hello +~~END~~ + +PRINT 'exec 2: small_proc_param'; +GO +EXEC dbo.small_proc_param @param1 = 20, @param2 = 'World'; +GO +~~START~~ +int#!#int#!#varchar +1#!#20#!#World +~~END~~ + + +PRINT '=== Test 3: Procedure with supported node types (WHILE, GOTO, TRY-CATCH) ==='; +GO +PRINT 'exec 1: proc_param_supported'; +GO +EXEC dbo.proc_param_supported @input_val = 10; +GO +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Starting test procedure Server SQLState: S0001)~~~~WARNING (Message: Skipped or continued Server SQLState: S0001)~~ + +~~START~~ +varchar#!#int#!#int +Success: 10#!#1#!#6 +~~END~~ + +PRINT 'exec 2: proc_param_supported'; +GO +EXEC dbo.proc_param_supported @input_val = 20; +GO +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Starting test procedure Server SQLState: S0001)~~~~WARNING (Message: Skipped or continued Server SQLState: S0001)~~ + +~~START~~ +varchar#!#int#!#int +Success: 5#!#2#!#6 +~~END~~ + + +PRINT '=== Test 4: Procedure with unsupported node type ==='; +GO +-- Serialization skipped at CREATE time; each new session falls back to full ANTLR recompile +PRINT 'exec 1: proc_param_unsupported'; +GO +EXEC dbo.proc_param_unsupported @max = 3; +GO +~~START~~ +int +3 +~~END~~ + +-- Second call (uses in-memory hash table cache) +PRINT 'exec 2: proc_param_unsupported'; +GO +EXEC dbo.proc_param_unsupported @max = 5; +GO +~~START~~ +int +5 +~~END~~ + + +PRINT '=== Test 5: Complex procedure ==='; +GO +PRINT 'exec 1: complex_proc'; +GO +EXEC dbo.complex_proc @input = 100, @flag = 1; +GO +~~START~~ +int#!#varchar +200#!#Flag is true +~~END~~ + +~~ROW COUNT: 1~~ + +~~START~~ +int#!#varchar +200#!#Flag is true +~~END~~ + +~~START~~ +nvarchar#!#int +input_val#!#100 +doubled_val#!#200 +~~END~~ + +PRINT 'exec 2: complex_proc'; +GO +EXEC dbo.complex_proc @input = 200, @flag = 0; +GO +~~START~~ +int#!#varchar +400#!#Flag is false +~~END~~ + +~~START~~ +nvarchar#!#int +input_val#!#200 +doubled_val#!#400 +~~END~~ + + +PRINT '=== Test 6: Same session - CREATE, EXEC, ALTER, DROP (samesession_proc) ==='; +GO +-- CREATE compiles and serializes parse tree to babelfish_func_ext; also populates pltsql hash table +-- Uses VARCHAR arg to trigger Day-1 bug (inputCollId mismatch on hash lookup during first EXEC after CREATE) +PRINT 'create: samesession_proc'; +GO +CREATE PROCEDURE dbo.samesession_proc + @test VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@test AS INT); + SELECT @num * 3 AS tripled; +END; +GO + +-- pltsql hash table miss due to lookup-hashkey mismatch (inputCollId field) (Day-1 bug for parameterized procedures with text types) +-- Should deserialize from babelfish_func_ext +PRINT 'exec 1: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '7'; +GO +~~START~~ +int +21 +~~END~~ + +-- pltsql hash table hit +PRINT 'exec 2: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '14'; +GO +~~START~~ +int +42 +~~END~~ + + +-- Test persistent cache invalidation with ALTER PROCEDURE +-- ALTER recompiles and re-serializes to babelfish_func_ext +PRINT 'alter: samesession_proc'; +GO +ALTER PROCEDURE dbo.samesession_proc + @test VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@test AS INT); + SELECT @num * 3 AS tripled, @num + 100 AS offset_val; + PRINT 'samesession_proc altered'; +END; +GO + +-- pltsql hash table miss after ALTER, should deserialize from updated cache +PRINT 'exec 1 after alter: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '7'; +GO +~~START~~ +int#!#int +21#!#107 +~~END~~ + +PRINT 'exec 2 after alter: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '21'; +GO +~~START~~ +int#!#int +63#!#121 +~~END~~ + + + +-- Test persistent cache invalidation with DROP PROCEDURE +-- DROP invalidates cache and should remove cache entries +PRINT 'drop: samesession_proc'; +GO +DROP PROCEDURE dbo.samesession_proc; +GO + +PRINT 'exec after drop: samesession_proc (should fail)'; +GO +EXEC dbo.samesession_proc @test = '7'; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: procedure dbo.samesession_proc(@test => unknown) does not exist)~~ + + +PRINT '=== Test 7: Old session procedure - EXEC, ALTER, DROP (oldsession_proc) ==='; +GO +-- oldsession_proc was created in prepare (different session); parse tree already in babelfish_func_ext +-- pltsql hash table miss -> deserializes from babelfish_func_ext +PRINT 'exec 1: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '42'; +GO +~~START~~ +int +42 +~~END~~ + +-- pltsql hash table hit +PRINT 'exec 2: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '99'; +GO +~~START~~ +int +99 +~~END~~ + + +-- Test persistent cache invalidation with ALTER PROCEDURE +-- ALTER recompiles and re-serializes to babelfish_func_ext +PRINT 'alter: oldsession_proc'; +GO +ALTER PROCEDURE dbo.oldsession_proc + @value VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@value AS INT); + SELECT @num * 2 AS doubled_value; + PRINT 'oldsession_proc altered'; +END; +GO + +-- pltsql hash table miss after ALTER, should deserialize from updated cache +PRINT 'exec 1 after alter: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '50'; +GO +~~START~~ +int +100 +~~END~~ + +-- pltsql hash table hit +PRINT 'exec 2 after alter: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '75'; +GO +~~START~~ +int +150 +~~END~~ + + +PRINT 'drop: oldsession_proc'; +GO +DROP PROCEDURE dbo.oldsession_proc; +GO + +PRINT 'exec after drop: oldsession_proc (should fail)'; +GO +EXEC dbo.oldsession_proc @value = '100'; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: procedure dbo.oldsession_proc(@value => unknown) does not exist)~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +PRINT '=== Test 8: Rename procedure with GUC off, then EXEC with GUC on ==='; +GO +-- rename_cache_proc was created with GUC off (no cache populated) +-- Rename it, then turn GUC on and execute — should ANTLR parse and cache with new name +PRINT 'rename: rename_cache_proc -> renamed_cache_proc'; +GO +EXEC sp_rename 'dbo.rename_cache_proc', 'renamed_cache_proc', 'object'; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +-- First exec after rename with GUC on: cache is NULL (rename NULLed it), +-- ANTLR parses, pltsql_update_func_cache_entry writes fresh cache +PRINT 'exec 1: renamed_cache_proc (after rename, GUC on)'; +GO +EXEC dbo.renamed_cache_proc @val = 5; +GO +~~START~~ +int +50 +~~END~~ + + +-- Second exec: should use cached result +PRINT 'exec 2: renamed_cache_proc'; +GO +EXEC dbo.renamed_cache_proc @val = 7; +GO +~~START~~ +int +70 +~~END~~ + + + +PRINT '=== Test 9: ALTER with GUC off, then EXEC with GUC on ==='; +GO +-- alter_guc_proc was created with GUC off (cache is NULL) +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +-- ALTER with GUC off: cache columns stay NULL +PRINT 'alter: alter_guc_proc (GUC off)'; +GO +ALTER PROCEDURE dbo.alter_guc_proc + @val INT +AS +BEGIN + SELECT @val + 100 AS big_increment; +END; +GO + +-- Turn GUC on and execute: cache miss (NULL), ANTLR parses, cache gets populated +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +PRINT 'exec 1: alter_guc_proc (GUC on after ALTER with GUC off)'; +GO +EXEC dbo.alter_guc_proc @val = 5; +GO +~~START~~ +int +105 +~~END~~ + + +-- Second exec: should use cached result +PRINT 'exec 2: alter_guc_proc'; +GO +EXEC dbo.alter_guc_proc @val = 10; +GO +~~START~~ +int +110 +~~END~~ + + + +PRINT '=== Test 10: Altered dependency - drop/recreate table used by cached proc ==='; +GO +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +-- First exec: ANTLR parses (cache was NULL from GUC-off CREATE), populates cache +PRINT 'exec 1: dep_table_proc (original table)'; +GO +EXEC dbo.dep_table_proc; +GO +~~START~~ +int#!#varchar +1#!#Alice +2#!#Bob +~~END~~ + + +-- Drop the dependency table and execute (expect failure) +PRINT 'drop dependency dep_test_table'; +GO +DROP TABLE dbo.dep_test_table; +GO + +-- Exec with GUC on: cached parse tree is no longer valid, +-- but should fail during execution when table not found +PRINT 'exec 2-a: dep_table_proc (after table drop, GUC on)'; +GO +EXEC dbo.dep_table_proc; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: relation "dbo.dep_test_table" does not exist)~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + +-- Exec with GUC on: cached parse tree is still valid, +-- but should fail during execution when table not found +PRINT 'exec 2-b: dep_table_proc (after table drop, GUC off)'; +GO +EXEC dbo.dep_table_proc; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: relation "dbo.dep_test_table" does not exist)~~ + + +-- recreate the dependency table with different schema +PRINT 'recreate dep_test_table with new column'; +GO +CREATE TABLE dbo.dep_test_table (id INT, name VARCHAR(50), extra INT); +GO +INSERT INTO dbo.dep_test_table VALUES (10, 'Charlie', 999); +GO +~~ROW COUNT: 1~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + +-- Exec with GUC on: cached parse tree is still valid (SELECT * is resolved at execution), +-- but the result should reflect the new table schema +PRINT 'exec 3-a: dep_table_proc (after table recreate, GUC on)'; +GO +EXEC dbo.dep_table_proc; +GO +~~START~~ +int#!#varchar#!#int +10#!#Charlie#!#999 +~~END~~ + + +-- Now test with GUC off +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +PRINT 'exec 3-b: dep_table_proc (after table recreate, GUC off)'; +GO +EXEC dbo.dep_table_proc; +GO +~~START~~ +int#!#varchar#!#int +10#!#Charlie#!#999 +~~END~~ + + + +PRINT '=== Test 11: Procedure with single OUT parameter ==='; +GO +PRINT 'exec 1: babel_6037_out_single'; +GO +DECLARE @out INT; +EXEC dbo.babel_6037_out_single @in_val = 5, @out_val = @out OUT; +SELECT @out AS out_val; +GO +~~START~~ +int +50 +~~END~~ + +PRINT 'exec 2: babel_6037_out_single'; +GO +DECLARE @out INT; +EXEC dbo.babel_6037_out_single @in_val = 100, @out_val = @out OUT; +SELECT @out AS out_val; +GO +~~START~~ +int +1000 +~~END~~ + + +PRINT '=== Test 12: Procedure with multiple OUT parameters ==='; +GO +PRINT 'exec 1: babel_6037_out_multi'; +GO +DECLARE @oval INT, @omsg VARCHAR(100); +EXEC dbo.babel_6037_out_multi @in_val = 7, @out_val = @oval OUT, @out_msg = @omsg OUT; +SELECT @oval AS out_val, @omsg AS out_msg; +GO +~~START~~ +int#!#varchar +14#!#Result: 14 +~~END~~ + +PRINT 'exec 2: babel_6037_out_multi'; +GO +DECLARE @oval INT, @omsg VARCHAR(100); +EXEC dbo.babel_6037_out_multi @in_val = 25, @out_val = @oval OUT, @out_msg = @omsg OUT; +SELECT @oval AS out_val, @omsg AS out_msg; +GO +~~START~~ +int#!#varchar +50#!#Result: 50 +~~END~~ + + +PRINT '=== Test 13: MSTVF ==='; +GO +PRINT 'exec 1: babel_6037_mstvf'; +GO +SELECT * FROM dbo.babel_6037_mstvf(10); +GO +~~START~~ +int#!#varchar +10#!#row1 +11#!#row2 +~~END~~ + +PRINT 'exec 2: babel_6037_mstvf'; +GO +SELECT * FROM dbo.babel_6037_mstvf(20); +GO +~~START~~ +int#!#varchar +20#!#row1 +21#!#row2 +~~END~~ + + +-- === Test 14: Enable/disable cache with full signature: schema.func(argtypes) === +PRINT '=== Test 14: Per-function cache with full signature ==='; +GO + +-- Verify antlr_cache_enabled defaults to false +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +0 +~~END~~ + + +-- Enable cache with schema.funcname(argtypes) +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_sig(integer)', true); +GO +~~START~~ +bit +1 +~~END~~ + + +-- Verify antlr_cache_enabled is now true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +1 +~~END~~ + + +-- Turn global GUC on and execute +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +EXEC dbo.perfunc_cache_sig @val = 4; +GO +~~START~~ +int +20 +~~END~~ + + +-- Disable cache with full signature +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_sig(integer)', false); +GO +~~START~~ +bit +0 +~~END~~ + + +-- Verify antlr_cache_enabled is now false +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +0 +~~END~~ + + +-- Verify cache columns are NULLed out (immediate invalidation) +SELECT + CASE WHEN antlr_parse_tree_text IS NULL THEN 'NULL' ELSE 'NOT NULL' END AS tree_text, + CASE WHEN antlr_parse_tree_bbf_version IS NULL THEN 'NULL' ELSE 'NOT NULL' END AS bbf_version +FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO +~~START~~ +varchar#!#varchar +NULL#!#NULL +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + + +-- === Test 15: Enable/disable with schema.funcname (no args) and funcname only (dbo default) === +PRINT '=== Test 15: Per-function cache with simple name and no-schema default ==='; +GO + +-- Enable with schema.funcname (no arg types, 2-key lookup) +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_name', true); +GO +~~START~~ +bit +1 +~~END~~ + + +-- Verify antlr_cache_enabled is now true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_name' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +1 +~~END~~ + + +-- Execute with global GUC on +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +EXEC dbo.perfunc_cache_name @val = 7; +GO +~~START~~ +int +17 +~~END~~ + + +-- Disable with schema.funcname +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_name', false); +GO +~~START~~ +bit +0 +~~END~~ + + +-- Verify disabled +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_name' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +0 +~~END~~ + + +-- Re-enable using just funcname (no schema, should default to dbo) +SELECT sys.enable_routine_parse_cache('perfunc_cache_name', true); +GO +~~START~~ +bit +1 +~~END~~ + + +-- Verify enabled again +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_name' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +1 +~~END~~ + + +-- Disable using just funcname +SELECT sys.enable_routine_parse_cache('perfunc_cache_name', false); +GO +~~START~~ +bit +0 +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + + +-- === Test 16: Global GUC off + per-function enable interaction === +PRINT '=== Test 16: GUC off + per-function flag interaction ==='; +GO + +-- Enable per-function cache using no-schema form +SELECT sys.enable_routine_parse_cache('perfunc_guc_override', true); +GO +~~START~~ +bit +1 +~~END~~ + + +-- Verify antlr_cache_enabled is true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_guc_override' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +1 +~~END~~ + + +-- Keep global GUC OFF +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +-- Execute with GUC off (falls back to ANTLR, cache columns stay NULL) +EXEC dbo.perfunc_guc_override @val = 9; +GO +~~START~~ +int +27 +~~END~~ + + +-- Verify cache columns are still NULL (GUC was off during CREATE, no cache populated) +SELECT + CASE WHEN antlr_parse_tree_text IS NULL THEN 'NULL' ELSE 'NOT NULL' END AS tree_text +FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_guc_override' AND nspname = 'master_dbo'; +GO +~~START~~ +varchar +NULL +~~END~~ + + +-- Turn GUC on, per-function flag is true, cache should get populated on exec +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +EXEC dbo.perfunc_guc_override @val = 11; +GO +~~START~~ +int +33 +~~END~~ + + +-- Turn GUC off again, per-function=true alone should still allow retrieval +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +EXEC dbo.perfunc_guc_override @val = 13; +GO +~~START~~ +int +39 +~~END~~ + + + +-- === Test 17: Default behavior (antlr_cache_enabled = false) === +PRINT '=== Test 17: Default behavior (antlr_cache_enabled = false) ==='; +GO + +-- Verify antlr_cache_enabled defaults to false +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_default_test' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +0 +~~END~~ + + +-- With global GUC on, the global GUC alone should still allow caching +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +EXEC dbo.perfunc_default_test @val = 1; +GO +~~START~~ +int +101 +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + + +-- === Test 18: Invalid function identifier handling === +PRINT '=== Test 18: Invalid function identifier handling ==='; +GO + +-- Non-existent function with full signature (should error) +SELECT sys.enable_routine_parse_cache('dbo.nonexistent_proc(integer)', true); +GO +~~START~~ +bit +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: function "nonexistent_proc(integer)" not found in schema "dbo")~~ + + +-- Non-existent function with schema.name (should error) +SELECT sys.enable_routine_parse_cache('dbo.nonexistent_proc', true); +GO +~~START~~ +bit +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: function "nonexistent_proc" not found in schema "dbo")~~ + + +-- Non-existent function with just name, no schema (should default to dbo and error) +SELECT sys.enable_routine_parse_cache('nonexistent_proc', true); +GO +~~START~~ +bit +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: function "nonexistent_proc" not found in schema "dbo")~~ + + + +-- === Test 19: ALTER procedure preserves per-function flag === +PRINT '=== Test 19: ALTER preserves per-function flag ==='; +GO + +-- Enable per-function cache +SELECT sys.enable_routine_parse_cache('dbo.perfunc_alter_test', true); +GO +~~START~~ +bit +1 +~~END~~ + + +-- Turn GUC on and execute to populate cache +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +EXEC dbo.perfunc_alter_test @val = 5; +GO +~~START~~ +int +6 +~~END~~ + + +-- ALTER the procedure +ALTER PROCEDURE dbo.perfunc_alter_test + @val INT +AS +BEGIN + SELECT @val + 1000 AS altered_result; +END; +GO + +-- Verify antlr_cache_enabled is still true after ALTER +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_alter_test' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +1 +~~END~~ + + +-- Execute altered procedure +EXEC dbo.perfunc_alter_test @val = 5; +GO +~~START~~ +int +1005 +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + + +-- === Test 20: DROP procedure removes per-function flag === +PRINT '=== Test 20: DROP removes per-function flag ==='; +GO + +-- Enable per-function cache +SELECT sys.enable_routine_parse_cache('dbo.perfunc_drop_test', true); +GO +~~START~~ +bit +1 +~~END~~ + + +-- Verify antlr_cache_enabled is true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_drop_test' AND nspname = 'master_dbo'; +GO +~~START~~ +bit +1 +~~END~~ + + +-- DROP the procedure +DROP PROCEDURE dbo.perfunc_drop_test; +GO + +-- Verify the babelfish_function_ext row is gone +SELECT COUNT(*) AS row_count FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_drop_test' AND nspname = 'master_dbo'; +GO +~~START~~ +int +0 +~~END~~ + + + +-- === Test 21: Custom schema test === +PRINT '=== Test 21: Custom schema per-function cache ==='; +GO + +-- Enable cache using custom schema.funcname +SELECT sys.enable_routine_parse_cache('test_cache_schema.perfunc_custom_schema', true); +GO +~~START~~ +bit +1 +~~END~~ + + +-- Verify antlr_cache_enabled is true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_custom_schema' AND nspname = 'test_cache_schema'; +GO +~~START~~ +bit +~~END~~ + + +-- Execute with GUC on +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +EXEC test_cache_schema.perfunc_custom_schema @val = 3; +GO +~~START~~ +int +53 +~~END~~ + + +-- Disable cache +SELECT sys.enable_routine_parse_cache('test_cache_schema.perfunc_custom_schema', false); +GO +~~START~~ +bit +0 +~~END~~ + + +-- Verify disabled +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_custom_schema' AND nspname = 'test_cache_schema'; +GO +~~START~~ +bit +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +-- Wrong schema: function exists in test_cache_schema but not in dbo (should error) +SELECT sys.enable_routine_parse_cache('dbo.perfunc_custom_schema', true); +GO +~~START~~ +bit +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: function "perfunc_custom_schema" not found in schema "dbo")~~ + + +-- Wrong schema: function exists in dbo but not in test_cache_schema (should error) +SELECT sys.enable_routine_parse_cache('test_cache_schema.perfunc_cache_sig', true); +GO +~~START~~ +bit +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: function "perfunc_cache_sig" not found in schema "test_cache_schema")~~ + + + +-- === Test 22: Parse cache validation (cached tree vs ANTLR comparison) === +PRINT '=== Test 22: Parse cache validation ==='; +GO + +-- Enable validation GUC — next cache-hit EXEC will compare cached vs ANTLR trees +SELECT set_config('babelfishpg_tsql.validate_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +-- Execute the validation procedure (triggers cached vs ANTLR comparison) +EXEC dbo.validate_cache_proc @input = 0, @name = 'test'; +GO +~~START~~ +int +~~END~~ + +~~START~~ +varchar#!#int#!#varchar +small#!#0#!#test +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + +-- Disable validation GUC +SELECT set_config('babelfishpg_tsql.validate_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + + +-- === Test 23: Proc created with cache OFF, executed with cache ON === +PRINT '=== Test 23: Cache populated at EXEC time ==='; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +~~START~~ +text +on +~~END~~ + + +-- First exec: cache is empty (created with GUC off), ANTLR parses, cache gets populated +EXEC dbo.nocache_create_proc @val = 'exec1'; +GO +~~START~~ +text +exec1 +~~END~~ + + +-- Second exec: should use cached result +EXEC dbo.nocache_create_proc @val = 'exec2'; +GO +~~START~~ +text +exec2 +~~END~~ + + +-- Verify cache was populated by the first exec +SELECT + CASE WHEN antlr_parse_tree_text IS NOT NULL THEN 'CACHED' ELSE 'NOT CACHED' END AS cache_status +FROM sys.babelfish_function_ext +WHERE funcname = 'nocache_create_proc' AND nspname = 'master_dbo'; +GO +~~START~~ +varchar +CACHED +~~END~~ + + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +~~START~~ +text +off +~~END~~ + + + +PRINT '=== All verification tests completed ==='; +GO diff --git a/test/JDBC/input/BABEL-6037-vu-cleanup.sql b/test/JDBC/input/BABEL-6037-vu-cleanup.sql new file mode 100644 index 00000000000..414597d2bc4 --- /dev/null +++ b/test/JDBC/input/BABEL-6037-vu-cleanup.sql @@ -0,0 +1,93 @@ +-- BABEL-6037 POC Test - Cleanup +-- Drops all test procedures created during testing + +PRINT 'Starting cleanup of BABEL-6037 POC test procedures...'; +GO + +-- Drop Test 1: Simple procedure without arguments +DROP PROCEDURE IF EXISTS dbo.small_proc; +PRINT 'Dropped: dbo.small_proc'; +GO + +-- Drop Test 2: Simple procedure with arguments +DROP PROCEDURE IF EXISTS dbo.small_proc_param; +PRINT 'Dropped: dbo.small_proc_param'; +GO + +-- Drop Test 3: Procedure with unsupported node type +DROP PROCEDURE IF EXISTS dbo.proc_param_supported; +PRINT 'Dropped: dbo.proc_with_unsupported'; +GO + +-- Drop Test 4: Procedure with unsupported node type +DROP PROCEDURE IF EXISTS dbo.proc_param_unsupported; +PRINT 'Dropped: dbo.proc_with_unsupported'; +GO + +-- Drop Test 5: Complex procedure +DROP PROCEDURE IF EXISTS dbo.complex_proc; +PRINT 'Dropped: dbo.complex_proc'; +GO + +-- Drop Test 6: Same-session procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.samesession_proc; +PRINT 'Dropped: dbo.samesession_proc'; +GO + +-- Drop Test 7: Old-session procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.oldsession_proc; +PRINT 'Dropped: dbo.newsession_proc'; +GO + +-- Drop Test 8: renamed_cache_proc procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.rename_cache_proc; +GO +DROP PROCEDURE IF EXISTS dbo.renamed_cache_proc; +GO + +-- Drop Test 9: alter_guc_proc procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.alter_guc_proc; +GO + +-- Drop Test 10: dep_table_proc procedure (if it still exists) +DROP PROCEDURE IF EXISTS dbo.dep_table_proc; +GO +DROP TABLE IF EXISTS dbo.dep_test_table; +GO + +-- Drop Test 11: Procedure with single OUT parameter +DROP PROCEDURE IF EXISTS dbo.babel_6037_out_single; +GO + +-- Drop Test 12: Procedure with multiple OUT parameters +DROP PROCEDURE IF EXISTS dbo.babel_6037_out_multi; +GO + +-- Drop Test 13: MSTVF +DROP FUNCTION IF EXISTS dbo.babel_6037_mstvf; +GO + +-- Per-function cache control test procedures +DROP PROCEDURE IF EXISTS dbo.perfunc_cache_sig; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_cache_name; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_guc_override; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_alter_test; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_drop_test; +GO +DROP PROCEDURE IF EXISTS dbo.perfunc_default_test; +GO +DROP PROCEDURE IF EXISTS test_cache_schema.perfunc_custom_schema; +GO +DROP SCHEMA IF EXISTS test_cache_schema; +GO +DROP PROCEDURE IF EXISTS dbo.validate_cache_proc; +GO +DROP PROCEDURE IF EXISTS dbo.nocache_create_proc; +GO + +PRINT 'Cleanup completed successfully'; +GO \ No newline at end of file diff --git a/test/JDBC/input/BABEL-6037-vu-prepare.sql b/test/JDBC/input/BABEL-6037-vu-prepare.sql new file mode 100644 index 00000000000..a5938892781 --- /dev/null +++ b/test/JDBC/input/BABEL-6037-vu-prepare.sql @@ -0,0 +1,387 @@ +-- BABEL-6037 +-- Creates test procedures for ANTLR parse tree serialization/deserialization + +-- 1. SESSION-LEVEL GUC `babelfishpg_tsql.enable_routine_parse_cache` +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +-- Test 1: Simple procedure without arguments +-- This tests basic serialization with minimal complexity +CREATE PROCEDURE dbo.small_proc +AS +BEGIN + DECLARE @var1 INT = 1; + SELECT @var1; +END; +GO + +-- Test 2: Simple procedure with arguments (from focused revision doc) +-- This tests parameter handling and variable serialization +CREATE PROCEDURE dbo.small_proc_param + @param1 INT, + @param2 VARCHAR(50) +AS +BEGIN + DECLARE @var1 INT = 1; + SELECT @var1, @param1, @param2; + CREATE TABLE #test(id int); +END; +GO + +-- Test 3: Procedure with supported node types (PRINT, WHILE, GOTO, TRY-CATCH, CASE, BREAK, CONTINUE) +-- This tests various statement types within a BLOCK statement +CREATE PROCEDURE dbo.proc_param_supported +@input_val INT = 10 +AS +BEGIN + DECLARE @counter INT = 0; + DECLARE @result VARCHAR(100) = ''; + DECLARE @status INT = 0; + + -- Test PRINT statement + PRINT 'Starting test procedure'; + + -- Test WHILE loop with BREAK and CONTINUE + WHILE @counter < 3 + BEGIN + SET @counter = @counter + 1; + + -- Test IF with EXECSQL + IF @counter = 2 + BEGIN + SELECT @result = 'Found two'; + END + END + + -- Test WHILE with BREAK and CONTINUE + SET @counter = 0; + WHILE 1 = 1 + BEGIN + SET @counter = @counter + 1; + + -- Test BREAK statement + IF @counter > 5 + BREAK; + + -- Test CONTINUE statement + IF @counter = 3 + CONTINUE; + + SET @result = @result + CAST(@counter AS VARCHAR); + END + + -- Test CASE statement + SET @status = CASE @input_val + WHEN 10 THEN 1 + WHEN 20 THEN 2 + WHEN 30 THEN 3 + ELSE 0 + END; + + -- Test GOTO and LABEL + IF @status = 0 + GOTO skip_section; + + skip_section: + PRINT 'Skipped or continued'; + + -- Test TRY-CATCH block + BEGIN TRY + -- Test EXECSQL with potential error + DECLARE @test_val INT; + SELECT @test_val = 100 / @input_val; + + -- Test ASSIGN statement + SET @result = 'Success: ' + CAST(@test_val AS VARCHAR); + END TRY + BEGIN CATCH + -- Error handling + SET @result = 'Error handled'; + END CATCH + + -- Test RETURN statement + IF @input_val < 0 + RETURN; + + -- Final output + SELECT @result AS FinalResult, @status AS Status, @counter AS Counter; +END; +GO + + +-- Test 4: Procedure with unsupported node type (EXEC_SP / sp_executesql) +-- PLTSQL_STMT_EXEC_SP is not in pltsql_is_serializable; triggers PLTSQL_SERIAL_UNSUPPORTED +-- Each new session falls back to full ANTLR recompile instead of using persistent cache +CREATE PROCEDURE dbo.proc_param_unsupported + @max INT +AS +BEGIN + DECLARE @sql NVARCHAR(100) = N'SELECT ' + CAST(@max AS NVARCHAR); + EXEC sp_executesql @sql; +END; +GO + + +-- Test 5: Complex procedure that may lead to serialize error +-- Tests nested blocks, multiple statement types, and IF conditions +CREATE PROCEDURE dbo.complex_proc + @input INT, + @flag BIT +AS +BEGIN + DECLARE @result INT; + DECLARE @temp VARCHAR(100); + + -- Test ASSIGN statement + SET @result = @input * 2; + + -- Test IF statement with nested block + IF @flag = 1 + BEGIN + SET @temp = 'Flag is true'; + SELECT @result, @temp; + + -- Nested temp table operations + CREATE TABLE #temp1(id INT, value VARCHAR(50)); + INSERT INTO #temp1 VALUES (@result, @temp); + SELECT * FROM #temp1; + END + ELSE + BEGIN + SET @temp = 'Flag is false'; + SELECT @result, @temp; + END; + + -- Test EXECSQL with UNPIVOT (complex SQL expression within serializable proc) + SELECT col, val + FROM (SELECT @input AS input_val, @result AS doubled_val) AS src + UNPIVOT (val FOR col IN (input_val, doubled_val)) AS unpvt; + + -- Test RETURN statement + RETURN @result; +END; +GO + +-- Test 6: Procedure for same-session testing (procedure created in verify file) + +-- Test 7: Procedure for new-session testing +-- This will be used to test ALTER and DROP in new session +CREATE PROCEDURE dbo.oldsession_proc + @value VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@value AS INT); + SELECT @num AS original_value; +END; +GO + +-- Test 8: Procedure for rename + GUC off/on testing +CREATE PROCEDURE dbo.rename_cache_proc + @val INT +AS +BEGIN + SELECT @val * 10 AS result; +END; +GO + +-- Test 9: Procedure for ALTER with GUC off then EXEC with GUC on +CREATE PROCEDURE dbo.alter_guc_proc + @val INT +AS +BEGIN + SELECT @val + 1 AS incremented; +END; +GO + +-- Test 10: Table + procedure for altered dependency testing +-- Procedure references a table; dropping/recreating the table tests cache staleness +CREATE TABLE dbo.dep_test_table (id INT, name VARCHAR(50)); +GO +INSERT INTO dbo.dep_test_table VALUES (1, 'Alice'), (2, 'Bob'); +GO + +CREATE PROCEDURE dbo.dep_table_proc +AS +BEGIN + SELECT * FROM dbo.dep_test_table; +END; +GO + +-- Test 11: Procedure with single OUT parameter +-- Tests out_param_varno re-derivation for single OUT arg on a procedure +-- (validator builds PLtsql_row, serialized into cache). +CREATE PROCEDURE dbo.babel_6037_out_single + @in_val INT, + @out_val INT OUT +AS +BEGIN + SET @out_val = @in_val * 10; +END; +GO + +-- Test 12: Procedure with multiple OUT parameters +-- For procedures with >1 OUT args, the validator builds a PLtsql_row datum +-- that gets serialized into the cache. The cache-hit path must find it +-- and set function->out_param_varno. +CREATE PROCEDURE dbo.babel_6037_out_multi + @in_val INT, + @out_val INT OUT, + @out_msg VARCHAR(100) OUT +AS +BEGIN + SET @out_val = @in_val * 2; + SET @out_msg = 'Result: ' + CAST(@out_val AS VARCHAR); +END; +GO + +-- Test 13: Multi-statement table-valued function (MSTVF) +-- MSTVFs use out_param_varno at runtime (pl_exec-2.c:1445) to build the +-- result table. The cache must preserve the ROW datum so the runtime can +-- find it. +CREATE FUNCTION dbo.babel_6037_mstvf(@in_val INT) +RETURNS @result TABLE (id INT, val VARCHAR(50)) +AS +BEGIN + INSERT INTO @result VALUES (@in_val, 'row1'); + INSERT INTO @result VALUES (@in_val + 1, 'row2'); + RETURN; +END; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +-- 2. Function-specific guc function `sys.enable_routine_parse_cache(, );` + +-- Test 14: Per-function cache enable/disable with full signature +CREATE PROCEDURE dbo.perfunc_cache_sig + @val INT +AS +BEGIN + SELECT @val * 5 AS result; +END; +GO + +-- Test 15: Per-function cache enable/disable with simple name (no arg types) +CREATE PROCEDURE dbo.perfunc_cache_name + @val INT +AS +BEGIN + SELECT @val + 10 AS result; +END; +GO + +-- Test 16: Global GUC off + per-function enable interaction +CREATE PROCEDURE dbo.perfunc_guc_override + @val INT +AS +BEGIN + SELECT @val * 3 AS result; +END; +GO + +-- Test 17: ALTER preserves per-function flag +CREATE PROCEDURE dbo.perfunc_alter_test + @val INT +AS +BEGIN + SELECT @val + 1 AS original_result; +END; +GO + +-- Test 18: DROP removes per-function flag +CREATE PROCEDURE dbo.perfunc_drop_test + @val INT +AS +BEGIN + SELECT @val * 2 AS result; +END; +GO + +-- Test 19: Default behavior (antlr_cache_enabled = false by default) +CREATE PROCEDURE dbo.perfunc_default_test + @val INT +AS +BEGIN + SELECT @val + 100 AS result; +END; +GO + +-- Test 20: Custom schema test +CREATE SCHEMA test_cache_schema; +GO + +CREATE PROCEDURE test_cache_schema.perfunc_custom_schema + @val INT +AS +BEGIN + SELECT @val + 50 AS result; +END; +GO + +-- Test 22: Validation procedure with complex statement types +-- Used with pltsql_validate_parse_cache GUC to compare cached vs ANTLR trees +SELECT set_config('babelfishpg_tsql.validate_parse_cache', 'on', false); +GO + +CREATE PROCEDURE dbo.validate_cache_proc + @input INT, + @name VARCHAR(50) +AS +BEGIN + DECLARE @counter INT; + DECLARE @result VARCHAR(100); + DECLARE @flag BIT; + + SET @counter = 0; + SET @flag = 1; + + -- IF/ELSE + IF @input > 10 + SET @result = 'large'; + ELSE + SET @result = 'small'; + + -- WHILE loop + WHILE @counter < @input + BEGIN + SET @counter = @counter + 1; + IF @counter = 5 + BREAK; + END; + + -- TRY/CATCH + BEGIN TRY + SELECT @counter / @input AS division_result; + END TRY + BEGIN CATCH + PRINT 'Error caught'; + END CATCH; + + -- GOTO + IF @flag = 0 + GOTO skip_section; + + SELECT @result AS final_result, @counter AS final_counter, @name AS input_name; + + skip_section: + PRINT 'validate_cache_proc completed'; +END; +GO + +PRINT 'All test procedures created successfully'; +GO + +-- Test 23: Procedure created with cache OFF, executed with cache ON in verify +-- Tests that pltsql_update_func_cache_entry populates cache at EXEC time +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +CREATE PROCEDURE dbo.nocache_create_proc + @val TEXT +AS +BEGIN + SELECT @val AS result; +END; +GO diff --git a/test/JDBC/input/BABEL-6037-vu-verify.sql b/test/JDBC/input/BABEL-6037-vu-verify.sql new file mode 100644 index 00000000000..a91a2d7884a --- /dev/null +++ b/test/JDBC/input/BABEL-6037-vu-verify.sql @@ -0,0 +1,676 @@ +-- BABEL-6037 POC Test - Verify +-- Tests procedure execution with parse tree caching +-- +-- Cache layers (see BABEL-6037 design): +-- CREATE/ALTER: compiles the procedure, serializes parse tree to babelfish_func_ext (persistent cache) +-- 1st exec in session: pltsql hash table miss -> deserializes from babelfish_func_ext, populates hash table +-- 2nd exec in session: pltsql hash table hit + +-- SET BABELFISH_STATISTICS PROFILE ON; +-- GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +PRINT '=== Test 1: Simple procedure without arguments ==='; +GO +-- First call (deserializes ANTLR parse tree cached at CREATE time) +PRINT 'exec 1: small_proc'; +GO +EXEC dbo.small_proc; +GO +-- Second call (uses in-memory hash table cache) +PRINT 'exec 2: small_proc'; +GO +EXEC dbo.small_proc; +GO + +PRINT '=== Test 2: Simple procedure with arguments ==='; +GO +PRINT 'exec 1: small_proc_param'; +GO +EXEC dbo.small_proc_param @param1 = 10, @param2 = 'Hello'; +GO +PRINT 'exec 2: small_proc_param'; +GO +EXEC dbo.small_proc_param @param1 = 20, @param2 = 'World'; +GO + +PRINT '=== Test 3: Procedure with supported node types (WHILE, GOTO, TRY-CATCH) ==='; +GO +PRINT 'exec 1: proc_param_supported'; +GO +EXEC dbo.proc_param_supported @input_val = 10; +GO +PRINT 'exec 2: proc_param_supported'; +GO +EXEC dbo.proc_param_supported @input_val = 20; +GO + +PRINT '=== Test 4: Procedure with unsupported node type ==='; +GO +-- Serialization skipped at CREATE time; each new session falls back to full ANTLR recompile +PRINT 'exec 1: proc_param_unsupported'; +GO +EXEC dbo.proc_param_unsupported @max = 3; +GO +-- Second call (uses in-memory hash table cache) +PRINT 'exec 2: proc_param_unsupported'; +GO +EXEC dbo.proc_param_unsupported @max = 5; +GO + +PRINT '=== Test 5: Complex procedure ==='; +GO +PRINT 'exec 1: complex_proc'; +GO +EXEC dbo.complex_proc @input = 100, @flag = 1; +GO +PRINT 'exec 2: complex_proc'; +GO +EXEC dbo.complex_proc @input = 200, @flag = 0; +GO + +PRINT '=== Test 6: Same session - CREATE, EXEC, ALTER, DROP (samesession_proc) ==='; +GO +-- CREATE compiles and serializes parse tree to babelfish_func_ext; also populates pltsql hash table +-- Uses VARCHAR arg to trigger Day-1 bug (inputCollId mismatch on hash lookup during first EXEC after CREATE) +PRINT 'create: samesession_proc'; +GO +CREATE PROCEDURE dbo.samesession_proc + @test VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@test AS INT); + SELECT @num * 3 AS tripled; +END; +GO + +-- pltsql hash table miss due to lookup-hashkey mismatch (inputCollId field) (Day-1 bug for parameterized procedures with text types) +-- Should deserialize from babelfish_func_ext +PRINT 'exec 1: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '7'; +GO +-- pltsql hash table hit +PRINT 'exec 2: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '14'; +GO + +-- Test persistent cache invalidation with ALTER PROCEDURE +-- ALTER recompiles and re-serializes to babelfish_func_ext +PRINT 'alter: samesession_proc'; +GO +ALTER PROCEDURE dbo.samesession_proc + @test VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@test AS INT); + SELECT @num * 3 AS tripled, @num + 100 AS offset_val; + PRINT 'samesession_proc altered'; +END; +GO + +-- pltsql hash table miss after ALTER, should deserialize from updated cache +PRINT 'exec 1 after alter: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '7'; +GO +PRINT 'exec 2 after alter: samesession_proc'; +GO +EXEC dbo.samesession_proc @test = '21'; +GO + + +-- Test persistent cache invalidation with DROP PROCEDURE +-- DROP invalidates cache and should remove cache entries +PRINT 'drop: samesession_proc'; +GO +DROP PROCEDURE dbo.samesession_proc; +GO + +PRINT 'exec after drop: samesession_proc (should fail)'; +GO +EXEC dbo.samesession_proc @test = '7'; +GO + +PRINT '=== Test 7: Old session procedure - EXEC, ALTER, DROP (oldsession_proc) ==='; +GO +-- oldsession_proc was created in prepare (different session); parse tree already in babelfish_func_ext +-- pltsql hash table miss -> deserializes from babelfish_func_ext +PRINT 'exec 1: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '42'; +GO +-- pltsql hash table hit +PRINT 'exec 2: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '99'; +GO + +-- Test persistent cache invalidation with ALTER PROCEDURE +-- ALTER recompiles and re-serializes to babelfish_func_ext +PRINT 'alter: oldsession_proc'; +GO +ALTER PROCEDURE dbo.oldsession_proc + @value VARCHAR(50) +AS +BEGIN + DECLARE @num INT; + SET @num = CAST(@value AS INT); + SELECT @num * 2 AS doubled_value; + PRINT 'oldsession_proc altered'; +END; +GO + +-- pltsql hash table miss after ALTER, should deserialize from updated cache +PRINT 'exec 1 after alter: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '50'; +GO +-- pltsql hash table hit +PRINT 'exec 2 after alter: oldsession_proc'; +GO +EXEC dbo.oldsession_proc @value = '75'; +GO + +PRINT 'drop: oldsession_proc'; +GO +DROP PROCEDURE dbo.oldsession_proc; +GO + +PRINT 'exec after drop: oldsession_proc (should fail)'; +GO +EXEC dbo.oldsession_proc @value = '100'; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +PRINT '=== Test 8: Rename procedure with GUC off, then EXEC with GUC on ==='; +GO +-- rename_cache_proc was created with GUC off (no cache populated) +-- Rename it, then turn GUC on and execute — should ANTLR parse and cache with new name +PRINT 'rename: rename_cache_proc -> renamed_cache_proc'; +GO +EXEC sp_rename 'dbo.rename_cache_proc', 'renamed_cache_proc', 'object'; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +-- First exec after rename with GUC on: cache is NULL (rename NULLed it), +-- ANTLR parses, pltsql_update_func_cache_entry writes fresh cache +PRINT 'exec 1: renamed_cache_proc (after rename, GUC on)'; +GO +EXEC dbo.renamed_cache_proc @val = 5; +GO + +-- Second exec: should use cached result +PRINT 'exec 2: renamed_cache_proc'; +GO +EXEC dbo.renamed_cache_proc @val = 7; +GO + + +PRINT '=== Test 9: ALTER with GUC off, then EXEC with GUC on ==='; +GO +-- alter_guc_proc was created with GUC off (cache is NULL) +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +-- ALTER with GUC off: cache columns stay NULL +PRINT 'alter: alter_guc_proc (GUC off)'; +GO +ALTER PROCEDURE dbo.alter_guc_proc + @val INT +AS +BEGIN + SELECT @val + 100 AS big_increment; +END; +GO + +-- Turn GUC on and execute: cache miss (NULL), ANTLR parses, cache gets populated +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +PRINT 'exec 1: alter_guc_proc (GUC on after ALTER with GUC off)'; +GO +EXEC dbo.alter_guc_proc @val = 5; +GO + +-- Second exec: should use cached result +PRINT 'exec 2: alter_guc_proc'; +GO +EXEC dbo.alter_guc_proc @val = 10; +GO + + +PRINT '=== Test 10: Altered dependency - drop/recreate table used by cached proc ==='; +GO +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +-- First exec: ANTLR parses (cache was NULL from GUC-off CREATE), populates cache +PRINT 'exec 1: dep_table_proc (original table)'; +GO +EXEC dbo.dep_table_proc; +GO + +-- Drop the dependency table and execute (expect failure) +PRINT 'drop dependency dep_test_table'; +GO +DROP TABLE dbo.dep_test_table; +GO + +-- Exec with GUC on: cached parse tree is no longer valid, +-- but should fail during execution when table not found +PRINT 'exec 2-a: dep_table_proc (after table drop, GUC on)'; +GO +EXEC dbo.dep_table_proc; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO +-- Exec with GUC on: cached parse tree is still valid, +-- but should fail during execution when table not found +PRINT 'exec 2-b: dep_table_proc (after table drop, GUC off)'; +GO +EXEC dbo.dep_table_proc; +GO + +-- recreate the dependency table with different schema +PRINT 'recreate dep_test_table with new column'; +GO +CREATE TABLE dbo.dep_test_table (id INT, name VARCHAR(50), extra INT); +GO +INSERT INTO dbo.dep_test_table VALUES (10, 'Charlie', 999); +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO +-- Exec with GUC on: cached parse tree is still valid (SELECT * is resolved at execution), +-- but the result should reflect the new table schema +PRINT 'exec 3-a: dep_table_proc (after table recreate, GUC on)'; +GO +EXEC dbo.dep_table_proc; +GO + +-- Now test with GUC off +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +PRINT 'exec 3-b: dep_table_proc (after table recreate, GUC off)'; +GO +EXEC dbo.dep_table_proc; +GO + + +PRINT '=== Test 11: Procedure with single OUT parameter ==='; +GO +PRINT 'exec 1: babel_6037_out_single'; +GO +DECLARE @out INT; +EXEC dbo.babel_6037_out_single @in_val = 5, @out_val = @out OUT; +SELECT @out AS out_val; +GO +PRINT 'exec 2: babel_6037_out_single'; +GO +DECLARE @out INT; +EXEC dbo.babel_6037_out_single @in_val = 100, @out_val = @out OUT; +SELECT @out AS out_val; +GO + +PRINT '=== Test 12: Procedure with multiple OUT parameters ==='; +GO +PRINT 'exec 1: babel_6037_out_multi'; +GO +DECLARE @oval INT, @omsg VARCHAR(100); +EXEC dbo.babel_6037_out_multi @in_val = 7, @out_val = @oval OUT, @out_msg = @omsg OUT; +SELECT @oval AS out_val, @omsg AS out_msg; +GO +PRINT 'exec 2: babel_6037_out_multi'; +GO +DECLARE @oval INT, @omsg VARCHAR(100); +EXEC dbo.babel_6037_out_multi @in_val = 25, @out_val = @oval OUT, @out_msg = @omsg OUT; +SELECT @oval AS out_val, @omsg AS out_msg; +GO + +PRINT '=== Test 13: MSTVF ==='; +GO +PRINT 'exec 1: babel_6037_mstvf'; +GO +SELECT * FROM dbo.babel_6037_mstvf(10); +GO +PRINT 'exec 2: babel_6037_mstvf'; +GO +SELECT * FROM dbo.babel_6037_mstvf(20); +GO + +-- === Test 14: Enable/disable cache with full signature: schema.func(argtypes) === +PRINT '=== Test 14: Per-function cache with full signature ==='; +GO + +-- Verify antlr_cache_enabled defaults to false +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO + +-- Enable cache with schema.funcname(argtypes) +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_sig(integer)', true); +GO + +-- Verify antlr_cache_enabled is now true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO + +-- Turn global GUC on and execute +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +EXEC dbo.perfunc_cache_sig @val = 4; +GO + +-- Disable cache with full signature +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_sig(integer)', false); +GO + +-- Verify antlr_cache_enabled is now false +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO + +-- Verify cache columns are NULLed out (immediate invalidation) +SELECT + CASE WHEN antlr_parse_tree_text IS NULL THEN 'NULL' ELSE 'NOT NULL' END AS tree_text, + CASE WHEN antlr_parse_tree_bbf_version IS NULL THEN 'NULL' ELSE 'NOT NULL' END AS bbf_version +FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_sig' AND nspname = 'master_dbo'; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + + +-- === Test 15: Enable/disable with schema.funcname (no args) and funcname only (dbo default) === +PRINT '=== Test 15: Per-function cache with simple name and no-schema default ==='; +GO + +-- Enable with schema.funcname (no arg types, 2-key lookup) +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_name', true); +GO + +-- Verify antlr_cache_enabled is now true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_name' AND nspname = 'master_dbo'; +GO + +-- Execute with global GUC on +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +EXEC dbo.perfunc_cache_name @val = 7; +GO + +-- Disable with schema.funcname +SELECT sys.enable_routine_parse_cache('dbo.perfunc_cache_name', false); +GO + +-- Verify disabled +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_name' AND nspname = 'master_dbo'; +GO + +-- Re-enable using just funcname (no schema, should default to dbo) +SELECT sys.enable_routine_parse_cache('perfunc_cache_name', true); +GO + +-- Verify enabled again +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_cache_name' AND nspname = 'master_dbo'; +GO + +-- Disable using just funcname +SELECT sys.enable_routine_parse_cache('perfunc_cache_name', false); +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + + +-- === Test 16: Global GUC off + per-function enable interaction === +PRINT '=== Test 16: GUC off + per-function flag interaction ==='; +GO + +-- Enable per-function cache using no-schema form +SELECT sys.enable_routine_parse_cache('perfunc_guc_override', true); +GO + +-- Verify antlr_cache_enabled is true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_guc_override' AND nspname = 'master_dbo'; +GO + +-- Keep global GUC OFF +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +-- Execute with GUC off (falls back to ANTLR, cache columns stay NULL) +EXEC dbo.perfunc_guc_override @val = 9; +GO + +-- Verify cache columns are still NULL (GUC was off during CREATE, no cache populated) +SELECT + CASE WHEN antlr_parse_tree_text IS NULL THEN 'NULL' ELSE 'NOT NULL' END AS tree_text +FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_guc_override' AND nspname = 'master_dbo'; +GO + +-- Turn GUC on, per-function flag is true, cache should get populated on exec +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +EXEC dbo.perfunc_guc_override @val = 11; +GO + +-- Turn GUC off again, per-function=true alone should still allow retrieval +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +EXEC dbo.perfunc_guc_override @val = 13; +GO + + +-- === Test 17: Default behavior (antlr_cache_enabled = false) === +PRINT '=== Test 17: Default behavior (antlr_cache_enabled = false) ==='; +GO + +-- Verify antlr_cache_enabled defaults to false +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_default_test' AND nspname = 'master_dbo'; +GO + +-- With global GUC on, the global GUC alone should still allow caching +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +EXEC dbo.perfunc_default_test @val = 1; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + + +-- === Test 18: Invalid function identifier handling === +PRINT '=== Test 18: Invalid function identifier handling ==='; +GO + +-- Non-existent function with full signature (should error) +SELECT sys.enable_routine_parse_cache('dbo.nonexistent_proc(integer)', true); +GO + +-- Non-existent function with schema.name (should error) +SELECT sys.enable_routine_parse_cache('dbo.nonexistent_proc', true); +GO + +-- Non-existent function with just name, no schema (should default to dbo and error) +SELECT sys.enable_routine_parse_cache('nonexistent_proc', true); +GO + + +-- === Test 19: ALTER procedure preserves per-function flag === +PRINT '=== Test 19: ALTER preserves per-function flag ==='; +GO + +-- Enable per-function cache +SELECT sys.enable_routine_parse_cache('dbo.perfunc_alter_test', true); +GO + +-- Turn GUC on and execute to populate cache +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +EXEC dbo.perfunc_alter_test @val = 5; +GO + +-- ALTER the procedure +ALTER PROCEDURE dbo.perfunc_alter_test + @val INT +AS +BEGIN + SELECT @val + 1000 AS altered_result; +END; +GO + +-- Verify antlr_cache_enabled is still true after ALTER +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_alter_test' AND nspname = 'master_dbo'; +GO + +-- Execute altered procedure +EXEC dbo.perfunc_alter_test @val = 5; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + + +-- === Test 20: DROP procedure removes per-function flag === +PRINT '=== Test 20: DROP removes per-function flag ==='; +GO + +-- Enable per-function cache +SELECT sys.enable_routine_parse_cache('dbo.perfunc_drop_test', true); +GO + +-- Verify antlr_cache_enabled is true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_drop_test' AND nspname = 'master_dbo'; +GO + +-- DROP the procedure +DROP PROCEDURE dbo.perfunc_drop_test; +GO + +-- Verify the babelfish_function_ext row is gone +SELECT COUNT(*) AS row_count FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_drop_test' AND nspname = 'master_dbo'; +GO + + +-- === Test 21: Custom schema test === +PRINT '=== Test 21: Custom schema per-function cache ==='; +GO + +-- Enable cache using custom schema.funcname +SELECT sys.enable_routine_parse_cache('test_cache_schema.perfunc_custom_schema', true); +GO + +-- Verify antlr_cache_enabled is true +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_custom_schema' AND nspname = 'test_cache_schema'; +GO + +-- Execute with GUC on +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +EXEC test_cache_schema.perfunc_custom_schema @val = 3; +GO + +-- Disable cache +SELECT sys.enable_routine_parse_cache('test_cache_schema.perfunc_custom_schema', false); +GO + +-- Verify disabled +SELECT antlr_cache_enabled FROM sys.babelfish_function_ext +WHERE funcname = 'perfunc_custom_schema' AND nspname = 'test_cache_schema'; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +-- Wrong schema: function exists in test_cache_schema but not in dbo (should error) +SELECT sys.enable_routine_parse_cache('dbo.perfunc_custom_schema', true); +GO + +-- Wrong schema: function exists in dbo but not in test_cache_schema (should error) +SELECT sys.enable_routine_parse_cache('test_cache_schema.perfunc_cache_sig', true); +GO + + +-- === Test 22: Parse cache validation (cached tree vs ANTLR comparison) === +PRINT '=== Test 22: Parse cache validation ==='; +GO + +-- Enable validation GUC — next cache-hit EXEC will compare cached vs ANTLR trees +SELECT set_config('babelfishpg_tsql.validate_parse_cache', 'on', false); +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +-- Execute the validation procedure (triggers cached vs ANTLR comparison) +EXEC dbo.validate_cache_proc @input = 0, @name = 'test'; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + +-- Disable validation GUC +SELECT set_config('babelfishpg_tsql.validate_parse_cache', 'off', false); +GO + + +-- === Test 23: Proc created with cache OFF, executed with cache ON === +PRINT '=== Test 23: Cache populated at EXEC time ==='; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'on', false); +GO + +-- First exec: cache is empty (created with GUC off), ANTLR parses, cache gets populated +EXEC dbo.nocache_create_proc @val = 'exec1'; +GO + +-- Second exec: should use cached result +EXEC dbo.nocache_create_proc @val = 'exec2'; +GO + +-- Verify cache was populated by the first exec +SELECT + CASE WHEN antlr_parse_tree_text IS NOT NULL THEN 'CACHED' ELSE 'NOT CACHED' END AS cache_status +FROM sys.babelfish_function_ext +WHERE funcname = 'nocache_create_proc' AND nspname = 'master_dbo'; +GO + +SELECT set_config('babelfishpg_tsql.enable_routine_parse_cache', 'off', false); +GO + + +PRINT '=== All verification tests completed ==='; +GO diff --git a/test/JDBC/upgrade/latest/schedule b/test/JDBC/upgrade/latest/schedule index a6f165165a2..13355725048 100644 --- a/test/JDBC/upgrade/latest/schedule +++ b/test/JDBC/upgrade/latest/schedule @@ -681,3 +681,4 @@ babel_convert_with_style forxml-raw-elements forxml-raw-elements-1gb-limit forxml-path-elements +BABEL-6037 diff --git a/test/python/expected/upgrade_validation/expected_dependency.out b/test/python/expected/upgrade_validation/expected_dependency.out index 483a6e7c740..eaf3da79ede 100644 --- a/test/python/expected/upgrade_validation/expected_dependency.out +++ b/test/python/expected/upgrade_validation/expected_dependency.out @@ -392,6 +392,7 @@ Function sys.dtobit(double precision) Function sys.dtofixeddecimal(double precision) Function sys.dtrunci2(double precision) Function sys.dtrunci8(double precision) +Function sys.enable_routine_parse_cache(text,boolean) Function sys.error() Function sys.error_line() Function sys.error_message()