Skip to content

Commit

Permalink
sql: parse PL/SQL selection directives
Browse files Browse the repository at this point in the history
PLSQL selection directives are of the form

$IF boolean_static_expression $THEN
   text
[ $ELSIF boolean_static_expression $THEN
   text
]...
[ $ELSE
   text
]
$END

In addition, boolean_static_expression conditions like

$$MY_CUSTOM_VAR > 3

can appear which confuses the current parser because it
thinks $$ is the start of dollar-quoted string and drops
the rest of the code.

This patch keeps only the $IF branch and skips all the code
between $ELSE or $ELSIF and $END. In addition, this patch
also drops $ from all identifiers starting with $$ behind
$IF and $ELSIF as these may be conditional variables and
not dollar-quoted strings.
  • Loading branch information
techee committed Mar 21, 2023
1 parent 6f4a48a commit 78a7342
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 7 deletions.
3 changes: 3 additions & 0 deletions Units/parser-sql.r/sql-plsql-selection-directive.d/args.ctags
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--sort=no
--extras=+q
--kinds-SQL=+d
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
demo_pkg input.sql /^create or replace package body demo_pkg is$/;" P
demo_pkg.test_func1 input.sql /^function test_func1 return INTEGER$/;" f package:demo_pkg
test_func1 input.sql /^function test_func1 return INTEGER$/;" f package:demo_pkg
demo_pkg.test_func2 input.sql /^function test_func2 return INTEGER$/;" f package:demo_pkg
test_func2 input.sql /^function test_func2 return INTEGER$/;" f package:demo_pkg
demo_pkg.test_func5 input.sql /^function test_func5 return INTEGER$/;" f package:demo_pkg
test_func5 input.sql /^function test_func5 return INTEGER$/;" f package:demo_pkg
42 changes: 42 additions & 0 deletions Units/parser-sql.r/sql-plsql-selection-directive.d/input.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
create or replace package body demo_pkg is

function test_func1 return INTEGER
as
begin
return 1;
end;

$IF $$MY_VERSION_CODE > 3 $THEN

function test_func2 return INTEGER
as
begin
return 3;
end;

$ELSIF $$MY_VERSION_CODE > 5 $THEN

function test_func3 return INTEGER
as
begin
return 5;
end;

$ELSE

function test_func4 return INTEGER
as
begin
return 7;
end;

$END

function test_func5 return INTEGER
as
begin
return 9;
end;

end demo_pkg;

72 changes: 65 additions & 7 deletions parsers/sql.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ enum eKeywordId {
KEYWORD_drop,
KEYWORD_else,
KEYWORD_elseif,
KEYWORD_elsif,
KEYWORD_end,
KEYWORD_endif,
KEYWORD_event,
Expand Down Expand Up @@ -177,7 +178,12 @@ typedef enum eTokenType {
TOKEN_CLOSE_SQUARE,
TOKEN_TILDE,
TOKEN_FORWARD_SLASH,
TOKEN_EQUAL
TOKEN_EQUAL,
TOKEN_PREPROC_IF,
TOKEN_PREPROC_ELSIF,
TOKEN_PREPROC_ELSE,
TOKEN_PREPROC_THEN,
TOKEN_PREPROC_END,
} tokenType;

typedef struct sTokenInfoSQL {
Expand Down Expand Up @@ -294,6 +300,7 @@ static const keywordTable SqlKeywordTable [] = {
{ "drop", KEYWORD_drop },
{ "else", KEYWORD_else },
{ "elseif", KEYWORD_elseif },
{ "elsif", KEYWORD_elsif },
{ "end", KEYWORD_end },
{ "endif", KEYWORD_endif },
{ "event", KEYWORD_event },
Expand Down Expand Up @@ -431,6 +438,7 @@ static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = {
[KEYWORD_drop] = {1 & 0&1&1&1 & 1&1 & 1},
[KEYWORD_else] = {1 & 1&1&1&1 & 1&1 & 1},
[KEYWORD_elseif] = {1 & 0&0&0&0 & 0&0 & 1},
[KEYWORD_elsif] = {0 & 0&0&0&0 & 0&1 & 0},
[KEYWORD_end] = {0 & 1&1&1&1 & 0&1 & 1},
[KEYWORD_endif] = {0 & 0&0&0&0 & 0&0 & 1},
[KEYWORD_event] = {0 & 0&0&0&0 & 0&0 & 0},
Expand Down Expand Up @@ -725,6 +733,10 @@ static bool isCCFlag(const char *str)
* The syntax for dollar-quoted string ca collide with PL/SQL inquiry directive ($$name).
* https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-language-fundamentals.html#GUID-E918087C-D5A8-4CEE-841B-5333DE6D4C15
* https://github.com/universal-ctags/ctags/issues/3006
* In addition, it can also collide with variable checks in PL/SQL selection directives such as:
* $IF $$my_var > 1 $THEN ... $END
* https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/plsql-language-fundamentals.html#GUID-78F2074C-C799-4CF9-9290-EB8473D0C8FB
*/
static tokenType parseDollarQuote (vString *const string, const int delimiter, int *promise)
{
Expand Down Expand Up @@ -752,8 +764,20 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter, i

if (c != delimiter)
{
/* damn that's not valid, what can we do? */
/* not a dollar quote */
keywordId kw = lookupCaseKeyword (tag+1, Lang_sql);
ungetcToInputFile (c);

if (kw == KEYWORD_if)
return TOKEN_PREPROC_IF;
else if (kw == KEYWORD_elsif)
return TOKEN_PREPROC_ELSIF;
else if (kw == KEYWORD_else)
return TOKEN_PREPROC_ELSE;
else if (kw == KEYWORD_then)
return TOKEN_PREPROC_THEN;
else if (kw == KEYWORD_end)
return TOKEN_PREPROC_END;
return TOKEN_UNDEFINED;
}

Expand Down Expand Up @@ -828,7 +852,7 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter, i
return TOKEN_STRING;
}

static void readToken (tokenInfo *const token)
static void readTokenFull (tokenInfo *const token, bool skippingPreproc)
{
int c;

Expand Down Expand Up @@ -952,10 +976,39 @@ static void readToken (tokenInfo *const token)
}

case '$':
token->type = parseDollarQuote (token->string, c, &token->promise);
token->lineNumber = getInputLineNumber ();
token->filePosition = getInputFilePosition ();
break;
{
tokenType t;
if (skippingPreproc)
{
int d = getcFromInputFile ();
if (d != '$')
ungetcToInputFile (d);
}
t = parseDollarQuote (token->string, c, &token->promise);
if (t == TOKEN_PREPROC_IF)
{
/* skip until $THEN and keep the content of this branch */
readTokenFull (token, true);
while (!isType (token, TOKEN_PREPROC_THEN) && !isType (token, TOKEN_EOF))
readTokenFull (token, true);
readTokenFull (token, false);
}
else if (!skippingPreproc && (t == TOKEN_PREPROC_ELSIF || t == TOKEN_PREPROC_ELSE))
{
/* skip until $END and drop $ELSIF and $ELSE branches */
readTokenFull (token, true);
while (!isType (token, TOKEN_PREPROC_END) && !isType (token, TOKEN_EOF))
readTokenFull (token, true);
readTokenFull (token, false);
}
else
{
token->type = t;
token->lineNumber = getInputLineNumber ();
token->filePosition = getInputFilePosition ();
}
break;
}

default:
if (! isIdentChar1 (c))
Expand All @@ -981,6 +1034,11 @@ static void readToken (tokenInfo *const token)
}
}

static void readToken (tokenInfo *const token)
{
readTokenFull (token, false);
}

/*
* reads an identifier, possibly quoted:
* identifier
Expand Down

0 comments on commit 78a7342

Please sign in to comment.