2175 lines
57 KiB
C
2175 lines
57 KiB
C
/*
|
|
* $Id: sql.c 761 2010-06-04 12:40:28Z dfishburn $
|
|
*
|
|
* Copyright (c) 2002-2003, Darren Hiebert
|
|
*
|
|
* This source code is released for free distribution under the
|
|
* terms of the GNU General Public License.
|
|
*
|
|
* This module contains functions for generating tags for PL/SQL language
|
|
* files.
|
|
*/
|
|
#include "third_party/ctags/general.h"
|
|
/* must always come first */
|
|
#include "third_party/ctags/debug.h"
|
|
#include "third_party/ctags/entry.h"
|
|
#include "third_party/ctags/keyword.h"
|
|
#include "third_party/ctags/parse.h"
|
|
#include "third_party/ctags/read.h"
|
|
#include "third_party/ctags/routines.h"
|
|
#include "third_party/ctags/vstring.h"
|
|
|
|
/*
|
|
* On-line "Oracle Database PL/SQL Language Reference":
|
|
* http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/toc.htm
|
|
*
|
|
* Sample PL/SQL code is available from:
|
|
* http://www.orafaq.com/faqscrpt.htm#GENPLSQL
|
|
*
|
|
* On-line SQL Anywhere Documentation
|
|
* http://www.ianywhere.com/developer/product_manuals/sqlanywhere/index.html
|
|
*/
|
|
|
|
/*
|
|
* MACROS
|
|
*/
|
|
#define isType(token, t) (boolean)((token)->type == (t))
|
|
#define isKeyword(token, k) (boolean)((token)->keyword == (k))
|
|
|
|
/*
|
|
* DATA DECLARATIONS
|
|
*/
|
|
|
|
typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
|
|
|
|
/*
|
|
* Used to specify type of keyword.
|
|
*/
|
|
typedef enum eKeywordId {
|
|
KEYWORD_NONE = -1,
|
|
KEYWORD_is,
|
|
KEYWORD_begin,
|
|
KEYWORD_body,
|
|
KEYWORD_cursor,
|
|
KEYWORD_declare,
|
|
KEYWORD_end,
|
|
KEYWORD_function,
|
|
KEYWORD_if,
|
|
KEYWORD_else,
|
|
KEYWORD_elseif,
|
|
KEYWORD_endif,
|
|
KEYWORD_loop,
|
|
KEYWORD_while,
|
|
KEYWORD_case,
|
|
KEYWORD_for,
|
|
KEYWORD_do,
|
|
KEYWORD_call,
|
|
KEYWORD_package,
|
|
KEYWORD_pragma,
|
|
KEYWORD_procedure,
|
|
KEYWORD_record,
|
|
KEYWORD_object,
|
|
KEYWORD_ref,
|
|
KEYWORD_rem,
|
|
KEYWORD_return,
|
|
KEYWORD_returns,
|
|
KEYWORD_subtype,
|
|
KEYWORD_table,
|
|
KEYWORD_trigger,
|
|
KEYWORD_type,
|
|
KEYWORD_index,
|
|
KEYWORD_event,
|
|
KEYWORD_publication,
|
|
KEYWORD_service,
|
|
KEYWORD_domain,
|
|
KEYWORD_datatype,
|
|
KEYWORD_result,
|
|
KEYWORD_url,
|
|
KEYWORD_internal,
|
|
KEYWORD_external,
|
|
KEYWORD_when,
|
|
KEYWORD_then,
|
|
KEYWORD_variable,
|
|
KEYWORD_exception,
|
|
KEYWORD_at,
|
|
KEYWORD_on,
|
|
KEYWORD_primary,
|
|
KEYWORD_references,
|
|
KEYWORD_unique,
|
|
KEYWORD_check,
|
|
KEYWORD_constraint,
|
|
KEYWORD_foreign,
|
|
KEYWORD_ml_table,
|
|
KEYWORD_ml_table_lang,
|
|
KEYWORD_ml_table_dnet,
|
|
KEYWORD_ml_table_java,
|
|
KEYWORD_ml_table_chk,
|
|
KEYWORD_ml_conn,
|
|
KEYWORD_ml_conn_lang,
|
|
KEYWORD_ml_conn_dnet,
|
|
KEYWORD_ml_conn_java,
|
|
KEYWORD_ml_conn_chk,
|
|
KEYWORD_ml_prop,
|
|
KEYWORD_local,
|
|
KEYWORD_temporary,
|
|
KEYWORD_drop,
|
|
KEYWORD_view,
|
|
KEYWORD_synonym,
|
|
KEYWORD_handler,
|
|
KEYWORD_comment,
|
|
KEYWORD_create,
|
|
KEYWORD_go
|
|
} keywordId;
|
|
|
|
/*
|
|
* Used to determine whether keyword is valid for the token language and
|
|
* what its ID is.
|
|
*/
|
|
typedef struct sKeywordDesc {
|
|
const char *name;
|
|
keywordId id;
|
|
} keywordDesc;
|
|
|
|
typedef enum eTokenType {
|
|
TOKEN_UNDEFINED,
|
|
TOKEN_BLOCK_LABEL_BEGIN,
|
|
TOKEN_BLOCK_LABEL_END,
|
|
TOKEN_CHARACTER,
|
|
TOKEN_CLOSE_PAREN,
|
|
TOKEN_COLON,
|
|
TOKEN_SEMICOLON,
|
|
TOKEN_COMMA,
|
|
TOKEN_IDENTIFIER,
|
|
TOKEN_KEYWORD,
|
|
TOKEN_OPEN_PAREN,
|
|
TOKEN_OPERATOR,
|
|
TOKEN_OTHER,
|
|
TOKEN_STRING,
|
|
TOKEN_PERIOD,
|
|
TOKEN_OPEN_CURLY,
|
|
TOKEN_CLOSE_CURLY,
|
|
TOKEN_OPEN_SQUARE,
|
|
TOKEN_CLOSE_SQUARE,
|
|
TOKEN_TILDE,
|
|
TOKEN_FORWARD_SLASH,
|
|
TOKEN_EQUAL
|
|
} tokenType;
|
|
|
|
typedef struct sTokenInfoSQL {
|
|
tokenType type;
|
|
keywordId keyword;
|
|
vString *string;
|
|
vString *scope;
|
|
int begin_end_nest_lvl;
|
|
unsigned long lineNumber;
|
|
fpos_t filePosition;
|
|
} tokenInfo;
|
|
|
|
/*
|
|
* DATA DEFINITIONS
|
|
*/
|
|
|
|
static langType Lang_sql;
|
|
|
|
static jmp_buf Exception;
|
|
|
|
typedef enum {
|
|
SQLTAG_CURSOR,
|
|
SQLTAG_PROTOTYPE,
|
|
SQLTAG_FUNCTION,
|
|
SQLTAG_FIELD,
|
|
SQLTAG_LOCAL_VARIABLE,
|
|
SQLTAG_BLOCK_LABEL,
|
|
SQLTAG_PACKAGE,
|
|
SQLTAG_PROCEDURE,
|
|
SQLTAG_RECORD,
|
|
SQLTAG_SUBTYPE,
|
|
SQLTAG_TABLE,
|
|
SQLTAG_TRIGGER,
|
|
SQLTAG_VARIABLE,
|
|
SQLTAG_INDEX,
|
|
SQLTAG_EVENT,
|
|
SQLTAG_PUBLICATION,
|
|
SQLTAG_SERVICE,
|
|
SQLTAG_DOMAIN,
|
|
SQLTAG_VIEW,
|
|
SQLTAG_SYNONYM,
|
|
SQLTAG_MLTABLE,
|
|
SQLTAG_MLCONN,
|
|
SQLTAG_MLPROP,
|
|
SQLTAG_COUNT
|
|
} sqlKind;
|
|
|
|
static kindOption SqlKinds[] = {
|
|
{TRUE, 'c', "cursor", "cursors"},
|
|
{FALSE, 'd', "prototype", "prototypes"},
|
|
{TRUE, 'f', "function", "functions"},
|
|
{TRUE, 'F', "field", "record fields"},
|
|
{FALSE, 'l', "local", "local variables"},
|
|
{TRUE, 'L', "label", "block label"},
|
|
{TRUE, 'P', "package", "packages"},
|
|
{TRUE, 'p', "procedure", "procedures"},
|
|
{FALSE, 'r', "record", "records"},
|
|
{TRUE, 's', "subtype", "subtypes"},
|
|
{TRUE, 't', "table", "tables"},
|
|
{TRUE, 'T', "trigger", "triggers"},
|
|
{TRUE, 'v', "variable", "variables"},
|
|
{TRUE, 'i', "index", "indexes"},
|
|
{TRUE, 'e', "event", "events"},
|
|
{TRUE, 'U', "publication", "publications"},
|
|
{TRUE, 'R', "service", "services"},
|
|
{TRUE, 'D', "domain", "domains"},
|
|
{TRUE, 'V', "view", "views"},
|
|
{TRUE, 'n', "synonym", "synonyms"},
|
|
{TRUE, 'x', "mltable", "MobiLink Table Scripts"},
|
|
{TRUE, 'y', "mlconn", "MobiLink Conn Scripts"},
|
|
{TRUE, 'z', "mlprop", "MobiLink Properties "}};
|
|
|
|
static const keywordDesc SqlKeywordTable[] = {
|
|
/* keyword keyword ID */
|
|
{"as", KEYWORD_is},
|
|
{"is", KEYWORD_is},
|
|
{"begin", KEYWORD_begin},
|
|
{"body", KEYWORD_body},
|
|
{"cursor", KEYWORD_cursor},
|
|
{"declare", KEYWORD_declare},
|
|
{"end", KEYWORD_end},
|
|
{"function", KEYWORD_function},
|
|
{"if", KEYWORD_if},
|
|
{"else", KEYWORD_else},
|
|
{"elseif", KEYWORD_elseif},
|
|
{"endif", KEYWORD_endif},
|
|
{"loop", KEYWORD_loop},
|
|
{"while", KEYWORD_while},
|
|
{"case", KEYWORD_case},
|
|
{"for", KEYWORD_for},
|
|
{"do", KEYWORD_do},
|
|
{"call", KEYWORD_call},
|
|
{"package", KEYWORD_package},
|
|
{"pragma", KEYWORD_pragma},
|
|
{"procedure", KEYWORD_procedure},
|
|
{"record", KEYWORD_record},
|
|
{"object", KEYWORD_object},
|
|
{"ref", KEYWORD_ref},
|
|
{"rem", KEYWORD_rem},
|
|
{"return", KEYWORD_return},
|
|
{"returns", KEYWORD_returns},
|
|
{"subtype", KEYWORD_subtype},
|
|
{"table", KEYWORD_table},
|
|
{"trigger", KEYWORD_trigger},
|
|
{"type", KEYWORD_type},
|
|
{"index", KEYWORD_index},
|
|
{"event", KEYWORD_event},
|
|
{"publication", KEYWORD_publication},
|
|
{"service", KEYWORD_service},
|
|
{"domain", KEYWORD_domain},
|
|
{"datatype", KEYWORD_datatype},
|
|
{"result", KEYWORD_result},
|
|
{"url", KEYWORD_url},
|
|
{"internal", KEYWORD_internal},
|
|
{"external", KEYWORD_external},
|
|
{"when", KEYWORD_when},
|
|
{"then", KEYWORD_then},
|
|
{"variable", KEYWORD_variable},
|
|
{"exception", KEYWORD_exception},
|
|
{"at", KEYWORD_at},
|
|
{"on", KEYWORD_on},
|
|
{"primary", KEYWORD_primary},
|
|
{"references", KEYWORD_references},
|
|
{"unique", KEYWORD_unique},
|
|
{"check", KEYWORD_check},
|
|
{"constraint", KEYWORD_constraint},
|
|
{"foreign", KEYWORD_foreign},
|
|
{"ml_add_table_script", KEYWORD_ml_table},
|
|
{"ml_add_lang_table_script", KEYWORD_ml_table_lang},
|
|
{"ml_add_dnet_table_script", KEYWORD_ml_table_dnet},
|
|
{"ml_add_java_table_script", KEYWORD_ml_table_java},
|
|
{"ml_add_lang_table_script_chk", KEYWORD_ml_table_chk},
|
|
{"ml_add_connection_script", KEYWORD_ml_conn},
|
|
{"ml_add_lang_connection_script", KEYWORD_ml_conn_lang},
|
|
{"ml_add_dnet_connection_script", KEYWORD_ml_conn_dnet},
|
|
{"ml_add_java_connection_script", KEYWORD_ml_conn_java},
|
|
{"ml_add_lang_conn_script_chk", KEYWORD_ml_conn_chk},
|
|
{"ml_add_property", KEYWORD_ml_prop},
|
|
{"local", KEYWORD_local},
|
|
{"temporary", KEYWORD_temporary},
|
|
{"drop", KEYWORD_drop},
|
|
{"view", KEYWORD_view},
|
|
{"synonym", KEYWORD_synonym},
|
|
{"handler", KEYWORD_handler},
|
|
{"comment", KEYWORD_comment},
|
|
{"create", KEYWORD_create},
|
|
{"go", KEYWORD_go}};
|
|
|
|
/*
|
|
* FUNCTION DECLARATIONS
|
|
*/
|
|
|
|
/* Recursive calls */
|
|
static void parseBlock(tokenInfo *const token, const boolean local);
|
|
static void parseDeclare(tokenInfo *const token, const boolean local);
|
|
static void parseKeywords(tokenInfo *const token);
|
|
static void parseSqlFile(tokenInfo *const token);
|
|
|
|
/*
|
|
* FUNCTION DEFINITIONS
|
|
*/
|
|
|
|
static boolean isIdentChar1(const int c) {
|
|
/*
|
|
* Other databases are less restrictive on the first character of
|
|
* an identifier.
|
|
* isIdentChar1 is used to identify the first character of an
|
|
* identifier, so we are removing some restrictions.
|
|
*/
|
|
return (boolean)(isalpha(c) || c == '@' || c == '_');
|
|
}
|
|
|
|
static boolean isIdentChar(const int c) {
|
|
return (boolean)(isalpha(c) || isdigit(c) || c == '$' || c == '@' ||
|
|
c == '_' || c == '#');
|
|
}
|
|
|
|
static boolean isCmdTerm(tokenInfo *const token) {
|
|
DebugStatement(debugPrintf(DEBUG_PARSE,
|
|
"\n isCmdTerm: token same tt:%d tk:%d\n",
|
|
token->type, token->keyword););
|
|
|
|
/*
|
|
* Based on the various customer sites I have been at
|
|
* the most common command delimiters are
|
|
* ;
|
|
* ~
|
|
* /
|
|
* go
|
|
* This routine will check for any of these, more
|
|
* can easily be added by modifying readToken and
|
|
* either adding the character to:
|
|
* enum eTokenType
|
|
* enum eTokenType
|
|
*/
|
|
return (isType(token, TOKEN_SEMICOLON) || isType(token, TOKEN_TILDE) ||
|
|
isType(token, TOKEN_FORWARD_SLASH) || isKeyword(token, KEYWORD_go));
|
|
}
|
|
|
|
static boolean isMatchedEnd(tokenInfo *const token, int nest_lvl) {
|
|
boolean terminated = FALSE;
|
|
/*
|
|
* Since different forms of SQL allow the use of
|
|
* BEGIN
|
|
* ...
|
|
* END
|
|
* blocks, some statements may not be terminated using
|
|
* the standard delimiters:
|
|
* ;
|
|
* ~
|
|
* /
|
|
* go
|
|
* This routine will check to see if we encounter and END
|
|
* for the matching nest level of BEGIN ... END statements.
|
|
* If we find one, then we can assume, the statement was terminated
|
|
* since we have fallen through to the END statement of the BEGIN
|
|
* block.
|
|
*/
|
|
if (nest_lvl > 0 && isKeyword(token, KEYWORD_end)) {
|
|
if (token->begin_end_nest_lvl == nest_lvl) terminated = TRUE;
|
|
}
|
|
|
|
return terminated;
|
|
}
|
|
|
|
static void buildSqlKeywordHash(void) {
|
|
const size_t count = sizeof(SqlKeywordTable) / sizeof(SqlKeywordTable[0]);
|
|
size_t i;
|
|
for (i = 0; i < count; ++i) {
|
|
const keywordDesc *const p = &SqlKeywordTable[i];
|
|
addKeyword(p->name, Lang_sql, (int)p->id);
|
|
}
|
|
}
|
|
|
|
static tokenInfo *newToken(void) {
|
|
tokenInfo *const token = xMalloc(1, tokenInfo);
|
|
|
|
token->type = TOKEN_UNDEFINED;
|
|
token->keyword = KEYWORD_NONE;
|
|
token->string = vStringNew();
|
|
token->scope = vStringNew();
|
|
token->begin_end_nest_lvl = 0;
|
|
token->lineNumber = getSourceLineNumber();
|
|
token->filePosition = getInputFilePosition();
|
|
|
|
return token;
|
|
}
|
|
|
|
static void deleteToken(tokenInfo *const token) {
|
|
vStringDelete(token->string);
|
|
vStringDelete(token->scope);
|
|
eFree(token);
|
|
}
|
|
|
|
/*
|
|
* Tag generation functions
|
|
*/
|
|
|
|
static void makeConstTag(tokenInfo *const token, const sqlKind kind) {
|
|
if (SqlKinds[kind].enabled) {
|
|
const char *const name = vStringValue(token->string);
|
|
tagEntryInfo e;
|
|
initTagEntry(&e, name);
|
|
|
|
e.lineNumber = token->lineNumber;
|
|
e.filePosition = token->filePosition;
|
|
e.kindName = SqlKinds[kind].name;
|
|
e.kind = SqlKinds[kind].letter;
|
|
|
|
makeTagEntry(&e);
|
|
}
|
|
}
|
|
|
|
static void makeSqlTag(tokenInfo *const token, const sqlKind kind) {
|
|
vString *fulltag;
|
|
|
|
if (SqlKinds[kind].enabled) {
|
|
/*
|
|
* If a scope has been added to the token, change the token
|
|
* string to include the scope when making the tag.
|
|
*/
|
|
if (vStringLength(token->scope) > 0) {
|
|
fulltag = vStringNew();
|
|
vStringCopy(fulltag, token->scope);
|
|
vStringCatS(fulltag, ".");
|
|
vStringCatS(fulltag, vStringValue(token->string));
|
|
vStringTerminate(fulltag);
|
|
vStringCopy(token->string, fulltag);
|
|
vStringDelete(fulltag);
|
|
}
|
|
makeConstTag(token, kind);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Parsing functions
|
|
*/
|
|
|
|
static void parseString(vString *const string, const int delimiter) {
|
|
boolean end = FALSE;
|
|
while (!end) {
|
|
int c = fileGetc();
|
|
if (c == EOF) end = TRUE;
|
|
/*
|
|
else if (c == '\\')
|
|
{
|
|
c = fileGetc(); // This maybe a ' or ". //
|
|
vStringPut(string, c);
|
|
}
|
|
*/
|
|
else if (c == delimiter)
|
|
end = TRUE;
|
|
else
|
|
vStringPut(string, c);
|
|
}
|
|
vStringTerminate(string);
|
|
}
|
|
|
|
/* Read a C identifier beginning with "firstChar" and places it into
|
|
* "name".
|
|
*/
|
|
static void parseIdentifier(vString *const string, const int firstChar) {
|
|
int c = firstChar;
|
|
Assert(isIdentChar1(c));
|
|
do {
|
|
vStringPut(string, c);
|
|
c = fileGetc();
|
|
} while (isIdentChar(c));
|
|
vStringTerminate(string);
|
|
if (!isspace(c)) fileUngetc(c); /* unget non-identifier character */
|
|
}
|
|
|
|
static void readToken(tokenInfo *const token) {
|
|
int c;
|
|
|
|
token->type = TOKEN_UNDEFINED;
|
|
token->keyword = KEYWORD_NONE;
|
|
vStringClear(token->string);
|
|
|
|
getNextChar:
|
|
do {
|
|
c = fileGetc();
|
|
token->lineNumber = getSourceLineNumber();
|
|
token->filePosition = getInputFilePosition();
|
|
/*
|
|
* Added " to the list of ignores, not sure what this
|
|
* might break but it gets by this issue:
|
|
* create table "t1" (...)
|
|
*
|
|
* Darren, the code passes all my tests for both
|
|
* Oracle and SQL Anywhere, but maybe you can tell me
|
|
* what this may effect.
|
|
*/
|
|
} while (c == '\t' || c == ' ' || c == '\n');
|
|
|
|
switch (c) {
|
|
case EOF:
|
|
longjmp(Exception, (int)ExceptionEOF);
|
|
break;
|
|
case '(':
|
|
token->type = TOKEN_OPEN_PAREN;
|
|
break;
|
|
case ')':
|
|
token->type = TOKEN_CLOSE_PAREN;
|
|
break;
|
|
case ':':
|
|
token->type = TOKEN_COLON;
|
|
break;
|
|
case ';':
|
|
token->type = TOKEN_SEMICOLON;
|
|
break;
|
|
case '.':
|
|
token->type = TOKEN_PERIOD;
|
|
break;
|
|
case ',':
|
|
token->type = TOKEN_COMMA;
|
|
break;
|
|
case '{':
|
|
token->type = TOKEN_OPEN_CURLY;
|
|
break;
|
|
case '}':
|
|
token->type = TOKEN_CLOSE_CURLY;
|
|
break;
|
|
case '~':
|
|
token->type = TOKEN_TILDE;
|
|
break;
|
|
case '[':
|
|
token->type = TOKEN_OPEN_SQUARE;
|
|
break;
|
|
case ']':
|
|
token->type = TOKEN_CLOSE_SQUARE;
|
|
break;
|
|
case '=':
|
|
token->type = TOKEN_EQUAL;
|
|
break;
|
|
|
|
case '\'':
|
|
case '"':
|
|
token->type = TOKEN_STRING;
|
|
parseString(token->string, c);
|
|
token->lineNumber = getSourceLineNumber();
|
|
token->filePosition = getInputFilePosition();
|
|
break;
|
|
|
|
case '-':
|
|
c = fileGetc();
|
|
if (c == '-') /* -- is this the start of a comment? */
|
|
{
|
|
fileSkipToCharacter('\n');
|
|
goto getNextChar;
|
|
} else {
|
|
if (!isspace(c)) fileUngetc(c);
|
|
token->type = TOKEN_OPERATOR;
|
|
}
|
|
break;
|
|
|
|
case '<':
|
|
case '>': {
|
|
const int initial = c;
|
|
int d = fileGetc();
|
|
if (d == initial) {
|
|
if (initial == '<')
|
|
token->type = TOKEN_BLOCK_LABEL_BEGIN;
|
|
else
|
|
token->type = TOKEN_BLOCK_LABEL_END;
|
|
} else {
|
|
fileUngetc(d);
|
|
token->type = TOKEN_UNDEFINED;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case '\\':
|
|
c = fileGetc();
|
|
if (c != '\\' && c != '"' && c != '\'' && !isspace(c)) fileUngetc(c);
|
|
token->type = TOKEN_CHARACTER;
|
|
token->lineNumber = getSourceLineNumber();
|
|
token->filePosition = getInputFilePosition();
|
|
break;
|
|
|
|
case '/': {
|
|
int d = fileGetc();
|
|
if ((d != '*') && /* is this the start of a comment? */
|
|
(d != '/')) /* is a one line comment? */
|
|
{
|
|
token->type = TOKEN_FORWARD_SLASH;
|
|
fileUngetc(d);
|
|
} else {
|
|
if (d == '*') {
|
|
do {
|
|
fileSkipToCharacter('*');
|
|
c = fileGetc();
|
|
if (c == '/')
|
|
break;
|
|
else
|
|
fileUngetc(c);
|
|
} while (c != EOF && c != '\0');
|
|
goto getNextChar;
|
|
} else if (d == '/') /* is this the start of a comment? */
|
|
{
|
|
fileSkipToCharacter('\n');
|
|
goto getNextChar;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (!isIdentChar1(c))
|
|
token->type = TOKEN_UNDEFINED;
|
|
else {
|
|
parseIdentifier(token->string, c);
|
|
token->lineNumber = getSourceLineNumber();
|
|
token->filePosition = getInputFilePosition();
|
|
token->keyword = analyzeToken(token->string, Lang_sql);
|
|
if (isKeyword(token, KEYWORD_rem)) {
|
|
vStringClear(token->string);
|
|
fileSkipToCharacter('\n');
|
|
goto getNextChar;
|
|
} else if (isKeyword(token, KEYWORD_NONE))
|
|
token->type = TOKEN_IDENTIFIER;
|
|
else
|
|
token->type = TOKEN_KEYWORD;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Token parsing functions
|
|
*/
|
|
|
|
/*
|
|
* static void addContext (tokenInfo* const parent, const tokenInfo* const
|
|
*child)
|
|
* {
|
|
* if (vStringLength (parent->string) > 0)
|
|
* {
|
|
* vStringCatS (parent->string, ".");
|
|
* }
|
|
* vStringCatS (parent->string, vStringValue(child->string));
|
|
* vStringTerminate(parent->string);
|
|
* }
|
|
*/
|
|
|
|
static void addToScope(tokenInfo *const token, vString *const extra) {
|
|
if (vStringLength(token->scope) > 0) {
|
|
vStringCatS(token->scope, ".");
|
|
}
|
|
vStringCatS(token->scope, vStringValue(extra));
|
|
vStringTerminate(token->scope);
|
|
}
|
|
|
|
/*
|
|
* Scanning functions
|
|
*/
|
|
|
|
static void findToken(tokenInfo *const token, const tokenType type) {
|
|
while (!isType(token, type)) {
|
|
readToken(token);
|
|
}
|
|
}
|
|
|
|
static void findCmdTerm(tokenInfo *const token, const boolean check_first) {
|
|
int begin_end_nest_lvl = token->begin_end_nest_lvl;
|
|
|
|
if (check_first) {
|
|
if (isCmdTerm(token)) return;
|
|
}
|
|
do {
|
|
readToken(token);
|
|
} while (!isCmdTerm(token) && !isMatchedEnd(token, begin_end_nest_lvl));
|
|
}
|
|
|
|
static void skipToMatched(tokenInfo *const token) {
|
|
int nest_level = 0;
|
|
tokenType open_token;
|
|
tokenType close_token;
|
|
|
|
switch (token->type) {
|
|
case TOKEN_OPEN_PAREN:
|
|
open_token = TOKEN_OPEN_PAREN;
|
|
close_token = TOKEN_CLOSE_PAREN;
|
|
break;
|
|
case TOKEN_OPEN_CURLY:
|
|
open_token = TOKEN_OPEN_CURLY;
|
|
close_token = TOKEN_CLOSE_CURLY;
|
|
break;
|
|
case TOKEN_OPEN_SQUARE:
|
|
open_token = TOKEN_OPEN_SQUARE;
|
|
close_token = TOKEN_CLOSE_SQUARE;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* This routine will skip to a matching closing token.
|
|
* It will also handle nested tokens like the (, ) below.
|
|
* ( name varchar(30), text binary(10) )
|
|
*/
|
|
|
|
if (isType(token, open_token)) {
|
|
nest_level++;
|
|
while (!(isType(token, close_token) && (nest_level == 0))) {
|
|
readToken(token);
|
|
if (isType(token, open_token)) {
|
|
nest_level++;
|
|
}
|
|
if (isType(token, close_token)) {
|
|
if (nest_level > 0) {
|
|
nest_level--;
|
|
}
|
|
}
|
|
}
|
|
readToken(token);
|
|
}
|
|
}
|
|
|
|
static void copyToken(tokenInfo *const dest, tokenInfo *const src) {
|
|
dest->lineNumber = src->lineNumber;
|
|
dest->filePosition = src->filePosition;
|
|
dest->type = src->type;
|
|
dest->keyword = src->keyword;
|
|
vStringCopy(dest->string, src->string);
|
|
vStringCopy(dest->scope, src->scope);
|
|
}
|
|
|
|
static void skipArgumentList(tokenInfo *const token) {
|
|
/*
|
|
* Other databases can have arguments with fully declared
|
|
* datatypes:
|
|
* ( name varchar(30), text binary(10) )
|
|
* So we must check for nested open and closing parantheses
|
|
*/
|
|
|
|
if (isType(token, TOKEN_OPEN_PAREN)) /* arguments? */
|
|
{
|
|
skipToMatched(token);
|
|
}
|
|
}
|
|
|
|
static void parseSubProgram(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
vString *saveScope = vStringNew();
|
|
|
|
/*
|
|
* This must handle both prototypes and the body of
|
|
* the procedures.
|
|
*
|
|
* Prototype:
|
|
* FUNCTION func_name RETURN integer;
|
|
* PROCEDURE proc_name( parameters );
|
|
* Procedure
|
|
* FUNCTION GET_ML_USERNAME RETURN VARCHAR2
|
|
* IS
|
|
* BEGIN
|
|
* RETURN v_sync_user_id;
|
|
* END GET_ML_USERNAME;
|
|
*
|
|
* PROCEDURE proc_name( parameters )
|
|
* IS
|
|
* BEGIN
|
|
* END;
|
|
* CREATE PROCEDURE proc_name( parameters )
|
|
* EXTERNAL NAME ... ;
|
|
* CREATE PROCEDURE proc_name( parameters )
|
|
* BEGIN
|
|
* END;
|
|
*
|
|
* CREATE FUNCTION f_GetClassName(
|
|
* IN @object VARCHAR(128)
|
|
* ,IN @code VARCHAR(128)
|
|
* )
|
|
* RETURNS VARCHAR(200)
|
|
* DETERMINISTIC
|
|
* BEGIN
|
|
*
|
|
* IF( @object = 'user_state' ) THEN
|
|
* SET something = something;
|
|
* END IF;
|
|
*
|
|
* RETURN @name;
|
|
* END;
|
|
*
|
|
* Note, a Package adds scope to the items within.
|
|
* create or replace package demo_pkg is
|
|
* test_var number;
|
|
* function test_func return varchar2;
|
|
* function more.test_func2 return varchar2;
|
|
* end demo_pkg;
|
|
* So the tags generated here, contain the package name:
|
|
* demo_pkg.test_var
|
|
* demo_pkg.test_func
|
|
* demo_pkg.more.test_func2
|
|
*/
|
|
const sqlKind kind =
|
|
isKeyword(token, KEYWORD_function) ? SQLTAG_FUNCTION : SQLTAG_PROCEDURE;
|
|
Assert(isKeyword(token, KEYWORD_function) ||
|
|
isKeyword(token, KEYWORD_procedure));
|
|
|
|
vStringCopy(saveScope, token->scope);
|
|
readToken(token);
|
|
copyToken(name, token);
|
|
readToken(token);
|
|
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
/*
|
|
* If this is an Oracle package, then the token->scope should
|
|
* already be set. If this is the case, also add this value to the
|
|
* scope.
|
|
* If this is not an Oracle package, chances are the scope should be
|
|
* blank and the value just read is the OWNER or CREATOR of the
|
|
* function and should not be considered part of the scope.
|
|
*/
|
|
if (vStringLength(saveScope) > 0) {
|
|
addToScope(token, name->string);
|
|
}
|
|
readToken(token);
|
|
copyToken(name, token);
|
|
readToken(token);
|
|
}
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
/* Reads to the next token after the TOKEN_CLOSE_PAREN */
|
|
skipArgumentList(token);
|
|
}
|
|
|
|
if (kind == SQLTAG_FUNCTION) {
|
|
if (isKeyword(token, KEYWORD_return) || isKeyword(token, KEYWORD_returns)) {
|
|
/* Read datatype */
|
|
readToken(token);
|
|
/*
|
|
* Read token after which could be the
|
|
* command terminator if a prototype
|
|
* or an open parantheses
|
|
*/
|
|
readToken(token);
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
/* Reads to the next token after the TOKEN_CLOSE_PAREN */
|
|
skipArgumentList(token);
|
|
}
|
|
}
|
|
}
|
|
if (isCmdTerm(token)) {
|
|
makeSqlTag(name, SQLTAG_PROTOTYPE);
|
|
} else {
|
|
while (
|
|
!(isKeyword(token, KEYWORD_is) || isKeyword(token, KEYWORD_begin) ||
|
|
isKeyword(token, KEYWORD_at) || isKeyword(token, KEYWORD_internal) ||
|
|
isKeyword(token, KEYWORD_external) || isKeyword(token, KEYWORD_url) ||
|
|
isType(token, TOKEN_EQUAL) || isCmdTerm(token))) {
|
|
if (isKeyword(token, KEYWORD_result)) {
|
|
readToken(token);
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
/* Reads to the next token after the TOKEN_CLOSE_PAREN */
|
|
skipArgumentList(token);
|
|
}
|
|
} else {
|
|
readToken(token);
|
|
}
|
|
}
|
|
if (isKeyword(token, KEYWORD_at) || isKeyword(token, KEYWORD_url) ||
|
|
isKeyword(token, KEYWORD_internal) ||
|
|
isKeyword(token, KEYWORD_external)) {
|
|
addToScope(token, name->string);
|
|
if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING) ||
|
|
!isKeyword(token, KEYWORD_NONE))
|
|
makeSqlTag(name, kind);
|
|
|
|
vStringClear(token->scope);
|
|
}
|
|
if (isType(token, TOKEN_EQUAL)) readToken(token);
|
|
|
|
if (isKeyword(token, KEYWORD_declare)) parseDeclare(token, FALSE);
|
|
|
|
if (isKeyword(token, KEYWORD_is) || isKeyword(token, KEYWORD_begin)) {
|
|
addToScope(token, name->string);
|
|
if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING) ||
|
|
!isKeyword(token, KEYWORD_NONE))
|
|
makeSqlTag(name, kind);
|
|
|
|
parseBlock(token, TRUE);
|
|
vStringClear(token->scope);
|
|
}
|
|
}
|
|
vStringCopy(token->scope, saveScope);
|
|
deleteToken(name);
|
|
vStringDelete(saveScope);
|
|
}
|
|
|
|
static void parseRecord(tokenInfo *const token) {
|
|
/*
|
|
* Make it a bit forgiving, this is called from
|
|
* multiple functions, parseTable, parseType
|
|
*/
|
|
if (!isType(token, TOKEN_OPEN_PAREN)) readToken(token);
|
|
|
|
Assert(isType(token, TOKEN_OPEN_PAREN));
|
|
do {
|
|
if (isType(token, TOKEN_COMMA) || isType(token, TOKEN_OPEN_PAREN))
|
|
readToken(token);
|
|
|
|
/*
|
|
* Create table statements can end with various constraints
|
|
* which must be excluded from the SQLTAG_FIELD.
|
|
* create table t1 (
|
|
* c1 integer,
|
|
* c2 char(30),
|
|
* c3 numeric(10,5),
|
|
* c4 integer,
|
|
* constraint whatever,
|
|
* primary key(c1),
|
|
* foreign key (),
|
|
* check ()
|
|
* )
|
|
*/
|
|
if (!(isKeyword(token, KEYWORD_primary) ||
|
|
isKeyword(token, KEYWORD_references) ||
|
|
isKeyword(token, KEYWORD_unique) || isKeyword(token, KEYWORD_check) ||
|
|
isKeyword(token, KEYWORD_constraint) ||
|
|
isKeyword(token, KEYWORD_foreign))) {
|
|
if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING))
|
|
makeSqlTag(token, SQLTAG_FIELD);
|
|
}
|
|
|
|
while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN) ||
|
|
isType(token, TOKEN_OPEN_PAREN))) {
|
|
readToken(token);
|
|
/*
|
|
* A table structure can look like this:
|
|
* create table t1 (
|
|
* c1 integer,
|
|
* c2 char(30),
|
|
* c3 numeric(10,5),
|
|
* c4 integer
|
|
* )
|
|
* We can't just look for a COMMA or CLOSE_PAREN
|
|
* since that will not deal with the numeric(10,5)
|
|
* case. So we need to skip the argument list
|
|
* when we find an open paren.
|
|
*/
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
/* Reads to the next token after the TOKEN_CLOSE_PAREN */
|
|
skipArgumentList(token);
|
|
}
|
|
}
|
|
} while (!isType(token, TOKEN_CLOSE_PAREN));
|
|
}
|
|
|
|
static void parseType(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
vString *saveScope = vStringNew();
|
|
|
|
vStringCopy(saveScope, token->scope);
|
|
/* If a scope has been set, add it to the name */
|
|
addToScope(name, token->scope);
|
|
readToken(name);
|
|
if (isType(name, TOKEN_IDENTIFIER)) {
|
|
readToken(token);
|
|
if (isKeyword(token, KEYWORD_is)) {
|
|
readToken(token);
|
|
addToScope(token, name->string);
|
|
switch (token->keyword) {
|
|
case KEYWORD_record:
|
|
case KEYWORD_object:
|
|
makeSqlTag(name, SQLTAG_RECORD);
|
|
parseRecord(token);
|
|
break;
|
|
|
|
case KEYWORD_table:
|
|
makeSqlTag(name, SQLTAG_TABLE);
|
|
break;
|
|
|
|
case KEYWORD_ref:
|
|
readToken(token);
|
|
if (isKeyword(token, KEYWORD_cursor)) makeSqlTag(name, SQLTAG_CURSOR);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
vStringClear(token->scope);
|
|
}
|
|
}
|
|
vStringCopy(token->scope, saveScope);
|
|
deleteToken(name);
|
|
vStringDelete(saveScope);
|
|
}
|
|
|
|
static void parseSimple(tokenInfo *const token, const sqlKind kind) {
|
|
/* This will simply make the tagname from the first word found */
|
|
readToken(token);
|
|
if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING))
|
|
makeSqlTag(token, kind);
|
|
}
|
|
|
|
static void parseDeclare(tokenInfo *const token, const boolean local) {
|
|
/*
|
|
* PL/SQL declares are of this format:
|
|
* IS|AS
|
|
* [declare]
|
|
* CURSOR curname ...
|
|
* varname1 datatype;
|
|
* varname2 datatype;
|
|
* varname3 datatype;
|
|
* begin
|
|
*/
|
|
|
|
if (isKeyword(token, KEYWORD_declare)) readToken(token);
|
|
while (!isKeyword(token, KEYWORD_begin) && !isKeyword(token, KEYWORD_end)) {
|
|
switch (token->keyword) {
|
|
case KEYWORD_cursor:
|
|
parseSimple(token, SQLTAG_CURSOR);
|
|
break;
|
|
case KEYWORD_function:
|
|
parseSubProgram(token);
|
|
break;
|
|
case KEYWORD_procedure:
|
|
parseSubProgram(token);
|
|
break;
|
|
case KEYWORD_subtype:
|
|
parseSimple(token, SQLTAG_SUBTYPE);
|
|
break;
|
|
case KEYWORD_trigger:
|
|
parseSimple(token, SQLTAG_TRIGGER);
|
|
break;
|
|
case KEYWORD_type:
|
|
parseType(token);
|
|
break;
|
|
|
|
default:
|
|
if (isType(token, TOKEN_IDENTIFIER)) {
|
|
if (local) {
|
|
makeSqlTag(token, SQLTAG_LOCAL_VARIABLE);
|
|
} else {
|
|
makeSqlTag(token, SQLTAG_VARIABLE);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
findToken(token, TOKEN_SEMICOLON);
|
|
readToken(token);
|
|
}
|
|
}
|
|
|
|
static void parseDeclareANSI(tokenInfo *const token, const boolean local) {
|
|
tokenInfo *const type = newToken();
|
|
/*
|
|
* ANSI declares are of this format:
|
|
* BEGIN
|
|
* DECLARE varname1 datatype;
|
|
* DECLARE varname2 datatype;
|
|
* ...
|
|
*
|
|
* This differ from PL/SQL where DECLARE preceeds the BEGIN block
|
|
* and the DECLARE keyword is not repeated.
|
|
*/
|
|
while (isKeyword(token, KEYWORD_declare)) {
|
|
readToken(token);
|
|
readToken(type);
|
|
|
|
if (isKeyword(type, KEYWORD_cursor))
|
|
makeSqlTag(token, SQLTAG_CURSOR);
|
|
else if (isKeyword(token, KEYWORD_local) &&
|
|
isKeyword(type, KEYWORD_temporary)) {
|
|
/*
|
|
* DECLARE LOCAL TEMPORARY TABLE table_name (
|
|
* c1 int,
|
|
* c2 int
|
|
* );
|
|
*/
|
|
readToken(token);
|
|
if (isKeyword(token, KEYWORD_table)) {
|
|
readToken(token);
|
|
if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) {
|
|
makeSqlTag(token, SQLTAG_TABLE);
|
|
}
|
|
}
|
|
} else if (isType(token, TOKEN_IDENTIFIER) || isType(token, TOKEN_STRING)) {
|
|
if (local)
|
|
makeSqlTag(token, SQLTAG_LOCAL_VARIABLE);
|
|
else
|
|
makeSqlTag(token, SQLTAG_VARIABLE);
|
|
}
|
|
findToken(token, TOKEN_SEMICOLON);
|
|
readToken(token);
|
|
}
|
|
deleteToken(type);
|
|
}
|
|
|
|
static void parseLabel(tokenInfo *const token) {
|
|
/*
|
|
* A label has this format:
|
|
* <<tobacco_dependency>>
|
|
* DECLARE
|
|
* v_senator VARCHAR2(100) := 'THURMOND, JESSE';
|
|
* BEGIN
|
|
* IF total_contributions (v_senator, 'TOBACCO') > 25000
|
|
* THEN
|
|
* <<alochol_dependency>>
|
|
* DECLARE
|
|
* v_senator VARCHAR2(100) := 'WHATEVERIT, TAKES';
|
|
* BEGIN
|
|
* ...
|
|
*/
|
|
|
|
Assert(isType(token, TOKEN_BLOCK_LABEL_BEGIN));
|
|
readToken(token);
|
|
if (isType(token, TOKEN_IDENTIFIER)) {
|
|
makeSqlTag(token, SQLTAG_BLOCK_LABEL);
|
|
readToken(token); /* read end of label */
|
|
}
|
|
}
|
|
|
|
static void parseStatements(tokenInfo *const token,
|
|
const boolean exit_on_endif) {
|
|
boolean isAnsi = TRUE;
|
|
boolean stmtTerm = FALSE;
|
|
do {
|
|
|
|
if (isType(token, TOKEN_BLOCK_LABEL_BEGIN))
|
|
parseLabel(token);
|
|
else {
|
|
switch (token->keyword) {
|
|
case KEYWORD_exception:
|
|
/*
|
|
* EXCEPTION
|
|
* <exception handler>;
|
|
*
|
|
* Where an exception handler could be:
|
|
* BEGIN
|
|
* WHEN OTHERS THEN
|
|
* x := x + 3;
|
|
* END;
|
|
* In this case we need to skip this keyword and
|
|
* move on to the next token without reading until
|
|
* TOKEN_SEMICOLON;
|
|
*/
|
|
readToken(token);
|
|
continue;
|
|
|
|
case KEYWORD_when:
|
|
/*
|
|
* WHEN statements can be used in exception clauses
|
|
* and CASE statements. The CASE statement should skip
|
|
* these given below we skip over to an END statement.
|
|
* But for an exception clause, we can have:
|
|
* EXCEPTION
|
|
* WHEN OTHERS THEN
|
|
* BEGIN
|
|
* x := x + 3;
|
|
* END;
|
|
* If we skip to the TOKEN_SEMICOLON, we miss the begin
|
|
* of a nested BEGIN END block. So read the next token
|
|
* after the THEN and restart the LOOP.
|
|
*/
|
|
while (!isKeyword(token, KEYWORD_then)) readToken(token);
|
|
|
|
readToken(token);
|
|
continue;
|
|
|
|
case KEYWORD_if:
|
|
/*
|
|
* We do not want to look for a ; since for an empty
|
|
* IF block, it would skip over the END.
|
|
* IF...THEN
|
|
* END IF;
|
|
*
|
|
* IF...THEN
|
|
* ELSE
|
|
* END IF;
|
|
*
|
|
* IF...THEN
|
|
* ELSEIF...THEN
|
|
* ELSE
|
|
* END IF;
|
|
*
|
|
* or non-ANSI
|
|
* IF ...
|
|
* BEGIN
|
|
* END
|
|
*/
|
|
while (!isKeyword(token, KEYWORD_then) &&
|
|
!isKeyword(token, KEYWORD_begin)) {
|
|
readToken(token);
|
|
}
|
|
|
|
if (isKeyword(token, KEYWORD_begin)) {
|
|
isAnsi = FALSE;
|
|
parseBlock(token, FALSE);
|
|
|
|
/*
|
|
* Handle the non-Ansi IF blocks.
|
|
* parseBlock consumes the END, so if the next
|
|
* token in a command terminator (like GO)
|
|
* we know we are done with this statement.
|
|
*/
|
|
if (isCmdTerm(token)) stmtTerm = TRUE;
|
|
} else {
|
|
readToken(token);
|
|
|
|
while (!(isKeyword(token, KEYWORD_end) ||
|
|
isKeyword(token, KEYWORD_endif))) {
|
|
if (isKeyword(token, KEYWORD_else) ||
|
|
isKeyword(token, KEYWORD_elseif))
|
|
readToken(token);
|
|
|
|
parseStatements(token, TRUE);
|
|
|
|
if (isCmdTerm(token)) readToken(token);
|
|
}
|
|
|
|
/*
|
|
* parseStatements returns when it finds an END, an IF
|
|
* should follow the END for ANSI anyway.
|
|
* IF...THEN
|
|
* END IF;
|
|
*/
|
|
if (isKeyword(token, KEYWORD_end)) readToken(token);
|
|
|
|
if (isKeyword(token, KEYWORD_if) ||
|
|
isKeyword(token, KEYWORD_endif)) {
|
|
readToken(token);
|
|
if (isCmdTerm(token)) stmtTerm = TRUE;
|
|
} else {
|
|
/*
|
|
* Well we need to do something here.
|
|
* There are lots of different END statements
|
|
* END;
|
|
* END CASE;
|
|
* ENDIF;
|
|
* ENDCASE;
|
|
*/
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEYWORD_loop:
|
|
case KEYWORD_case:
|
|
case KEYWORD_for:
|
|
/*
|
|
* LOOP...
|
|
* END LOOP;
|
|
*
|
|
* CASE
|
|
* WHEN '1' THEN
|
|
* END CASE;
|
|
*
|
|
* FOR loop_name AS cursor_name CURSOR FOR ...
|
|
* DO
|
|
* END FOR;
|
|
*/
|
|
if (isKeyword(token, KEYWORD_for)) {
|
|
/* loop name */
|
|
readToken(token);
|
|
/* AS */
|
|
readToken(token);
|
|
|
|
while (!isKeyword(token, KEYWORD_is)) {
|
|
/*
|
|
* If this is not an AS keyword this is
|
|
* not a proper FOR statement and should
|
|
* simply be ignored
|
|
*/
|
|
return;
|
|
}
|
|
|
|
while (!isKeyword(token, KEYWORD_do)) readToken(token);
|
|
}
|
|
|
|
readToken(token);
|
|
while (!isKeyword(token, KEYWORD_end)) {
|
|
/*
|
|
if ( isKeyword (token, KEYWORD_else) ||
|
|
isKeyword (token, KEYWORD_elseif) )
|
|
readToken (token);
|
|
*/
|
|
|
|
parseStatements(token, FALSE);
|
|
|
|
if (isCmdTerm(token)) readToken(token);
|
|
}
|
|
|
|
if (isKeyword(token, KEYWORD_end)) readToken(token);
|
|
|
|
/*
|
|
* Typically ended with
|
|
* END LOOP [loop name];
|
|
* END CASE
|
|
* END FOR [loop name];
|
|
*/
|
|
if (isKeyword(token, KEYWORD_loop) ||
|
|
isKeyword(token, KEYWORD_case) || isKeyword(token, KEYWORD_for))
|
|
readToken(token);
|
|
|
|
if (isCmdTerm(token)) stmtTerm = TRUE;
|
|
|
|
break;
|
|
|
|
case KEYWORD_create:
|
|
readToken(token);
|
|
parseKeywords(token);
|
|
break;
|
|
|
|
case KEYWORD_declare:
|
|
case KEYWORD_begin:
|
|
parseBlock(token, TRUE);
|
|
break;
|
|
|
|
case KEYWORD_end:
|
|
break;
|
|
|
|
default:
|
|
readToken(token);
|
|
break;
|
|
}
|
|
/*
|
|
* Not all statements must end in a semi-colon
|
|
* begin
|
|
* if current publisher <> 'publish' then
|
|
* signal UE_FailStatement
|
|
* end if
|
|
* end;
|
|
* The last statement prior to an end ("signal" above) does
|
|
* not need a semi-colon, nor does the end if, since it is
|
|
* also the last statement prior to the end of the block.
|
|
*
|
|
* So we must read to the first semi-colon or an END block
|
|
*/
|
|
while (!stmtTerm &&
|
|
!(isKeyword(token, KEYWORD_end) || (isCmdTerm(token)))) {
|
|
if (isKeyword(token, KEYWORD_endif) && exit_on_endif) return;
|
|
|
|
if (isType(token, TOKEN_COLON)) {
|
|
/*
|
|
* A : can signal a loop name
|
|
* myloop:
|
|
* LOOP
|
|
* LEAVE myloop;
|
|
* END LOOP;
|
|
* Unfortunately, labels do not have a
|
|
* cmd terminator, therefore we have to check
|
|
* if the next token is a keyword and process
|
|
* it accordingly.
|
|
*/
|
|
readToken(token);
|
|
if (isKeyword(token, KEYWORD_loop) ||
|
|
isKeyword(token, KEYWORD_while) || isKeyword(token, KEYWORD_for))
|
|
/* parseStatements (token); */
|
|
return;
|
|
}
|
|
|
|
readToken(token);
|
|
|
|
if (isType(token, TOKEN_OPEN_PAREN) ||
|
|
isType(token, TOKEN_OPEN_CURLY) || isType(token, TOKEN_OPEN_SQUARE))
|
|
skipToMatched(token);
|
|
|
|
/*
|
|
* Since we know how to parse various statements
|
|
* if we detect them, parse them to completion
|
|
*/
|
|
if (isType(token, TOKEN_BLOCK_LABEL_BEGIN) ||
|
|
isKeyword(token, KEYWORD_exception) ||
|
|
isKeyword(token, KEYWORD_loop) || isKeyword(token, KEYWORD_case) ||
|
|
isKeyword(token, KEYWORD_for) || isKeyword(token, KEYWORD_begin))
|
|
parseStatements(token, FALSE);
|
|
else if (isKeyword(token, KEYWORD_if))
|
|
parseStatements(token, TRUE);
|
|
}
|
|
}
|
|
/*
|
|
* We assumed earlier all statements ended with a command terminator.
|
|
* See comment above, now, only read if the current token
|
|
* is not a command terminator.
|
|
*/
|
|
if (isCmdTerm(token) && !stmtTerm) stmtTerm = TRUE;
|
|
|
|
} while (!isKeyword(token, KEYWORD_end) &&
|
|
!(exit_on_endif && isKeyword(token, KEYWORD_endif)) && !stmtTerm);
|
|
}
|
|
|
|
static void parseBlock(tokenInfo *const token, const boolean local) {
|
|
if (isType(token, TOKEN_BLOCK_LABEL_BEGIN)) {
|
|
parseLabel(token);
|
|
readToken(token);
|
|
}
|
|
if (!isKeyword(token, KEYWORD_begin)) {
|
|
readToken(token);
|
|
/*
|
|
* These are Oracle style declares which generally come
|
|
* between an IS/AS and BEGIN block.
|
|
*/
|
|
parseDeclare(token, local);
|
|
}
|
|
if (isKeyword(token, KEYWORD_begin)) {
|
|
readToken(token);
|
|
/*
|
|
* Check for ANSI declarations which always follow
|
|
* a BEGIN statement. This routine will not advance
|
|
* the token if none are found.
|
|
*/
|
|
parseDeclareANSI(token, local);
|
|
token->begin_end_nest_lvl++;
|
|
while (!isKeyword(token, KEYWORD_end)) {
|
|
parseStatements(token, FALSE);
|
|
|
|
if (isCmdTerm(token)) readToken(token);
|
|
}
|
|
token->begin_end_nest_lvl--;
|
|
|
|
/*
|
|
* Read the next token (we will assume
|
|
* it is the command delimiter)
|
|
*/
|
|
readToken(token);
|
|
|
|
/*
|
|
* Check if the END block is terminated
|
|
*/
|
|
if (!isCmdTerm(token)) {
|
|
/*
|
|
* Not sure what to do here at the moment.
|
|
* I think the routine that calls parseBlock
|
|
* must expect the next token has already
|
|
* been read since it is possible this
|
|
* token is not a command delimiter.
|
|
*/
|
|
/* findCmdTerm (token, FALSE); */
|
|
}
|
|
}
|
|
}
|
|
|
|
static void parsePackage(tokenInfo *const token) {
|
|
/*
|
|
* Packages can be specified in a number of ways:
|
|
* CREATE OR REPLACE PACKAGE pkg_name AS
|
|
* or
|
|
* CREATE OR REPLACE PACKAGE owner.pkg_name AS
|
|
* or by specifying a package body
|
|
* CREATE OR REPLACE PACKAGE BODY pkg_name AS
|
|
* CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS
|
|
*/
|
|
tokenInfo *const name = newToken();
|
|
readToken(name);
|
|
if (isKeyword(name, KEYWORD_body)) {
|
|
/*
|
|
* Ignore the BODY tag since we will process
|
|
* the body or prototypes in the same manner
|
|
*/
|
|
readToken(name);
|
|
}
|
|
/* Check for owner.pkg_name */
|
|
while (!isKeyword(token, KEYWORD_is)) {
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(name);
|
|
}
|
|
}
|
|
if (isKeyword(token, KEYWORD_is)) {
|
|
if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING))
|
|
makeSqlTag(name, SQLTAG_PACKAGE);
|
|
addToScope(token, name->string);
|
|
parseBlock(token, FALSE);
|
|
vStringClear(token->scope);
|
|
}
|
|
findCmdTerm(token, FALSE);
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseTable(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats:
|
|
* create table t1 (c1 int);
|
|
* create global tempoary table t2 (c1 int);
|
|
* create table "t3" (c1 int);
|
|
* create table bob.t4 (c1 int);
|
|
* create table bob."t5" (c1 int);
|
|
* create table "bob"."t6" (c1 int);
|
|
* create table bob."t7" (c1 int);
|
|
* Proxy tables use this format:
|
|
* create existing table bob."t7" AT '...';
|
|
* SQL Server and Sybase formats
|
|
* create table OnlyTable (
|
|
* create table dbo.HasOwner (
|
|
* create table [dbo].[HasOwnerSquare] (
|
|
* create table master.dbo.HasDb (
|
|
* create table master..HasDbNoOwner (
|
|
* create table [master].dbo.[HasDbAndOwnerSquare] (
|
|
* create table [master]..[HasDbNoOwnerSquare] (
|
|
*/
|
|
|
|
/* This could be a database, owner or table name */
|
|
readToken(name);
|
|
if (isType(name, TOKEN_OPEN_SQUARE)) {
|
|
readToken(name);
|
|
/* Read close square */
|
|
readToken(token);
|
|
}
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
/*
|
|
* This could be a owner or table name.
|
|
* But this is also a special case since the table can be
|
|
* referenced with a blank owner:
|
|
* dbname..tablename
|
|
*/
|
|
readToken(name);
|
|
if (isType(name, TOKEN_OPEN_SQUARE)) {
|
|
readToken(name);
|
|
/* Read close square */
|
|
readToken(token);
|
|
}
|
|
/* Check if a blank name was provided */
|
|
if (isType(name, TOKEN_PERIOD)) {
|
|
readToken(name);
|
|
if (isType(name, TOKEN_OPEN_SQUARE)) {
|
|
readToken(name);
|
|
/* Read close square */
|
|
readToken(token);
|
|
}
|
|
}
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
/* This can only be the table name */
|
|
readToken(name);
|
|
if (isType(name, TOKEN_OPEN_SQUARE)) {
|
|
readToken(name);
|
|
/* Read close square */
|
|
readToken(token);
|
|
}
|
|
readToken(token);
|
|
}
|
|
}
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
|
|
makeSqlTag(name, SQLTAG_TABLE);
|
|
vStringCopy(token->scope, name->string);
|
|
parseRecord(token);
|
|
vStringClear(token->scope);
|
|
}
|
|
} else if (isKeyword(token, KEYWORD_at)) {
|
|
if (isType(name, TOKEN_IDENTIFIER)) {
|
|
makeSqlTag(name, SQLTAG_TABLE);
|
|
}
|
|
}
|
|
findCmdTerm(token, FALSE);
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseIndex(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
tokenInfo *const owner = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* create index i1 on t1(c1) create index "i2" on t1(c1)
|
|
* create virtual unique clustered index "i3" on t1(c1)
|
|
* create unique clustered index "i4" on t1(c1)
|
|
* create clustered index "i5" on t1(c1)
|
|
* create bitmap index "i6" on t1(c1)
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(name);
|
|
readToken(token);
|
|
}
|
|
if (isKeyword(token, KEYWORD_on) &&
|
|
(isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING))) {
|
|
readToken(owner);
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(owner);
|
|
readToken(token);
|
|
}
|
|
addToScope(name, owner->string);
|
|
makeSqlTag(name, SQLTAG_INDEX);
|
|
}
|
|
findCmdTerm(token, FALSE);
|
|
deleteToken(name);
|
|
deleteToken(owner);
|
|
}
|
|
|
|
static void parseEvent(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* create event e1 handler begin end;
|
|
* create event "e2" handler begin end;
|
|
* create event dba."e3" handler begin end;
|
|
* create event "dba"."e4" handler begin end;
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(name);
|
|
}
|
|
while (!(isKeyword(token, KEYWORD_handler) ||
|
|
(isType(token, TOKEN_SEMICOLON)))) {
|
|
readToken(token);
|
|
}
|
|
|
|
if (isKeyword(token, KEYWORD_handler) || isType(token, TOKEN_SEMICOLON)) {
|
|
makeSqlTag(name, SQLTAG_EVENT);
|
|
}
|
|
|
|
if (isKeyword(token, KEYWORD_handler)) {
|
|
readToken(token);
|
|
if (isKeyword(token, KEYWORD_begin)) {
|
|
parseBlock(token, TRUE);
|
|
}
|
|
findCmdTerm(token, TRUE);
|
|
}
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseTrigger(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
tokenInfo *const table = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* create or replace trigger tr1 begin end;
|
|
* create trigger "tr2" begin end;
|
|
* drop trigger "droptr1";
|
|
* create trigger "tr3" CALL sp_something();
|
|
* create trigger "owner"."tr4" begin end;
|
|
* create trigger "tr5" not valid;
|
|
* create trigger "tr6" begin end;
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(name);
|
|
readToken(token);
|
|
}
|
|
|
|
while (!isKeyword(token, KEYWORD_on) && !isCmdTerm(token)) {
|
|
readToken(token);
|
|
}
|
|
|
|
/*if (! isType (token, TOKEN_SEMICOLON) ) */
|
|
if (!isCmdTerm(token)) {
|
|
readToken(table);
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(table);
|
|
readToken(token);
|
|
}
|
|
|
|
while (!(isKeyword(token, KEYWORD_begin) ||
|
|
(isKeyword(token, KEYWORD_call)) || (isCmdTerm(token)))) {
|
|
if (isKeyword(token, KEYWORD_declare)) {
|
|
addToScope(token, name->string);
|
|
parseDeclare(token, TRUE);
|
|
vStringClear(token->scope);
|
|
} else
|
|
readToken(token);
|
|
}
|
|
|
|
if (isKeyword(token, KEYWORD_begin) || isKeyword(token, KEYWORD_call)) {
|
|
addToScope(name, table->string);
|
|
makeSqlTag(name, SQLTAG_TRIGGER);
|
|
addToScope(token, table->string);
|
|
if (isKeyword(token, KEYWORD_begin)) {
|
|
parseBlock(token, TRUE);
|
|
}
|
|
vStringClear(token->scope);
|
|
}
|
|
}
|
|
|
|
findCmdTerm(token, TRUE);
|
|
deleteToken(name);
|
|
deleteToken(table);
|
|
}
|
|
|
|
static void parsePublication(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* create or replace publication pu1 ()
|
|
* create publication "pu2" ()
|
|
* create publication dba."pu3" ()
|
|
* create publication "dba"."pu4" ()
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(name);
|
|
readToken(token);
|
|
}
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
|
|
makeSqlTag(name, SQLTAG_PUBLICATION);
|
|
}
|
|
}
|
|
findCmdTerm(token, FALSE);
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseService(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* CREATE SERVICE s1 TYPE 'HTML'
|
|
* AUTHORIZATION OFF USER DBA AS
|
|
* SELECT *
|
|
* FROM SYS.SYSTABLE;
|
|
* CREATE SERVICE "s2" TYPE 'HTML'
|
|
* AUTHORIZATION OFF USER DBA AS
|
|
* CALL sp_Something();
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if (isKeyword(token, KEYWORD_type)) {
|
|
if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
|
|
makeSqlTag(name, SQLTAG_SERVICE);
|
|
}
|
|
}
|
|
findCmdTerm(token, FALSE);
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseDomain(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* CREATE DOMAIN|DATATYPE [AS] your_name ...;
|
|
*/
|
|
|
|
readToken(name);
|
|
if (isKeyword(name, KEYWORD_is)) {
|
|
readToken(name);
|
|
}
|
|
readToken(token);
|
|
if (isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) {
|
|
makeSqlTag(name, SQLTAG_DOMAIN);
|
|
}
|
|
findCmdTerm(token, FALSE);
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseDrop(tokenInfo *const token) {
|
|
/*
|
|
* This deals with these formats
|
|
* DROP TABLE|PROCEDURE|DOMAIN|DATATYPE name;
|
|
*
|
|
* Just simply skip over these statements.
|
|
* They are often confused with PROCEDURE prototypes
|
|
* since the syntax is similar, this effectively deals with
|
|
* the issue for all types.
|
|
*/
|
|
|
|
findCmdTerm(token, FALSE);
|
|
}
|
|
|
|
static void parseVariable(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* create variable varname1 integer;
|
|
* create variable @varname2 integer;
|
|
* create variable "varname3" integer;
|
|
* drop variable @varname3;
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) &&
|
|
!isType(token, TOKEN_SEMICOLON)) {
|
|
makeSqlTag(name, SQLTAG_VARIABLE);
|
|
}
|
|
findCmdTerm(token, TRUE);
|
|
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseSynonym(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* create variable varname1 integer;
|
|
* create variable @varname2 integer;
|
|
* create variable "varname3" integer;
|
|
* drop variable @varname3;
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) &&
|
|
isKeyword(token, KEYWORD_for)) {
|
|
makeSqlTag(name, SQLTAG_SYNONYM);
|
|
}
|
|
findCmdTerm(token, TRUE);
|
|
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseView(tokenInfo *const token) {
|
|
tokenInfo *const name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* create variable varname1 integer;
|
|
* create variable @varname2 integer;
|
|
* create variable "varname3" integer;
|
|
* drop variable @varname3;
|
|
*/
|
|
|
|
readToken(name);
|
|
readToken(token);
|
|
if (isType(token, TOKEN_PERIOD)) {
|
|
readToken(name);
|
|
readToken(token);
|
|
}
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
skipArgumentList(token);
|
|
}
|
|
|
|
while (!(isKeyword(token, KEYWORD_is) || isType(token, TOKEN_SEMICOLON))) {
|
|
readToken(token);
|
|
}
|
|
|
|
if ((isType(name, TOKEN_IDENTIFIER) || isType(name, TOKEN_STRING)) &&
|
|
isKeyword(token, KEYWORD_is)) {
|
|
makeSqlTag(name, SQLTAG_VIEW);
|
|
}
|
|
|
|
findCmdTerm(token, TRUE);
|
|
|
|
deleteToken(name);
|
|
}
|
|
|
|
static void parseMLTable(tokenInfo *const token) {
|
|
tokenInfo *const version = newToken();
|
|
tokenInfo *const table = newToken();
|
|
tokenInfo *const event = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* call dbo.ml_add_table_script( 'version', 'table_name', 'event',
|
|
* 'some SQL statement'
|
|
* );
|
|
*/
|
|
|
|
readToken(token);
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
readToken(version);
|
|
readToken(token);
|
|
while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
|
|
readToken(token);
|
|
}
|
|
|
|
if (isType(token, TOKEN_COMMA)) {
|
|
readToken(table);
|
|
readToken(token);
|
|
while (
|
|
!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
|
|
readToken(token);
|
|
}
|
|
|
|
if (isType(token, TOKEN_COMMA)) {
|
|
readToken(event);
|
|
|
|
if (isType(version, TOKEN_STRING) && isType(table, TOKEN_STRING) &&
|
|
isType(event, TOKEN_STRING)) {
|
|
addToScope(version, table->string);
|
|
addToScope(version, event->string);
|
|
makeSqlTag(version, SQLTAG_MLTABLE);
|
|
}
|
|
}
|
|
if (!isType(token, TOKEN_CLOSE_PAREN))
|
|
findToken(token, TOKEN_CLOSE_PAREN);
|
|
}
|
|
}
|
|
|
|
findCmdTerm(token, TRUE);
|
|
|
|
deleteToken(version);
|
|
deleteToken(table);
|
|
deleteToken(event);
|
|
}
|
|
|
|
static void parseMLConn(tokenInfo *const token) {
|
|
tokenInfo *const version = newToken();
|
|
tokenInfo *const event = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* call ml_add_connection_script( 'version', 'event',
|
|
* 'some SQL statement'
|
|
* );
|
|
*/
|
|
|
|
readToken(token);
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
readToken(version);
|
|
readToken(token);
|
|
while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
|
|
readToken(token);
|
|
}
|
|
|
|
if (isType(token, TOKEN_COMMA)) {
|
|
readToken(event);
|
|
|
|
if (isType(version, TOKEN_STRING) && isType(event, TOKEN_STRING)) {
|
|
addToScope(version, event->string);
|
|
makeSqlTag(version, SQLTAG_MLCONN);
|
|
}
|
|
}
|
|
if (!isType(token, TOKEN_CLOSE_PAREN)) findToken(token, TOKEN_CLOSE_PAREN);
|
|
}
|
|
|
|
findCmdTerm(token, TRUE);
|
|
|
|
deleteToken(version);
|
|
deleteToken(event);
|
|
}
|
|
|
|
static void parseMLProp(tokenInfo *const token) {
|
|
tokenInfo *const component = newToken();
|
|
tokenInfo *const prop_set_name = newToken();
|
|
tokenInfo *const prop_name = newToken();
|
|
|
|
/*
|
|
* This deals with these formats
|
|
* ml_add_property (
|
|
* 'comp_name',
|
|
* 'prop_set_name',
|
|
* 'prop_name',
|
|
* 'prop_value'
|
|
* )
|
|
*/
|
|
|
|
readToken(token);
|
|
if (isType(token, TOKEN_OPEN_PAREN)) {
|
|
readToken(component);
|
|
readToken(token);
|
|
while (!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
|
|
readToken(token);
|
|
}
|
|
|
|
if (isType(token, TOKEN_COMMA)) {
|
|
readToken(prop_set_name);
|
|
readToken(token);
|
|
while (
|
|
!(isType(token, TOKEN_COMMA) || isType(token, TOKEN_CLOSE_PAREN))) {
|
|
readToken(token);
|
|
}
|
|
|
|
if (isType(token, TOKEN_COMMA)) {
|
|
readToken(prop_name);
|
|
|
|
if (isType(component, TOKEN_STRING) &&
|
|
isType(prop_set_name, TOKEN_STRING) &&
|
|
isType(prop_name, TOKEN_STRING)) {
|
|
addToScope(component, prop_set_name->string);
|
|
addToScope(component, prop_name->string);
|
|
makeSqlTag(component, SQLTAG_MLPROP);
|
|
}
|
|
}
|
|
if (!isType(token, TOKEN_CLOSE_PAREN))
|
|
findToken(token, TOKEN_CLOSE_PAREN);
|
|
}
|
|
}
|
|
|
|
findCmdTerm(token, TRUE);
|
|
|
|
deleteToken(component);
|
|
deleteToken(prop_set_name);
|
|
deleteToken(prop_name);
|
|
}
|
|
|
|
static void parseComment(tokenInfo *const token) {
|
|
/*
|
|
* This deals with this statement:
|
|
* COMMENT TO PRESERVE FORMAT ON PROCEDURE "DBA"."test" IS
|
|
* {create PROCEDURE DBA."test"()
|
|
* BEGIN
|
|
* signal dave;
|
|
* END
|
|
* }
|
|
* ;
|
|
* The comment can contain anything between the CURLY
|
|
* braces
|
|
* COMMENT ON USER "admin" IS
|
|
* 'Administration Group'
|
|
* ;
|
|
* Or it could be a simple string with no curly braces
|
|
*/
|
|
while (!isKeyword(token, KEYWORD_is)) {
|
|
readToken(token);
|
|
}
|
|
readToken(token);
|
|
if (isType(token, TOKEN_OPEN_CURLY)) {
|
|
findToken(token, TOKEN_CLOSE_CURLY);
|
|
}
|
|
|
|
findCmdTerm(token, TRUE);
|
|
}
|
|
|
|
static void parseKeywords(tokenInfo *const token) {
|
|
switch (token->keyword) {
|
|
case KEYWORD_begin:
|
|
parseBlock(token, FALSE);
|
|
break;
|
|
case KEYWORD_comment:
|
|
parseComment(token);
|
|
break;
|
|
case KEYWORD_cursor:
|
|
parseSimple(token, SQLTAG_CURSOR);
|
|
break;
|
|
case KEYWORD_datatype:
|
|
parseDomain(token);
|
|
break;
|
|
case KEYWORD_declare:
|
|
parseBlock(token, FALSE);
|
|
break;
|
|
case KEYWORD_domain:
|
|
parseDomain(token);
|
|
break;
|
|
case KEYWORD_drop:
|
|
parseDrop(token);
|
|
break;
|
|
case KEYWORD_event:
|
|
parseEvent(token);
|
|
break;
|
|
case KEYWORD_function:
|
|
parseSubProgram(token);
|
|
break;
|
|
case KEYWORD_if:
|
|
parseStatements(token, FALSE);
|
|
break;
|
|
case KEYWORD_index:
|
|
parseIndex(token);
|
|
break;
|
|
case KEYWORD_ml_table:
|
|
parseMLTable(token);
|
|
break;
|
|
case KEYWORD_ml_table_lang:
|
|
parseMLTable(token);
|
|
break;
|
|
case KEYWORD_ml_table_dnet:
|
|
parseMLTable(token);
|
|
break;
|
|
case KEYWORD_ml_table_java:
|
|
parseMLTable(token);
|
|
break;
|
|
case KEYWORD_ml_table_chk:
|
|
parseMLTable(token);
|
|
break;
|
|
case KEYWORD_ml_conn:
|
|
parseMLConn(token);
|
|
break;
|
|
case KEYWORD_ml_conn_lang:
|
|
parseMLConn(token);
|
|
break;
|
|
case KEYWORD_ml_conn_dnet:
|
|
parseMLConn(token);
|
|
break;
|
|
case KEYWORD_ml_conn_java:
|
|
parseMLConn(token);
|
|
break;
|
|
case KEYWORD_ml_conn_chk:
|
|
parseMLConn(token);
|
|
break;
|
|
case KEYWORD_ml_prop:
|
|
parseMLProp(token);
|
|
break;
|
|
case KEYWORD_package:
|
|
parsePackage(token);
|
|
break;
|
|
case KEYWORD_procedure:
|
|
parseSubProgram(token);
|
|
break;
|
|
case KEYWORD_publication:
|
|
parsePublication(token);
|
|
break;
|
|
case KEYWORD_service:
|
|
parseService(token);
|
|
break;
|
|
case KEYWORD_subtype:
|
|
parseSimple(token, SQLTAG_SUBTYPE);
|
|
break;
|
|
case KEYWORD_synonym:
|
|
parseSynonym(token);
|
|
break;
|
|
case KEYWORD_table:
|
|
parseTable(token);
|
|
break;
|
|
case KEYWORD_trigger:
|
|
parseTrigger(token);
|
|
break;
|
|
case KEYWORD_type:
|
|
parseType(token);
|
|
break;
|
|
case KEYWORD_variable:
|
|
parseVariable(token);
|
|
break;
|
|
case KEYWORD_view:
|
|
parseView(token);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void parseSqlFile(tokenInfo *const token) {
|
|
do {
|
|
readToken(token);
|
|
|
|
if (isType(token, TOKEN_BLOCK_LABEL_BEGIN))
|
|
parseLabel(token);
|
|
else
|
|
parseKeywords(token);
|
|
} while (!isKeyword(token, KEYWORD_end));
|
|
}
|
|
|
|
static void initialize(const langType language) {
|
|
Assert(sizeof(SqlKinds) / sizeof(SqlKinds[0]) == SQLTAG_COUNT);
|
|
Lang_sql = language;
|
|
buildSqlKeywordHash();
|
|
}
|
|
|
|
static void findSqlTags(void) {
|
|
tokenInfo *const token = newToken();
|
|
exception_t exception = (exception_t)(setjmp(Exception));
|
|
|
|
while (exception == ExceptionNone) parseSqlFile(token);
|
|
|
|
deleteToken(token);
|
|
}
|
|
|
|
extern parserDefinition *SqlParser(void) {
|
|
static const char *const extensions[] = {"sql", NULL};
|
|
parserDefinition *def = parserNew("SQL");
|
|
def->kinds = SqlKinds;
|
|
def->kindCount = KIND_COUNT(SqlKinds);
|
|
def->extensions = extensions;
|
|
def->parser = findSqlTags;
|
|
def->initialize = initialize;
|
|
return def;
|
|
}
|
|
|
|
/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
|