diff --git a/contrib/babelfishpg_tsql/src/hooks.c b/contrib/babelfishpg_tsql/src/hooks.c index 29cdae90b1..5f95de9e66 100644 --- a/contrib/babelfishpg_tsql/src/hooks.c +++ b/contrib/babelfishpg_tsql/src/hooks.c @@ -133,6 +133,11 @@ static void insert_pltsql_function_defaults(HeapTuple func_tuple, List *defaults static int print_pltsql_function_arguments(StringInfo buf, HeapTuple proctup, bool print_table_args, bool print_defaults); static void pltsql_GetNewObjectId(VariableCache variableCache); static void pltsql_validate_var_datatype_scale(const TypeName *typeName, Type typ); +static bool pltsql_bbfCustomProcessUtility(ParseState *pstate, + PlannedStmt *pstmt, + const char *queryString, + ProcessUtilityContext context, + ParamListInfo params, QueryCompletion *qc); /***************************************** * Executor Hooks @@ -198,6 +203,7 @@ static validate_var_datatype_scale_hook_type prev_validate_var_datatype_scale_ho static modify_RangeTblFunction_tupdesc_hook_type prev_modify_RangeTblFunction_tupdesc_hook = NULL; static fill_missing_values_in_copyfrom_hook_type prev_fill_missing_values_in_copyfrom_hook = NULL; static check_rowcount_hook_type prev_check_rowcount_hook = NULL; +static bbfCustomProcessUtility_hook_type prev_bbfCustomProcessUtility_hook = NULL; static sortby_nulls_hook_type prev_sortby_nulls_hook = NULL; static table_variable_satisfies_visibility_hook_type prev_table_variable_satisfies_visibility = NULL; static table_variable_satisfies_update_hook_type prev_table_variable_satisfies_update = NULL; @@ -325,6 +331,9 @@ InstallExtendedHooks(void) prev_check_rowcount_hook = check_rowcount_hook; check_rowcount_hook = bbf_check_rowcount_hook; + prev_bbfCustomProcessUtility_hook = bbfCustomProcessUtility_hook; + bbfCustomProcessUtility_hook = pltsql_bbfCustomProcessUtility; + prev_sortby_nulls_hook = sortby_nulls_hook; sortby_nulls_hook = sort_nulls_first; @@ -394,6 +403,7 @@ UninstallExtendedHooks(void) modify_RangeTblFunction_tupdesc_hook = prev_modify_RangeTblFunction_tupdesc_hook; fill_missing_values_in_copyfrom_hook = prev_fill_missing_values_in_copyfrom_hook; check_rowcount_hook = prev_check_rowcount_hook; + bbfCustomProcessUtility_hook = prev_bbfCustomProcessUtility_hook; sortby_nulls_hook = prev_sortby_nulls_hook; table_variable_satisfies_visibility_hook = prev_table_variable_satisfies_visibility; table_variable_satisfies_update_hook = prev_table_variable_satisfies_update; @@ -406,6 +416,54 @@ UninstallExtendedHooks(void) /***************************************** * Hook Functions *****************************************/ +static bool +pltsql_bbfCustomProcessUtility(ParseState *pstate, PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, + ParamListInfo params, QueryCompletion *qc) +{ + Node *parsetree = pstmt->utilityStmt; + + switch (nodeTag(parsetree)) + { + case T_CreateFunctionStmt: + { + return pltsql_createFunction(pstate, pstmt, queryString, context, params); + break; + } + case T_CreatedbStmt: + { + if (sql_dialect == SQL_DIALECT_TSQL) + { + create_bbf_db(pstate, (CreatedbStmt *) parsetree); + return true; + } + break; + } + case T_DropdbStmt: + { + if (sql_dialect == SQL_DIALECT_TSQL) + { + DropdbStmt *stmt = (DropdbStmt *) parsetree; + drop_bbf_db(stmt->dbname, stmt->missing_ok, false); + return true; + } + break; + } + case T_TransactionStmt: + { + if (NestedTranCount > 0 || (sql_dialect == SQL_DIALECT_TSQL && !IsTransactionBlockActive())) + { + PLTsqlProcessTransaction(parsetree, params, qc); + return true; + } + break; + } + default: + return false; + break; + } + return false; +} + static void pltsql_GetNewObjectId(VariableCache variableCache) diff --git a/contrib/babelfishpg_tsql/src/pl_handler.c b/contrib/babelfishpg_tsql/src/pl_handler.c index b1c088f087..b757367211 100644 --- a/contrib/babelfishpg_tsql/src/pl_handler.c +++ b/contrib/babelfishpg_tsql/src/pl_handler.c @@ -1882,95 +1882,6 @@ bbf_table_var_lookup(const char *relname, Oid relnamespace) return relid; } -/* - * Transaction processing using tsql semantics - */ -static void -PLTsqlProcessTransaction(Node *parsetree, - ParamListInfo params, - QueryCompletion *qc) -{ - char *txnName = NULL; - TransactionStmt *stmt = (TransactionStmt *) parsetree; - - if (params != NULL && params->numParams > 0 && !params->params[0].isnull) - { - Oid typOutput; - bool typIsVarlena; - FmgrInfo finfo; - - Assert(params->numParams == 1); - getTypeOutputInfo(params->params[0].ptype, &typOutput, &typIsVarlena); - fmgr_info(typOutput, &finfo); - txnName = OutputFunctionCall(&finfo, params->params[0].value); - } - else - txnName = stmt->savepoint_name; - - if (txnName != NULL && strlen(txnName) > TSQL_TXN_NAME_LIMIT / 2) - ereport(ERROR, - (errcode(ERRCODE_NAME_TOO_LONG), - errmsg("Transaction name length %zu above limit %u", - strlen(txnName), TSQL_TXN_NAME_LIMIT / 2))); - - if (AbortCurTransaction) - { - if (stmt->kind == TRANS_STMT_BEGIN || - stmt->kind == TRANS_STMT_COMMIT || - stmt->kind == TRANS_STMT_SAVEPOINT) - ereport(ERROR, - (errcode(ERRCODE_TRANSACTION_ROLLBACK), - errmsg("The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction."))); - } - - switch (stmt->kind) - { - case TRANS_STMT_BEGIN: - { - PLTsqlStartTransaction(txnName); - } - break; - - case TRANS_STMT_COMMIT: - { - if (exec_state_call_stack && - exec_state_call_stack->estate && - exec_state_call_stack->estate->insert_exec && - NestedTranCount <= 1) - ereport(ERROR, - (errcode(ERRCODE_TRANSACTION_ROLLBACK), - errmsg("Cannot use the COMMIT statement within an INSERT-EXEC statement unless BEGIN TRANSACTION is used first."))); - - PLTsqlCommitTransaction(qc, stmt->chain); - } - break; - - case TRANS_STMT_ROLLBACK: - { - if (exec_state_call_stack && - exec_state_call_stack->estate && - exec_state_call_stack->estate->insert_exec) - ereport(ERROR, - (errcode(ERRCODE_TRANSACTION_ROLLBACK), - errmsg("Cannot use the ROLLBACK statement within an INSERT-EXEC statement."))); - - PLTsqlRollbackTransaction(txnName, qc, stmt->chain); - } - break; - - case TRANS_STMT_SAVEPOINT: - RequireTransactionBlock(true, "SAVEPOINT"); - DefineSavepoint(txnName); - break; - - default: - ereport(ERROR, - (errcode(ERRCODE_INVALID_TRANSACTION_INITIATION), - errmsg("Unsupported transaction command : %d", stmt->kind))); - break; - } -} - /* * It returns TRUE when we should not execute the utility statement, * e.g., CREATE FUNCTION, in EXPLAIN ONLY MODE. @@ -2141,180 +2052,6 @@ bbf_ProcessUtility(PlannedStmt *pstmt, switch (nodeTag(parsetree)) { - case T_CreateFunctionStmt: - { - CreateFunctionStmt *stmt = (CreateFunctionStmt *) parsetree; - bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND); - bool needCleanup; - ListCell *option, - *location_cell = NULL; - Node *tbltypStmt = NULL; - Node *trigStmt = NULL; - ObjectAddress tbltyp; - ObjectAddress address; - int origname_location = -1; - - /* - * All event trigger calls are done only when isCompleteQuery - * is true - */ - needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery(); - - /* - * PG_TRY block is to ensure we call - * EventTriggerEndCompleteQuery - */ - PG_TRY(); - { - if (isCompleteQuery) - EventTriggerDDLCommandStart(parsetree); - - foreach(option, stmt->options) - { - DefElem *defel = (DefElem *) lfirst(option); - - if (strcmp(defel->defname, "tbltypStmt") == 0) - { - /* - * tbltypStmt is an implicit option in tsql - * dialect, we use this mechanism to create tsql - * style multi-statement table-valued function and - * its return (table) type in one statement. - */ - tbltypStmt = defel->arg; - } - else if (strcmp(defel->defname, "trigStmt") == 0) - { - /* - * trigStmt is an implicit option in tsql dialect, - * we use this mechanism to create tsql style - * function and trigger in one statement. - */ - trigStmt = defel->arg; - } - else if (strcmp(defel->defname, "location") == 0) - { - /* - * location is an implicit option in tsql dialect, - * we use this mechanism to store location of - * function name so that we can extract original - * input function name from queryString. - */ - origname_location = intVal((Node *) defel->arg); - location_cell = option; - pfree(defel); - } - } - - /* - * delete location cell if it exists as it is for internal - * use only - */ - if (location_cell) - stmt->options = list_delete_cell(stmt->options, location_cell); - - /* - * For tbltypStmt, we need to first process the CreateStmt - * to create the type that will be used as the function's - * return type. Then, after the function is created, add a - * dependency between the type and the function. - */ - if (tbltypStmt) - { - /* Handle tbltypStmt, which is a CreateStmt */ - PlannedStmt *wrapper; - - wrapper = makeNode(PlannedStmt); - wrapper->commandType = CMD_UTILITY; - wrapper->canSetTag = false; - wrapper->utilityStmt = tbltypStmt; - wrapper->stmt_location = pstmt->stmt_location; - wrapper->stmt_len = pstmt->stmt_len; - - ProcessUtility(wrapper, - queryString, - readOnlyTree, - PROCESS_UTILITY_SUBCOMMAND, - params, - NULL, - None_Receiver, - NULL); - - /* Need CCI between commands */ - CommandCounterIncrement(); - } - - address = CreateFunction(pstate, stmt); - - /* - * Store function/procedure related metadata in babelfish - * catalog - */ - pltsql_store_func_default_positions(address, stmt->parameters, queryString, origname_location); - - if (tbltypStmt || restore_tsql_tabletype) - { - /* - * Add internal dependency between the table type and - * the function. - */ - tbltyp.classId = TypeRelationId; - tbltyp.objectId = typenameTypeId(pstate, - stmt->returnType); - tbltyp.objectSubId = 0; - recordDependencyOn(&tbltyp, &address, DEPENDENCY_INTERNAL); - } - - /* - * For trigStmt, we need to process the CreateTrigStmt - * after the function is created, and record bidirectional - * dependency so that Drop Trigger CASCADE will drop the - * implicit trigger function. Create trigger takes care of - * dependency addition. - */ - if (trigStmt) - { - (void) CreateTrigger((CreateTrigStmt *) trigStmt, - pstate->p_sourcetext, InvalidOid, InvalidOid, - InvalidOid, InvalidOid, address.objectId, - InvalidOid, NULL, false, false); - } - - /* - * Remember the object so that ddl_command_end event - * triggers have access to it. - */ - EventTriggerCollectSimpleCommand(address, InvalidObjectAddress, - parsetree); - - if (isCompleteQuery) - { - EventTriggerSQLDrop(parsetree); - EventTriggerDDLCommandEnd(parsetree); - } - - } - PG_CATCH(); - { - if (needCleanup) - EventTriggerEndCompleteQuery(); - PG_RE_THROW(); - } - PG_END_TRY(); - - if (needCleanup) - EventTriggerEndCompleteQuery(); - return; - } - case T_TransactionStmt: - { - if (NestedTranCount > 0 || (sql_dialect == SQL_DIALECT_TSQL && !IsTransactionBlockActive())) - { - PLTsqlProcessTransaction(parsetree, params, qc); - return; - } - break; - } case T_TruncateStmt: { if (sql_dialect == SQL_DIALECT_TSQL) @@ -3297,22 +3034,6 @@ bbf_ProcessUtility(PlannedStmt *pstmt, return; } } - case T_CreatedbStmt: - if (sql_dialect == SQL_DIALECT_TSQL) - { - create_bbf_db(pstate, (CreatedbStmt *) parsetree); - return; - } - break; - case T_DropdbStmt: - if (sql_dialect == SQL_DIALECT_TSQL) - { - DropdbStmt *stmt = (DropdbStmt *) parsetree; - - drop_bbf_db(stmt->dbname, stmt->missing_ok, false); - return; - } - break; case T_GrantRoleStmt: if (sql_dialect == SQL_DIALECT_TSQL) { diff --git a/contrib/babelfishpg_tsql/src/pltsql.h b/contrib/babelfishpg_tsql/src/pltsql.h index 647ab41b31..dcfde38c93 100644 --- a/contrib/babelfishpg_tsql/src/pltsql.h +++ b/contrib/babelfishpg_tsql/src/pltsql.h @@ -31,6 +31,7 @@ #include "utils/plancache.h" #include "utils/portal.h" #include "utils/typcache.h" +#include "tcop/utility.h" #include "dynavec.h" #include "dynastack.h" @@ -1975,6 +1976,10 @@ extern void pltsql_read_procedure_info(StringInfo inout_str, Oid *atttypid, Oid *atttypmod, int *attcollation); +void PLTsqlProcessTransaction(Node *parsetree, + ParamListInfo params, + QueryCompletion *qc); + extern void PLTsqlStartTransaction(char *txnName); extern void PLTsqlCommitTransaction(QueryCompletion *qc, bool chain); @@ -2016,6 +2021,8 @@ extern void remove_trailing_spaces(char *name); extern Oid tsql_get_proc_nsp_oid(Oid object_id); extern Oid tsql_get_constraint_nsp_oid(Oid object_id, Oid user_id); extern Oid tsql_get_trigger_rel_oid(Oid object_id); +extern bool pltsql_createFunction(ParseState *pstate, PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, + ParamListInfo params); typedef struct { diff --git a/contrib/babelfishpg_tsql/src/pltsql_utils.c b/contrib/babelfishpg_tsql/src/pltsql_utils.c index d3c6f3a84f..7a9226d553 100644 --- a/contrib/babelfishpg_tsql/src/pltsql_utils.c +++ b/contrib/babelfishpg_tsql/src/pltsql_utils.c @@ -22,6 +22,8 @@ #include "access/table.h" #include "access/genam.h" #include "catalog.h" +#include "parser/gramparse.h" +#include "hooks.h" #include "tcop/utility.h" #include "multidb.h" @@ -36,6 +38,12 @@ bool is_tsql_any_char_datatype(Oid oid); /* sys.char / sys.nchar / * sys.varchar / sys.nvarchar */ bool is_tsql_text_ntext_or_image_datatype(Oid oid); +bool +pltsql_createFunction(ParseState *pstate, PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, + ParamListInfo params); + +extern bool restore_tsql_tabletype; + /* * Following the rule for locktag fields of advisory locks: * field1: MyDatabaseId ... ensures locks are local to each database @@ -51,6 +59,279 @@ const uint64 PLTSQL_LOCKTAG_OFFSET = 0xABCDEF; (uint32) ((((int64) key16) + PLTSQL_LOCKTAG_OFFSET) >> 32), \ (uint32) (((int64) key16) + PLTSQL_LOCKTAG_OFFSET), \ 3) +/* + * Transaction processing using tsql semantics + */ +void +PLTsqlProcessTransaction(Node *parsetree, + ParamListInfo params, + QueryCompletion *qc) +{ + char *txnName = NULL; + TransactionStmt *stmt = (TransactionStmt *) parsetree; + + if (params != NULL && params->numParams > 0 && !params->params[0].isnull) + { + Oid typOutput; + bool typIsVarlena; + FmgrInfo finfo; + + Assert(params->numParams == 1); + getTypeOutputInfo(params->params[0].ptype, &typOutput, &typIsVarlena); + fmgr_info(typOutput, &finfo); + txnName = OutputFunctionCall(&finfo, params->params[0].value); + } + else + txnName = stmt->savepoint_name; + + if (txnName != NULL && strlen(txnName) > TSQL_TXN_NAME_LIMIT / 2) + ereport(ERROR, + (errcode(ERRCODE_NAME_TOO_LONG), + errmsg("Transaction name length %zu above limit %u", + strlen(txnName), TSQL_TXN_NAME_LIMIT / 2))); + + if (AbortCurTransaction) + { + if (stmt->kind == TRANS_STMT_BEGIN || + stmt->kind == TRANS_STMT_COMMIT || + stmt->kind == TRANS_STMT_SAVEPOINT) + ereport(ERROR, + (errcode(ERRCODE_TRANSACTION_ROLLBACK), + errmsg("The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction."))); + } + + switch (stmt->kind) + { + case TRANS_STMT_BEGIN: + { + PLTsqlStartTransaction(txnName); + } + break; + + case TRANS_STMT_COMMIT: + { + if (exec_state_call_stack && + exec_state_call_stack->estate && + exec_state_call_stack->estate->insert_exec && + NestedTranCount <= 1) + ereport(ERROR, + (errcode(ERRCODE_TRANSACTION_ROLLBACK), + errmsg("Cannot use the COMMIT statement within an INSERT-EXEC statement unless BEGIN TRANSACTION is used first."))); + + PLTsqlCommitTransaction(qc, stmt->chain); + } + break; + + case TRANS_STMT_ROLLBACK: + { + if (exec_state_call_stack && + exec_state_call_stack->estate && + exec_state_call_stack->estate->insert_exec) + ereport(ERROR, + (errcode(ERRCODE_TRANSACTION_ROLLBACK), + errmsg("Cannot use the ROLLBACK statement within an INSERT-EXEC statement."))); + PLTsqlRollbackTransaction(txnName, qc, stmt->chain); + } + break; + + case TRANS_STMT_SAVEPOINT: + RequireTransactionBlock(true, "SAVEPOINT"); + DefineSavepoint(txnName); + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_TRANSACTION_INITIATION), + errmsg("Unsupported transaction command : %d", stmt->kind))); + break; + } +} + + +bool +pltsql_createFunction(ParseState *pstate, PlannedStmt *pstmt, const char *queryString, ProcessUtilityContext context, + ParamListInfo params) +{ + Node *parsetree = pstmt->utilityStmt; + CreateFunctionStmt *stmt = (CreateFunctionStmt *)parsetree; + ListCell *option, *location_cell = NULL; + DefElem *language_item = NULL; + char *language = NULL; + ObjectAddress address; + bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND); + bool needCleanup; + Node *tbltypStmt = NULL; + Node *trigStmt = NULL; + ObjectAddress tbltyp; + int origname_location = -1; + + pstate->p_sourcetext = queryString; + + foreach(option, stmt->options) + { + DefElem *defel = (DefElem *)lfirst(option); + + if (strcmp(defel->defname, "language") == 0) + { + if (language_item) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"), + parser_errposition(pstate, defel->location))); + language_item = defel; + } + } + + if (language_item) + language = strVal(language_item->arg); + + if(!((language && !strcmp(language,"pltsql")) || sql_dialect == SQL_DIALECT_TSQL)) + { + return false; + } + else + { + /* All event trigger calls are done only when isCompleteQuery is true */ + needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery(); + + /* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */ + PG_TRY(); + { + if (isCompleteQuery) + EventTriggerDDLCommandStart(parsetree); + + foreach (option, stmt->options) + { + DefElem *defel = (DefElem *)lfirst(option); + if (strcmp(defel->defname, "tbltypStmt") == 0) + { + /* + * tbltypStmt is an implicit option in tsql dialect, + * we use this mechanism to create tsql style + * multi-statement table-valued function and its + * return (table) type in one statement. + */ + tbltypStmt = defel->arg; + } + else if (strcmp(defel->defname, "trigStmt") == 0) + { + /* + * trigStmt is an implicit option in tsql dialect, + * we use this mechanism to create tsql style function + * and trigger in one statement. + */ + trigStmt = defel->arg; + } + else if (strcmp(defel->defname, "location") == 0) + { + /* + * location is an implicit option in tsql dialect, + * we use this mechanism to store location of function + * name so that we can extract original input function + * name from queryString. + */ + origname_location = intVal((Node *)defel->arg); + location_cell = option; + pfree(defel); + } + } + + /* delete location cell if it exists as it is for internal use only */ + if (location_cell) + stmt->options = list_delete_cell(stmt->options, location_cell); + + /* + * For tbltypStmt, we need to first process the CreateStmt + * to create the type that will be used as the function's + * return type. Then, after the function is created, add a + * dependency between the type and the function. + */ + if (tbltypStmt) + { + /* Handle tbltypStmt, which is a CreateStmt */ + PlannedStmt *wrapper; + + wrapper = makeNode(PlannedStmt); + wrapper->commandType = CMD_UTILITY; + wrapper->canSetTag = false; + wrapper->utilityStmt = tbltypStmt; + wrapper->stmt_location = pstmt->stmt_location; + wrapper->stmt_len = pstmt->stmt_len; + + ProcessUtility(wrapper, + queryString, + false, + PROCESS_UTILITY_SUBCOMMAND, + params, + NULL, + None_Receiver, + NULL); + + /* Need CCI between commands */ + CommandCounterIncrement(); + } + + address = CreateFunction(pstate, stmt); + + /* Store function/procedure related metadata in babelfish catalog */ + pltsql_store_func_default_positions(address, stmt->parameters, queryString, origname_location); + + if (tbltypStmt || restore_tsql_tabletype) + { + /* + * Add internal dependency between the table type and + * the function. + */ + tbltyp.classId = TypeRelationId; + tbltyp.objectId = typenameTypeId(pstate, + stmt->returnType); + tbltyp.objectSubId = 0; + recordDependencyOn(&tbltyp, &address, DEPENDENCY_INTERNAL); + } + + /* + * For trigStmt, we need to process the CreateTrigStmt after + * the function is created, and record bidirectional + * dependency so that Drop Trigger CASCADE will drop the + * implicit trigger function. + * Create trigger takes care of dependency addition. + */ + if (trigStmt) + { + (void)CreateTrigger((CreateTrigStmt *)trigStmt, + pstate->p_sourcetext, InvalidOid, InvalidOid, + InvalidOid, InvalidOid, address.objectId, + InvalidOid, NULL, false, false); + } + + /* + * Remember the object so that ddl_command_end event triggers have + * access to it. + */ + EventTriggerCollectSimpleCommand(address, InvalidObjectAddress, + parsetree); + + if (isCompleteQuery) + { + EventTriggerSQLDrop(parsetree); + EventTriggerDDLCommandEnd(parsetree); + } + } + + PG_CATCH(); + { + if (needCleanup) + EventTriggerEndCompleteQuery(); + PG_RE_THROW(); + } + PG_END_TRY(); + + if (needCleanup) + EventTriggerEndCompleteQuery(); + + return true; + } +} /* * Setup default typmod for sys types/domains when typmod isn't specified