diff --git a/contrib/babelfishpg_tsql/src/cursor.c b/contrib/babelfishpg_tsql/src/cursor.c index abc2f95e3a5..b47a8fc0fa2 100644 --- a/contrib/babelfishpg_tsql/src/cursor.c +++ b/contrib/babelfishpg_tsql/src/cursor.c @@ -1449,6 +1449,10 @@ execute_sp_cursoropen_common(int *stmt_handle, int *cursor_handle, const char *s Portal portal; MemoryContext oldcontext; MemoryContext savedPortalCxt; + PLtsql_stmt_execsql *parse_result; + PLtsql_function *func; + char *stmt_copy; + char *tsql_stmt = NULL; /* Use this for potentially modified statement */ /* * Connect to SPI manager. should be handled in the same way with @@ -1468,10 +1472,53 @@ execute_sp_cursoropen_common(int *stmt_handle, int *cursor_handle, const char *s if (prepare) { + /* + * This entire block is to parse the statement by antlr and use the statement for + * cursor execution. This is necessary in some use case, for example when we have + * PostgreSQL reversed keywords in query which is valid in TSQL. + * Antlr parser will add the quotes at necessary places in query so that Postgres engine + * can resolve this query correctly. + */ + if (stmt) + { + /* Copy the original statement, because we don't want to use the resultant query in every case. */ + stmt_copy = pstrdup(stmt); + /* Send to antlr parser */ + func = pltsql_compile_inline(stmt_copy, NULL); + + /* + * Check the node list of type PLtsql_stmt_type. Cursor only support single statement. + * If there are more than 1 statement we will through the error. func->action-body + * returned by pltsql_compile_inline generally contains two default nodes, PLTSQL_STMT_INIT being first + * and PLTSQL_STMT_RETURN being last. In case of empty statement only PLTSQL_STMT_RETURN node will be present. + * So total number of nodes should be 3 for valid cursor execution. Actual query statement will be at second + * position of type PLTSQL_STMT_EXECSQL. + * + * This is defensive code, where we only assign the tsql_stmt variable to parsed query, + * if the cmd_type is PLTSQL_STMT_EXECSQL. There might be other types of cmd_type like + * PLTSQL_STMT_EXECSQL (for procedures), for them we will keep the old behavior. + * list length should be checked first before trying to deference second node from the list. + * FIXME:We are handling only PLtsql_stmt_execsql type statement, but TSQL support procedure with + * single statement. We also need to explore all other statement types for completion. + */ + if(list_length(func->action->body) == 3 && + ((PLtsql_stmt *) lsecond(func->action->body))->cmd_type == PLTSQL_STMT_EXECSQL) + { + parse_result = (PLtsql_stmt_execsql *) lsecond(func->action->body); + tsql_stmt = pstrdup(parse_result->sqlstmt->query); + } + + /*Free up function memory as this is not needed anymore */ + pltsql_free_function_memory(func); + } + /* prepare plan and insert a cursor entry */ - plan = SPI_prepare_cursor(stmt, nBindParams, boundParamsOidList, cursor_options); + plan = SPI_prepare_cursor(tsql_stmt?tsql_stmt:stmt, nBindParams, boundParamsOidList, cursor_options); if (plan == NULL) return 1; /* procedure failed */ + /* Free memory if tsql_stmt is not null */ + if (tsql_stmt) + pfree((void *)tsql_stmt); if (save_plan) { @@ -1887,4 +1934,4 @@ reset_cached_cursor(void) /* Reset sp_cursor_params. */ reset_sp_cursor_params(); -} \ No newline at end of file +} diff --git a/test/JDBC/expected/BABEL-SPCURSOR.out b/test/JDBC/expected/BABEL-SPCURSOR.out index 964cb45b59c..e9047b9b1c4 100644 --- a/test/JDBC/expected/BABEL-SPCURSOR.out +++ b/test/JDBC/expected/BABEL-SPCURSOR.out @@ -468,7 +468,28 @@ int#!#int#!#int#!#int#!#int ~~END~~ +# cursor with procedure execution test +CREATE PROCEDURE p1 AS BEGIN select * from babel_cursor_t1 end +go +DECLARE @cursor_handle int; + EXEC sp_cursoropen @cursor_handle OUTPUT, N'exec p1', 2, 8193; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: cannot open CALL query as cursor)~~ + + +# cursor with empty statement will result in error +DECLARE @cursor_handle int; + EXEC sp_cursoropen @cursor_handle OUTPUT, N'', 2, 8193; +go +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: cannot open multi-query plan as cursor)~~ + + DROP TABLE babel_cursor_t1 DROP TABLE t1812 +DROP PROCEDURE p1 GO diff --git a/test/JDBC/expected/TestCursorFetchNext.out b/test/JDBC/expected/TestCursorFetchNext.out index 1661eec9ed0..ff5a268ba42 100644 --- a/test/JDBC/expected/TestCursorFetchNext.out +++ b/test/JDBC/expected/TestCursorFetchNext.out @@ -64,6 +64,44 @@ int#!#smallint#!#bigint#!#tinyint#!#bit #cursor#!#fetch#!#next #cursor#!#fetch#!#afterlast #cursor#!#fetch#!#next +cursor#!#close +~~SUCCESS~~ +cursor#!#open#!#SELECT a year, b month, c quarter FROM test_cursors_fetch_next#!#TYPE_SCROLL_INSENSITIVE#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +~~SUCCESS~~ +cursor#!#fetch#!#next +~~START~~ +int#!#smallint#!#bigint +0#!#0#!#0 +~~END~~ + +cursor#!#fetch#!#first +~~START~~ +int#!#smallint#!#bigint +0#!#0#!#0 +~~END~~ + +cursor#!#fetch#!#next +~~START~~ +int#!#smallint#!#bigint +#!##!# +~~END~~ + +cursor#!#fetch#!#last +~~START~~ +int#!#smallint#!#bigint +211234#!#9780#!#891372401 +~~END~~ + +cursor#!#close +~~SUCCESS~~ +cursor#!#open#!#DECLARE @var INT; select @var = 5; select @var + 1;#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +~~SUCCESS~~ +cursor#!#fetch#!#next +~~START~~ +int +6 +~~END~~ + cursor#!#close ~~SUCCESS~~ DROP TABLE test_cursors_fetch_next diff --git a/test/JDBC/expected/TestCursorPrepExecFetchNext.out b/test/JDBC/expected/TestCursorPrepExecFetchNext.out index fabaa55313d..79bff8b6214 100644 --- a/test/JDBC/expected/TestCursorPrepExecFetchNext.out +++ b/test/JDBC/expected/TestCursorPrepExecFetchNext.out @@ -69,6 +69,38 @@ int#!#smallint#!#bigint#!#tinyint#!#bit #cursor#!#fetch#!#next #cursor#!#fetch#!#afterlast #cursor#!#fetch#!#next +cursor#!#close +~~SUCCESS~~ +cursor#!#open#!#prepst#!#SELECT a year, b month, c quarter from test_cursor_prep_exec_fetch_next#!#TYPE_SCROLL_INSENSITIVE#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +~~SUCCESS~~ +cursor#!#fetch#!#next +~~START~~ +int#!#smallint#!#bigint +0#!#0#!#0 +~~END~~ + +cursor#!#fetch#!#first +~~START~~ +int#!#smallint#!#bigint +0#!#0#!#0 +~~END~~ + +cursor#!#fetch#!#next +~~START~~ +int#!#smallint#!#bigint +#!##!# +~~END~~ + +cursor#!#close +~~SUCCESS~~ +cursor#!#open#!#DECLARE @var INT; select @var = 5; select @var + 1;#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +~~SUCCESS~~ +cursor#!#fetch#!#next +~~START~~ +int +6 +~~END~~ + cursor#!#close ~~SUCCESS~~ DROP TABLE test_cursor_prep_exec_fetch_next @@ -285,4 +317,11 @@ float#!#real#!#money#!#smallmoney #cursor#!#fetch#!#next cursor#!#close ~~SUCCESS~~ +# Cursor with multiple selects should return error +cursor#!#open#!#prepst#!#SELECT * FROM test_cursor_prep_exec_fetch_next;SELECT a FROM test_cursor_prep_exec_fetch_next#!#TYPE_SCROLL_INSENSITIVE#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: cannot open multi-query plan as cursor)~~ + + DROP TABLE test_cursor_prep_exec_fetch_next diff --git a/test/JDBC/input/BABEL-SPCURSOR.sql b/test/JDBC/input/BABEL-SPCURSOR.sql index 6b2e02776d8..b258baaeca8 100644 --- a/test/JDBC/input/BABEL-SPCURSOR.sql +++ b/test/JDBC/input/BABEL-SPCURSOR.sql @@ -225,7 +225,20 @@ exec sp_cursorclose @p2; exec sp_cursorunprepare @p1; go +# cursor with procedure execution test +CREATE PROCEDURE p1 AS BEGIN select * from babel_cursor_t1 end +go +DECLARE @cursor_handle int; + EXEC sp_cursoropen @cursor_handle OUTPUT, N'exec p1', 2, 8193; +go + +# cursor with empty statement will result in error +DECLARE @cursor_handle int; + EXEC sp_cursoropen @cursor_handle OUTPUT, N'', 2, 8193; +go + DROP TABLE babel_cursor_t1 DROP TABLE t1812 +DROP PROCEDURE p1 GO diff --git a/test/JDBC/input/cursors/TestCursorFetchNext.txt b/test/JDBC/input/cursors/TestCursorFetchNext.txt index 22ffd004d35..7a114a42bcc 100644 --- a/test/JDBC/input/cursors/TestCursorFetchNext.txt +++ b/test/JDBC/input/cursors/TestCursorFetchNext.txt @@ -20,6 +20,15 @@ cursor#!#fetch#!#next #cursor#!#fetch#!#afterlast #cursor#!#fetch#!#next cursor#!#close +cursor#!#open#!#SELECT a year, b month, c quarter FROM test_cursors_fetch_next#!#TYPE_SCROLL_INSENSITIVE#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +cursor#!#fetch#!#next +cursor#!#fetch#!#first +cursor#!#fetch#!#next +cursor#!#fetch#!#last +cursor#!#close +cursor#!#open#!#DECLARE @var INT; select @var = 5; select @var + 1;#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +cursor#!#fetch#!#next +cursor#!#close DROP TABLE test_cursors_fetch_next CREATE TABLE test_cursors_fetch_next(a CHAR(30), b VARCHAR(30), c NCHAR(30), d NVARCHAR(30)); diff --git a/test/JDBC/input/cursors/TestCursorPrepExecFetchNext.txt b/test/JDBC/input/cursors/TestCursorPrepExecFetchNext.txt index e2627d990c2..fdfaee867b1 100644 --- a/test/JDBC/input/cursors/TestCursorPrepExecFetchNext.txt +++ b/test/JDBC/input/cursors/TestCursorPrepExecFetchNext.txt @@ -23,6 +23,14 @@ cursor#!#fetch#!#next #cursor#!#fetch#!#afterlast #cursor#!#fetch#!#next cursor#!#close +cursor#!#open#!#prepst#!#SELECT a year, b month, c quarter from test_cursor_prep_exec_fetch_next#!#TYPE_SCROLL_INSENSITIVE#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +cursor#!#fetch#!#next +cursor#!#fetch#!#first +cursor#!#fetch#!#next +cursor#!#close +cursor#!#open#!#DECLARE @var INT; select @var = 5; select @var + 1;#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT +cursor#!#fetch#!#next +cursor#!#close DROP TABLE test_cursor_prep_exec_fetch_next CREATE TABLE test_cursor_prep_exec_fetch_next(a CHAR(30), b VARCHAR(30), c NCHAR(30), d NVARCHAR(30)); @@ -94,4 +102,7 @@ cursor#!#fetch#!#next #cursor#!#fetch#!#afterlast #cursor#!#fetch#!#next cursor#!#close +# Cursor with multiple selects should return error +cursor#!#open#!#prepst#!#SELECT * FROM test_cursor_prep_exec_fetch_next;SELECT a FROM test_cursor_prep_exec_fetch_next#!#TYPE_SCROLL_INSENSITIVE#!#CONCUR_READ_ONLY#!#CLOSE_CURSORS_AT_COMMIT + DROP TABLE test_cursor_prep_exec_fetch_next