Skip to content

Commit

Permalink
Merge pull request #3012 from masatake/sql-plsql-predefined-inquiry-d…
Browse files Browse the repository at this point in the history
…irectives

SQL: introduce a heuristic to distinguish PL/SQL inquiry directives and PostgreSQL dollar quoted strings
  • Loading branch information
masatake authored May 10, 2021
2 parents 6342569 + 293050e commit 63f5d38
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 5 deletions.
1 change: 1 addition & 0 deletions Units/parser-sql.r/sql-plsql-ccflags.d/args.ctags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--sort=no
2 changes: 2 additions & 0 deletions Units/parser-sql.r/sql-plsql-ccflags.d/expected.tags
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
UW_Flag input.sql /^ALTER SESSION SET PLSQL_CCFlags = 'UW_Flag:1, Some_Flag:2, PLSQL_CCFlags:42';$/;" C
Some_Flag input.sql /^ALTER SESSION SET PLSQL_CCFlags = 'UW_Flag:1, Some_Flag:2, PLSQL_CCFlags:42';$/;" C
2 changes: 2 additions & 0 deletions Units/parser-sql.r/sql-plsql-inquiry-directive.d/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
This test case is taken from https://github.com/universal-ctags/ctags/issues/3006,
an issue reported by @bagl.
3 changes: 3 additions & 0 deletions Units/parser-sql.r/sql-plsql-inquiry-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
14 changes: 14 additions & 0 deletions Units/parser-sql.r/sql-plsql-inquiry-directive.d/expected.tags
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
demo_pkg input.sql /^create or replace package body demo_pkg is$/;" P
demo_pkg.test_var input.sql /^test_var varchar2(64) := $$PLSQL_UNIT;$/;" v package:demo_pkg
test_var input.sql /^test_var varchar2(64) := $$PLSQL_UNIT;$/;" v package:demo_pkg
demo_pkg.test_func input.sql /^function test_func return varchar2$/;" f package:demo_pkg
test_func input.sql /^function test_func return varchar2$/;" f package:demo_pkg
demo_pkg1 input-1.sql /^create or replace package body demo_pkg1 is$/;" P
demo_pkg1.test_var1 input-1.sql /^test_var1 varchar2(64) := $$PLSQL_UNIT_OWNER;$/;" v package:demo_pkg1
test_var1 input-1.sql /^test_var1 varchar2(64) := $$PLSQL_UNIT_OWNER;$/;" v package:demo_pkg1
demo_pkg1.test_func1 input-1.sql /^function test_func1 return varchar2$/;" f package:demo_pkg1
test_func1 input-1.sql /^function test_func1 return varchar2$/;" f package:demo_pkg1
Some_Flag input-2.sql /^PLSQL_CCFlags = 'Some_Flag:1, PLSQL_CCFlags:99'$/;" C
foo input-2.sql /^CREATE TABLE foo($/;" t
foo.col input-2.sql /^ col text$/;" E table:foo
col input-2.sql /^ col text$/;" E table:foo
14 changes: 14 additions & 0 deletions Units/parser-sql.r/sql-plsql-inquiry-directive.d/input-1.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Derrived from from https://github.com/universal-ctags/ctags/issues/3006
-- submitted by bagl
create or replace package body demo_pkg1 is

test_var1 varchar2(64) := $$PLSQL_UNIT_OWNER;

function test_func1 return varchar2
as
begin
return test_var1;
end;

end demo_pkg1;

11 changes: 11 additions & 0 deletions Units/parser-sql.r/sql-plsql-inquiry-directive.d/input-2.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ALTER SESSION SET
PLSQL_CCFlags = 'Some_Flag:1, PLSQL_CCFlags:99'
/
BEGIN
DBMS_OUTPUT.PUT_LINE($$Some_Flag);
END;
/

CREATE TABLE foo(
col text
);
19 changes: 19 additions & 0 deletions Units/parser-sql.r/sql-plsql-inquiry-directive.d/input.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Taken from https://github.com/universal-ctags/ctags/issues/3006
-- submitted by bagl
// Fails to create tags after PLSQL inquiry directive is used
// https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-language-fundamentals.html#GUID-E918087C-D5A8-4CEE-841B-5333DE6D4C15
//
// 'parseDollarQuote' causing troubles?
//
create or replace package body demo_pkg is

test_var varchar2(64) := $$PLSQL_UNIT;

function test_func return varchar2
as
begin
return test_var;
end;

end demo_pkg;

116 changes: 111 additions & 5 deletions parsers/sql.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ enum eKeywordId {
KEYWORD_on,
KEYWORD_package,
KEYWORD_pragma,
KEYWORD_inquiry_directive,
KEYWORD_primary,
KEYWORD_procedure,
KEYWORD_publication,
Expand Down Expand Up @@ -215,6 +216,7 @@ typedef enum {
SQLTAG_MLTABLE,
SQLTAG_MLCONN,
SQLTAG_MLPROP,
SQLTAG_PLSQL_CCFLAGS,
SQLTAG_COUNT
} sqlKind;

Expand All @@ -241,7 +243,8 @@ static kindDefinition SqlKinds [] = {
{ true, 'n', "synonym", "synonyms" },
{ true, 'x', "mltable", "MobiLink Table Scripts" },
{ true, 'y', "mlconn", "MobiLink Conn Scripts" },
{ true, 'z', "mlprop", "MobiLink Properties " }
{ true, 'z', "mlprop", "MobiLink Properties" },
{ true, 'C', "ccflag", "PLSQL_CCFLAGS" },
};

static const keywordTable SqlKeywordTable [] = {
Expand Down Expand Up @@ -326,6 +329,27 @@ static const keywordTable SqlKeywordTable [] = {
{ "without", KEYWORD_without },
};

const static struct keywordGroup predefinedInquiryDirective = {
.value = KEYWORD_inquiry_directive,
.addingUnlessExisting = false,
.keywords = {
/* https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-language-fundamentals.html#GUID-3DABF5E1-AC84-448B-810F-31196991EA10 */
"PLSQL_LINE",
"PLSQL_UNIT",
"PLSQL_UNIT_OWNER",
"PLSQL_UNIT_TYPE",
/* https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/overview.html#GUID-DF63BC59-22C2-4BA8-9240-F74D505D5102 */
"PLSCOPE_SETTINGS",
"PLSQL_CCFLAGS",
"PLSQL_CODE_TYPE",
"PLSQL_OPTIMIZE_LEVEL",
"PLSQL_WARNINGS",
"NLS_LENGTH_SEMANTICS",
"PERMIT_92_WRAP_FORMAT",
NULL
},
};

/* A table representing whether a keyword is "reserved word" or not.
* "reserved word" cannot be used as an name.
* See https://dev.mysql.com/doc/refman/8.0/en/keywords.html about the
Expand Down Expand Up @@ -388,6 +412,7 @@ static struct SqlReservedWord SqlReservedWord [SQLKEYWORD_COUNT] = {
[KEYWORD_handler] = {0 & 0&0&0&0 & 0&0 & 0},
[KEYWORD_if] = {1 & 0&0&0&0 & 0&1 & 1},
[KEYWORD_index] = {1 & 0&0&0&0 & 1&1 & 1},
[KEYWORD_inquiry_directive] = { 0 },
[KEYWORD_internal] = {1 & 0&1&1&0 & 0&0 & 0},
[KEYWORD_is] = {0, SqlReservedWordPredicatorForIsOrAs},
[KEYWORD_local] = {0 & 0&1&1&1 & 0&0 & 0},
Expand Down Expand Up @@ -621,8 +646,18 @@ static void parseIdentifier (vString *const string, const int firstChar)
ungetcToInputFile (c); /* unget non-identifier character */
}

static bool isCCFlag(const char *str)
{
return (anyKindEntryInScope(CORK_NIL, str, SQLTAG_PLSQL_CCFLAGS) != 0);
}

/* Parse a PostgreSQL: dollar-quoted string
* https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING */
* https://www.postgresql.org/docs/current/static/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING
*
* 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
*/
static tokenType parseDollarQuote (vString *const string, const int delimiter)
{
unsigned int len = 0;
Expand All @@ -641,17 +676,40 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter)
}
tag[len] = 0;

bool empty_tag = (len == 2);

if (c != delimiter)
{
/* damn that's not valid, what can we do? */
ungetcToInputFile (c);
return TOKEN_UNDEFINED;
}

/* and read the content (until a matching end tag) */
while ((c = getcFromInputFile ()) != EOF)
{
if (c != delimiter)
{
vStringPut (string, c);
if (empty_tag
&& (KEYWORD_inquiry_directive == lookupCaseKeyword (vStringValue (string),
Lang_sql)
|| isCCFlag(vStringValue (string))))
{
/* PL/SQL inquiry directives */
int c0 = getcFromInputFile ();

if (c0 != delimiter && (isalnum(c0) || c0 == '_'))
{
vStringPut (string, c0);
continue;
}

ungetcToInputFile (c0);
/* Oracle PL/SQL's inquiry directive ($$name) */
return TOKEN_UNDEFINED;
}
}
else
{
char *end_p = tag;
Expand All @@ -662,13 +720,13 @@ static tokenType parseDollarQuote (vString *const string, const int delimiter)
end_p++;
}

if (c != EOF)
ungetcToInputFile (c);

if (! *end_p) /* full tag match */
break;
else
{
ungetcToInputFile (c);
vStringNCatS (string, tag, (size_t) (end_p - tag));
}
}
}

Expand Down Expand Up @@ -2672,12 +2730,58 @@ static void parseComment (tokenInfo *const token)
findCmdTerm (token, true);
}

static void parseCCFLAGS (tokenInfo *const token)
{
readToken(token);
if (!isType (token, TOKEN_EQUAL))
{
findCmdTerm (token, true);
return;
}

readToken(token);
if (!isType (token, TOKEN_STRING))
{
findCmdTerm (token, true);
return;
}

bool in_var = true;
const char *s = vStringValue(token->string);
vString *ccflag = vStringNew();
/* http://web.deu.edu.tr/doc/oracle/B19306_01/server.102/b14237/initparams158.htm#REFRN10261 */
while (*s)
{
if (in_var && isIdentChar1((int)*s))
vStringPut(ccflag, *s);
else if (*s == ':' && !vStringIsEmpty(ccflag))
{
if (lookupCaseKeyword(vStringValue(ccflag), Lang_sql)
!= KEYWORD_inquiry_directive)
{
int index = makeSimpleTag(ccflag, SQLTAG_PLSQL_CCFLAGS);
registerEntry(index);
vStringClear(ccflag);
in_var = false;
}
}
else if (*s == ',')
in_var = true;
s++;
}
vStringDelete(ccflag);

}

static void parseKeywords (tokenInfo *const token)
{
switch (token->keyword)
{
case KEYWORD_begin: parseBlock (token, false); break;
case KEYWORD_inquiry_directive:
if (strcasecmp(vStringValue(token->string), "PLSQL_CCFLAGS") == 0)
parseCCFLAGS (token);
break;
case KEYWORD_comment: parseComment (token); break;
case KEYWORD_cursor: parseSimple (token, SQLTAG_CURSOR); break;
case KEYWORD_datatype: parseDomain (token); break;
Expand Down Expand Up @@ -2737,6 +2841,7 @@ static void initialize (const langType language)
{
Assert (ARRAY_SIZE (SqlKinds) == SQLTAG_COUNT);
Lang_sql = language;
addKeywordGroup (&predefinedInquiryDirective, language);
}

static void findSqlTags (void)
Expand All @@ -2759,5 +2864,6 @@ extern parserDefinition* SqlParser (void)
def->initialize = initialize;
def->keywordTable = SqlKeywordTable;
def->keywordCount = ARRAY_SIZE (SqlKeywordTable);
def->useCork = CORK_QUEUE | CORK_SYMTAB;
return def;
}

0 comments on commit 63f5d38

Please sign in to comment.