/* * $Id: sql.c 574 2007-06-26 23:05:27Z 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 FILES */ #include "general.h" /* must always come first */ #include /* to define isalpha () */ #include #ifdef DEBUG #include #endif #include "debug.h" #include "entry.h" #include "keyword.h" #include "parse.h" #include "read.h" #include "routines.h" #include "vstring.h" /* * On-line PL/SQL Reference Guide: * http://info-it.umsystem.edu/oradocs/doc/server/doc/PLS23/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_loop, KEYWORD_case, KEYWORD_for, 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_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_local, KEYWORD_temporary, KEYWORD_drop, KEYWORD_view, KEYWORD_synonym, KEYWORD_handler, KEYWORD_comment, 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_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_TILDE, TOKEN_FORWARD_SLASH } tokenType; typedef struct sTokenInfo { tokenType type; keywordId keyword; vString * string; vString * scope; 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_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" } }; static const keywordDesc SqlKeywordTable [] = { /* keyword keyword ID */ { "as", KEYWORD_is }, { "begin", KEYWORD_begin }, { "body", KEYWORD_body }, { "cursor", KEYWORD_cursor }, { "declare", KEYWORD_declare }, { "end", KEYWORD_end }, { "function", KEYWORD_function }, { "if", KEYWORD_if }, { "is", KEYWORD_is }, { "loop", KEYWORD_loop }, { "case", KEYWORD_case }, { "for", KEYWORD_for }, { "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 }, { "result", KEYWORD_result }, { "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 }, { "local", KEYWORD_local }, { "temporary", KEYWORD_temporary }, { "drop", KEYWORD_drop }, { "view", KEYWORD_view }, { "synonym", KEYWORD_synonym }, { "handler", KEYWORD_handler }, { "comment", KEYWORD_comment }, { "go", KEYWORD_go } }; /* * FUNCTION DECLARATIONS */ /* Recursive calls */ static void parseBlock (tokenInfo *const token, const boolean local); /* * 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) { #ifdef DEBUGed printf( "\n isCmdTerm: token same tt:%d tk:%d\n" , token->type , token->keyword ); #endif /* * 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 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 (); 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 int skipToCharacter (const int c) { int d; do { d = fileGetc (); } while (d != EOF && d != c); return d; } static void parseString (vString *const string, const int delimiter) { boolean end = FALSE; int c; while (! end) { c = fileGetc (); if (c == EOF) end = TRUE; 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 keywordId analyzeToken (vString *const name) { vString *keyword = vStringNew (); keywordId result; vStringCopyToLower (keyword, name); result = (keywordId) lookupKeyword (vStringValue (keyword), Lang_sql); vStringDelete (keyword); return result; } static void readToken (tokenInfo *const token) { int c; token->type = TOKEN_UNDEFINED; token->keyword = KEYWORD_NONE; vStringClear (token->string); getNextChar: do { c = fileGetc (); /* * 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_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 '\'': 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? */ { skipToCharacter ('\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 '/': { 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 { skipToCharacter ('*'); 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? */ { skipToCharacter ('\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); if (isKeyword (token, KEYWORD_rem)) { vStringClear (token->string); skipToCharacter ('\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) { if ( check_first ) { if ( isCmdTerm(token) ) return; } do { readToken (token); } while ( !isCmdTerm(token) ); } static void skipArgumentList (tokenInfo *const token) { int nest_level = 0; /* * 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? */ { nest_level++; while (! (isType (token, TOKEN_CLOSE_PAREN) && (nest_level == 0))) { readToken (token); if (isType (token, TOKEN_OPEN_PAREN)) { nest_level++; } if (isType (token, TOKEN_CLOSE_PAREN)) { if (nest_level > 0) { nest_level--; } } } readToken (token); } } static void parseSubProgram (tokenInfo *const token) { tokenInfo *const name = newToken (); /* * 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; */ const sqlKind kind = isKeyword (token, KEYWORD_function) ? SQLTAG_FUNCTION : SQLTAG_PROCEDURE; Assert (isKeyword (token, KEYWORD_function) || isKeyword (token, KEYWORD_procedure)); readToken (name); readToken (token); if (isType (token, TOKEN_PERIOD)) { readToken (name); readToken (token); } skipArgumentList (token); if (kind == SQLTAG_FUNCTION) { if (isKeyword (token, KEYWORD_return)) { /* Read datatype */ readToken (token); /* * Read token after which could be the * command terminator if a prototype */ readToken (token); } } if( isCmdTerm (token) ) { makeSqlTag (name, SQLTAG_PROTOTYPE); } else { while (!(isKeyword (token, KEYWORD_is) || isKeyword (token, KEYWORD_begin) || isCmdTerm (token) ) ) { /* read return type */ readToken (token); } 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); } } deleteToken (name); } 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: * <> * DECLARE * v_senator VARCHAR2(100) := 'THURMOND, JESSE'; * BEGIN * IF total_contributions (v_senator, 'TOBACCO') > 25000 * THEN * <> * 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) { do { if (isType (token, TOKEN_BLOCK_LABEL_BEGIN)) parseLabel (token); else { switch (token->keyword) { case KEYWORD_exception: /* * EXCEPTION * ; * * 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; */ while (! isKeyword (token, KEYWORD_then)) readToken (token); parseStatements (token); /* * parseStatements returns when it finds an END, an IF * statement should be followed by an IF (ANSI anyway) * so read the following IF as well */ readToken (token); break; case KEYWORD_loop: case KEYWORD_case: case KEYWORD_for: /* * LOOP... * END LOOP; * * FOR loop_name AS cursor_name CURSOR FOR ... * END FOR; */ readToken (token); parseStatements (token); break; case KEYWORD_declare: case KEYWORD_begin: parseBlock (token, TRUE); 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 (! (isKeyword (token, KEYWORD_end) || (isType (token, TOKEN_SEMICOLON))) ) { readToken (token); } } /* * We assumed earlier all statements ended with a semi-colon, * see comment above, now, only read if the current token * is not a semi-colon */ if ( isType (token, TOKEN_SEMICOLON) ) { readToken (token); } } while (! isKeyword (token, KEYWORD_end)); } 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); while (! isKeyword (token, KEYWORD_end)) { parseStatements (token); } 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); parseBlock (token, FALSE); } 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 '...'; */ 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_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, FALSE); } 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))) ) { readToken (token); if ( isKeyword (token, KEYWORD_declare) ) { addToScope(token, name->string); parseDeclare(token, TRUE); vStringClear(token->scope); } } 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 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 parseSqlFile (tokenInfo *const token) { do { readToken (token); if (isType (token, TOKEN_BLOCK_LABEL_BEGIN)) parseLabel (token); else 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_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_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; } } 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: */