From c6a05e35f125a3ff7543bab68076d4685971f9de Mon Sep 17 00:00:00 2001 From: Shard Gupta <40919925+shardgupta@users.noreply.github.com> Date: Wed, 4 Sep 2024 21:18:17 +0530 Subject: [PATCH 1/5] [OSS-ONLY] Upgrade System.Data.SqlClient version for fix depedabot warning (#2910) Task: BABEL-OSS Signed-off-by: Kuntal Ghosh --- test/dotnet/dotnet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/dotnet/dotnet.csproj b/test/dotnet/dotnet.csproj index c5e9aed6b8..a45cff35d1 100644 --- a/test/dotnet/dotnet.csproj +++ b/test/dotnet/dotnet.csproj @@ -10,7 +10,7 @@ - + From 0825299c98af156256d44d069623f1e547a9a294 Mon Sep 17 00:00:00 2001 From: Nirmit Shah <44708230+shah-nirmit@users.noreply.github.com> Date: Thu, 5 Sep 2024 11:55:11 +0530 Subject: [PATCH 2/5] Fix tsql_stat_get_activity() and tsql_read_current_status() to consider only valid tds backends (#2905) Fixed the tsql_read_current_status to increment LocalNumBackends only when it has a valid pid, also fixed tsql_stat_get_activity to use numbackends as calculated by TDS extension insteand of pg_stat_activity. Task: BABEL-5219 Signed-off-by: Nirmit Shah --- contrib/babelfishpg_tds/src/backend/tds/tds.c | 21 +- .../babelfishpg_tds/src/backend/tds/tds_srv.c | 1 + contrib/babelfishpg_tds/src/include/tds_int.h | 1 + contrib/babelfishpg_tsql/runtime/functions.c | 5 +- contrib/babelfishpg_tsql/src/pltsql.h | 1 + test/JDBC/expected/GRANT_SCHEMA.out | 4 +- test/JDBC/expected/kill-vu-cleanup.out | 26 + test/JDBC/expected/kill-vu-verify.out | 325 +++++++ test/JDBC/expected/single_db/GRANT_SCHEMA.out | 4 +- .../expected/single_db/kill-vu-cleanup.out | 85 ++ .../expected/single_db/kill-vu-verify.out | 865 ++++++++++++++++++ test/JDBC/input/GRANT_SCHEMA.mix | 2 +- test/JDBC/input/kill-vu-cleanup.mix | 27 + test/JDBC/input/kill-vu-verify.mix | 179 ++++ 14 files changed, 1538 insertions(+), 8 deletions(-) create mode 100644 test/JDBC/expected/single_db/kill-vu-cleanup.out create mode 100644 test/JDBC/expected/single_db/kill-vu-verify.out diff --git a/contrib/babelfishpg_tds/src/backend/tds/tds.c b/contrib/babelfishpg_tds/src/backend/tds/tds.c index 82f9fd8ef4..52772244a0 100644 --- a/contrib/babelfishpg_tds/src/backend/tds/tds.c +++ b/contrib/babelfishpg_tds/src/backend/tds/tds.c @@ -594,6 +594,21 @@ tdsstat_fetch_stat_local_tdsentry(int beid) return localentry; } +/* ---------- + * tdsstat_fetch_stat_numbackends() - + * + * Support function for the SQL-callable pgstat* functions. Returns + * the number of sessions known in the localTdsStatusTable, i.e. + * the maximum 1-based index to pass to tdsstat_fetch_stat_local_tdsentry(). + * ---------- + */ +int +tdsstat_fetch_stat_numbackends(void) +{ + tdsstat_read_current_status(); + return localNumBackends; +} + /* ---------- * tdsstat_read_current_status() - * @@ -735,14 +750,16 @@ tdsstat_read_current_status(void) /* Only valid entries get included into the local array */ if (localentry->tdsStatus.st_procpid > 0) + { BackendIdGetTransactionIds(i, &localentry->backend_xid, &localentry->backend_xmin, &localentry->backend_subxact_count, &localentry->backend_subxact_overflowed); - localentry++; - localNumBackends++; + localentry++; + localNumBackends++; + } } localTdsStatusTable = localtable; diff --git a/contrib/babelfishpg_tds/src/backend/tds/tds_srv.c b/contrib/babelfishpg_tds/src/backend/tds/tds_srv.c index 63ce388c47..9a80d6a3a6 100644 --- a/contrib/babelfishpg_tds/src/backend/tds/tds_srv.c +++ b/contrib/babelfishpg_tds/src/backend/tds/tds_srv.c @@ -196,6 +196,7 @@ pe_tds_init(void) pltsql_plugin_handler_ptr->get_tds_database_backend_count = &get_tds_database_backend_count; pltsql_plugin_handler_ptr->get_stat_values = &tds_stat_get_activity; pltsql_plugin_handler_ptr->invalidate_stat_view = &invalidate_stat_table; + pltsql_plugin_handler_ptr->get_tds_numbackends = &tdsstat_fetch_stat_numbackends; pltsql_plugin_handler_ptr->get_host_name = &get_tds_host_name; pltsql_plugin_handler_ptr->get_client_pid = &get_tds_client_pid; pltsql_plugin_handler_ptr->get_context_info = &get_tds_context_info; diff --git a/contrib/babelfishpg_tds/src/include/tds_int.h b/contrib/babelfishpg_tds/src/include/tds_int.h index 4c5ec4d111..0b12085292 100644 --- a/contrib/babelfishpg_tds/src/include/tds_int.h +++ b/contrib/babelfishpg_tds/src/include/tds_int.h @@ -338,6 +338,7 @@ extern void TdsSetAtAtStatVariable(TdsAtAtVarType at_at_var, int intVal, uint64 extern void TdsSetDatabaseStatVariable(int16 db_id); extern bool get_tds_database_backend_count(int16 db_id, bool ignore_current_connection); extern bool tds_stat_get_activity(Datum *values, bool *nulls, int len, int pid, int curr_backend); +extern int tdsstat_fetch_stat_numbackends(void); extern void invalidate_stat_table(void); extern char *get_tds_host_name(void); extern uint32_t get_tds_client_pid(void); diff --git a/contrib/babelfishpg_tsql/runtime/functions.c b/contrib/babelfishpg_tsql/runtime/functions.c index 4f34ea8106..60284758fb 100644 --- a/contrib/babelfishpg_tsql/runtime/functions.c +++ b/contrib/babelfishpg_tsql/runtime/functions.c @@ -1747,7 +1747,7 @@ Datum tsql_stat_get_activity(PG_FUNCTION_ARGS) { Oid sysadmin_oid = get_role_oid("sysadmin", false); - int num_backends = pgstat_fetch_stat_numbackends(); + int num_backends = 0; int curr_backend; char *view_name = text_to_cstring(PG_GETARG_TEXT_PP(0)); int pid = -1; @@ -1833,6 +1833,9 @@ tsql_stat_get_activity(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); + if (*pltsql_protocol_plugin_ptr && (*pltsql_protocol_plugin_ptr)->get_tds_numbackends) + num_backends = (*pltsql_protocol_plugin_ptr)->get_tds_numbackends(); + /* 1-based index */ for (curr_backend = 1; curr_backend <= num_backends; curr_backend++) { diff --git a/contrib/babelfishpg_tsql/src/pltsql.h b/contrib/babelfishpg_tsql/src/pltsql.h index 4849535a7a..e9923f2fb1 100644 --- a/contrib/babelfishpg_tsql/src/pltsql.h +++ b/contrib/babelfishpg_tsql/src/pltsql.h @@ -1693,6 +1693,7 @@ typedef struct PLtsql_protocol_plugin bool (*get_tds_database_backend_count) (int16 db_id, bool ignore_current_connection); bool (*get_stat_values) (Datum *values, bool *nulls, int len, int pid, int curr_backend); void (*invalidate_stat_view) (void); + int (*get_tds_numbackends) (void); char *(*get_host_name) (void); uint32_t (*get_client_pid) (void); Datum (*get_datum_from_byte_ptr) (StringInfo buf, int datatype, int scale); diff --git a/test/JDBC/expected/GRANT_SCHEMA.out b/test/JDBC/expected/GRANT_SCHEMA.out index f15ceda260..f8d07063ee 100644 --- a/test/JDBC/expected/GRANT_SCHEMA.out +++ b/test/JDBC/expected/GRANT_SCHEMA.out @@ -6073,12 +6073,12 @@ go -- psql -- should have 2 entries for master and babel_4344_d1 databases -select schema_name, object_name, permission, grantee from sys.babelfish_schema_permissions where object_name = 'babel_4344_t1'; +select schema_name, object_name, permission, grantee from sys.babelfish_schema_permissions where object_name = 'babel_4344_t1' ORDER BY grantee; go ~~START~~ "sys"."varchar"#!#"sys"."varchar"#!#int4#!#"sys"."varchar" -dbo#!#babel_4344_t1#!#2#!#master_guest dbo#!#babel_4344_t1#!#2#!#babel_4344_d1_guest +dbo#!#babel_4344_t1#!#2#!#master_guest ~~END~~ diff --git a/test/JDBC/expected/kill-vu-cleanup.out b/test/JDBC/expected/kill-vu-cleanup.out index 3c34eb7d8a..856c606229 100644 --- a/test/JDBC/expected/kill-vu-cleanup.out +++ b/test/JDBC/expected/kill-vu-cleanup.out @@ -17,3 +17,29 @@ go DROP TABLE tab_kill_test go +DROP Database test_kill_db1 +GO +DROP Database test_kill_db2 +GO +DROP Database test_kill_db3 +GO +DROP Database test_kill_db4 +GO +DROP Database test_kill_db5 +GO +DROP Database test_kill_db6 +GO +DROP Database test_kill_db7 +GO +DROP Database test_kill_db8 +GO +DROP Database test_kill_db9 +GO +DROP Database test_kill_db10 +GO +DROP Database test_kill_db11 +GO + +DROP LOGIN test_kill; +GO + diff --git a/test/JDBC/expected/kill-vu-verify.out b/test/JDBC/expected/kill-vu-verify.out index 71b11a7da9..ec919836f7 100644 --- a/test/JDBC/expected/kill-vu-verify.out +++ b/test/JDBC/expected/kill-vu-verify.out @@ -438,3 +438,328 @@ go int ~~END~~ + +-- tsql +-- BABEL-5219 Multiple Session Kill +CREATE LOGIN test_kill WITH PASSWORD = '12345678' +GO + +-- CREATE 11 Databases +create database test_kill_db1 +GO +use test_kill_db1 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db2 +GO +use test_kill_db2 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db3 +GO +use test_kill_db3 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db4 +GO +use test_kill_db4 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db5 +GO +use test_kill_db5 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db6 +GO +use test_kill_db6 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db7 +GO +use test_kill_db7 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db8 +GO +use test_kill_db8 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db9 +GO +use test_kill_db9 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + + +create database test_kill_db10 +GO +use test_kill_db10 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db11 +GO +use test_kill_db11 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +-- tsql user=test_kill password=12345678 database=test_kill_db1 +-- CREATE 11 sessions +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db2 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db3 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db4 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db5 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db6 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db7 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db8 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db9 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db10 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db11 +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql +SELECT count(*) from sys.dm_exec_sessions where login_name = 'test_kill' +GO +~~START~~ +int +11 +~~END~~ + + +SELECT COUNT(*) from pg_stat_activity where usename = 'test_kill' +GO +~~START~~ +int +11 +~~END~~ + + +-- Kill Sessions +USE [MASTER] +DECLARE @UserName NVARCHAR(255) = 'test_kill' +DECLARE @SPID INT +-- Creates temp table to store SPIDs +IF OBJECT_ID('tempdb..#UserSessions') IS NOT NULL +BEGIN +-- dropa tabela se existir +DROP TABLE #UserSessions +END +CREATE TABLE #UserSessions (SPID INT) +-- Inserting SPIDs from User Sessions into the Temporary Table +INSERT INTO #UserSessions (SPID) +SELECT session_id +FROM sys.dm_exec_sessions +WHERE login_name = @UserName +-- Loop to kill user sessions +DECLARE @RowCount INT = (SELECT COUNT(*) FROM #UserSessions) +DECLARE @Counter INT = 1 +DECLARE @CmdKill NVARCHAR(100) +WHILE @Counter <= @RowCount +BEGIN +SELECT TOP 1 @SPID = SPID FROM #UserSessions +SET @CmdKill = 'KILL ' + CAST(@SPID AS NVARCHAR(10)) +execute(@CmdKill) +DELETE FROM #UserSessions WHERE SPID = @SPID +SET @Counter = @Counter + 1 +END +-- Temporary Table Cleaning +DROP TABLE #UserSessions +GO +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 11~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + + +-- allow the kill to complete; this might take a while under heavy workload +exec pg_sleep 5 +go + +SELECT count(*) from sys.dm_exec_sessions where login_name = 'test_kill' +GO +~~START~~ +int +0 +~~END~~ + + +SELECT COUNT(*) from pg_stat_activity where usename = 'test_kill' +GO +~~START~~ +int +0 +~~END~~ + diff --git a/test/JDBC/expected/single_db/GRANT_SCHEMA.out b/test/JDBC/expected/single_db/GRANT_SCHEMA.out index cedc4a6e56..119c2d1e1c 100644 --- a/test/JDBC/expected/single_db/GRANT_SCHEMA.out +++ b/test/JDBC/expected/single_db/GRANT_SCHEMA.out @@ -6073,12 +6073,12 @@ go -- psql -- should have 2 entries for master and babel_4344_d1 databases -select schema_name, object_name, permission, grantee from sys.babelfish_schema_permissions where object_name = 'babel_4344_t1'; +select schema_name, object_name, permission, grantee from sys.babelfish_schema_permissions where object_name = 'babel_4344_t1' ORDER BY grantee; go ~~START~~ "sys"."varchar"#!#"sys"."varchar"#!#int4#!#"sys"."varchar" -dbo#!#babel_4344_t1#!#2#!#master_guest dbo#!#babel_4344_t1#!#2#!#babel_4344_d1_guest +dbo#!#babel_4344_t1#!#2#!#master_guest ~~END~~ diff --git a/test/JDBC/expected/single_db/kill-vu-cleanup.out b/test/JDBC/expected/single_db/kill-vu-cleanup.out new file mode 100644 index 0000000000..65fa5863e2 --- /dev/null +++ b/test/JDBC/expected/single_db/kill-vu-cleanup.out @@ -0,0 +1,85 @@ +-- tsql +DROP LOGIN victim_user_tds +go + +DROP TABLE tab_kill_spid +go + +DROP PROCEDURE kill_proc_1 +go + +DROP PROCEDURE kill_proc_2 +go + +DROP PROCEDURE kill_proc_3 +go + +DROP TABLE tab_kill_test +go + +DROP Database test_kill_db1 +GO +DROP Database test_kill_db2 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db2" does not exist)~~ + +DROP Database test_kill_db3 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db3" does not exist)~~ + +DROP Database test_kill_db4 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db4" does not exist)~~ + +DROP Database test_kill_db5 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db5" does not exist)~~ + +DROP Database test_kill_db6 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db6" does not exist)~~ + +DROP Database test_kill_db7 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db7" does not exist)~~ + +DROP Database test_kill_db8 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db8" does not exist)~~ + +DROP Database test_kill_db9 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db9" does not exist)~~ + +DROP Database test_kill_db10 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db10" does not exist)~~ + +DROP Database test_kill_db11 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db11" does not exist)~~ + + +DROP LOGIN test_kill; +GO + diff --git a/test/JDBC/expected/single_db/kill-vu-verify.out b/test/JDBC/expected/single_db/kill-vu-verify.out new file mode 100644 index 0000000000..f88e73fb6a --- /dev/null +++ b/test/JDBC/expected/single_db/kill-vu-verify.out @@ -0,0 +1,865 @@ +-- tsql +create table tab_kill_spid(spid int) +go +create login victim_user_tds with password = '12345678'; +go + +-- tsql user=victim_user_tds password=12345678 +select 1 +go +~~START~~ +int +1 +~~END~~ + +-- not allowed: no sysadmin role +KILL 1 +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: User does not have permission to use the KILL statement)~~ + + +-- tsql user=jdbc_user password=12345678 +/* find a TDS session that is not the current one */ +declare @victim_user_tds int +declare @sql varchar(20) +select top 1 @victim_user_tds = session_id from sys.dm_exec_sessions where login_name = 'victim_user_tds' and session_id <> @@spid +if @victim_user_tds is null +begin +print 'ERROR: no victim spid found' +end +insert tab_kill_spid values(@victim_user_tds) +set @sql = 'kill ' + convert(varchar, @victim_user_tds) +execute(@sql) +go +~~ROW COUNT: 1~~ + + +-- allow the kill to complete; this might take a while under heavy workload +exec pg_sleep 10 +go + +-- verify session does not exist anymore +declare @victim_user_tds int +select @victim_user_tds = spid from tab_kill_spid +select count(distinct session_id) from sys.dm_exec_sessions where session_id = @victim_user_tds +go +~~START~~ +int +0 +~~END~~ + + + +/* Cannot currently be tested: trying to kill a PG session + * Reason is that the error message contains the spid number, which is not predicatable + * and will therefore always lead to a test failure + */ +go + +-- KILL in a procedure: allowed +CREATE PROC kill_proc_1 +as +KILL 1 +go +EXECUTE kill_proc_1 +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Process ID 1 is not an active process ID)~~ + + +-- KILL not allowed in a SQL function: not allowed +CREATE FUNCTION kill_func() RETURNS INT +AS +BEGIN +KILL 1 +END +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Invalid use of a side-effecting operator 'KILL' within a function.)~~ + + +-- try to kill current TDS session +declare @sql varchar(100) +set @sql = 'kill ' + convert(varchar, @@spid) +execute(@sql) +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Cannot use KILL to kill your own process.)~~ + + +-- kill non-existing spid +KILL 1 +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Process ID 1 is not an active process ID)~~ + + +KILL -123 +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: syntax error near '-' at line 1 and character position 5)~~ + + +KILL 999999999999999999999999999999999999999999999 +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Session ID -1 is not valid)~~ + + +KILL 123 WITH STATUSONLY +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'KILL with STATUSONLY' is not currently supported in Babelfish)~~ + + +KILL UOW WITH STATUSONLY +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'KILL with STATUSONLY' is not currently supported in Babelfish)~~ + + +KILL 'A0499C66-F938-45CA-BF7E-E2B6194B48CF' +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'KILL with a session ID string' is not currently supported in Babelfish)~~ + + +KILL 'A0499C66-F938-45CA-BF7E-E2B6194B48CF' with statusonly +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'KILL with STATUSONLY' is not currently supported in Babelfish)~~ + + +KILL STATS JOB 123 +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'KILL with STATS JOB' is not currently supported in Babelfish)~~ + + +KILL QUERY NOTIFICATION SUBSCRIPTION ALL +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'KILL with QUERY NOTIFICATION' is not currently supported in Babelfish)~~ + + +KILL QUERY NOTIFICATION SUBSCRIPTION 123 +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'KILL with QUERY NOTIFICATION' is not currently supported in Babelfish)~~ + + + +-- error semantics tests: +-- basic: cannot use KILL inside a transaction +BEGIN TRAN +go +KILL 1 +go +~~ERROR (Code: 6615)~~ + +~~ERROR (Message: KILL command cannot be used inside user transactions.)~~ + +ROLLBACK +go + +CREATE TABLE tab_kill_test(a INT) +go + +-- cannot use KILL in a transaction, XACT_ABORT=OFF +SET XACT_ABORT OFF +go +BEGIN TRANSACTION +INSERT tab_kill_test values(1) +go +~~ROW COUNT: 1~~ + +PRINT 'before kill' +INSERT tab_kill_test values(2) +KILL 1 +PRINT 'after kill' +INSERT tab_kill_test values(3) +go +~~WARNING (Code: 0)~~ + +~~WARNING (Message: before kill Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~ERROR (Code: 6615)~~ + +~~ERROR (Message: KILL command cannot be used inside user transactions.)~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: before kill Server SQLState: S0001)~~~~WARNING (Message: after kill Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +SELECT @@TRANCOUNT +go +~~START~~ +int +1 +~~END~~ + +SELECT * FROM tab_kill_test +go +~~START~~ +int +1 +2 +3 +~~END~~ + +ROLLBACK +go + +-- cannot use KILL in a transaction, XACT_ABORT=ON +SET XACT_ABORT ON +go +BEGIN TRANSACTION +INSERT tab_kill_test values(1) +go +~~ROW COUNT: 1~~ + +PRINT 'before kill' +INSERT tab_kill_test values(2) +KILL 1 +PRINT 'after kill' +INSERT tab_kill_test values(3) +go +~~WARNING (Code: 0)~~ + +~~WARNING (Message: before kill Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~ERROR (Code: 6615)~~ + +~~ERROR (Message: KILL command cannot be used inside user transactions.)~~ + +SELECT @@TRANCOUNT +go +~~START~~ +int +0 +~~END~~ + +SELECT * FROM tab_kill_test +go +~~START~~ +int +~~END~~ + + +-- batch is not aborted when KILL in transaction, XACT_ABORT=OFF +SET XACT_ABORT OFF +go +BEGIN TRANSACTION +INSERT tab_kill_test values(1) +go +~~ROW COUNT: 1~~ + +PRINT 'before kill' +INSERT tab_kill_test values(2) +KILL 1 +PRINT 'after kill' +INSERT tab_kill_test values(3) +go +~~WARNING (Code: 0)~~ + +~~WARNING (Message: before kill Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~ERROR (Code: 6615)~~ + +~~ERROR (Message: KILL command cannot be used inside user transactions.)~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: before kill Server SQLState: S0001)~~~~WARNING (Message: after kill Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +SELECT @@TRANCOUNT +go +~~START~~ +int +1 +~~END~~ + +SELECT * FROM tab_kill_test +go +~~START~~ +int +1 +2 +3 +~~END~~ + +ROLLBACK +go + +-- respects XACT_ABORT=ON +SET XACT_ABORT ON +go +BEGIN TRANSACTION +INSERT tab_kill_test values(1) +go +~~ROW COUNT: 1~~ + +PRINT 'before kill' +INSERT tab_kill_test values(2) +KILL 1 +PRINT 'after kill' +INSERT tab_kill_test values(3) +go +~~WARNING (Code: 0)~~ + +~~WARNING (Message: before kill Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~ERROR (Code: 6615)~~ + +~~ERROR (Message: KILL command cannot be used inside user transactions.)~~ + +SELECT @@TRANCOUNT +go +~~START~~ +int +0 +~~END~~ + +SELECT * FROM tab_kill_test +go +~~START~~ +int +~~END~~ + + +-- KILL in procedure, XACT_ABORT=OFF +CREATE PROCEDURE kill_proc_2 +AS +BEGIN +PRINT 'before kill' +INSERT tab_kill_test values(2) +KILL 1 +PRINT 'after kill' +INSERT tab_kill_test values(3) +END +go +SET XACT_ABORT OFF +go +BEGIN TRANSACTION +INSERT tab_kill_test values(1) +EXECUTE kill_proc_2 +go +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ERROR (Code: 6615)~~ + +~~ERROR (Message: KILL command cannot be used inside user transactions.)~~ + +~~ROW COUNT: 1~~ + +SELECT @@TRANCOUNT +go +~~START~~ +int +1 +~~END~~ + +SELECT * FROM tab_kill_test +go +~~START~~ +int +1 +2 +3 +~~END~~ + +ROLLBACK +go + +-- KILL in procedure, XACT_ABORT=ON +CREATE PROCEDURE kill_proc_3 +AS +BEGIN +PRINT 'before kill' +INSERT tab_kill_test values(2) +KILL 1 +PRINT 'after kill' +INSERT tab_kill_test values(3) +END +go +SET XACT_ABORT ON +go +BEGIN TRANSACTION +INSERT tab_kill_test values(1) +EXECUTE kill_proc_3 +go +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ERROR (Code: 6615)~~ + +~~ERROR (Message: KILL command cannot be used inside user transactions.)~~ + +SELECT @@TRANCOUNT +go +~~START~~ +int +0 +~~END~~ + +SELECT * FROM tab_kill_test +go +~~START~~ +int +~~END~~ + + +-- tsql +-- BABEL-5219 Multiple Session Kill +CREATE LOGIN test_kill WITH PASSWORD = '12345678' +GO + +-- CREATE 11 Databases +create database test_kill_db1 +GO +use test_kill_db1 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db2 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db2 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db2" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db3 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db3 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db3" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db4 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db4 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db4" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db5 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db5 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db5" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db6 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db6 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db6" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db7 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db7 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db7" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db8 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db8 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db8" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db9 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db9 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db9" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + + +create database test_kill_db10 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db10 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db10" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +create database test_kill_db11 +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Only one user database allowed under single-db mode. User database "test_kill_db1" already exists)~~ + +use test_kill_db11 +GO +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db11" does not exist)~~ + +CREATE USER user_kill FOR LOGIN test_kill; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: role "test_kill_db1_user_kill" already exists)~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db1 +-- CREATE 11 sessions +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db2 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db2" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db3 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db3" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db4 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db4" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db5 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db5" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db6 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db6" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db7 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db7" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db8 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db8" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db9 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db9" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db10 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db10" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql user=test_kill password=12345678 database=test_kill_db11 +~~ERROR (Code: 911)~~ + +~~ERROR (Message: database "test_kill_db11" does not exist )~~ + +SELECT 1 +GO +~~START~~ +int +1 +~~END~~ + + +-- tsql +SELECT count(*) from sys.dm_exec_sessions where login_name = 'test_kill' +GO +~~START~~ +int +1 +~~END~~ + + +SELECT COUNT(*) from pg_stat_activity where usename = 'test_kill' +GO +~~START~~ +int +1 +~~END~~ + + +-- Kill Sessions +USE [MASTER] +DECLARE @UserName NVARCHAR(255) = 'test_kill' +DECLARE @SPID INT +-- Creates temp table to store SPIDs +IF OBJECT_ID('tempdb..#UserSessions') IS NOT NULL +BEGIN +-- dropa tabela se existir +DROP TABLE #UserSessions +END +CREATE TABLE #UserSessions (SPID INT) +-- Inserting SPIDs from User Sessions into the Temporary Table +INSERT INTO #UserSessions (SPID) +SELECT session_id +FROM sys.dm_exec_sessions +WHERE login_name = @UserName +-- Loop to kill user sessions +DECLARE @RowCount INT = (SELECT COUNT(*) FROM #UserSessions) +DECLARE @Counter INT = 1 +DECLARE @CmdKill NVARCHAR(100) +WHILE @Counter <= @RowCount +BEGIN +SELECT TOP 1 @SPID = SPID FROM #UserSessions +SET @CmdKill = 'KILL ' + CAST(@SPID AS NVARCHAR(10)) +execute(@CmdKill) +DELETE FROM #UserSessions WHERE SPID = @SPID +SET @Counter = @Counter + 1 +END +-- Temporary Table Cleaning +DROP TABLE #UserSessions +GO +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + +~~WARNING (Code: 0)~~ + +~~WARNING (Message: Changed database context to 'master'. Server SQLState: S0001)~~ + +~~ROW COUNT: 1~~ + + +-- allow the kill to complete; this might take a while under heavy workload +exec pg_sleep 5 +go + +SELECT count(*) from sys.dm_exec_sessions where login_name = 'test_kill' +GO +~~START~~ +int +0 +~~END~~ + + +SELECT COUNT(*) from pg_stat_activity where usename = 'test_kill' +GO +~~START~~ +int +0 +~~END~~ + diff --git a/test/JDBC/input/GRANT_SCHEMA.mix b/test/JDBC/input/GRANT_SCHEMA.mix index 5c6e7da53b..b6a8412fa8 100644 --- a/test/JDBC/input/GRANT_SCHEMA.mix +++ b/test/JDBC/input/GRANT_SCHEMA.mix @@ -3277,7 +3277,7 @@ go -- psql -- should have 2 entries for master and babel_4344_d1 databases -select schema_name, object_name, permission, grantee from sys.babelfish_schema_permissions where object_name = 'babel_4344_t1'; +select schema_name, object_name, permission, grantee from sys.babelfish_schema_permissions where object_name = 'babel_4344_t1' ORDER BY grantee; go -- tsql diff --git a/test/JDBC/input/kill-vu-cleanup.mix b/test/JDBC/input/kill-vu-cleanup.mix index 3c34eb7d8a..a94e770e8a 100644 --- a/test/JDBC/input/kill-vu-cleanup.mix +++ b/test/JDBC/input/kill-vu-cleanup.mix @@ -1,3 +1,4 @@ +-- single_db_mode_expected -- tsql DROP LOGIN victim_user_tds go @@ -17,3 +18,29 @@ go DROP TABLE tab_kill_test go +DROP Database test_kill_db1 +GO +DROP Database test_kill_db2 +GO +DROP Database test_kill_db3 +GO +DROP Database test_kill_db4 +GO +DROP Database test_kill_db5 +GO +DROP Database test_kill_db6 +GO +DROP Database test_kill_db7 +GO +DROP Database test_kill_db8 +GO +DROP Database test_kill_db9 +GO +DROP Database test_kill_db10 +GO +DROP Database test_kill_db11 +GO + +DROP LOGIN test_kill; +GO + diff --git a/test/JDBC/input/kill-vu-verify.mix b/test/JDBC/input/kill-vu-verify.mix index 39c8e18ca4..784f86c86f 100644 --- a/test/JDBC/input/kill-vu-verify.mix +++ b/test/JDBC/input/kill-vu-verify.mix @@ -1,3 +1,4 @@ +-- single_db_mode_expected -- tsql create table tab_kill_spid(spid int) go @@ -225,3 +226,181 @@ SELECT @@TRANCOUNT go SELECT * FROM tab_kill_test go + +-- BABEL-5219 Multiple Session Kill +-- tsql +CREATE LOGIN test_kill WITH PASSWORD = '12345678' +GO + +-- CREATE 11 Databases +create database test_kill_db1 +GO +use test_kill_db1 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db2 +GO +use test_kill_db2 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db3 +GO +use test_kill_db3 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db4 +GO +use test_kill_db4 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db5 +GO +use test_kill_db5 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db6 +GO +use test_kill_db6 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db7 +GO +use test_kill_db7 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db8 +GO +use test_kill_db8 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db9 +GO +use test_kill_db9 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + + +create database test_kill_db10 +GO +use test_kill_db10 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +create database test_kill_db11 +GO +use test_kill_db11 +GO +CREATE USER user_kill FOR LOGIN test_kill; +go + +-- CREATE 11 sessions +-- tsql user=test_kill password=12345678 database=test_kill_db1 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db2 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db3 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db4 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db5 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db6 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db7 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db8 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db9 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db10 +SELECT 1 +GO + +-- tsql user=test_kill password=12345678 database=test_kill_db11 +SELECT 1 +GO + +-- tsql +SELECT count(*) from sys.dm_exec_sessions where login_name = 'test_kill' +GO + +SELECT COUNT(*) from pg_stat_activity where usename = 'test_kill' +GO + +-- Kill Sessions +USE [MASTER] +DECLARE @UserName NVARCHAR(255) = 'test_kill' +DECLARE @SPID INT +-- Creates temp table to store SPIDs +IF OBJECT_ID('tempdb..#UserSessions') IS NOT NULL +BEGIN +-- dropa tabela se existir +DROP TABLE #UserSessions +END +CREATE TABLE #UserSessions (SPID INT) +-- Inserting SPIDs from User Sessions into the Temporary Table +INSERT INTO #UserSessions (SPID) +SELECT session_id +FROM sys.dm_exec_sessions +WHERE login_name = @UserName +-- Loop to kill user sessions +DECLARE @RowCount INT = (SELECT COUNT(*) FROM #UserSessions) +DECLARE @Counter INT = 1 +DECLARE @CmdKill NVARCHAR(100) +WHILE @Counter <= @RowCount +BEGIN +SELECT TOP 1 @SPID = SPID FROM #UserSessions +SET @CmdKill = 'KILL ' + CAST(@SPID AS NVARCHAR(10)) +execute(@CmdKill) +DELETE FROM #UserSessions WHERE SPID = @SPID +SET @Counter = @Counter + 1 +END +-- Temporary Table Cleaning +DROP TABLE #UserSessions +GO + +-- allow the kill to complete; this might take a while under heavy workload +exec pg_sleep 5 +go + +SELECT count(*) from sys.dm_exec_sessions where login_name = 'test_kill' +GO + +SELECT COUNT(*) from pg_stat_activity where usename = 'test_kill' +GO \ No newline at end of file From 8bd47d713e051b21860da8d67f0c51ea4bad8dce Mon Sep 17 00:00:00 2001 From: Jake Owen Date: Thu, 5 Sep 2024 13:20:34 -0400 Subject: [PATCH 3/5] Add ALTER FUNCTION support in Babelfish (#2892) This change adds support for ALTER FUNCTION syntax in Babelfish for functions and inline table valued functions. This commit does not include support for ALTER FUNCTION on multi-statement table valued functions. This commit builds off of the logic of ALTER PROCEDURE by adding support for return types and general function handling. AlterFunctionStmt are built in the parser, then we extract the relevent metadata to construct a new function. The OID and ACL of the original function remain the same. Task: BABEL-4937 Signed-off-by: Jake Owen --- .../src/backend_parser/gram-tsql-rule.y | 64 +++ .../src/backend_parser/parser.c | 1 + contrib/babelfishpg_tsql/src/pl_handler.c | 102 ++++- .../src/tsqlUnsupportedFeatureHandler.cpp | 4 +- test/JDBC/expected/alter-function-schema.out | 376 ++++++++++++++++++ .../expected/alter-function-vu-cleanup.out | 22 + .../expected/alter-function-vu-prepare.out | 51 +++ .../expected/alter-function-vu-verify.out | 266 +++++++++++++ ...lter-procedure-15_8-or-16_4-vu-cleanup.out | 21 + ...lter-procedure-15_8-or-16_4-vu-prepare.out | 53 +++ ...alter-procedure-15_8-or-16_4-vu-verify.out | 368 +++++++++++++++++ ...ocedure-before-15_8-or-16_4-vu-prepare.out | 13 + ...rocedure-before-15_8-or-16_4-vu-verify.out | 13 - .../expected/alter-procedure-vu-cleanup.out | 3 - .../expected/alter-procedure-vu-prepare.out | 8 - .../expected/alter-procedure-vu-verify.out | 14 - .../input/alter/alter-function-schema.mix | 277 +++++++++++++ .../input/alter/alter-function-vu-cleanup.sql | 22 + .../input/alter/alter-function-vu-prepare.sql | 41 ++ .../input/alter/alter-function-vu-verify.sql | 160 ++++++++ ...lter-procedure-15_8-or-16_4-vu-cleanup.sql | 21 + ...lter-procedure-15_8-or-16_4-vu-prepare.sql | 45 +++ ...alter-procedure-15_8-or-16_4-vu-verify.sql | 217 ++++++++++ ...ocedure-before-15_8-or-16_4-vu-prepare.sql | 11 +- ...rocedure-before-15_8-or-16_4-vu-verify.sql | 9 - .../alter/alter-procedure-vu-cleanup.sql | 3 - .../alter/alter-procedure-vu-prepare.sql | 8 - .../input/alter/alter-procedure-vu-verify.sql | 10 - test/JDBC/jdbc_schedule | 6 + test/JDBC/upgrade/15_8/schedule | 1 + test/JDBC/upgrade/16_4/schedule | 2 +- test/JDBC/upgrade/latest/schedule | 1 + 32 files changed, 2119 insertions(+), 94 deletions(-) create mode 100644 test/JDBC/expected/alter-function-schema.out create mode 100644 test/JDBC/expected/alter-function-vu-cleanup.out create mode 100644 test/JDBC/expected/alter-function-vu-prepare.out create mode 100644 test/JDBC/expected/alter-function-vu-verify.out create mode 100644 test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-cleanup.out create mode 100644 test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-prepare.out create mode 100644 test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-verify.out create mode 100644 test/JDBC/input/alter/alter-function-schema.mix create mode 100644 test/JDBC/input/alter/alter-function-vu-cleanup.sql create mode 100644 test/JDBC/input/alter/alter-function-vu-prepare.sql create mode 100644 test/JDBC/input/alter/alter-function-vu-verify.sql create mode 100644 test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-cleanup.sql create mode 100644 test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-prepare.sql create mode 100644 test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-verify.sql diff --git a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y index cfdf11893f..684ff08741 100644 --- a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y +++ b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y @@ -4092,6 +4092,70 @@ tsql_AlterFunctionStmt: n->actions = list_concat(list_make3(lang, body, location), $5); // piggy-back on actions to just put the new proc body instead $$ = (Node *) n; } + | TSQL_ALTER FUNCTION func_name tsql_createfunc_args + RETURNS func_return tsql_createfunc_options opt_as tokens_remaining + { + ObjectWithArgs *owa = makeNode(ObjectWithArgs); + AlterFunctionStmt *n = makeNode(AlterFunctionStmt); + DefElem *lang = makeDefElem("language", (Node *) makeString("pltsql"), @1); + DefElem *body = makeDefElem("as", (Node *) list_make1(makeString($9)), @9); + DefElem *location = makeDefElem("location", (Node *) makeInteger(@3), @3); + /* + * Adding a option for volatility with value STABLE. + * Function created from tsql dialect will be created as STABLE + * by default + */ + DefElem *vol = makeDefElem("volatility", (Node *) makeString("stable"), @1); + + /* Remove return defelem from list after extracting in pl_handler*/ + DefElem *ret = makeDefElem("return", (Node *) $6, @6); + + /* Fill in the ObjectWithArgs node */ + owa->objname = $3; + owa->objargs = extractArgTypes($4); + owa->objfuncargs = $4; + + n->objtype = OBJECT_PROCEDURE; /* Set as proc to avoid psql alter func impl */ + n->func = owa; + n->actions = list_concat(list_make5(lang, body, location, vol, ret), $7); // piggy-back on actions to just put the new proc body instead + $$ = (Node *) n; + } + | TSQL_ALTER FUNCTION func_name tsql_createfunc_args + RETURNS TABLE opt_as tokens_remaining + { + ObjectWithArgs *owa = makeNode(ObjectWithArgs); + AlterFunctionStmt *n = makeNode(AlterFunctionStmt); + DefElem *lang = makeDefElem("language", (Node *) makeString("pltsql"), @1); + DefElem *body = makeDefElem("as", (Node *) list_make1(makeString($8)), @8); + DefElem *location = makeDefElem("location", (Node *) makeInteger(@3), @3); + TypeName *returnType = SystemTypeName("record"); + DefElem *ret; + + /* + * Do not include table parameters here, will be added in + * pltsql_validator() + */ + + owa->objname = $3; + owa->objargs = extractArgTypes($4); + owa->objfuncargs = $4; + + /* + * Use RECORD type here. In case of single result column, + * will be changed to that column's type in + * pltsql_validator() + */ + + returnType = SystemTypeName("record"); + returnType->setof = true; + returnType->location = @6; + ret = makeDefElem("return", (Node *) returnType, @6); + + n->objtype = OBJECT_PROCEDURE; /* Set as proc to avoid psql alter func impl */ + n->func = owa; + n->actions = list_make4(lang, body, location, ret); // piggy-back on actions to just put the new proc body instead + $$ = (Node *)n; + } ; /* diff --git a/contrib/babelfishpg_tsql/src/backend_parser/parser.c b/contrib/babelfishpg_tsql/src/backend_parser/parser.c index 640e8512ed..1e59ea861a 100644 --- a/contrib/babelfishpg_tsql/src/backend_parser/parser.c +++ b/contrib/babelfishpg_tsql/src/backend_parser/parser.c @@ -322,6 +322,7 @@ pgtsql_base_yylex(YYSTYPE *lvalp, YYLTYPE * llocp, core_yyscan_t yyscanner) { case PROCEDURE: case TSQL_PROC: + case FUNCTION: cur_token = TSQL_ALTER; break; } diff --git a/contrib/babelfishpg_tsql/src/pl_handler.c b/contrib/babelfishpg_tsql/src/pl_handler.c index a415998b5d..0dc7acaf76 100644 --- a/contrib/babelfishpg_tsql/src/pl_handler.c +++ b/contrib/babelfishpg_tsql/src/pl_handler.c @@ -139,7 +139,7 @@ static void call_prev_ProcessUtility(PlannedStmt *pstmt, DestReceiver *dest, QueryCompletion *qc); static void set_pgtype_byval(List *name, bool byval); -static void pltsql_proc_get_oid_proname_proacl(AlterFunctionStmt *stmt, ParseState *pstate, Oid *oid, Acl **acl, bool *isSameFunc); +static void pltsql_proc_get_oid_proname_proacl(AlterFunctionStmt *stmt, ParseState *pstate, Oid *oid, Acl **acl, bool *isSameFunc, bool is_proc); static void pg_proc_update_oid_acl(ObjectAddress address, Oid oid, Acl *acl); static void bbf_func_ext_update_proc_definition(Oid oid); static bool pltsql_truncate_identifier(char *ident, int len, bool warn); @@ -2406,13 +2406,13 @@ bbf_ProcessUtility(PlannedStmt *pstmt, { if (sql_dialect == SQL_DIALECT_TSQL) { - /* - * For ALTER PROC, we will: - * 1. Save important pg_proc metadata from the current proc (oid, proacl) - * 2. drop the current proc - * 3. create the new proc - * 4. update the pg_proc entry for the new proc with metadata from the old proc - * 5. update the babelfish_function_ext entry for the existing proc with new metadata based on the new proc + /* + * For ALTER PROC/FUNC, we will: + * 1. Save important pg_proc metadata from the current proc/func (oid, proacl) + * 2. drop the current proc/func + * 3. create the new proc/func + * 4. update the pg_proc entry for the new proc with metadata from the old proc/func + * 5. update the babelfish_function_ext entry for the existing proc/func with new metadata based on the new proc/func */ AlterFunctionStmt *stmt = (AlterFunctionStmt *) parsetree; bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND); @@ -2422,9 +2422,12 @@ bbf_ProcessUtility(PlannedStmt *pstmt, bool isSameProc; ObjectAddress address; CreateFunctionStmt *cfs; - ListCell *option, *location_cell = NULL; + ListCell *option, *location_cell = NULL, *return_cell = NULL; int origname_location = -1; bool with_recompile = false; + ListCell *parameter; + + cfs = makeNode(CreateFunctionStmt); if (!IS_TDS_CLIENT()) { @@ -2469,37 +2472,83 @@ bbf_ProcessUtility(PlannedStmt *pstmt, */ with_recompile = true; } + else if (strcmp(defel->defname, "return") == 0) + { + cfs->returnType = (TypeName *) defel->arg; + return_cell = option; + pfree(defel); + stmt->objtype = OBJECT_FUNCTION; + } } /* delete location cell if it exists as it is for internal use only */ if (location_cell) stmt->actions = list_delete_cell(stmt->actions, location_cell); + if (return_cell) + { + if(location_cell) + return_cell -= 1; + stmt->actions = list_delete_cell(stmt->actions, return_cell); + cfs->is_procedure = false; + } else + { + cfs->returnType = NULL; + cfs->is_procedure = true; + } + /* make a CreateFunctionStmt to pass into CreateFunction() */ - cfs = makeNode(CreateFunctionStmt); - cfs->is_procedure = true; cfs->replace = true; cfs->funcname = stmt->func->objname; cfs->parameters = stmt->func->objfuncargs; - cfs->returnType = NULL; cfs->options = stmt->actions; - pltsql_proc_get_oid_proname_proacl(stmt, pstate, &oldoid, &proacl, &isSameProc); - if (!isSameProc) /* i.e. different signature */ + foreach(parameter, cfs->parameters) + { + FunctionParameter* fp = (FunctionParameter*) lfirst(parameter); + if(fp->mode == FUNC_PARAM_TABLE) + { + fp->argType->setof = false; + } + } + + pltsql_proc_get_oid_proname_proacl(stmt, pstate, &oldoid, &proacl, &isSameProc, cfs->is_procedure); + if(get_bbf_function_tuple_from_proctuple(SearchSysCache1(PROCOID, ObjectIdGetDatum(oldoid))) == NULL) + { + /* Detect PSQL functions and throw error */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("No existing TSQL procedure found with the name for ALTER PROCEDURE"))); + } + if(!cfs->is_procedure) + { + /* + * Postgres does not allow us to create functions with different return types + * so we need to delete and recreate them + */ RemoveFunctionById(oldoid); - address = CreateFunction(pstate, cfs); /* if this is the same proc, will just update the existing one */ - pg_proc_update_oid_acl(address, oldoid, proacl); + isSameProc = false; + CommandCounterIncrement(); + } + else if (!isSameProc) /* i.e. different signature */ + { + RemoveFunctionById(oldoid); + } + + /* if this is the same procedure, it will update the existing one */ + address = CreateFunction(pstate, cfs); /* Update function/procedure related metadata in babelfish catalog */ pltsql_store_func_default_positions(address, cfs->parameters, queryString, origname_location, with_recompile); - /* Increase counter after bbf_func_ext modified in pltsql_store_func_default_positions*/ + /* Increase counter after bbf_func_ext modified in pltsql_store_func_default_positions*/ CommandCounterIncrement(); - bbf_func_ext_update_proc_definition(oldoid); + bbf_func_ext_update_proc_definition(address.objectId); + pg_proc_update_oid_acl(address, oldoid, proacl); if (!isSameProc) { /* * When the signatures differ we need to manually update the 'function_args' column in * the 'bbf_schema_permissions' catalog */ - alter_bbf_schema_permissions_catalog(stmt->func, cfs->parameters, OBJECT_PROCEDURE, oldoid); + alter_bbf_schema_permissions_catalog(stmt->func, cfs->parameters, stmt->objtype, oldoid); } /* Clean up table entries for the create function statement */ deleteDependencyRecordsFor(DefaultAclRelationId, address.objectId, false); @@ -4206,12 +4255,13 @@ call_prev_ProcessUtility(PlannedStmt *pstmt, * the found proc is the exact same proc as requested (i.e. the parameters match). */ static void -pltsql_proc_get_oid_proname_proacl(AlterFunctionStmt *stmt, ParseState *pstate, Oid *oid, Acl **acl, bool *isSameFunc) +pltsql_proc_get_oid_proname_proacl(AlterFunctionStmt *stmt, ParseState *pstate, Oid *oid, Acl **acl, bool *isSameFunc, bool is_proc) { int spi_rc; char *funcname, *query; bool isnull; Oid schemaOid, funcOid; + Datum aclDatum; MemoryContext oldMemoryContext = CurrentMemoryContext; @@ -4238,7 +4288,11 @@ pltsql_proc_get_oid_proname_proacl(AlterFunctionStmt *stmt, ParseState *pstate, *oid = DatumGetObjectId(SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1, &isnull)); MemoryContextSwitchTo(oldMemoryContext); - *acl = aclcopy(DatumGetAclP(SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull))); + aclDatum = SPI_getbinval(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 2, &isnull); + if(DatumGetPointer(aclDatum) == NULL) + *acl = NULL; + else + *acl = aclcopy(DatumGetAclP(aclDatum)); if ((spi_rc = SPI_finish()) != SPI_OK_FINISH) elog(ERROR, "SPI_finish() failed in pltsql_proc_get_oid_proname_proacl with return code %d", spi_rc); @@ -4294,7 +4348,10 @@ pg_proc_update_oid_acl(ObjectAddress address, Oid oid, Acl *acl) memset(replaces, 0, sizeof(replaces)); values[Anum_pg_proc_oid - 1] = ObjectIdGetDatum(oid); replaces[Anum_pg_proc_oid - 1] = true; - values[Anum_pg_proc_proacl - 1] = PointerGetDatum(acl); + if(acl) + values[Anum_pg_proc_proacl - 1] = PointerGetDatum(acl); + else + nulls[Anum_pg_proc_proacl - 1] = true; replaces[Anum_pg_proc_proacl - 1] = true; newtup = heap_modify_tuple(proctup, RelationGetDescr(rel), values, nulls, replaces); @@ -4302,6 +4359,7 @@ pg_proc_update_oid_acl(ObjectAddress address, Oid oid, Acl *acl) /* Clean up */ ReleaseSysCache(proctup); + heap_freetuple(newtup); table_close(rel, RowExclusiveLock); } diff --git a/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp b/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp index f5be400969..fb8e5c5668 100644 --- a/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp +++ b/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp @@ -292,8 +292,8 @@ antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitKill_statement(TSqlParser: antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitCreate_or_alter_function(TSqlParser::Create_or_alter_functionContext *ctx) { - if (ctx->ALTER()) - handle(INSTR_UNSUPPORTED_TSQL_ALTER_FUNCTION, "ALTER FUNCTION", getLineAndPos(ctx->ALTER())); + if (ctx->ALTER() && ctx->func_body_returns_table()) + handle(INSTR_UNSUPPORTED_TSQL_ALTER_FUNCTION, "ALTER FUNCTION on multi-statement table valued functions", getLineAndPos(ctx->ALTER())); std::vector options; if (ctx->func_body_returns_select()) diff --git a/test/JDBC/expected/alter-function-schema.out b/test/JDBC/expected/alter-function-schema.out new file mode 100644 index 0000000000..af92ac1573 --- /dev/null +++ b/test/JDBC/expected/alter-function-schema.out @@ -0,0 +1,376 @@ +-- tsql +-- Test altering a basic function and an inline tvf function on same schema +create login alter_func_l1 with password = '12345678' +go + +ALTER ROLE sysadmin ADD MEMBER alter_func_l1 +GO + +-- tsql user=alter_func_l1 password=12345678 +create database alter_func_db1 +go + +use alter_func_db1 +go + +create schema alter_func_schema1 +go + + +CREATE TABLE alter_func_users_t ([Id] int, [firstname] varchar(50), [lastname] varchar(50), [email] varchar(50)); +CREATE TABLE alter_func_orders_t ([Id] int, [userid] int, [productid] int, [quantity] int, [orderdate] Date); +INSERT INTO alter_func_users_t VALUES (1, 'j', 'o', 'testemail'), (1, 'e', 'l', 'testemail2'); +INSERT INTO alter_func_orders_t VALUES (1, 1, 1, 5, '2023-06-25'), (2, 1, 1, 6, '2023-06-25'); +go +~~ROW COUNT: 2~~ + +~~ROW COUNT: 2~~ + + +create function alter_func_schema1.f1() returns int begin return 2 end +go + +create function alter_func_schema1.f2() returns TABLE as return (select * from alter_func_users_t) +go + +-- psql +select schema_name, object_name, permission, grantee, object_type, function_args, grantor from sys.babelfish_schema_permissions where schema_name = 'alter_func_schema1' collate sys.database_default order by object_name; +go +~~START~~ +"sys"."varchar"#!#"sys"."varchar"#!#int4#!#"sys"."varchar"#!#bpchar#!#text#!#"sys"."varchar" +~~END~~ + + +-- tsql user=alter_func_l1 password=12345678 +select alter_func_schema1.f1() +go +~~START~~ +int +2 +~~END~~ + + +select alter_func_schema1.f2() +go +~~START~~ +varchar +(1,j,o,testemail) +(1,e,l,testemail2) +~~END~~ + + +alter function alter_func_schema1.f1(@param1 int) returns int begin return @param1 end +go + +alter function alter_func_schema1.f2() returns TABLE as return (select * from alter_func_orders_t) +go + +select alter_func_schema1.f1(5) +go +~~START~~ +int +5 +~~END~~ + + +select alter_func_schema1.f2() +go +~~START~~ +varchar +(1,1,1,5,2023-06-25) +(2,1,1,6,2023-06-25) +~~END~~ + + +drop function alter_func_schema1.f1 +go + +drop function alter_func_schema1.f2 +go + +drop table alter_func_users_t +go + +drop table alter_func_orders_t +go + +-- psql +select schema_name, object_name, permission, grantee, object_type, function_args, grantor from sys.babelfish_schema_permissions where schema_name = 'alter_func_schema1' collate sys.database_default order by object_name; +go +~~START~~ +"sys"."varchar"#!#"sys"."varchar"#!#int4#!#"sys"."varchar"#!#bpchar#!#text#!#"sys"."varchar" +~~END~~ + + +-- tsql user=alter_func_l1 password=12345678 +drop schema alter_func_schema1; +go + +use master +go + +drop database alter_func_db1 +go + +-- psql +-- Need to terminate active session before cleaning up the login +SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL) +WHERE sys.suser_name(usesysid) = 'alter_func_l1' AND backend_type = 'client backend' AND usesysid IS NOT NULL; +go +~~START~~ +bool +t +~~END~~ + + +-- Wait to sync with another session +SELECT pg_sleep(1); +go +~~START~~ +void + +~~END~~ + + +-- tsql +drop login alter_func_l1; +go + +-- psql currentSchema=master_dbo,public +-- Test defining two functions with same name in psql then attempting to alter in tsql +create function psql_func_f1() +returns int +language plpgsql +as +$$ +DECLARE +BEGIN +return 1; +end; +$$; +go + +create function psql_func_f1(a integer) +returns int +language plpgsql +as +$$ +BEGIN +return a; +end; +$$; +go + +-- psql currentSchema=master_dbo,public +drop function psql_func_f1(a integer) +go + + +CREATE TABLE cars ( + brand VARCHAR(255), + model VARCHAR(255), + year INT +); +INSERT INTO cars (brand, model, year) +VALUES ('Ford', 'Mustang', 1964); +go +~~ROW COUNT: 1~~ + + +create function psql_func_tvf1() returns table(brand VARCHAR(255), model VARCHAR(255), year INT) +language plpgsql +as $$ +begin + return query +select * from cars; +end; +$$; +go + +select psql_func_tvf1() +go +~~START~~ +record +(Ford,Mustang,1964) +~~END~~ + + +-- tsql +-- Test attempting to alter psql functions in tsql +alter function psql_func_f1() returns int begin return 2 end +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: No existing TSQL procedure found with the name for ALTER PROCEDURE)~~ + + +alter function psql_func_tvf1() returns table +as +return (select * from cars) +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: No existing TSQL procedure found with the name for ALTER PROCEDURE)~~ + + +-- psql currentSchema=master_dbo,public +drop function psql_func_f1() +go + +drop function psql_func_tvf1() +go + +drop table cars; +go + +-- tsql +-- Test creating two of the same functions on different schemas +create login alter_func_l2 with password = '12345678' +go + +ALTER ROLE sysadmin ADD MEMBER alter_func_l2 +GO + +-- tsql user=alter_func_l2 password=12345678 +create database alter_func_db2 +go + +use alter_func_db2 +go + +create schema alter_func_schema2 +go + +create schema alter_func_schema3 +go + +create function alter_func_schema2.f1() returns int begin return 2 end +go + +create function alter_func_schema3.f1() returns int begin return 2 end +go + +select alter_func_schema2.f1() +go +~~START~~ +int +2 +~~END~~ + + +select alter_func_schema3.f1() +go +~~START~~ +int +2 +~~END~~ + + +alter function alter_func_schema2.f1(@param1 int) returns int begin return @param1 end +go + +alter function alter_func_schema3.f1(@param1 int) returns int begin return @param1 end +go + +select alter_func_schema2.f1(5) +go +~~START~~ +int +5 +~~END~~ + + +select alter_func_schema3.f1(5) +go +~~START~~ +int +5 +~~END~~ + + +drop function alter_func_schema2.f1(@param1 int) +go + +drop function alter_func_schema3.f1(@param1 int) +go + +drop schema alter_func_schema2 +go + +drop schema alter_func_schema3 +go + +use master +go + +drop database alter_func_db2 +go + +-- psql +-- Need to terminate active session before cleaning up the login +SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL) +WHERE sys.suser_name(usesysid) = 'alter_func_l2' AND backend_type = 'client backend' AND usesysid IS NOT NULL; +go +~~START~~ +bool +t +~~END~~ + + +-- Wait to sync with another session +SELECT pg_sleep(1); +go +~~START~~ +void + +~~END~~ + + +-- tsql +drop login alter_func_l2; +go + +-- psql currentSchema=master_dbo,public + +-- Test psql functions altered with security definer do not throw StartTransactionCommand: unexpected state STARTED error +create function psql_func_f2() +returns int +language plpgsql +as +$$ +DECLARE +BEGIN +return 1; +end; +$$; +alter function psql_func_f2() security definer; +go + +drop function psql_func_f2; +go + +set babelfishpg_tsql.sql_dialect = "tsql"; +GO + +create function f1() returns int begin return 2 end +go + +-- Test alter function using tsql dialect in PSQL port throws error +alter function f1() returns int begin return 3 end +go +~~ERROR (Code: 0)~~ + +~~ERROR (Message: ERROR: TSQL ALTER PROCEDURE is not supported from PostgreSQL endpoint. + Server SQLState: 0A000)~~ + + +drop function f1() +go + +select set_config('babelfishpg_tsql.sql_dialect', 'postgres', null); +go +~~START~~ +text +postgres +~~END~~ + diff --git a/test/JDBC/expected/alter-function-vu-cleanup.out b/test/JDBC/expected/alter-function-vu-cleanup.out new file mode 100644 index 0000000000..55e7aa2eef --- /dev/null +++ b/test/JDBC/expected/alter-function-vu-cleanup.out @@ -0,0 +1,22 @@ +drop function alter_func_f1 +go + +drop function alter_func_f2 +go + +drop function alter_func_f3 +go + +drop function alter_func_f4 +go + +drop function alter_func_f5; +go + +drop function alter_func_f6; +go + +drop table alter_func_users +go +drop table alter_func_orders +go diff --git a/test/JDBC/expected/alter-function-vu-prepare.out b/test/JDBC/expected/alter-function-vu-prepare.out new file mode 100644 index 0000000000..2bbad8bfc4 --- /dev/null +++ b/test/JDBC/expected/alter-function-vu-prepare.out @@ -0,0 +1,51 @@ + +CREATE TABLE alter_func_users ([Id] int, [firstname] varchar(50), [lastname] varchar(50), [email] varchar(50)); +CREATE TABLE alter_func_orders ([Id] int, [userid] int, [productid] int, [quantity] int, [orderdate] Date); +INSERT INTO alter_func_users VALUES (1, 'j', 'o', 'testemail'), (1, 'e', 'l', 'testemail2'); +INSERT INTO alter_func_orders VALUES (1, 1, 1, 5, '2023-06-25'), (2, 1, 1, 6, '2023-06-25'); +GO +~~ROW COUNT: 2~~ + +~~ROW COUNT: 2~~ + + +create function alter_func_f1() returns int begin return 2 end +go + +create function alter_func_f2(@param1 int) returns int begin return @param1 end +go + +create function alter_func_f3(@param1 int) returns int +begin + if (@param1 < 2) + begin + return 1 + end + else + begin + return @param1 + end +end +go + +create function alter_func_f4() returns TABLE as return (select * from alter_func_users) +go + +-- Test Case: Alter function in prepare file +alter function alter_func_f4() returns TABLE as return (select * from alter_func_orders) +go + +select alter_func_f4() +go +~~START~~ +varchar +(1,1,1,5,2023-06-25) +(2,1,1,6,2023-06-25) +~~END~~ + + +create function alter_func_f5() returns @result TABLE([Id] int) as begin insert @result select 1 return end +go + +create function alter_func_f6(@p1 int, @p2 int=123, @p3 int) returns int as begin return @p1 + @p2 + @p3 end +go diff --git a/test/JDBC/expected/alter-function-vu-verify.out b/test/JDBC/expected/alter-function-vu-verify.out new file mode 100644 index 0000000000..2b2a3ee48a --- /dev/null +++ b/test/JDBC/expected/alter-function-vu-verify.out @@ -0,0 +1,266 @@ +-- Test Case 1: Alter function body +alter function alter_func_f1() returns int begin return 2 end +go + +-- Expect to return 2 +select alter_func_f1() +go +~~START~~ +int +2 +~~END~~ + + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f1'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_func_f1#!#SQL#!#CREATE function alter_func_f1() returns int begin return 2 end +~~END~~ + + +-- Test Case 2: Alter function parameters, body, and return type +ALTER function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +go + +select alter_func_f2('testing') +go +~~START~~ +varchar +testing +~~END~~ + + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f2'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_func_f2#!#SQL#!#CREATE function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +~~END~~ + + +-- Expect error for no parameter provided +select alter_func_f2() +go +~~ERROR (Code: 201)~~ + +~~ERROR (Message: function alter_func_f2 expects parameter "@param2", which was not supplied.)~~ + + +-- Test Case 3: Expect error for altering func that does not exist +ALTER function alter_fake_func(@param1 int) returns int +begin + return 1 +end +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: No existing procedure found with the name for ALTER PROCEDURE)~~ + + + +-- Test Case 4: Alter parameter type and function body +ALTER function alter_func_f2(@param2 int) returns varchar(10) +begin + if (@param2 = 1) + BEGIN + return @param2 + END + ELSE + BEGIN + return -1 + END +end +go + +select alter_func_f2(1) +go +~~START~~ +varchar +1 +~~END~~ + + +select alter_func_f2(2) +go +~~START~~ +varchar +-1 +~~END~~ + + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f2'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_func_f2#!#SQL#!#CREATE function alter_func_f2(@param2 int) returns varchar(10) begin if (@param2 = 1) BEGIN return @param2 END ELSE BEGIN return -1 ENDend +~~END~~ + + +-- Test Case 5: Transaction - begin, alter func, rollback +-- - expect alter to not go through +BEGIN TRANSACTION +go + +ALTER function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +go + +ROLLBACK +go + +-- Expect error +select alter_func_f2('test') +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: invalid input syntax for type integer: "test")~~ + + +-- Expect return 1 +select alter_func_f2(1) +go +~~START~~ +varchar +1 +~~END~~ + + +-- Test Case 6: Transaction - begin, alter func, modify row, commit +-- - expect both changes to take place +BEGIN TRANSACTION +GO + +ALTER function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +go + +INSERT INTO alter_func_users VALUES (3, 'newuser', 'lastname', 'testemail3') +go +~~ROW COUNT: 1~~ + + +COMMIT +GO + +select alter_func_f2('test') +go +~~START~~ +varchar +test +~~END~~ + + +select * from alter_func_users +go +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +3#!#newuser#!#lastname#!#testemail3 +~~END~~ + + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f2'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_func_f2#!#SQL#!#CREATE function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +~~END~~ + + +-- Test Case 7: Transaction - begin, alter func, modify row, commit +-- - expect both changes to not go through +BEGIN TRANSACTION +GO + +alter function alter_func_f2() returns int begin return 2 end +go + +INSERT INTO alter_func_users VALUES (4, 'newest_user', 'lastname3', 'testemail4') +go +~~ROW COUNT: 1~~ + + +ROLLBACK +GO + +-- Expect error for no parameter +select alter_func_f2() +go +~~ERROR (Code: 201)~~ + +~~ERROR (Message: function alter_func_f2 expects parameter "@param2", which was not supplied.)~~ + + +-- Expect only 3 rows +select * from alter_func_users +go +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +3#!#newuser#!#lastname#!#testemail3 +~~END~~ + + +-- Test Case 8: Expect error from altering function to select from +-- table row that does not exist +alter function alter_func_f2() returns TABLE +as + return ( + select address from alter_func_users + ) +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: column "address" does not exist)~~ + + +-- Test Case 9: Expect error for attempting to alter multi statement tvf +-- Alter Func Multi-statement tvf support will be added after BABEL-5149 is resolved +alter function alter_func_f5() +returns @result TABLE(Id int) as begin +insert into @result values (2) +return +end +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'ALTER FUNCTION on multi-statement table valued functions' is not currently supported in Babelfish)~~ + + +-- Test Case 10: Expect error for altering function in an illegal way +-- select statements are not allowed in functions not returning a table +alter function alter_func_f3(@param1 int) returns int +begin + select * from alter_func_users + return @param1 +end +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: SELECT statement returning result to a client cannot be used in a function)~~ + + +-- Test Case 11: Alter function with default values +select alter_func_f6(1, default, 100) +go +~~START~~ +int +224 +~~END~~ + + +alter function alter_func_f6 (@p1 int = 345, @p2 int=123, @p3 int) returns int as begin return @p1 + @p2 + @p3 end +go + +select alter_func_f6(default, default, 100) +go +~~START~~ +int +568 +~~END~~ + diff --git a/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-cleanup.out b/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-cleanup.out new file mode 100644 index 0000000000..85b1be8cf4 --- /dev/null +++ b/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-cleanup.out @@ -0,0 +1,21 @@ +DROP PROCEDURE alter_proc_p1 +GO + +DROP PROCEDURE alter_proc_p2 +GO + +DROP PROCEDURE alter_proc_p3 +GO + +DROP PROCEDURE alter_proc_p4 +GO + +DROP PROCEDURE alter_proc_p5 +GO + +DROP TABLE alter_proc_users +DROP TABLE alter_proc_orders +GO + +drop function alter_proc_f1 +go diff --git a/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-prepare.out b/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-prepare.out new file mode 100644 index 0000000000..b83c2dcbf9 --- /dev/null +++ b/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-prepare.out @@ -0,0 +1,53 @@ + +CREATE TABLE alter_proc_users ([Id] int, [firstname] varchar(50), [lastname] varchar(50), [email] varchar(50)); +CREATE TABLE alter_proc_orders ([Id] int, [userid] int, [productid] int, [quantity] int, [orderdate] Date); +INSERT INTO alter_proc_users VALUES (1, 'j', 'o', 'testemail'), (1, 'e', 'l', 'testemail2'); +INSERT INTO alter_proc_orders VALUES (1, 1, 1, 5, '2023-06-25'), (2, 1, 1, 6, '2023-06-25'); +GO +~~ROW COUNT: 2~~ + +~~ROW COUNT: 2~~ + + +CREATE PROCEDURE alter_proc_p1 +AS + select * from alter_proc_users +GO + +create procedure alter_proc_p2 +AS + exec alter_proc_p1 +go + +create procedure alter_proc_p3 as select 1 +go + +create procedure alter_proc_p4 as select 1 +go + + +create function alter_proc_f1() +returns int +AS BEGIN + return 1 +END +go + +create procedure alter_proc_p5 as select 10 +go + +alter procedure alter_proc_p5 @dateParam date as select @dateParam +go + +-- Test Case: attempt to alter function, expect error for being unsupported +alter function alter_proc_f1() +returns int +as +BEGIN + return 5 +END +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'ALTER FUNCTION' is not currently supported in Babelfish)~~ + diff --git a/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-verify.out b/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-verify.out new file mode 100644 index 0000000000..f7eb78bbb8 --- /dev/null +++ b/test/JDBC/expected/alter-procedure-15_8-or-16_4-vu-verify.out @@ -0,0 +1,368 @@ +exec alter_proc_p1 +go +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +~~END~~ + + +-- Test Case: Expect error for procedure with same name +CREATE PROCEDURE alter_proc_p1 @param1 int +AS + select * from alter_proc_orders +GO +~~ERROR (Code: 2714)~~ + +~~ERROR (Message: Function 'alter_proc_p1' already exists with the same name)~~ + + +-- Test Case: Expect error for altering proc that does not exist +ALTER PROCEDURE alter_fake_proc @param1 int +AS + select * from alter_proc_orders +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: No existing procedure found with the name for ALTER PROCEDURE)~~ + + +-- Test Case: Modify the procedure body, and check information_schema updated with spaces +/* Leading comment not included: BABEL-5140 */ ALTER -- test comment + PROCEDURE alter_proc_p1 +AS + select * from alter_proc_orders +GO + +exec alter_proc_p1 +go +~~START~~ +int#!#int#!#int#!#int#!#date +1#!#1#!#1#!#5#!#2023-06-25 +2#!#1#!#1#!#6#!#2023-06-25 +~~END~~ + + +exec alter_proc_p2 +go +~~START~~ +int#!#int#!#int#!#int#!#date +1#!#1#!#1#!#5#!#2023-06-25 +2#!#1#!#1#!#6#!#2023-06-25 +~~END~~ + + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p1'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p1#!#SQL#!#CREATE -- test comment PROCEDURE alter_proc_p1AS select * from alter_proc_orders +~~END~~ + + + +-- Test Case: Modify the procedure body, add a parameter, use "proc" +-- instead of "procedure" + ALTER /* TEST COMMENT */ + PROC alter_proc_p1 + @param INT +AS + IF (@param = 1) + BEGIN + select * from alter_proc_users + END + ELSE + BEGIN + select * from alter_proc_orders + END +GO + +exec alter_proc_p1 @param = 1 +GO +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +~~END~~ + + +exec alter_proc_p1 @param = 2 +GO +~~START~~ +int#!#int#!#int#!#int#!#date +1#!#1#!#1#!#5#!#2023-06-25 +2#!#1#!#1#!#6#!#2023-06-25 +~~END~~ + + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p1'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p1#!#SQL#!#CREATE /* TEST COMMENT */ PROC alter_proc_p1 @param INTAS IF (@param = 1) BEGIN select * from alter_proc_users END ELSE BEGIN select * from alter_proc_orders END +~~END~~ + + +-- Test Case: Expect error because no parameter provided +exec alter_proc_p2 +go +~~ERROR (Code: 201)~~ + +~~ERROR (Message: procedure alter_proc_p1 expects parameter "@param", which was not supplied.)~~ + + + +-- Test Case: Alter the parameter type and procedure body +ALTER PROCEDURE alter_proc_p1 + @param date +AS + IF (@param = '2020-01-01') + BEGIN + select * from alter_proc_users + END + ELSE + BEGIN + select * from alter_proc_orders + END +GO + +exec alter_proc_p1 @param = '2020-01-01' +GO +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +~~END~~ + + +exec alter_proc_p1 @param = '2020-01-02' +GO +~~START~~ +int#!#int#!#int#!#int#!#date +1#!#1#!#1#!#5#!#2023-06-25 +2#!#1#!#1#!#6#!#2023-06-25 +~~END~~ + + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p1'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p1#!#SQL#!#CREATE PROCEDURE alter_proc_p1 @param dateAS IF (@param = '2020-01-01') BEGIN select * from alter_proc_users END ELSE BEGIN select * from alter_proc_orders END +~~END~~ + + +-- Test Case: Modify the procedure body to call another modified proc +alter procedure alter_proc_p2 +AS + exec alter_proc_p1 @param = '2020-01-01' +GO + +exec alter_proc_p2 +go +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +~~END~~ + + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p2'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p2#!#SQL#!#CREATE procedure alter_proc_p2AS exec alter_proc_p1 @param = '2020-01-01' +~~END~~ + + +-- Test Case: Transaction - begin, alter, rollback +-- - expect alter to not go through +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 as select 2 +go + +ROLLBACK +go + +exec alter_proc_p3 +go +~~START~~ +int +1 +~~END~~ + + +-- Test Case: Transaction - begin, alter proc, modify row, commit +-- - expect both changes to take place +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 @z int as select 500 + @z +go + +INSERT INTO alter_proc_users VALUES (3, 'newuser', 'lastname', 'testemail3') +go +~~ROW COUNT: 1~~ + + +COMMIT +GO + +exec alter_proc_p3 @z = 500 +go +~~START~~ +int +1000 +~~END~~ + + +select * from alter_proc_users +go +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +3#!#newuser#!#lastname#!#testemail3 +~~END~~ + + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p3'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p3#!#SQL#!#CREATE procedure alter_proc_p3 @z int as select 500 + @z +~~END~~ + + + +-- Test Case: Transaction - begin, alter proc, modify row, commit +-- - expect both changes to not go through +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 as select 1000 +go + +INSERT INTO alter_proc_users VALUES (4, 'newest_user', 'lastname3', 'testemail4') +go +~~ROW COUNT: 1~~ + + +ROLLBACK +GO + +-- Expect this to error with no param provided +exec alter_proc_p3 +go +~~ERROR (Code: 201)~~ + +~~ERROR (Message: procedure alter_proc_p3 expects parameter "@z", which was not supplied.)~~ + + +select * from alter_proc_users +go +~~START~~ +int#!#varchar#!#varchar#!#varchar +1#!#j#!#o#!#testemail +1#!#e#!#l#!#testemail2 +3#!#newuser#!#lastname#!#testemail3 +~~END~~ + + + +-- Test Case: Transaction - alter procedure to select from table row that does not exist +-- which would result in error if committed +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 as + select fake_column from alter_proc_users +go + +ROLLBACK +GO + +-- Expect this to error with no param provided +exec alter_proc_p3 +go +~~ERROR (Code: 201)~~ + +~~ERROR (Message: procedure alter_proc_p3 expects parameter "@z", which was not supplied.)~~ + + + + + + +-- Test Case: confirm information_schema.routines is updated properly with comments +alter +/* + * test comment 1 + */ +-- test comment 2 +procedure alter_proc_p4 as select 3 +go + +exec alter_proc_p4 +go +~~START~~ +int +3 +~~END~~ + + +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p4'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p4#!#SQL#!#CREATE /* * test comment 1 */-- test comment 2procedure alter_proc_p4 as select 3 +~~END~~ + + + + +-- Test Case: confirm information_schema.routines is updated properly with comments +alter +-- test comment 1 +procedure alter_proc_p4 as select 4 +go + +exec alter_proc_p4 +go +~~START~~ +int +4 +~~END~~ + + +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p4'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p4#!#SQL#!#CREATE -- test comment 1procedure alter_proc_p4 as select 4 +~~END~~ + + +-- Test Case: confirm procedure altered in 'alter-procedure-vu-prepare' is properly updated +exec alter_proc_p5 @dateParam = '2000-01-01' +go +~~START~~ +date +2000-01-01 +~~END~~ + + +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p4'; +go +~~START~~ +nvarchar#!#nvarchar#!#nvarchar +alter_proc_p4#!#SQL#!#CREATE -- test comment 1procedure alter_proc_p4 as select 4 +~~END~~ + diff --git a/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-prepare.out b/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-prepare.out index cc0b1922a7..8af8d46e86 100644 --- a/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-prepare.out +++ b/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-prepare.out @@ -73,3 +73,16 @@ go alter procedure alter_proc_p5 @dateParam date as select @dateParam go + +-- Test Case: attempt to alter function, expect error for being unsupported +alter function alter_proc_f1() +returns int +as +BEGIN + return 5 +END +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'ALTER FUNCTION' is not currently supported in Babelfish)~~ + diff --git a/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-verify.out b/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-verify.out index a5ef2b4435..9fbf8a4c3c 100644 --- a/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-verify.out +++ b/test/JDBC/expected/alter-procedure-before-15_8-or-16_4-vu-verify.out @@ -124,19 +124,6 @@ int#!#int#!#int#!#int#!#date ~~END~~ --- Test Case: attempt to alter function, expect error for being unsupported -alter function alter_proc_f1() -returns int -as -BEGIN - return 5 -END -go -~~ERROR (Code: 33557097)~~ - -~~ERROR (Message: 'ALTER FUNCTION' is not currently supported in Babelfish)~~ - - -- Test Case: Confirm transaction updates procedure correctly exec alter_proc_p3 @z = 500 go diff --git a/test/JDBC/expected/alter-procedure-vu-cleanup.out b/test/JDBC/expected/alter-procedure-vu-cleanup.out index 85b1be8cf4..a505d634af 100644 --- a/test/JDBC/expected/alter-procedure-vu-cleanup.out +++ b/test/JDBC/expected/alter-procedure-vu-cleanup.out @@ -16,6 +16,3 @@ GO DROP TABLE alter_proc_users DROP TABLE alter_proc_orders GO - -drop function alter_proc_f1 -go diff --git a/test/JDBC/expected/alter-procedure-vu-prepare.out b/test/JDBC/expected/alter-procedure-vu-prepare.out index c6dbecb129..faf95e5538 100644 --- a/test/JDBC/expected/alter-procedure-vu-prepare.out +++ b/test/JDBC/expected/alter-procedure-vu-prepare.out @@ -25,14 +25,6 @@ go create procedure alter_proc_p4 as select 1 go - -create function alter_proc_f1() -returns int -AS BEGIN - return 1 -END -go - create procedure alter_proc_p5 as select 10 go diff --git a/test/JDBC/expected/alter-procedure-vu-verify.out b/test/JDBC/expected/alter-procedure-vu-verify.out index ba6c7e137c..f7eb78bbb8 100644 --- a/test/JDBC/expected/alter-procedure-vu-verify.out +++ b/test/JDBC/expected/alter-procedure-vu-verify.out @@ -179,20 +179,6 @@ alter_proc_p2#!#SQL#!#CREATE procedure alter_proc_p2AS exec ~~END~~ - --- Test Case: attempt to alter function, expect error for being unsupported -alter function alter_proc_f1() -returns int -as -BEGIN - return 5 -END -go -~~ERROR (Code: 33557097)~~ - -~~ERROR (Message: 'ALTER FUNCTION' is not currently supported in Babelfish)~~ - - -- Test Case: Transaction - begin, alter, rollback -- - expect alter to not go through BEGIN TRANSACTION diff --git a/test/JDBC/input/alter/alter-function-schema.mix b/test/JDBC/input/alter/alter-function-schema.mix new file mode 100644 index 0000000000..fb67a7b65e --- /dev/null +++ b/test/JDBC/input/alter/alter-function-schema.mix @@ -0,0 +1,277 @@ +-- Test altering a basic function and an inline tvf function on same schema +-- tsql +create login alter_func_l1 with password = '12345678' +go + +ALTER ROLE sysadmin ADD MEMBER alter_func_l1 +GO + +-- tsql user=alter_func_l1 password=12345678 +create database alter_func_db1 +go + +use alter_func_db1 +go + +create schema alter_func_schema1 +go + +CREATE TABLE alter_func_users_t ([Id] int, [firstname] varchar(50), [lastname] varchar(50), [email] varchar(50)); +CREATE TABLE alter_func_orders_t ([Id] int, [userid] int, [productid] int, [quantity] int, [orderdate] Date); + +INSERT INTO alter_func_users_t VALUES (1, 'j', 'o', 'testemail'), (1, 'e', 'l', 'testemail2'); +INSERT INTO alter_func_orders_t VALUES (1, 1, 1, 5, '2023-06-25'), (2, 1, 1, 6, '2023-06-25'); +go + +create function alter_func_schema1.f1() returns int begin return 2 end +go + +create function alter_func_schema1.f2() returns TABLE as return (select * from alter_func_users_t) +go + +-- psql +select schema_name, object_name, permission, grantee, object_type, function_args, grantor from sys.babelfish_schema_permissions where schema_name = 'alter_func_schema1' collate sys.database_default order by object_name; +go + +-- tsql user=alter_func_l1 password=12345678 +select alter_func_schema1.f1() +go + +select alter_func_schema1.f2() +go + +alter function alter_func_schema1.f1(@param1 int) returns int begin return @param1 end +go + +alter function alter_func_schema1.f2() returns TABLE as return (select * from alter_func_orders_t) +go + +select alter_func_schema1.f1(5) +go + +select alter_func_schema1.f2() +go + +drop function alter_func_schema1.f1 +go + +drop function alter_func_schema1.f2 +go + +drop table alter_func_users_t +go + +drop table alter_func_orders_t +go + +-- psql +select schema_name, object_name, permission, grantee, object_type, function_args, grantor from sys.babelfish_schema_permissions where schema_name = 'alter_func_schema1' collate sys.database_default order by object_name; +go + +-- tsql user=alter_func_l1 password=12345678 +drop schema alter_func_schema1; +go + +use master +go + +drop database alter_func_db1 +go + +-- psql +-- Need to terminate active session before cleaning up the login +SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL) +WHERE sys.suser_name(usesysid) = 'alter_func_l1' AND backend_type = 'client backend' AND usesysid IS NOT NULL; +go + +-- Wait to sync with another session +SELECT pg_sleep(1); +go + +-- tsql +drop login alter_func_l1; +go + +-- psql currentSchema=master_dbo,public +-- Test defining two functions with same name in psql then attempting to alter in tsql +create function psql_func_f1() +returns int +language plpgsql +as +$$ +DECLARE +BEGIN +return 1; +end; +$$; +go + +create function psql_func_f1(a integer) +returns int +language plpgsql +as +$$ +BEGIN +return a; +end; +$$; +go + +-- psql currentSchema=master_dbo,public +drop function psql_func_f1(a integer) +go + +CREATE TABLE cars ( + brand VARCHAR(255), + model VARCHAR(255), + year INT +); + +INSERT INTO cars (brand, model, year) +VALUES ('Ford', 'Mustang', 1964); +go + +create function psql_func_tvf1() returns table(brand VARCHAR(255), model VARCHAR(255), year INT) +language plpgsql +as $$ +begin + return query +select * from cars; +end; +$$; +go + +select psql_func_tvf1() +go + +-- Test attempting to alter psql functions in tsql +-- tsql +alter function psql_func_f1() returns int begin return 2 end +go + +alter function psql_func_tvf1() returns table +as +return (select * from cars) +go + +-- psql currentSchema=master_dbo,public +drop function psql_func_f1() +go + +drop function psql_func_tvf1() +go + +drop table cars; +go + +-- Test creating two of the same functions on different schemas +-- tsql +create login alter_func_l2 with password = '12345678' +go + +ALTER ROLE sysadmin ADD MEMBER alter_func_l2 +GO + +-- tsql user=alter_func_l2 password=12345678 +create database alter_func_db2 +go + +use alter_func_db2 +go + +create schema alter_func_schema2 +go + +create schema alter_func_schema3 +go + +create function alter_func_schema2.f1() returns int begin return 2 end +go + +create function alter_func_schema3.f1() returns int begin return 2 end +go + +select alter_func_schema2.f1() +go + +select alter_func_schema3.f1() +go + +alter function alter_func_schema2.f1(@param1 int) returns int begin return @param1 end +go + +alter function alter_func_schema3.f1(@param1 int) returns int begin return @param1 end +go + +select alter_func_schema2.f1(5) +go + +select alter_func_schema3.f1(5) +go + +drop function alter_func_schema2.f1(@param1 int) +go + +drop function alter_func_schema3.f1(@param1 int) +go + +drop schema alter_func_schema2 +go + +drop schema alter_func_schema3 +go + +use master +go + +drop database alter_func_db2 +go + +-- psql +-- Need to terminate active session before cleaning up the login +SELECT pg_terminate_backend(pid) FROM pg_stat_get_activity(NULL) +WHERE sys.suser_name(usesysid) = 'alter_func_l2' AND backend_type = 'client backend' AND usesysid IS NOT NULL; +go + +-- Wait to sync with another session +SELECT pg_sleep(1); +go + +-- tsql +drop login alter_func_l2; +go + +-- psql currentSchema=master_dbo,public +-- Test psql functions altered with security definer do not throw StartTransactionCommand: unexpected state STARTED error +create function psql_func_f2() +returns int +language plpgsql +as +$$ +DECLARE +BEGIN +return 1; +end; +$$; + +alter function psql_func_f2() security definer; +go + +drop function psql_func_f2; +go + +set babelfishpg_tsql.sql_dialect = "tsql"; +GO + +create function f1() returns int begin return 2 end +go + +-- Test alter function using tsql dialect in PSQL port throws error +alter function f1() returns int begin return 3 end +go + +drop function f1() +go + +select set_config('babelfishpg_tsql.sql_dialect', 'postgres', null); +go \ No newline at end of file diff --git a/test/JDBC/input/alter/alter-function-vu-cleanup.sql b/test/JDBC/input/alter/alter-function-vu-cleanup.sql new file mode 100644 index 0000000000..20e3a75870 --- /dev/null +++ b/test/JDBC/input/alter/alter-function-vu-cleanup.sql @@ -0,0 +1,22 @@ +drop function alter_func_f1 +go + +drop function alter_func_f2 +go + +drop function alter_func_f3 +go + +drop function alter_func_f4 +go + +drop function alter_func_f5; +go + +drop function alter_func_f6; +go + +drop table alter_func_users +go +drop table alter_func_orders +go \ No newline at end of file diff --git a/test/JDBC/input/alter/alter-function-vu-prepare.sql b/test/JDBC/input/alter/alter-function-vu-prepare.sql new file mode 100644 index 0000000000..b4aea1bfbc --- /dev/null +++ b/test/JDBC/input/alter/alter-function-vu-prepare.sql @@ -0,0 +1,41 @@ +CREATE TABLE alter_func_users ([Id] int, [firstname] varchar(50), [lastname] varchar(50), [email] varchar(50)); +CREATE TABLE alter_func_orders ([Id] int, [userid] int, [productid] int, [quantity] int, [orderdate] Date); + +INSERT INTO alter_func_users VALUES (1, 'j', 'o', 'testemail'), (1, 'e', 'l', 'testemail2'); +INSERT INTO alter_func_orders VALUES (1, 1, 1, 5, '2023-06-25'), (2, 1, 1, 6, '2023-06-25'); +GO + +create function alter_func_f1() returns int begin return 2 end +go + +create function alter_func_f2(@param1 int) returns int begin return @param1 end +go + +create function alter_func_f3(@param1 int) returns int +begin + if (@param1 < 2) + begin + return 1 + end + else + begin + return @param1 + end +end +go + +create function alter_func_f4() returns TABLE as return (select * from alter_func_users) +go + +-- Test Case: Alter function in prepare file +alter function alter_func_f4() returns TABLE as return (select * from alter_func_orders) +go + +select alter_func_f4() +go + +create function alter_func_f5() returns @result TABLE([Id] int) as begin insert @result select 1 return end +go + +create function alter_func_f6(@p1 int, @p2 int=123, @p3 int) returns int as begin return @p1 + @p2 + @p3 end +go \ No newline at end of file diff --git a/test/JDBC/input/alter/alter-function-vu-verify.sql b/test/JDBC/input/alter/alter-function-vu-verify.sql new file mode 100644 index 0000000000..f17b688880 --- /dev/null +++ b/test/JDBC/input/alter/alter-function-vu-verify.sql @@ -0,0 +1,160 @@ +-- Test Case 1: Alter function body +alter function alter_func_f1() returns int begin return 2 end +go + +-- Expect to return 2 +select alter_func_f1() +go + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f1'; +go + +-- Test Case 2: Alter function parameters, body, and return type +ALTER function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +go + +select alter_func_f2('testing') +go + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f2'; +go + +-- Expect error for no parameter provided +select alter_func_f2() +go + +-- Test Case 3: Expect error for altering func that does not exist +ALTER function alter_fake_func(@param1 int) returns int +begin + return 1 +end +GO + +-- Test Case 4: Alter parameter type and function body +ALTER function alter_func_f2(@param2 int) returns varchar(10) +begin + if (@param2 = 1) + BEGIN + return @param2 + END + + ELSE + BEGIN + return -1 + END +end +go + +select alter_func_f2(1) +go + +select alter_func_f2(2) +go + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f2'; +go + +-- Test Case 5: Transaction - begin, alter func, rollback +-- - expect alter to not go through +BEGIN TRANSACTION +go + +ALTER function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +go + +ROLLBACK +go + +-- Expect error +select alter_func_f2('test') +go + +-- Expect return 1 +select alter_func_f2(1) +go + +-- Test Case 6: Transaction - begin, alter func, modify row, commit +-- - expect both changes to take place +BEGIN TRANSACTION +GO + +ALTER function alter_func_f2(@param2 varchar(10)) returns varchar(10) begin return @param2 end +go + +INSERT INTO alter_func_users VALUES (3, 'newuser', 'lastname', 'testemail3') +go + +COMMIT +GO + +select alter_func_f2('test') +go + +select * from alter_func_users +go + +-- Confirm information schema is correctly updated with "CREATE FUNC [new definition]" +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_func_f2'; +go + +-- Test Case 7: Transaction - begin, alter func, modify row, commit +-- - expect both changes to not go through +BEGIN TRANSACTION +GO + +alter function alter_func_f2() returns int begin return 2 end +go + +INSERT INTO alter_func_users VALUES (4, 'newest_user', 'lastname3', 'testemail4') +go + +ROLLBACK +GO + +-- Expect error for no parameter +select alter_func_f2() +go + +-- Expect only 3 rows +select * from alter_func_users +go + +-- Test Case 8: Expect error from altering function to select from +-- table row that does not exist +alter function alter_func_f2() returns TABLE +as + return ( + select address from alter_func_users + ) +go + +-- Test Case 9: Expect error for attempting to alter multi statement tvf +-- Alter Func Multi-statement tvf support will be added after BABEL-5149 is resolved +alter function alter_func_f5() +returns @result TABLE(Id int) as begin +insert into @result values (2) +return +end +go + +-- Test Case 10: Expect error for altering function in an illegal way +-- select statements are not allowed in functions not returning a table +alter function alter_func_f3(@param1 int) returns int +begin + select * from alter_func_users + return @param1 +end +go + +-- Test Case 11: Alter function with default values +select alter_func_f6(1, default, 100) +go + +alter function alter_func_f6 (@p1 int = 345, @p2 int=123, @p3 int) returns int as begin return @p1 + @p2 + @p3 end +go + +select alter_func_f6(default, default, 100) +go \ No newline at end of file diff --git a/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-cleanup.sql b/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-cleanup.sql new file mode 100644 index 0000000000..71b54d0e92 --- /dev/null +++ b/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-cleanup.sql @@ -0,0 +1,21 @@ +DROP PROCEDURE alter_proc_p1 +GO + +DROP PROCEDURE alter_proc_p2 +GO + +DROP PROCEDURE alter_proc_p3 +GO + +DROP PROCEDURE alter_proc_p4 +GO + +DROP PROCEDURE alter_proc_p5 +GO + +DROP TABLE alter_proc_users +DROP TABLE alter_proc_orders +GO + +drop function alter_proc_f1 +go \ No newline at end of file diff --git a/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-prepare.sql b/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-prepare.sql new file mode 100644 index 0000000000..4964a86bd1 --- /dev/null +++ b/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-prepare.sql @@ -0,0 +1,45 @@ +CREATE TABLE alter_proc_users ([Id] int, [firstname] varchar(50), [lastname] varchar(50), [email] varchar(50)); +CREATE TABLE alter_proc_orders ([Id] int, [userid] int, [productid] int, [quantity] int, [orderdate] Date); + +INSERT INTO alter_proc_users VALUES (1, 'j', 'o', 'testemail'), (1, 'e', 'l', 'testemail2'); +INSERT INTO alter_proc_orders VALUES (1, 1, 1, 5, '2023-06-25'), (2, 1, 1, 6, '2023-06-25'); +GO + +CREATE PROCEDURE alter_proc_p1 +AS + select * from alter_proc_users +GO + +create procedure alter_proc_p2 +AS + exec alter_proc_p1 +go + +create procedure alter_proc_p3 as select 1 +go + +create procedure alter_proc_p4 as select 1 +go + +create function alter_proc_f1() +returns int +AS BEGIN + return 1 +END + +go + +create procedure alter_proc_p5 as select 10 +go + +alter procedure alter_proc_p5 @dateParam date as select @dateParam +go + +-- Test Case: attempt to alter function, expect error for being unsupported +alter function alter_proc_f1() +returns int +as +BEGIN + return 5 +END +go diff --git a/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-verify.sql b/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-verify.sql new file mode 100644 index 0000000000..64277e060a --- /dev/null +++ b/test/JDBC/input/alter/alter-procedure-15_8-or-16_4-vu-verify.sql @@ -0,0 +1,217 @@ +exec alter_proc_p1 +go + +-- Test Case: Expect error for procedure with same name +CREATE PROCEDURE alter_proc_p1 @param1 int +AS + select * from alter_proc_orders +GO + +-- Test Case: Expect error for altering proc that does not exist +ALTER PROCEDURE alter_fake_proc @param1 int +AS + select * from alter_proc_orders +GO + +-- Test Case: Modify the procedure body, and check information_schema updated with spaces +/* Leading comment not included: BABEL-5140 */ ALTER -- test comment + PROCEDURE alter_proc_p1 +AS + select * from alter_proc_orders +GO + +exec alter_proc_p1 +go + +exec alter_proc_p2 +go + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p1'; +go + +-- Test Case: Modify the procedure body, add a parameter, use "proc" +-- instead of "procedure" + ALTER /* TEST COMMENT */ + PROC alter_proc_p1 + @param INT +AS + IF (@param = 1) + BEGIN + select * from alter_proc_users + END + + ELSE + BEGIN + select * from alter_proc_orders + END +GO + +exec alter_proc_p1 @param = 1 +GO + +exec alter_proc_p1 @param = 2 +GO + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p1'; +go + +-- Test Case: Expect error because no parameter provided +exec alter_proc_p2 +go + +-- Test Case: Alter the parameter type and procedure body +ALTER PROCEDURE alter_proc_p1 + @param date +AS + IF (@param = '2020-01-01') + BEGIN + select * from alter_proc_users + END + + ELSE + BEGIN + select * from alter_proc_orders + END +GO + +exec alter_proc_p1 @param = '2020-01-01' +GO + +exec alter_proc_p1 @param = '2020-01-02' +GO + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p1'; +go + +-- Test Case: Modify the procedure body to call another modified proc +alter procedure alter_proc_p2 +AS + exec alter_proc_p1 @param = '2020-01-01' +GO + +exec alter_proc_p2 +go + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p2'; +go + +-- Test Case: Transaction - begin, alter, rollback +-- - expect alter to not go through +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 as select 2 +go + +ROLLBACK +go + +exec alter_proc_p3 +go + +-- Test Case: Transaction - begin, alter proc, modify row, commit +-- - expect both changes to take place +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 @z int as select 500 + @z +go + +INSERT INTO alter_proc_users VALUES (3, 'newuser', 'lastname', 'testemail3') +go + +COMMIT +GO + +exec alter_proc_p3 @z = 500 +go + +select * from alter_proc_users +go + +-- Ensure information schema uses "CREATE" instead of "ALTER" with updated definition +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p3'; +go + +-- Test Case: Transaction - begin, alter proc, modify row, commit +-- - expect both changes to not go through + +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 as select 1000 +go + +INSERT INTO alter_proc_users VALUES (4, 'newest_user', 'lastname3', 'testemail4') +go + +ROLLBACK +GO + +-- Expect this to error with no param provided +exec alter_proc_p3 +go + +select * from alter_proc_users +go + +-- Test Case: Transaction - alter procedure to select from table row that does not exist +-- which would result in error if committed + +BEGIN TRANSACTION +go + +alter procedure alter_proc_p3 as + select fake_column from alter_proc_users +go + +ROLLBACK +GO + +-- Expect this to error with no param provided +exec alter_proc_p3 +go + + +-- Test Case: confirm information_schema.routines is updated properly with comments +alter + +/* + * test comment 1 + */ + +-- test comment 2 + +procedure alter_proc_p4 as select 3 +go + +exec alter_proc_p4 +go + +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p4'; +go + +-- Test Case: confirm information_schema.routines is updated properly with comments +alter + +-- test comment 1 + +procedure alter_proc_p4 as select 4 +go + +exec alter_proc_p4 +go + +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p4'; +go + +-- Test Case: confirm procedure altered in 'alter-procedure-vu-prepare' is properly updated +exec alter_proc_p5 @dateParam = '2000-01-01' +go + +select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p4'; +go \ No newline at end of file diff --git a/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-prepare.sql b/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-prepare.sql index 0e29df5320..9faef12e1d 100644 --- a/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-prepare.sql +++ b/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-prepare.sql @@ -66,4 +66,13 @@ create procedure alter_proc_p5 as select 10 go alter procedure alter_proc_p5 @dateParam date as select @dateParam -go \ No newline at end of file +go + +-- Test Case: attempt to alter function, expect error for being unsupported +alter function alter_proc_f1() +returns int +as +BEGIN + return 5 +END +go diff --git a/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-verify.sql b/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-verify.sql index 5a4f19673d..38e6a00e4e 100644 --- a/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-verify.sql +++ b/test/JDBC/input/alter/alter-procedure-before-15_8-or-16_4-vu-verify.sql @@ -69,15 +69,6 @@ GO exec alter_proc_p1 @param = '2020-01-02' GO --- Test Case: attempt to alter function, expect error for being unsupported -alter function alter_proc_f1() -returns int -as -BEGIN - return 5 -END -go - -- Test Case: Confirm transaction updates procedure correctly exec alter_proc_p3 @z = 500 go diff --git a/test/JDBC/input/alter/alter-procedure-vu-cleanup.sql b/test/JDBC/input/alter/alter-procedure-vu-cleanup.sql index 71b54d0e92..a505d634af 100644 --- a/test/JDBC/input/alter/alter-procedure-vu-cleanup.sql +++ b/test/JDBC/input/alter/alter-procedure-vu-cleanup.sql @@ -16,6 +16,3 @@ GO DROP TABLE alter_proc_users DROP TABLE alter_proc_orders GO - -drop function alter_proc_f1 -go \ No newline at end of file diff --git a/test/JDBC/input/alter/alter-procedure-vu-prepare.sql b/test/JDBC/input/alter/alter-procedure-vu-prepare.sql index d7d78d6cf4..b2753a5a28 100644 --- a/test/JDBC/input/alter/alter-procedure-vu-prepare.sql +++ b/test/JDBC/input/alter/alter-procedure-vu-prepare.sql @@ -21,14 +21,6 @@ go create procedure alter_proc_p4 as select 1 go -create function alter_proc_f1() -returns int -AS BEGIN - return 1 -END - -go - create procedure alter_proc_p5 as select 10 go diff --git a/test/JDBC/input/alter/alter-procedure-vu-verify.sql b/test/JDBC/input/alter/alter-procedure-vu-verify.sql index aef3b214fa..64277e060a 100644 --- a/test/JDBC/input/alter/alter-procedure-vu-verify.sql +++ b/test/JDBC/input/alter/alter-procedure-vu-verify.sql @@ -99,16 +99,6 @@ go select ROUTINE_NAME, ROUTINE_BODY, ROUTINE_DEFINITION from information_schema.routines where SPECIFIC_NAME LIKE 'alter_proc_p2'; go --- Test Case: attempt to alter function, expect error for being unsupported - -alter function alter_proc_f1() -returns int -as -BEGIN - return 5 -END -go - -- Test Case: Transaction - begin, alter, rollback -- - expect alter to not go through BEGIN TRANSACTION diff --git a/test/JDBC/jdbc_schedule b/test/JDBC/jdbc_schedule index 9f6416b2a3..795c4f4e0e 100644 --- a/test/JDBC/jdbc_schedule +++ b/test/JDBC/jdbc_schedule @@ -369,6 +369,11 @@ ignore#!#alter-procedure-before-15_8-or-16_4-vu-prepare ignore#!#alter-procedure-before-15_8-or-16_4-vu-verify ignore#!#alter-procedure-before-15_8-or-16_4-vu-cleanup + +# These tests are meant for upgrade scenario prior to 15_9 or 16_5 release +ignore#!#alter-procedure-15_8-or-16_4-vu-prepare +ignore#!#alter-procedure-15_8-or-16_4-vu-verify +ignore#!#alter-procedure-15_8-or-16_4-vu-cleanup ignore#!#string_agg-before-15_9-or-16_5-vu-prepare ignore#!#string_agg-before-15_9-or-16_5-vu-verify ignore#!#string_agg-before-15_9-or-16_5-vu-cleanup @@ -378,3 +383,4 @@ ignore#!#string_agg-before-14_5-vu-cleanup ignore#!#string_agg_within-16_4-vu-prepare ignore#!#string_agg_within-16_4-vu-verify ignore#!#string_agg_within-16_4-vu-cleanup + diff --git a/test/JDBC/upgrade/15_8/schedule b/test/JDBC/upgrade/15_8/schedule index 55266cf2c0..7c0549cad4 100644 --- a/test/JDBC/upgrade/15_8/schedule +++ b/test/JDBC/upgrade/15_8/schedule @@ -537,6 +537,7 @@ replicate string_agg-before-15_9-or-16_5 space replace +alter-procedure-15_8-or-16_4 binary-datatype-operators cast-varchar-to-time diff --git a/test/JDBC/upgrade/16_4/schedule b/test/JDBC/upgrade/16_4/schedule index 27b2941703..100b364656 100644 --- a/test/JDBC/upgrade/16_4/schedule +++ b/test/JDBC/upgrade/16_4/schedule @@ -526,7 +526,7 @@ babel-4475 babel-3254 babel-4517 babel_table_type -alter-procedure +alter-procedure-15_8-or-16_4 1_GRANT_SCHEMA BABEL-4707 BABEL_4817 diff --git a/test/JDBC/upgrade/latest/schedule b/test/JDBC/upgrade/latest/schedule index 325fd131e9..fe876fe320 100644 --- a/test/JDBC/upgrade/latest/schedule +++ b/test/JDBC/upgrade/latest/schedule @@ -527,6 +527,7 @@ babel-3254 babel-4517 babel_table_type alter-procedure +alter-function 1_GRANT_SCHEMA BABEL-4707 BABEL_4817 From 670b113350fdd1e8e6be200bf386b79700993b79 Mon Sep 17 00:00:00 2001 From: Jason Teng Date: Thu, 5 Sep 2024 13:28:33 -0400 Subject: [PATCH 4/5] Revert "Revert "Fix concurrency issues with ENR temp tables. (#2686)" (#2875)" (#2895) This reverts commit 6886c3c21df262cd32d6b458cab2c2ebd53f4210. Co-authored-by: Jason Teng --- contrib/babelfishpg_tsql/src/guc.c | 2 +- contrib/babelfishpg_tsql/src/hooks.c | 24 ++++ test/JDBC/expected/temp_oid.out | 2 +- test/JDBC/expected/temp_table_jdbc.out | 1 - .../java/com/sqlsamples/JDBCTempTable.java | 123 +++++++++++++++++- test/JDBC/target/JDBC-testsuite-1.0.0.jar | Bin 0 -> 60262 bytes 6 files changed, 142 insertions(+), 10 deletions(-) create mode 100644 test/JDBC/target/JDBC-testsuite-1.0.0.jar diff --git a/contrib/babelfishpg_tsql/src/guc.c b/contrib/babelfishpg_tsql/src/guc.c index 03bd375536..518e904ab6 100644 --- a/contrib/babelfishpg_tsql/src/guc.c +++ b/contrib/babelfishpg_tsql/src/guc.c @@ -1163,7 +1163,7 @@ define_custom_variables(void) gettext_noop("Temp oid buffer size"), NULL, &temp_oid_buffer_size, - 0, 0, 131072, + 65536, 0, 131072, PGC_SUSET, GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE | GUC_DISALLOW_IN_AUTO_FILE, NULL, NULL, NULL); diff --git a/contrib/babelfishpg_tsql/src/hooks.c b/contrib/babelfishpg_tsql/src/hooks.c index 4a5bce7e80..585bc829f2 100644 --- a/contrib/babelfishpg_tsql/src/hooks.c +++ b/contrib/babelfishpg_tsql/src/hooks.c @@ -55,6 +55,8 @@ #include "parser/scansup.h" #include "replication/logical.h" #include "rewrite/rewriteHandler.h" +#include "storage/lock.h" +#include "storage/sinvaladt.h" #include "tcop/utility.h" #include "utils/builtins.h" #include "utils/fmgroids.h" @@ -163,6 +165,8 @@ static void pltsql_GetNewObjectId(VariableCache variableCache); static Oid pltsql_GetNewTempObjectId(void); static Oid pltsql_GetNewTempOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn); static bool set_and_persist_temp_oid_buffer_start(Oid new_oid); +static bool pltsql_is_local_only_inval_msg(const SharedInvalidationMessage *msg); +static EphemeralNamedRelation pltsql_get_tsql_enr_from_oid(Oid oid); static void pltsql_validate_var_datatype_scale(const TypeName *typeName, Type typ); static bool pltsql_bbfCustomProcessUtility(ParseState *pstate, PlannedStmt *pstmt, @@ -240,6 +244,8 @@ static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; static GetNewObjectId_hook_type prev_GetNewObjectId_hook = NULL; static GetNewTempObjectId_hook_type prev_GetNewTempObjectId_hook = NULL; static GetNewTempOidWithIndex_hook_type prev_GetNewTempOidWithIndex_hook = NULL; +static pltsql_is_local_only_inval_msg_hook_type prev_pltsql_is_local_only_inval_msg_hook = NULL; +static pltsql_get_tsql_enr_from_oid_hook_type prev_pltsql_get_tsql_enr_from_oid_hook = NULL; static inherit_view_constraints_from_table_hook_type prev_inherit_view_constraints_from_table = NULL; static bbfViewHasInsteadofTrigger_hook_type prev_bbfViewHasInsteadofTrigger_hook = NULL; static detect_numeric_overflow_hook_type prev_detect_numeric_overflow_hook = NULL; @@ -367,6 +373,12 @@ InstallExtendedHooks(void) prev_GetNewTempOidWithIndex_hook = GetNewTempOidWithIndex_hook; GetNewTempOidWithIndex_hook = pltsql_GetNewTempOidWithIndex; + prev_pltsql_is_local_only_inval_msg_hook = pltsql_is_local_only_inval_msg_hook; + pltsql_is_local_only_inval_msg_hook = pltsql_is_local_only_inval_msg; + + prev_pltsql_get_tsql_enr_from_oid_hook = pltsql_get_tsql_enr_from_oid_hook; + pltsql_get_tsql_enr_from_oid_hook = pltsql_get_tsql_enr_from_oid; + prev_inherit_view_constraints_from_table = inherit_view_constraints_from_table_hook; inherit_view_constraints_from_table_hook = preserve_view_constraints_from_base_table; TriggerRecuresiveCheck_hook = plsql_TriggerRecursiveCheck; @@ -4685,6 +4697,18 @@ static bool set_and_persist_temp_oid_buffer_start(Oid new_oid) return true; } +static bool +pltsql_is_local_only_inval_msg(const SharedInvalidationMessage *msg) +{ + return temp_oid_buffer_size > 0 && (msg->id == SHAREDINVALRELCACHE_ID && msg->rc.local_only); +} + +static EphemeralNamedRelation +pltsql_get_tsql_enr_from_oid(const Oid oid) +{ + return temp_oid_buffer_size > 0 ? get_ENR_withoid(currentQueryEnv, oid, ENR_TSQL_TEMP) : NULL; +} + /* * Modify the Tuple Descriptor to match the expected * result set. Currently used only for T-SQL OPENQUERY. diff --git a/test/JDBC/expected/temp_oid.out b/test/JDBC/expected/temp_oid.out index c0cedfe860..508f2ef726 100644 --- a/test/JDBC/expected/temp_oid.out +++ b/test/JDBC/expected/temp_oid.out @@ -3,7 +3,7 @@ show babelfishpg_tsql.temp_oid_buffer_size GO ~~START~~ text -0 +65536 ~~END~~ diff --git a/test/JDBC/expected/temp_table_jdbc.out b/test/JDBC/expected/temp_table_jdbc.out index 015a827d62..e69de29bb2 100644 --- a/test/JDBC/expected/temp_table_jdbc.out +++ b/test/JDBC/expected/temp_table_jdbc.out @@ -1 +0,0 @@ -Cannot create trigger on a temporary object. \ No newline at end of file diff --git a/test/JDBC/src/main/java/com/sqlsamples/JDBCTempTable.java b/test/JDBC/src/main/java/com/sqlsamples/JDBCTempTable.java index f115e522e8..82e3785c71 100644 --- a/test/JDBC/src/main/java/com/sqlsamples/JDBCTempTable.java +++ b/test/JDBC/src/main/java/com/sqlsamples/JDBCTempTable.java @@ -28,11 +28,13 @@ public static void runTest(BufferedWriter bw, Logger logger) { long startTime = System.nanoTime(); try { - // TODO: re-enable the temp table OID tests when the full fix is ready - // check_oids_equal(bw); - // test_oid_buffer(bw, logger); - // concurrency_test(bw); - // psql_test(bw, logger); + if (check_temp_oid_buffer_enabled(bw, logger)) { + check_oids_equal(bw); + test_oid_buffer(bw, logger); + psql_test(bw, logger); + test_lock_contention(bw, logger); + } + concurrency_test(bw); test_trigger_on_temp_table(bw, logger); } catch (Exception e) { try { @@ -46,6 +48,21 @@ public static void runTest(BufferedWriter bw, Logger logger) { curr_exec_time = endTime - startTime; } + private static boolean check_temp_oid_buffer_enabled(BufferedWriter bw, Logger logger) throws Exception { + String temp_oid_buffer_size = "babelfishpg_tsql.temp_oid_buffer_size"; + Connection c = DriverManager.getConnection(connectionString); + JDBCCrossDialect cx = new JDBCCrossDialect(c); + Connection psql = cx.getPsqlConnection("-- psql", bw, logger); + Statement check_guc = psql.createStatement(); + ResultSet rs = check_guc.executeQuery("SHOW " + temp_oid_buffer_size); + if (!rs.next()) { + bw.write("Table is missing."); + bw.newLine(); + } + int buffer_size = Integer.parseInt(rs.getString(temp_oid_buffer_size)); + return buffer_size != 0; + } + /* * Helper function that creates the specified number of connections, creates a temp table on each connection, and returns whether all the OIDs are equal or not. */ @@ -320,8 +337,14 @@ private static void test_trigger_on_temp_table(BufferedWriter bw, Logger logger) queryString = "CREATE TABLE #t1(a int)"; s.execute(queryString); queryString = "CREATE TRIGGER bar ON #t1 FOR INSERT AS BEGIN SELECT 1 END"; - s.execute(queryString); - + try { + s.execute(queryString); + } catch (Exception e) { + if (!e.getMessage().equals("Cannot create trigger on a temporary object.")) { + bw.write(e.getMessage()); + bw.newLine(); + } + } Connection c2 = connections.get(1); s = c2.createStatement(); queryString = "DROP TRIGGER bar"; @@ -334,6 +357,65 @@ private static void test_trigger_on_temp_table(BufferedWriter bw, Logger logger) } } } + + private static void test_lock_contention(BufferedWriter bw, Logger logger) throws Exception { + String connectionString = initializeConnectionString(); + + /* Sanity check of pg_locks catalog */ + Connection c = DriverManager.getConnection(connectionString); + Statement s = c.createStatement(); + ResultSet rs; + s.execute("BEGIN TRAN"); + s.execute("CREATE TABLE #t1 (a INT)"); + rs = s.executeQuery("SELECT relation FROM pg_locks JOIN sys.babelfish_get_enr_list() ON (relation = reloid) where relname = '#t1'"); + if (rs.next()) { + bw.write("Unexpected lock acquisition on a TSQL temp table.\n"); + } + s.execute("ALTER TABLE #t1 ADD b AS a + 1"); + rs = s.executeQuery("SELECT relation FROM pg_locks JOIN sys.babelfish_get_enr_list() ON (relation = reloid) where relname = '#t1'"); + if (rs.next()) { + bw.write("Unexpected lock acquisition on a TSQL temp table.\n"); + } + s.execute("ALTER TABLE #t1 DROP COLUMN b"); + rs = s.executeQuery("SELECT relation FROM pg_locks JOIN sys.babelfish_get_enr_list() ON (relation = reloid) where relname = '#t1'"); + if (rs.next()) { + bw.write("Unexpected lock acquisition on a TSQL temp table.\n"); + } + s.execute("DROP TABLE #t1"); + rs = s.executeQuery("SELECT relation FROM pg_locks JOIN sys.babelfish_get_enr_list() ON (relation = reloid) where relname = '#t1'"); + if (rs.next()) { + bw.write("Unexpected lock acquisition on a TSQL temp table.\n"); + } + c.close(); + + int num_connections = 2; + + ArrayList cxns = new ArrayList<>(); + + ArrayList threads = new ArrayList<>(); + + /* Create connections */ + for (int i = 0; i < num_connections; i++) { + Connection connection = DriverManager.getConnection(connectionString); + cxns.add(connection); + Thread t = new Thread(new LockContentionWorker(connection, bw)); + threads.add(t); + t.start(); + } + + /* + * Unfortunately, setQueryTimeout (used in the worker thread) does not always work correctly. + * So we need to manaully try to detect a hanging thread. + */ + Thread.sleep(1000); + for (Thread t : threads) + { + if (t.isAlive()) { + bw.write("Lock contention detected.\n"); + return; + } + } + } } class Worker implements Runnable { @@ -381,4 +463,31 @@ public void run() { e.printStackTrace(); } } +} + +class LockContentionWorker implements Runnable { + + private Connection c; + private BufferedWriter bw; + + LockContentionWorker(Connection c, BufferedWriter bw) { + this.c = c; + this.bw = bw; + } + + public void run() { + try { + try { + Statement s = c.createStatement(); + s.setQueryTimeout(1); + s.execute("BEGIN TRAN;"); + s.execute("CREATE TABLE #temp_table1 (a int primary key not null identity, b as a + 1, c text);"); + } catch (Exception e) { + bw.write(e.getMessage()); + bw.newLine(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } } \ No newline at end of file diff --git a/test/JDBC/target/JDBC-testsuite-1.0.0.jar b/test/JDBC/target/JDBC-testsuite-1.0.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..80377cab5f71bc55f7ea493211605e22f7e68cf0 GIT binary patch literal 60262 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 M0e^-`T)RE{e^rSFz5oCK literal 0 HcmV?d00001 From 2160271355ffdda57255f9dd666a50ffa29c7faf Mon Sep 17 00:00:00 2001 From: P Aswini Kumar Date: Tue, 10 Sep 2024 17:07:29 +0530 Subject: [PATCH 5/5] More specific error message on creation of windows login for NT Service domain (#2828) Currently in babelfish, when a new windows login is created for NT Service domain - it raises an error on space character. With this change babelfish will check for the NT Service domain and print an error specifically indicating this is not supported in Babelfish and added relevant test cases. NT Service may be spelled in mixed case. Issues Resolved: BABEL-4212 Signed-off-by: P Aswini Kumar --- contrib/babelfishpg_tsql/src/pl_handler.c | 12 ++ contrib/babelfishpg_tsql/src/rolecmds.c | 25 ++++ contrib/babelfishpg_tsql/src/rolecmds.h | 2 + test/JDBC/expected/BABEL-UNSUPPORTED.out | 114 ++++++++++++++++++ .../chinese_prc_ci_as/BABEL-UNSUPPORTED.out | 114 ++++++++++++++++++ test/JDBC/input/BABEL-UNSUPPORTED.sql | 58 +++++++++ 6 files changed, 325 insertions(+) diff --git a/contrib/babelfishpg_tsql/src/pl_handler.c b/contrib/babelfishpg_tsql/src/pl_handler.c index 0dc7acaf76..128a28da38 100644 --- a/contrib/babelfishpg_tsql/src/pl_handler.c +++ b/contrib/babelfishpg_tsql/src/pl_handler.c @@ -2701,6 +2701,8 @@ bbf_ProcessUtility(PlannedStmt *pstmt, if (from_windows && orig_loginname) { + char* domain_name = NULL; + /* * The login name must contain '\' if it is * windows login or else throw error. @@ -2740,6 +2742,16 @@ bbf_ProcessUtility(PlannedStmt *pstmt, ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("'%s' is not a valid name because it contains invalid characters.", orig_loginname))); + /* + * Check whether the domain name is supported + * or not + */ + domain_name = get_windows_domain_name(orig_loginname); + if(windows_domain_is_not_supported(domain_name)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("'%s' domain is not yet supported in Babelfish.", domain_name))); + /* * Check whether the domain name contains invalid characters or not. */ diff --git a/contrib/babelfishpg_tsql/src/rolecmds.c b/contrib/babelfishpg_tsql/src/rolecmds.c index 89d20b6fd7..a109eb4e5b 100644 --- a/contrib/babelfishpg_tsql/src/rolecmds.c +++ b/contrib/babelfishpg_tsql/src/rolecmds.c @@ -2355,6 +2355,31 @@ windows_login_contains_invalid_chars(char *input) return false; } +/* + * Extract the domain name from the orig_loginname + */ +char* +get_windows_domain_name(char* input){ + char *pos_slash = strchr(input, '\\'); + int domain_len = pos_slash - input; + char *domain_name = palloc(domain_len+1); + strncpy(domain_name, input, domain_len); + domain_name[domain_len] = '\0'; + return domain_name; +} + +/* + * Check whether the domain name is supported or not + */ +bool +windows_domain_is_not_supported(char* domain_name) +{ + if(strcasecmp(domain_name, "nt service") == 0) { + return true; + } + return false; +} + /** * Domain name checks, doesnot allow characters like "<>&*|quotes spaces" * */ diff --git a/contrib/babelfishpg_tsql/src/rolecmds.h b/contrib/babelfishpg_tsql/src/rolecmds.h index f0041785ad..7c89e18e94 100644 --- a/contrib/babelfishpg_tsql/src/rolecmds.h +++ b/contrib/babelfishpg_tsql/src/rolecmds.h @@ -81,5 +81,7 @@ extern char *convertToUPN(char *input); extern bool windows_login_contains_invalid_chars(char *input); extern bool windows_domain_contains_invalid_chars(char *input); extern bool check_windows_logon_length(char *input); +extern char* get_windows_domain_name(char* input); +extern bool windows_domain_is_not_supported(char* domain_name); #endif diff --git a/test/JDBC/expected/BABEL-UNSUPPORTED.out b/test/JDBC/expected/BABEL-UNSUPPORTED.out index b4572aace0..46b9b9d7b8 100644 --- a/test/JDBC/expected/BABEL-UNSUPPORTED.out +++ b/test/JDBC/expected/BABEL-UNSUPPORTED.out @@ -2992,3 +2992,117 @@ GO ~~ERROR (Message: 'EXTERNAL NAME' is not currently supported in Babelfish)~~ +-- create login from windows +-- Add 'dummydomain' domain entry +exec sys.babelfish_add_domain_mapping_entry 'dummydomain', 'dummydomain.babel'; +GO + +CREATE LOGIN [NT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Service' domain is not yet supported in Babelfish.)~~ + + +CREATE LOGIN [\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: The login name '\MSSQLSERVER' is invalid. The domain can not be empty.)~~ + + +CREATE LOGIN [NT Servicesomething\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Servicesomething\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [NT ServiceNT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT ServiceNT Service\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [NT ServiceNT SerViCe\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT ServiceNT SerViCe\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [somethingNT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'somethingNT Service\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [NT Service\NT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: The login name 'NT Service\NT Service\MSSQLSERVER' has invalid length. Login name length should be between 1 and 20 for windows login.)~~ + + +CREATE LOGIN [NT S\ervice\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT S\ervice\MSSQLSERVER' is not a valid name because it contains invalid characters.)~~ + + +CREATE LOGIN [NT Service\\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Service\\MSSQLSERVER' is not a valid name because it contains invalid characters.)~~ + + +CREATE LOGIN ["NT Service"\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: '"NT Service"\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [[NT Service]\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: syntax error near '\' at line 1 and character position 26)~~ + + +CREATE LOGIN [["NT Service"]\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: syntax error near '\' at line 1 and character position 28)~~ + + +CREATE LOGIN [NT Service\MSSQLSERVER] FROM WINDOWS WITH DEFAULT_DATABASE=[test] +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Service' domain is not yet supported in Babelfish.)~~ + + +CREATE LOGIN [NT SerViCe\MSSQLSERVER] FROM WINDOWS WITH DEFAULT_DATABASE=[test] +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT SerViCe' domain is not yet supported in Babelfish.)~~ + + +CREATE LOGIN [dummydomain\NT Service] FROM WINDOWS +GO + +-- Dropping 'nt service@DUMMYDOMAIN.BABEL' +DROP LOGIN [dummydomain\NT Service] +GO + +-- Remove entry for 'dummydomain' +exec babelfish_remove_domain_mapping_entry 'dummydomain' +GO + diff --git a/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out b/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out index b4572aace0..46b9b9d7b8 100644 --- a/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out +++ b/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out @@ -2992,3 +2992,117 @@ GO ~~ERROR (Message: 'EXTERNAL NAME' is not currently supported in Babelfish)~~ +-- create login from windows +-- Add 'dummydomain' domain entry +exec sys.babelfish_add_domain_mapping_entry 'dummydomain', 'dummydomain.babel'; +GO + +CREATE LOGIN [NT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Service' domain is not yet supported in Babelfish.)~~ + + +CREATE LOGIN [\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: The login name '\MSSQLSERVER' is invalid. The domain can not be empty.)~~ + + +CREATE LOGIN [NT Servicesomething\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Servicesomething\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [NT ServiceNT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT ServiceNT Service\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [NT ServiceNT SerViCe\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT ServiceNT SerViCe\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [somethingNT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'somethingNT Service\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [NT Service\NT Service\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: The login name 'NT Service\NT Service\MSSQLSERVER' has invalid length. Login name length should be between 1 and 20 for windows login.)~~ + + +CREATE LOGIN [NT S\ervice\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT S\ervice\MSSQLSERVER' is not a valid name because it contains invalid characters.)~~ + + +CREATE LOGIN [NT Service\\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Service\\MSSQLSERVER' is not a valid name because it contains invalid characters.)~~ + + +CREATE LOGIN ["NT Service"\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: '"NT Service"\MSSQLSERVER' is not valid because the domain name contains invalid characters.)~~ + + +CREATE LOGIN [[NT Service]\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: syntax error near '\' at line 1 and character position 26)~~ + + +CREATE LOGIN [["NT Service"]\MSSQLSERVER] FROM WINDOWS +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: syntax error near '\' at line 1 and character position 28)~~ + + +CREATE LOGIN [NT Service\MSSQLSERVER] FROM WINDOWS WITH DEFAULT_DATABASE=[test] +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT Service' domain is not yet supported in Babelfish.)~~ + + +CREATE LOGIN [NT SerViCe\MSSQLSERVER] FROM WINDOWS WITH DEFAULT_DATABASE=[test] +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: 'NT SerViCe' domain is not yet supported in Babelfish.)~~ + + +CREATE LOGIN [dummydomain\NT Service] FROM WINDOWS +GO + +-- Dropping 'nt service@DUMMYDOMAIN.BABEL' +DROP LOGIN [dummydomain\NT Service] +GO + +-- Remove entry for 'dummydomain' +exec babelfish_remove_domain_mapping_entry 'dummydomain' +GO + diff --git a/test/JDBC/input/BABEL-UNSUPPORTED.sql b/test/JDBC/input/BABEL-UNSUPPORTED.sql index acc487bc66..16ba866f4e 100644 --- a/test/JDBC/input/BABEL-UNSUPPORTED.sql +++ b/test/JDBC/input/BABEL-UNSUPPORTED.sql @@ -1657,6 +1657,64 @@ AS EXTERNAL NAME babel_3571 GO +-- create login from windows +-- Add 'dummydomain' domain entry +exec sys.babelfish_add_domain_mapping_entry 'dummydomain', 'dummydomain.babel'; +GO + +CREATE LOGIN [NT Service\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [NT Servicesomething\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [NT ServiceNT Service\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [NT ServiceNT SerViCe\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [somethingNT Service\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [NT Service\NT Service\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [NT S\ervice\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [NT Service\\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN ["NT Service"\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [[NT Service]\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [["NT Service"]\MSSQLSERVER] FROM WINDOWS +GO + +CREATE LOGIN [NT Service\MSSQLSERVER] FROM WINDOWS WITH DEFAULT_DATABASE=[test] +GO + +CREATE LOGIN [NT SerViCe\MSSQLSERVER] FROM WINDOWS WITH DEFAULT_DATABASE=[test] +GO + +CREATE LOGIN [dummydomain\NT Service] FROM WINDOWS +GO + +-- Dropping 'nt service@DUMMYDOMAIN.BABEL' +DROP LOGIN [dummydomain\NT Service] +GO + +-- Remove entry for 'dummydomain' +exec babelfish_remove_domain_mapping_entry 'dummydomain' +GO + -- INSERT BULK is No op. No point in failing this INSERT BULK babel_3571 ( ID INT PRIMARY KEY NOT NULL IDENTITY(1,1),