/sql.c
C | 2371 lines | 1564 code | 209 blank | 598 comment | 259 complexity | a85152c03064c2719a3442134b11326e MD5 | raw file
Possible License(s): GPL-2.0
1/*
2 * $Id$
3 *
4 * Copyright (c) 2002-2003, Darren Hiebert
5 *
6 * This source code is released for free distribution under the terms of the
7 * GNU General Public License.
8 *
9 * This module contains functions for generating tags for PL/SQL language
10 * files.
11 */
12
13/*
14 * INCLUDE FILES
15 */
16#include "general.h" /* must always come first */
17
18#include <ctype.h> /* to define isalpha () */
19#include <setjmp.h>
20#ifdef DEBUG
21#include <stdio.h>
22#endif
23
24#include "debug.h"
25#include "entry.h"
26#include "keyword.h"
27#include "parse.h"
28#include "read.h"
29#include "routines.h"
30#include "vstring.h"
31
32/*
33 * On-line "Oracle Database PL/SQL Language Reference":
34 * http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28370/toc.htm
35 *
36 * Sample PL/SQL code is available from:
37 * http://www.orafaq.com/faqscrpt.htm#GENPLSQL
38 *
39 * On-line SQL Anywhere Documentation
40 * http://www.ianywhere.com/developer/product_manuals/sqlanywhere/index.html
41 */
42
43/*
44 * MACROS
45 */
46#define isType(token,t) (boolean) ((token)->type == (t))
47#define isKeyword(token,k) (boolean) ((token)->keyword == (k))
48
49/*
50 * DATA DECLARATIONS
51 */
52
53typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
54
55/*
56 * Used to specify type of keyword.
57 */
58typedef enum eKeywordId {
59 KEYWORD_NONE = -1,
60 KEYWORD_is,
61 KEYWORD_begin,
62 KEYWORD_body,
63 KEYWORD_cursor,
64 KEYWORD_declare,
65 KEYWORD_end,
66 KEYWORD_function,
67 KEYWORD_if,
68 KEYWORD_else,
69 KEYWORD_elseif,
70 KEYWORD_endif,
71 KEYWORD_loop,
72 KEYWORD_while,
73 KEYWORD_case,
74 KEYWORD_for,
75 KEYWORD_do,
76 KEYWORD_call,
77 KEYWORD_package,
78 KEYWORD_pragma,
79 KEYWORD_procedure,
80 KEYWORD_record,
81 KEYWORD_object,
82 KEYWORD_ref,
83 KEYWORD_rem,
84 KEYWORD_return,
85 KEYWORD_returns,
86 KEYWORD_subtype,
87 KEYWORD_table,
88 KEYWORD_trigger,
89 KEYWORD_type,
90 KEYWORD_index,
91 KEYWORD_event,
92 KEYWORD_publication,
93 KEYWORD_service,
94 KEYWORD_domain,
95 KEYWORD_datatype,
96 KEYWORD_result,
97 KEYWORD_url,
98 KEYWORD_internal,
99 KEYWORD_external,
100 KEYWORD_when,
101 KEYWORD_then,
102 KEYWORD_variable,
103 KEYWORD_exception,
104 KEYWORD_at,
105 KEYWORD_on,
106 KEYWORD_primary,
107 KEYWORD_references,
108 KEYWORD_unique,
109 KEYWORD_check,
110 KEYWORD_constraint,
111 KEYWORD_foreign,
112 KEYWORD_ml_table,
113 KEYWORD_ml_table_lang,
114 KEYWORD_ml_table_dnet,
115 KEYWORD_ml_table_java,
116 KEYWORD_ml_table_chk,
117 KEYWORD_ml_conn,
118 KEYWORD_ml_conn_lang,
119 KEYWORD_ml_conn_dnet,
120 KEYWORD_ml_conn_java,
121 KEYWORD_ml_conn_chk,
122 KEYWORD_ml_prop,
123 KEYWORD_local,
124 KEYWORD_temporary,
125 KEYWORD_drop,
126 KEYWORD_view,
127 KEYWORD_synonym,
128 KEYWORD_handler,
129 KEYWORD_comment,
130 KEYWORD_create,
131 KEYWORD_go
132} keywordId;
133
134/*
135 * Used to determine whether keyword is valid for the token language and
136 * what its ID is.
137 */
138typedef struct sKeywordDesc {
139 const char *name;
140 keywordId id;
141} keywordDesc;
142
143typedef enum eTokenType {
144 TOKEN_UNDEFINED,
145 TOKEN_BLOCK_LABEL_BEGIN,
146 TOKEN_BLOCK_LABEL_END,
147 TOKEN_CHARACTER,
148 TOKEN_CLOSE_PAREN,
149 TOKEN_COLON,
150 TOKEN_SEMICOLON,
151 TOKEN_COMMA,
152 TOKEN_IDENTIFIER,
153 TOKEN_KEYWORD,
154 TOKEN_OPEN_PAREN,
155 TOKEN_OPERATOR,
156 TOKEN_OTHER,
157 TOKEN_STRING,
158 TOKEN_PERIOD,
159 TOKEN_OPEN_CURLY,
160 TOKEN_CLOSE_CURLY,
161 TOKEN_OPEN_SQUARE,
162 TOKEN_CLOSE_SQUARE,
163 TOKEN_TILDE,
164 TOKEN_FORWARD_SLASH,
165 TOKEN_EQUAL
166} tokenType;
167
168typedef struct sTokenInfoSQL {
169 tokenType type;
170 keywordId keyword;
171 vString * string;
172 vString * scope;
173 int begin_end_nest_lvl;
174 unsigned long lineNumber;
175 fpos_t filePosition;
176} tokenInfo;
177
178/*
179 * DATA DEFINITIONS
180 */
181
182static langType Lang_sql;
183
184static jmp_buf Exception;
185
186typedef enum {
187 SQLTAG_CURSOR,
188 SQLTAG_PROTOTYPE,
189 SQLTAG_FUNCTION,
190 SQLTAG_FIELD,
191 SQLTAG_LOCAL_VARIABLE,
192 SQLTAG_BLOCK_LABEL,
193 SQLTAG_PACKAGE,
194 SQLTAG_PROCEDURE,
195 SQLTAG_RECORD,
196 SQLTAG_SUBTYPE,
197 SQLTAG_TABLE,
198 SQLTAG_TRIGGER,
199 SQLTAG_VARIABLE,
200 SQLTAG_INDEX,
201 SQLTAG_EVENT,
202 SQLTAG_PUBLICATION,
203 SQLTAG_SERVICE,
204 SQLTAG_DOMAIN,
205 SQLTAG_VIEW,
206 SQLTAG_SYNONYM,
207 SQLTAG_MLTABLE,
208 SQLTAG_MLCONN,
209 SQLTAG_MLPROP,
210 SQLTAG_COUNT
211} sqlKind;
212
213static kindOption SqlKinds [] = {
214 { TRUE, 'c', "cursor", "cursors" },
215 { FALSE, 'd', "prototype", "prototypes" },
216 { TRUE, 'f', "function", "functions" },
217 { TRUE, 'F', "field", "record fields" },
218 { FALSE, 'l', "local", "local variables" },
219 { TRUE, 'L', "label", "block label" },
220 { TRUE, 'P', "package", "packages" },
221 { TRUE, 'p', "procedure", "procedures" },
222 { FALSE, 'r', "record", "records" },
223 { TRUE, 's', "subtype", "subtypes" },
224 { TRUE, 't', "table", "tables" },
225 { TRUE, 'T', "trigger", "triggers" },
226 { TRUE, 'v', "variable", "variables" },
227 { TRUE, 'i', "index", "indexes" },
228 { TRUE, 'e', "event", "events" },
229 { TRUE, 'U', "publication", "publications" },
230 { TRUE, 'R', "service", "services" },
231 { TRUE, 'D', "domain", "domains" },
232 { TRUE, 'V', "view", "views" },
233 { TRUE, 'n', "synonym", "synonyms" },
234 { TRUE, 'x', "mltable", "MobiLink Table Scripts" },
235 { TRUE, 'y', "mlconn", "MobiLink Conn Scripts" },
236 { TRUE, 'z', "mlprop", "MobiLink Properties " }
237};
238
239static const keywordDesc SqlKeywordTable [] = {
240 /* keyword keyword ID */
241 { "as", KEYWORD_is },
242 { "is", KEYWORD_is },
243 { "begin", KEYWORD_begin },
244 { "body", KEYWORD_body },
245 { "cursor", KEYWORD_cursor },
246 { "declare", KEYWORD_declare },
247 { "end", KEYWORD_end },
248 { "function", KEYWORD_function },
249 { "if", KEYWORD_if },
250 { "else", KEYWORD_else },
251 { "elseif", KEYWORD_elseif },
252 { "endif", KEYWORD_endif },
253 { "loop", KEYWORD_loop },
254 { "while", KEYWORD_while },
255 { "case", KEYWORD_case },
256 { "for", KEYWORD_for },
257 { "do", KEYWORD_do },
258 { "call", KEYWORD_call },
259 { "package", KEYWORD_package },
260 { "pragma", KEYWORD_pragma },
261 { "procedure", KEYWORD_procedure },
262 { "record", KEYWORD_record },
263 { "object", KEYWORD_object },
264 { "ref", KEYWORD_ref },
265 { "rem", KEYWORD_rem },
266 { "return", KEYWORD_return },
267 { "returns", KEYWORD_returns },
268 { "subtype", KEYWORD_subtype },
269 { "table", KEYWORD_table },
270 { "trigger", KEYWORD_trigger },
271 { "type", KEYWORD_type },
272 { "index", KEYWORD_index },
273 { "event", KEYWORD_event },
274 { "publication", KEYWORD_publication },
275 { "service", KEYWORD_service },
276 { "domain", KEYWORD_domain },
277 { "datatype", KEYWORD_datatype },
278 { "result", KEYWORD_result },
279 { "url", KEYWORD_url },
280 { "internal", KEYWORD_internal },
281 { "external", KEYWORD_external },
282 { "when", KEYWORD_when },
283 { "then", KEYWORD_then },
284 { "variable", KEYWORD_variable },
285 { "exception", KEYWORD_exception },
286 { "at", KEYWORD_at },
287 { "on", KEYWORD_on },
288 { "primary", KEYWORD_primary },
289 { "references", KEYWORD_references },
290 { "unique", KEYWORD_unique },
291 { "check", KEYWORD_check },
292 { "constraint", KEYWORD_constraint },
293 { "foreign", KEYWORD_foreign },
294 { "ml_add_table_script", KEYWORD_ml_table },
295 { "ml_add_lang_table_script", KEYWORD_ml_table_lang },
296 { "ml_add_dnet_table_script", KEYWORD_ml_table_dnet },
297 { "ml_add_java_table_script", KEYWORD_ml_table_java },
298 { "ml_add_lang_table_script_chk", KEYWORD_ml_table_chk },
299 { "ml_add_connection_script", KEYWORD_ml_conn },
300 { "ml_add_lang_connection_script", KEYWORD_ml_conn_lang },
301 { "ml_add_dnet_connection_script", KEYWORD_ml_conn_dnet },
302 { "ml_add_java_connection_script", KEYWORD_ml_conn_java },
303 { "ml_add_lang_conn_script_chk", KEYWORD_ml_conn_chk },
304 { "ml_add_property", KEYWORD_ml_prop },
305 { "local", KEYWORD_local },
306 { "temporary", KEYWORD_temporary },
307 { "drop", KEYWORD_drop },
308 { "view", KEYWORD_view },
309 { "synonym", KEYWORD_synonym },
310 { "handler", KEYWORD_handler },
311 { "comment", KEYWORD_comment },
312 { "create", KEYWORD_create },
313 { "go", KEYWORD_go }
314};
315
316/*
317 * FUNCTION DECLARATIONS
318 */
319
320/* Recursive calls */
321static void parseBlock (tokenInfo *const token, const boolean local);
322static void parseDeclare (tokenInfo *const token, const boolean local);
323static void parseKeywords (tokenInfo *const token);
324static void parseSqlFile (tokenInfo *const token);
325
326/*
327 * FUNCTION DEFINITIONS
328 */
329
330static boolean isIdentChar1 (const int c)
331{
332 /*
333 * Other databases are less restrictive on the first character of
334 * an identifier.
335 * isIdentChar1 is used to identify the first character of an
336 * identifier, so we are removing some restrictions.
337 */
338 return (boolean)
339 (isalpha (c) || c == '@' || c == '_' );
340}
341
342static boolean isIdentChar (const int c)
343{
344 return (boolean)
345 (isalpha (c) || isdigit (c) || c == '$' ||
346 c == '@' || c == '_' || c == '#');
347}
348
349static boolean isCmdTerm (tokenInfo *const token)
350{
351 DebugStatement (
352 debugPrintf (DEBUG_PARSE
353 , "\n isCmdTerm: token same tt:%d tk:%d\n"
354 , token->type
355 , token->keyword
356 );
357 );
358
359 /*
360 * Based on the various customer sites I have been at
361 * the most common command delimiters are
362 * ;
363 * ~
364 * /
365 * go
366 * This routine will check for any of these, more
367 * can easily be added by modifying readToken and
368 * either adding the character to:
369 * enum eTokenType
370 * enum eTokenType
371 */
372 return ( isType (token, TOKEN_SEMICOLON) ||
373 isType (token, TOKEN_TILDE) ||
374 isType (token, TOKEN_FORWARD_SLASH) ||
375 isKeyword (token, KEYWORD_go)
376 );
377}
378
379static boolean isMatchedEnd(tokenInfo *const token, int nest_lvl)
380{
381 boolean terminated = FALSE;
382 /*
383 * Since different forms of SQL allow the use of
384 * BEGIN
385 * ...
386 * END
387 * blocks, some statements may not be terminated using
388 * the standard delimiters:
389 * ;
390 * ~
391 * /
392 * go
393 * This routine will check to see if we encounter and END
394 * for the matching nest level of BEGIN ... END statements.
395 * If we find one, then we can assume, the statement was terminated
396 * since we have fallen through to the END statement of the BEGIN
397 * block.
398 */
399 if ( nest_lvl > 0 && isKeyword (token, KEYWORD_end) )
400 {
401 if ( token->begin_end_nest_lvl == nest_lvl )
402 terminated = TRUE;
403 }
404
405 return terminated;
406}
407
408static void buildSqlKeywordHash (void)
409{
410 const size_t count = sizeof (SqlKeywordTable) /
411 sizeof (SqlKeywordTable [0]);
412 size_t i;
413 for (i = 0 ; i < count ; ++i)
414 {
415 const keywordDesc* const p = &SqlKeywordTable [i];
416 addKeyword (p->name, Lang_sql, (int) p->id);
417 }
418}
419
420static tokenInfo *newToken (void)
421{
422 tokenInfo *const token = xMalloc (1, tokenInfo);
423
424 token->type = TOKEN_UNDEFINED;
425 token->keyword = KEYWORD_NONE;
426 token->string = vStringNew ();
427 token->scope = vStringNew ();
428 token->begin_end_nest_lvl = 0;
429 token->lineNumber = getSourceLineNumber ();
430 token->filePosition = getInputFilePosition ();
431
432 return token;
433}
434
435static void deleteToken (tokenInfo *const token)
436{
437 vStringDelete (token->string);
438 vStringDelete (token->scope);
439 eFree (token);
440}
441
442/*
443 * Tag generation functions
444 */
445
446static void makeConstTag (tokenInfo *const token, const sqlKind kind)
447{
448 if (SqlKinds [kind].enabled)
449 {
450 const char *const name = vStringValue (token->string);
451 tagEntryInfo e;
452 initTagEntry (&e, name);
453
454 e.lineNumber = token->lineNumber;
455 e.filePosition = token->filePosition;
456 e.kindName = SqlKinds [kind].name;
457 e.kind = SqlKinds [kind].letter;
458
459 makeTagEntry (&e);
460 }
461}
462
463static void makeSqlTag (tokenInfo *const token, const sqlKind kind)
464{
465 vString * fulltag;
466
467 if (SqlKinds [kind].enabled)
468 {
469 /*
470 * If a scope has been added to the token, change the token
471 * string to include the scope when making the tag.
472 */
473 if ( vStringLength(token->scope) > 0 )
474 {
475 fulltag = vStringNew ();
476 vStringCopy(fulltag, token->scope);
477 vStringCatS (fulltag, ".");
478 vStringCatS (fulltag, vStringValue(token->string));
479 vStringTerminate(fulltag);
480 vStringCopy(token->string, fulltag);
481 vStringDelete (fulltag);
482 }
483 makeConstTag (token, kind);
484 }
485}
486
487/*
488 * Parsing functions
489 */
490
491static void parseString (vString *const string, const int delimiter)
492{
493 boolean end = FALSE;
494 while (! end)
495 {
496 int c = fileGetc ();
497 if (c == EOF)
498 end = TRUE;
499 /*
500 else if (c == '\\')
501 {
502 c = fileGetc(); // This maybe a ' or ". //
503 vStringPut(string, c);
504 }
505 */
506 else if (c == delimiter)
507 end = TRUE;
508 else
509 vStringPut (string, c);
510 }
511 vStringTerminate (string);
512}
513
514/* Read a C identifier beginning with "firstChar" and places it into "name".
515*/
516static void parseIdentifier (vString *const string, const int firstChar)
517{
518 int c = firstChar;
519 Assert (isIdentChar1 (c));
520 do
521 {
522 vStringPut (string, c);
523 c = fileGetc ();
524 } while (isIdentChar (c));
525 vStringTerminate (string);
526 if (!isspace (c))
527 fileUngetc (c); /* unget non-identifier character */
528}
529
530static void readToken (tokenInfo *const token)
531{
532 int c;
533
534 token->type = TOKEN_UNDEFINED;
535 token->keyword = KEYWORD_NONE;
536 vStringClear (token->string);
537
538getNextChar:
539 do
540 {
541 c = fileGetc ();
542 token->lineNumber = getSourceLineNumber ();
543 token->filePosition = getInputFilePosition ();
544 /*
545 * Added " to the list of ignores, not sure what this
546 * might break but it gets by this issue:
547 * create table "t1" (...)
548 *
549 * Darren, the code passes all my tests for both
550 * Oracle and SQL Anywhere, but maybe you can tell me
551 * what this may effect.
552 */
553 }
554 while (c == '\t' || c == ' ' || c == '\n');
555
556 switch (c)
557 {
558 case EOF: longjmp (Exception, (int)ExceptionEOF); break;
559 case '(': token->type = TOKEN_OPEN_PAREN; break;
560 case ')': token->type = TOKEN_CLOSE_PAREN; break;
561 case ':': token->type = TOKEN_COLON; break;
562 case ';': token->type = TOKEN_SEMICOLON; break;
563 case '.': token->type = TOKEN_PERIOD; break;
564 case ',': token->type = TOKEN_COMMA; break;
565 case '{': token->type = TOKEN_OPEN_CURLY; break;
566 case '}': token->type = TOKEN_CLOSE_CURLY; break;
567 case '~': token->type = TOKEN_TILDE; break;
568 case '[': token->type = TOKEN_OPEN_SQUARE; break;
569 case ']': token->type = TOKEN_CLOSE_SQUARE; break;
570 case '=': token->type = TOKEN_EQUAL; break;
571
572 case '\'':
573 case '"':
574 token->type = TOKEN_STRING;
575 parseString (token->string, c);
576 token->lineNumber = getSourceLineNumber ();
577 token->filePosition = getInputFilePosition ();
578 break;
579
580 case '-':
581 c = fileGetc ();
582 if (c == '-') /* -- is this the start of a comment? */
583 {
584 fileSkipToCharacter ('\n');
585 goto getNextChar;
586 }
587 else
588 {
589 if (!isspace (c))
590 fileUngetc (c);
591 token->type = TOKEN_OPERATOR;
592 }
593 break;
594
595 case '<':
596 case '>':
597 {
598 const int initial = c;
599 int d = fileGetc ();
600 if (d == initial)
601 {
602 if (initial == '<')
603 token->type = TOKEN_BLOCK_LABEL_BEGIN;
604 else
605 token->type = TOKEN_BLOCK_LABEL_END;
606 }
607 else
608 {
609 fileUngetc (d);
610 token->type = TOKEN_UNDEFINED;
611 }
612 break;
613 }
614
615 case '\\':
616 c = fileGetc ();
617 if (c != '\\' && c != '"' && c != '\'' && !isspace (c))
618 fileUngetc (c);
619 token->type = TOKEN_CHARACTER;
620 token->lineNumber = getSourceLineNumber ();
621 token->filePosition = getInputFilePosition ();
622 break;
623
624 case '/':
625 {
626 int d = fileGetc ();
627 if ( (d != '*') && /* is this the start of a comment? */
628 (d != '/') ) /* is a one line comment? */
629 {
630 token->type = TOKEN_FORWARD_SLASH;
631 fileUngetc (d);
632 }
633 else
634 {
635 if (d == '*')
636 {
637 do
638 {
639 fileSkipToCharacter ('*');
640 c = fileGetc ();
641 if (c == '/')
642 break;
643 else
644 fileUngetc (c);
645 } while (c != EOF && c != '\0');
646 goto getNextChar;
647 }
648 else if (d == '/') /* is this the start of a comment? */
649 {
650 fileSkipToCharacter ('\n');
651 goto getNextChar;
652 }
653 }
654 break;
655 }
656
657 default:
658 if (! isIdentChar1 (c))
659 token->type = TOKEN_UNDEFINED;
660 else
661 {
662 parseIdentifier (token->string, c);
663 token->lineNumber = getSourceLineNumber ();
664 token->filePosition = getInputFilePosition ();
665 token->keyword = analyzeToken (token->string, Lang_sql);
666 if (isKeyword (token, KEYWORD_rem))
667 {
668 vStringClear (token->string);
669 fileSkipToCharacter ('\n');
670 goto getNextChar;
671 }
672 else if (isKeyword (token, KEYWORD_NONE))
673 token->type = TOKEN_IDENTIFIER;
674 else
675 token->type = TOKEN_KEYWORD;
676 }
677 break;
678 }
679}
680
681/*
682 * Token parsing functions
683 */
684
685/*
686 * static void addContext (tokenInfo* const parent, const tokenInfo* const child)
687 * {
688 * if (vStringLength (parent->string) > 0)
689 * {
690 * vStringCatS (parent->string, ".");
691 * }
692 * vStringCatS (parent->string, vStringValue(child->string));
693 * vStringTerminate(parent->string);
694 * }
695 */
696
697static void addToScope (tokenInfo* const token, vString* const extra)
698{
699 if (vStringLength (token->scope) > 0)
700 {
701 vStringCatS (token->scope, ".");
702 }
703 vStringCatS (token->scope, vStringValue(extra));
704 vStringTerminate(token->scope);
705}
706
707/*
708 * Scanning functions
709 */
710
711static void findToken (tokenInfo *const token, const tokenType type)
712{
713 while (! isType (token, type))
714 {
715 readToken (token);
716 }
717}
718
719static void findCmdTerm (tokenInfo *const token, const boolean check_first)
720{
721 int begin_end_nest_lvl = token->begin_end_nest_lvl;
722
723 if ( check_first )
724 {
725 if ( isCmdTerm(token) )
726 return;
727 }
728 do
729 {
730 readToken (token);
731 } while ( !isCmdTerm(token) && !isMatchedEnd(token, begin_end_nest_lvl) );
732}
733
734static void skipToMatched(tokenInfo *const token)
735{
736 int nest_level = 0;
737 tokenType open_token;
738 tokenType close_token;
739
740 switch (token->type)
741 {
742 case TOKEN_OPEN_PAREN:
743 open_token = TOKEN_OPEN_PAREN;
744 close_token = TOKEN_CLOSE_PAREN;
745 break;
746 case TOKEN_OPEN_CURLY:
747 open_token = TOKEN_OPEN_CURLY;
748 close_token = TOKEN_CLOSE_CURLY;
749 break;
750 case TOKEN_OPEN_SQUARE:
751 open_token = TOKEN_OPEN_SQUARE;
752 close_token = TOKEN_CLOSE_SQUARE;
753 break;
754 default:
755 return;
756 }
757
758 /*
759 * This routine will skip to a matching closing token.
760 * It will also handle nested tokens like the (, ) below.
761 * ( name varchar(30), text binary(10) )
762 */
763
764 if (isType (token, open_token))
765 {
766 nest_level++;
767 while (! (isType (token, close_token) && (nest_level == 0)))
768 {
769 readToken (token);
770 if (isType (token, open_token))
771 {
772 nest_level++;
773 }
774 if (isType (token, close_token))
775 {
776 if (nest_level > 0)
777 {
778 nest_level--;
779 }
780 }
781 }
782 readToken (token);
783 }
784}
785
786static void copyToken (tokenInfo *const dest, tokenInfo *const src)
787{
788 dest->lineNumber = src->lineNumber;
789 dest->filePosition = src->filePosition;
790 dest->type = src->type;
791 dest->keyword = src->keyword;
792 vStringCopy(dest->string, src->string);
793 vStringCopy(dest->scope, src->scope);
794}
795
796static void skipArgumentList (tokenInfo *const token)
797{
798 /*
799 * Other databases can have arguments with fully declared
800 * datatypes:
801 * ( name varchar(30), text binary(10) )
802 * So we must check for nested open and closing parantheses
803 */
804
805 if (isType (token, TOKEN_OPEN_PAREN)) /* arguments? */
806 {
807 skipToMatched (token);
808 }
809}
810
811static void parseSubProgram (tokenInfo *const token)
812{
813 tokenInfo *const name = newToken ();
814 vString * saveScope = vStringNew ();
815
816 /*
817 * This must handle both prototypes and the body of
818 * the procedures.
819 *
820 * Prototype:
821 * FUNCTION func_name RETURN integer;
822 * PROCEDURE proc_name( parameters );
823 * Procedure
824 * FUNCTION GET_ML_USERNAME RETURN VARCHAR2
825 * IS
826 * BEGIN
827 * RETURN v_sync_user_id;
828 * END GET_ML_USERNAME;
829 *
830 * PROCEDURE proc_name( parameters )
831 * IS
832 * BEGIN
833 * END;
834 * CREATE PROCEDURE proc_name( parameters )
835 * EXTERNAL NAME ... ;
836 * CREATE PROCEDURE proc_name( parameters )
837 * BEGIN
838 * END;
839 *
840 * CREATE FUNCTION f_GetClassName(
841 * IN @object VARCHAR(128)
842 * ,IN @code VARCHAR(128)
843 * )
844 * RETURNS VARCHAR(200)
845 * DETERMINISTIC
846 * BEGIN
847 *
848 * IF( @object = 'user_state' ) THEN
849 * SET something = something;
850 * END IF;
851 *
852 * RETURN @name;
853 * END;
854 *
855 * Note, a Package adds scope to the items within.
856 * create or replace package demo_pkg is
857 * test_var number;
858 * function test_func return varchar2;
859 * function more.test_func2 return varchar2;
860 * end demo_pkg;
861 * So the tags generated here, contain the package name:
862 * demo_pkg.test_var
863 * demo_pkg.test_func
864 * demo_pkg.more.test_func2
865 */
866 const sqlKind kind = isKeyword (token, KEYWORD_function) ?
867 SQLTAG_FUNCTION : SQLTAG_PROCEDURE;
868 Assert (isKeyword (token, KEYWORD_function) ||
869 isKeyword (token, KEYWORD_procedure));
870
871 vStringCopy(saveScope, token->scope);
872 readToken (token);
873 copyToken (name, token);
874 readToken (token);
875
876 if (isType (token, TOKEN_PERIOD))
877 {
878 /*
879 * If this is an Oracle package, then the token->scope should
880 * already be set. If this is the case, also add this value to the
881 * scope.
882 * If this is not an Oracle package, chances are the scope should be
883 * blank and the value just read is the OWNER or CREATOR of the
884 * function and should not be considered part of the scope.
885 */
886 if ( vStringLength(saveScope) > 0 )
887 {
888 addToScope(token, name->string);
889 }
890 readToken (token);
891 copyToken (name, token);
892 readToken (token);
893 }
894 if (isType (token, TOKEN_OPEN_PAREN))
895 {
896 /* Reads to the next token after the TOKEN_CLOSE_PAREN */
897 skipArgumentList(token);
898 }
899
900 if (kind == SQLTAG_FUNCTION)
901 {
902 if (isKeyword (token, KEYWORD_return) || isKeyword (token, KEYWORD_returns))
903 {
904 /* Read datatype */
905 readToken (token);
906 /*
907 * Read token after which could be the
908 * command terminator if a prototype
909 * or an open parantheses
910 */
911 readToken (token);
912 if (isType (token, TOKEN_OPEN_PAREN))
913 {
914 /* Reads to the next token after the TOKEN_CLOSE_PAREN */
915 skipArgumentList(token);
916 }
917 }
918 }
919 if( isCmdTerm (token) )
920 {
921 makeSqlTag (name, SQLTAG_PROTOTYPE);
922 }
923 else
924 {
925 while (!(isKeyword (token, KEYWORD_is) ||
926 isKeyword (token, KEYWORD_begin) ||
927 isKeyword (token, KEYWORD_at) ||
928 isKeyword (token, KEYWORD_internal) ||
929 isKeyword (token, KEYWORD_external) ||
930 isKeyword (token, KEYWORD_url) ||
931 isType (token, TOKEN_EQUAL) ||
932 isCmdTerm (token)
933 )
934 )
935 {
936 if ( isKeyword (token, KEYWORD_result) )
937 {
938 readToken (token);
939 if (isType (token, TOKEN_OPEN_PAREN))
940 {
941 /* Reads to the next token after the TOKEN_CLOSE_PAREN */
942 skipArgumentList(token);
943 }
944 } else {
945 readToken (token);
946 }
947 }
948 if (isKeyword (token, KEYWORD_at) ||
949 isKeyword (token, KEYWORD_url) ||
950 isKeyword (token, KEYWORD_internal) ||
951 isKeyword (token, KEYWORD_external) )
952 {
953 addToScope(token, name->string);
954 if (isType (name, TOKEN_IDENTIFIER) ||
955 isType (name, TOKEN_STRING) ||
956 !isKeyword (token, KEYWORD_NONE)
957 )
958 makeSqlTag (name, kind);
959
960 vStringClear (token->scope);
961 }
962 if ( isType (token, TOKEN_EQUAL) )
963 readToken (token);
964
965 if ( isKeyword (token, KEYWORD_declare) )
966 parseDeclare (token, FALSE);
967
968 if (isKeyword (token, KEYWORD_is) ||
969 isKeyword (token, KEYWORD_begin) )
970 {
971 addToScope(token, name->string);
972 if (isType (name, TOKEN_IDENTIFIER) ||
973 isType (name, TOKEN_STRING) ||
974 !isKeyword (token, KEYWORD_NONE)
975 )
976 makeSqlTag (name, kind);
977
978 parseBlock (token, TRUE);
979 vStringClear (token->scope);
980 }
981 }
982 vStringCopy(token->scope, saveScope);
983 deleteToken (name);
984 vStringDelete(saveScope);
985}
986
987static void parseRecord (tokenInfo *const token)
988{
989 /*
990 * Make it a bit forgiving, this is called from
991 * multiple functions, parseTable, parseType
992 */
993 if (!isType (token, TOKEN_OPEN_PAREN))
994 readToken (token);
995
996 Assert (isType (token, TOKEN_OPEN_PAREN));
997 do
998 {
999 if ( isType (token, TOKEN_COMMA) || isType (token, TOKEN_OPEN_PAREN) )
1000 readToken (token);
1001
1002 /*
1003 * Create table statements can end with various constraints
1004 * which must be excluded from the SQLTAG_FIELD.
1005 * create table t1 (
1006 * c1 integer,
1007 * c2 char(30),
1008 * c3 numeric(10,5),
1009 * c4 integer,
1010 * constraint whatever,
1011 * primary key(c1),
1012 * foreign key (),
1013 * check ()
1014 * )
1015 */
1016 if (! (isKeyword(token, KEYWORD_primary) ||
1017 isKeyword(token, KEYWORD_references) ||
1018 isKeyword(token, KEYWORD_unique) ||
1019 isKeyword(token, KEYWORD_check) ||
1020 isKeyword(token, KEYWORD_constraint) ||
1021 isKeyword(token, KEYWORD_foreign) ) )
1022 {
1023 if (isType (token, TOKEN_IDENTIFIER) ||
1024 isType (token, TOKEN_STRING))
1025 makeSqlTag (token, SQLTAG_FIELD);
1026 }
1027
1028 while (!(isType (token, TOKEN_COMMA) ||
1029 isType (token, TOKEN_CLOSE_PAREN) ||
1030 isType (token, TOKEN_OPEN_PAREN)
1031 ))
1032 {
1033 readToken (token);
1034 /*
1035 * A table structure can look like this:
1036 * create table t1 (
1037 * c1 integer,
1038 * c2 char(30),
1039 * c3 numeric(10,5),
1040 * c4 integer
1041 * )
1042 * We can't just look for a COMMA or CLOSE_PAREN
1043 * since that will not deal with the numeric(10,5)
1044 * case. So we need to skip the argument list
1045 * when we find an open paren.
1046 */
1047 if (isType (token, TOKEN_OPEN_PAREN))
1048 {
1049 /* Reads to the next token after the TOKEN_CLOSE_PAREN */
1050 skipArgumentList(token);
1051 }
1052 }
1053 } while (! isType (token, TOKEN_CLOSE_PAREN));
1054}
1055
1056static void parseType (tokenInfo *const token)
1057{
1058 tokenInfo *const name = newToken ();
1059 vString * saveScope = vStringNew ();
1060
1061 vStringCopy(saveScope, token->scope);
1062 /* If a scope has been set, add it to the name */
1063 addToScope (name, token->scope);
1064 readToken (name);
1065 if (isType (name, TOKEN_IDENTIFIER))
1066 {
1067 readToken (token);
1068 if (isKeyword (token, KEYWORD_is))
1069 {
1070 readToken (token);
1071 addToScope (token, name->string);
1072 switch (token->keyword)
1073 {
1074 case KEYWORD_record:
1075 case KEYWORD_object:
1076 makeSqlTag (name, SQLTAG_RECORD);
1077 parseRecord (token);
1078 break;
1079
1080 case KEYWORD_table:
1081 makeSqlTag (name, SQLTAG_TABLE);
1082 break;
1083
1084 case KEYWORD_ref:
1085 readToken (token);
1086 if (isKeyword (token, KEYWORD_cursor))
1087 makeSqlTag (name, SQLTAG_CURSOR);
1088 break;
1089
1090 default: break;
1091 }
1092 vStringClear (token->scope);
1093 }
1094 }
1095 vStringCopy(token->scope, saveScope);
1096 deleteToken (name);
1097 vStringDelete(saveScope);
1098}
1099
1100static void parseSimple (tokenInfo *const token, const sqlKind kind)
1101{
1102 /* This will simply make the tagname from the first word found */
1103 readToken (token);
1104 if (isType (token, TOKEN_IDENTIFIER) ||
1105 isType (token, TOKEN_STRING))
1106 makeSqlTag (token, kind);
1107}
1108
1109static void parseDeclare (tokenInfo *const token, const boolean local)
1110{
1111 /*
1112 * PL/SQL declares are of this format:
1113 * IS|AS
1114 * [declare]
1115 * CURSOR curname ...
1116 * varname1 datatype;
1117 * varname2 datatype;
1118 * varname3 datatype;
1119 * begin
1120 */
1121
1122 if (isKeyword (token, KEYWORD_declare))
1123 readToken (token);
1124 while (! isKeyword (token, KEYWORD_begin) && ! isKeyword (token, KEYWORD_end))
1125 {
1126 switch (token->keyword)
1127 {
1128 case KEYWORD_cursor: parseSimple (token, SQLTAG_CURSOR); break;
1129 case KEYWORD_function: parseSubProgram (token); break;
1130 case KEYWORD_procedure: parseSubProgram (token); break;
1131 case KEYWORD_subtype: parseSimple (token, SQLTAG_SUBTYPE); break;
1132 case KEYWORD_trigger: parseSimple (token, SQLTAG_TRIGGER); break;
1133 case KEYWORD_type: parseType (token); break;
1134
1135 default:
1136 if (isType (token, TOKEN_IDENTIFIER))
1137 {
1138 if (local)
1139 {
1140 makeSqlTag (token, SQLTAG_LOCAL_VARIABLE);
1141 }
1142 else
1143 {
1144 makeSqlTag (token, SQLTAG_VARIABLE);
1145 }
1146 }
1147 break;
1148 }
1149 findToken (token, TOKEN_SEMICOLON);
1150 readToken (token);
1151 }
1152}
1153
1154static void parseDeclareANSI (tokenInfo *const token, const boolean local)
1155{
1156 tokenInfo *const type = newToken ();
1157 /*
1158 * ANSI declares are of this format:
1159 * BEGIN
1160 * DECLARE varname1 datatype;
1161 * DECLARE varname2 datatype;
1162 * ...
1163 *
1164 * This differ from PL/SQL where DECLARE preceeds the BEGIN block
1165 * and the DECLARE keyword is not repeated.
1166 */
1167 while (isKeyword (token, KEYWORD_declare))
1168 {
1169 readToken (token);
1170 readToken (type);
1171
1172 if (isKeyword (type, KEYWORD_cursor))
1173 makeSqlTag (token, SQLTAG_CURSOR);
1174 else if (isKeyword (token, KEYWORD_local) &&
1175 isKeyword (type, KEYWORD_temporary))
1176 {
1177 /*
1178 * DECLARE LOCAL TEMPORARY TABLE table_name (
1179 * c1 int,
1180 * c2 int
1181 * );
1182 */
1183 readToken (token);
1184 if (isKeyword (token, KEYWORD_table))
1185 {
1186 readToken (token);
1187 if (isType(token, TOKEN_IDENTIFIER) ||
1188 isType(token, TOKEN_STRING) )
1189 {
1190 makeSqlTag (token, SQLTAG_TABLE);
1191 }
1192 }
1193 }
1194 else if (isType (token, TOKEN_IDENTIFIER) ||
1195 isType (token, TOKEN_STRING))
1196 {
1197 if (local)
1198 makeSqlTag (token, SQLTAG_LOCAL_VARIABLE);
1199 else
1200 makeSqlTag (token, SQLTAG_VARIABLE);
1201 }
1202 findToken (token, TOKEN_SEMICOLON);
1203 readToken (token);
1204 }
1205 deleteToken (type);
1206}
1207
1208static void parseLabel (tokenInfo *const token)
1209{
1210 /*
1211 * A label has this format:
1212 * <<tobacco_dependency>>
1213 * DECLARE
1214 * v_senator VARCHAR2(100) := 'THURMOND, JESSE';
1215 * BEGIN
1216 * IF total_contributions (v_senator, 'TOBACCO') > 25000
1217 * THEN
1218 * <<alochol_dependency>>
1219 * DECLARE
1220 * v_senator VARCHAR2(100) := 'WHATEVERIT, TAKES';
1221 * BEGIN
1222 * ...
1223 */
1224
1225 Assert (isType (token, TOKEN_BLOCK_LABEL_BEGIN));
1226 readToken (token);
1227 if (isType (token, TOKEN_IDENTIFIER))
1228 {
1229 makeSqlTag (token, SQLTAG_BLOCK_LABEL);
1230 readToken (token); /* read end of label */
1231 }
1232}
1233
1234static void parseStatements (tokenInfo *const token, const boolean exit_on_endif )
1235{
1236 boolean isAnsi = TRUE;
1237 boolean stmtTerm = FALSE;
1238 do
1239 {
1240
1241 if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
1242 parseLabel (token);
1243 else
1244 {
1245 switch (token->keyword)
1246 {
1247 case KEYWORD_exception:
1248 /*
1249 * EXCEPTION
1250 * <exception handler>;
1251 *
1252 * Where an exception handler could be:
1253 * BEGIN
1254 * WHEN OTHERS THEN
1255 * x := x + 3;
1256 * END;
1257 * In this case we need to skip this keyword and
1258 * move on to the next token without reading until
1259 * TOKEN_SEMICOLON;
1260 */
1261 readToken (token);
1262 continue;
1263
1264 case KEYWORD_when:
1265 /*
1266 * WHEN statements can be used in exception clauses
1267 * and CASE statements. The CASE statement should skip
1268 * these given below we skip over to an END statement.
1269 * But for an exception clause, we can have:
1270 * EXCEPTION
1271 * WHEN OTHERS THEN
1272 * BEGIN
1273 * x := x + 3;
1274 * END;
1275 * If we skip to the TOKEN_SEMICOLON, we miss the begin
1276 * of a nested BEGIN END block. So read the next token
1277 * after the THEN and restart the LOOP.
1278 */
1279 while (! isKeyword (token, KEYWORD_then))
1280 readToken (token);
1281
1282 readToken (token);
1283 continue;
1284
1285 case KEYWORD_if:
1286 /*
1287 * We do not want to look for a ; since for an empty
1288 * IF block, it would skip over the END.
1289 * IF...THEN
1290 * END IF;
1291 *
1292 * IF...THEN
1293 * ELSE
1294 * END IF;
1295 *
1296 * IF...THEN
1297 * ELSEIF...THEN
1298 * ELSE
1299 * END IF;
1300 *
1301 * or non-ANSI
1302 * IF ...
1303 * BEGIN
1304 * END
1305 */
1306 while ( ! isKeyword (token, KEYWORD_then) &&
1307 ! isKeyword (token, KEYWORD_begin) )
1308 {
1309 readToken (token);
1310 }
1311
1312 if( isKeyword (token, KEYWORD_begin ) )
1313 {
1314 isAnsi = FALSE;
1315 parseBlock(token, FALSE);
1316
1317 /*
1318 * Handle the non-Ansi IF blocks.
1319 * parseBlock consumes the END, so if the next
1320 * token in a command terminator (like GO)
1321 * we know we are done with this statement.
1322 */
1323 if ( isCmdTerm (token) )
1324 stmtTerm = TRUE;
1325 }
1326 else
1327 {
1328 readToken (token);
1329
1330 while( ! (isKeyword (token, KEYWORD_end ) ||
1331 isKeyword (token, KEYWORD_endif ) )
1332 )
1333 {
1334 if ( isKeyword (token, KEYWORD_else) ||
1335 isKeyword (token, KEYWORD_elseif) )
1336 readToken (token);
1337
1338 parseStatements (token, TRUE);
1339
1340 if ( isCmdTerm(token) )
1341 readToken (token);
1342
1343 }
1344
1345 /*
1346 * parseStatements returns when it finds an END, an IF
1347 * should follow the END for ANSI anyway.
1348 * IF...THEN
1349 * END IF;
1350 */
1351 if( isKeyword (token, KEYWORD_end ) )
1352 readToken (token);
1353
1354 if( isKeyword (token, KEYWORD_if ) || isKeyword (token, KEYWORD_endif ) )
1355 {
1356 readToken (token);
1357 if ( isCmdTerm(token) )
1358 stmtTerm = TRUE;
1359 }
1360 else
1361 {
1362 /*
1363 * Well we need to do something here.
1364 * There are lots of different END statements
1365 * END;
1366 * END CASE;
1367 * ENDIF;
1368 * ENDCASE;
1369 */
1370 }
1371 }
1372 break;
1373
1374 case KEYWORD_loop:
1375 case KEYWORD_case:
1376 case KEYWORD_for:
1377 /*
1378 * LOOP...
1379 * END LOOP;
1380 *
1381 * CASE
1382 * WHEN '1' THEN
1383 * END CASE;
1384 *
1385 * FOR loop_name AS cursor_name CURSOR FOR ...
1386 * DO
1387 * END FOR;
1388 */
1389 if( isKeyword (token, KEYWORD_for ) )
1390 {
1391 /* loop name */
1392 readToken (token);
1393 /* AS */
1394 readToken (token);
1395
1396 while ( ! isKeyword (token, KEYWORD_is) )
1397 {
1398 /*
1399 * If this is not an AS keyword this is
1400 * not a proper FOR statement and should
1401 * simply be ignored
1402 */
1403 return;
1404 }
1405
1406 while ( ! isKeyword (token, KEYWORD_do) )
1407 readToken (token);
1408 }
1409
1410
1411 readToken (token);
1412 while( ! isKeyword (token, KEYWORD_end ) )
1413 {
1414 /*
1415 if ( isKeyword (token, KEYWORD_else) ||
1416 isKeyword (token, KEYWORD_elseif) )
1417 readToken (token);
1418 */
1419
1420 parseStatements (token, FALSE);
1421
1422 if ( isCmdTerm(token) )
1423 readToken (token);
1424 }
1425
1426
1427 if( isKeyword (token, KEYWORD_end ) )
1428 readToken (token);
1429
1430 /*
1431 * Typically ended with
1432 * END LOOP [loop name];
1433 * END CASE
1434 * END FOR [loop name];
1435 */
1436 if ( isKeyword (token, KEYWORD_loop) ||
1437 isKeyword (token, KEYWORD_case) ||
1438 isKeyword (token, KEYWORD_for) )
1439 readToken (token);
1440
1441 if ( isCmdTerm(token) )
1442 stmtTerm = TRUE;
1443
1444 break;
1445
1446 case KEYWORD_create:
1447 readToken (token);
1448 parseKeywords(token);
1449 break;
1450
1451 case KEYWORD_declare:
1452 case KEYWORD_begin:
1453 parseBlock (token, TRUE);
1454 break;
1455
1456 case KEYWORD_end:
1457 break;
1458
1459 default:
1460 readToken (token);
1461 break;
1462 }
1463 /*
1464 * Not all statements must end in a semi-colon
1465 * begin
1466 * if current publisher <> 'publish' then
1467 * signal UE_FailStatement
1468 * end if
1469 * end;
1470 * The last statement prior to an end ("signal" above) does
1471 * not need a semi-colon, nor does the end if, since it is
1472 * also the last statement prior to the end of the block.
1473 *
1474 * So we must read to the first semi-colon or an END block
1475 */
1476 while ( ! stmtTerm &&
1477 ! ( isKeyword (token, KEYWORD_end) ||
1478 (isCmdTerm(token)) )
1479 )
1480 {
1481 if ( isKeyword (token, KEYWORD_endif) &&
1482 exit_on_endif )
1483 return;
1484
1485 if (isType (token, TOKEN_COLON) )
1486 {
1487 /*
1488 * A : can signal a loop name
1489 * myloop:
1490 * LOOP
1491 * LEAVE myloop;
1492 * END LOOP;
1493 * Unfortunately, labels do not have a
1494 * cmd terminator, therefore we have to check
1495 * if the next token is a keyword and process
1496 * it accordingly.
1497 */
1498 readToken (token);
1499 if ( isKeyword (token, KEYWORD_loop) ||
1500 isKeyword (token, KEYWORD_while) ||
1501 isKeyword (token, KEYWORD_for) )
1502 /* parseStatements (token); */
1503 return;
1504 }
1505
1506 readToken (token);
1507
1508 if (isType (token, TOKEN_OPEN_PAREN) ||
1509 isType (token, TOKEN_OPEN_CURLY) ||
1510 isType (token, TOKEN_OPEN_SQUARE) )
1511 skipToMatched (token);
1512
1513 /*
1514 * Since we know how to parse various statements
1515 * if we detect them, parse them to completion
1516 */
1517 if (isType (token, TOKEN_BLOCK_LABEL_BEGIN) ||
1518 isKeyword (token, KEYWORD_exception) ||
1519 isKeyword (token, KEYWORD_loop) ||
1520 isKeyword (token, KEYWORD_case) ||
1521 isKeyword (token, KEYWORD_for) ||
1522 isKeyword (token, KEYWORD_begin) )
1523 parseStatements (token, FALSE);
1524 else if (isKeyword (token, KEYWORD_if))
1525 parseStatements (token, TRUE);
1526
1527 }
1528 }
1529 /*
1530 * We assumed earlier all statements ended with a command terminator.
1531 * See comment above, now, only read if the current token
1532 * is not a command terminator.
1533 */
1534 if ( isCmdTerm(token) && ! stmtTerm )
1535 stmtTerm = TRUE;
1536
1537 } while (! isKeyword (token, KEYWORD_end) &&
1538 ! (exit_on_endif && isKeyword (token, KEYWORD_endif) ) &&
1539 ! stmtTerm );
1540}
1541
1542static void parseBlock (tokenInfo *const token, const boolean local)
1543{
1544 if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
1545 {
1546 parseLabel (token);
1547 readToken (token);
1548 }
1549 if (! isKeyword (token, KEYWORD_begin))
1550 {
1551 readToken (token);
1552 /*
1553 * These are Oracle style declares which generally come
1554 * between an IS/AS and BEGIN block.
1555 */
1556 parseDeclare (token, local);
1557 }
1558 if (isKeyword (token, KEYWORD_begin))
1559 {
1560 readToken (token);
1561 /*
1562 * Check for ANSI declarations which always follow
1563 * a BEGIN statement. This routine will not advance
1564 * the token if none are found.
1565 */
1566 parseDeclareANSI (token, local);
1567 token->begin_end_nest_lvl++;
1568 while (! isKeyword (token, KEYWORD_end))
1569 {
1570 parseStatements (token, FALSE);
1571
1572 if ( isCmdTerm(token) )
1573 readToken (token);
1574 }
1575 token->begin_end_nest_lvl--;
1576
1577 /*
1578 * Read the next token (we will assume
1579 * it is the command delimiter)
1580 */
1581 readToken (token);
1582
1583 /*
1584 * Check if the END block is terminated
1585 */
1586 if ( !isCmdTerm (token) )
1587 {
1588 /*
1589 * Not sure what to do here at the moment.
1590 * I think the routine that calls parseBlock
1591 * must expect the next token has already
1592 * been read since it is possible this
1593 * token is not a command delimiter.
1594 */
1595 /* findCmdTerm (token, FALSE); */
1596 }
1597 }
1598}
1599
1600static void parsePackage (tokenInfo *const token)
1601{
1602 /*
1603 * Packages can be specified in a number of ways:
1604 * CREATE OR REPLACE PACKAGE pkg_name AS
1605 * or
1606 * CREATE OR REPLACE PACKAGE owner.pkg_name AS
1607 * or by specifying a package body
1608 * CREATE OR REPLACE PACKAGE BODY pkg_name AS
1609 * CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS
1610 */
1611 tokenInfo *const name = newToken ();
1612 readToken (name);
1613 if (isKeyword (name, KEYWORD_body))
1614 {
1615 /*
1616 * Ignore the BODY tag since we will process
1617 * the body or prototypes in the same manner
1618 */
1619 readToken (name);
1620 }
1621 /* Check for owner.pkg_name */
1622 while (! isKeyword (token, KEYWORD_is))
1623 {
1624 readToken (token);
1625 if ( isType(token, TOKEN_PERIOD) )
1626 {
1627 readToken (name);
1628 }
1629 }
1630 if (isKeyword (token, KEYWORD_is))
1631 {
1632 if (isType (name, TOKEN_IDENTIFIER) ||
1633 isType (name, TOKEN_STRING))
1634 makeSqlTag (name, SQLTAG_PACKAGE);
1635 addToScope (token, name->string);
1636 parseBlock (token, FALSE);
1637 vStringClear (token->scope);
1638 }
1639 findCmdTerm (token, FALSE);
1640 deleteToken (name);
1641}
1642
1643static void parseTable (tokenInfo *const token)
1644{
1645 tokenInfo *const name = newToken ();
1646
1647 /*
1648 * This deals with these formats:
1649 * create table t1 (c1 int);
1650 * create global tempoary table t2 (c1 int);
1651 * create table "t3" (c1 int);
1652 * create table bob.t4 (c1 int);
1653 * create table bob."t5" (c1 int);
1654 * create table "bob"."t6" (c1 int);
1655 * create table bob."t7" (c1 int);
1656 * Proxy tables use this format:
1657 * create existing table bob."t7" AT '...';
1658 * SQL Server and Sybase formats
1659 * create table OnlyTable (
1660 * create table dbo.HasOwner (
1661 * create table [dbo].[HasOwnerSquare] (
1662 * create table master.dbo.HasDb (
1663 * create table master..HasDbNoOwner (
1664 * create table [master].dbo.[HasDbAndOwnerSquare] (
1665 * create table [master]..[HasDbNoOwnerSquare] (
1666 */
1667
1668 /* This could be a database, owner or table name */
1669 readToken (name);
1670 if (isType (name, TOKEN_OPEN_SQUARE))
1671 {
1672 readToken (name);
1673 /* Read close square */
1674 readToken (token);
1675 }
1676 readToken (token);
1677 if (isType (token, TOKEN_PERIOD))
1678 {
1679 /*
1680 * This could be a owner or table name.
1681 * But this is also a special case since the table can be
1682 * referenced with a blank owner:
1683 * dbname..tablename
1684 */
1685 readToken (name);
1686 if (isType (name, TOKEN_OPEN_SQUARE))
1687 {
1688 readToken (name);
1689 /* Read close square */
1690 readToken (token);
1691 }
1692 /* Check if a blank name was provided */
1693 if (isType (name, TOKEN_PERIOD))
1694 {
1695 readToken (name);
1696 if (isType (name, TOKEN_OPEN_SQUARE))
1697 {
1698 readToken (name);
1699 /* Read close square */
1700 readToken (token);
1701 }
1702 }
1703 readToken (token);
1704 if (isType (token, TOKEN_PERIOD))
1705 {
1706 /* This can only be the table name */
1707 readToken (name);
1708 if (isType (name, TOKEN_OPEN_SQUARE))
1709 {
1710 readToken (name);
1711 /* Read close square */
1712 readToken (token);
1713 }
1714 readToken (token);
1715 }
1716 }
1717 if (isType (token, TOKEN_OPEN_PAREN))
1718 {
1719 if (isType (name, TOKEN_IDENTIFIER) ||
1720 isType (name, TOKEN_STRING))
1721 {
1722 makeSqlTag (name, SQLTAG_TABLE);
1723 vStringCopy(token->scope, name->string);
1724 parseRecord (token);
1725 vStringClear (token->scope);
1726 }
1727 }
1728 else if (isKeyword (token, KEYWORD_at))
1729 {
1730 if (isType (name, TOKEN_IDENTIFIER))
1731 {
1732 makeSqlTag (name, SQLTAG_TABLE);
1733 }
1734 }
1735 findCmdTerm (token, FALSE);
1736 deleteToken (name);
1737}
1738
1739static void parseIndex (tokenInfo *const token)
1740{
1741 tokenInfo *const name = newToken ();
1742 tokenInfo *const owner = newToken ();
1743
1744 /*
1745 * This deals with these formats
1746 * create index i1 on t1(c1) create index "i2" on t1(c1)
1747 * create virtual unique clustered index "i3" on t1(c1)
1748 * create unique clustered index "i4" on t1(c1)
1749 * create clustered index "i5" on t1(c1)
1750 * create bitmap index "i6" on t1(c1)
1751 */
1752
1753 readToken (name);
1754 readToken (token);
1755 if (isType (token, TOKEN_PERIOD))
1756 {
1757 readToken (name);
1758 readToken (token);
1759 }
1760 if ( isKeyword (token, KEYWORD_on) &&
1761 (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING) ) )
1762 {
1763 readToken (owner);
1764 readToken (token);
1765 if (isType (token, TOKEN_PERIOD))
1766 {
1767 readToken (owner);
1768 readToken (token);
1769 }
1770 addToScope(name, owner->string);
1771 makeSqlTag (name, SQLTAG_INDEX);
1772 }
1773 findCmdTerm (token, FALSE);
1774 deleteToken (name);
1775 deleteToken (owner);
1776}
1777
1778static void parseEvent (tokenInfo *const token)
1779{
1780 tokenInfo *const name = newToken ();
1781
1782 /*
1783 * This deals with these formats
1784 * create event e1 handler begin end;
1785 * create event "e2" handler begin end;
1786 * create event dba."e3" handler begin end;
1787 * create event "dba"."e4" handler begin end;
1788 */
1789
1790 readToken (name);
1791 readToken (token);
1792 if (isType (token, TOKEN_PERIOD))
1793 {
1794 readToken (name);
1795 }
1796 while (! (isKeyword (token, KEYWORD_handler) ||
1797 (isType (token, TOKEN_SEMICOLON))) )
1798 {
1799 readToken (token);
1800 }
1801
1802 if ( isKeyword (token, KEYWORD_handler) ||
1803 isType (token, TOKEN_SEMICOLON) )
1804 {
1805 makeSqlTag (name, SQLTAG_EVENT);
1806 }
1807
1808 if (isKeyword (token, KEYWORD_handler))
1809 {
1810 readToken (token);
1811 if ( isKeyword (token, KEYWORD_begin) )
1812 {
1813 parseBlock (token, TRUE);
1814 }
1815 findCmdTerm (token, TRUE);
1816 }
1817 deleteToken (name);
1818}
1819
1820static void parseTrigger (tokenInfo *const token)
1821{
1822 tokenInfo *const name = newToken ();
1823 tokenInfo *const table = newToken ();
1824
1825 /*
1826 * This deals with these formats
1827 * create or replace trigger tr1 begin end;
1828 * create trigger "tr2" begin end;
1829 * drop trigger "droptr1";
1830 * create trigger "tr3" CALL sp_something();
1831 * create trigger "owner"."tr4" begin end;
1832 * create trigger "tr5" not valid;
1833 * create trigger "tr6" begin end;
1834 */
1835
1836 readToken (name);
1837 readToken (token);
1838 if (isType (token, TOKEN_PERIOD))
1839 {
1840 readToken (name);
1841 readToken (token);
1842 }
1843
1844 while ( !isKeyword (token, KEYWORD_on) &&
1845 !isCmdTerm (token) )
1846 {
1847 readToken (token);
1848 }
1849
1850 /*if (! isType (token, TOKEN_SEMICOLON) ) */
1851 if (! isCmdTerm (token) )
1852 {
1853 readToken (table);
1854 readToken (token);
1855 if (isType (token, TOKEN_PERIOD))
1856 {
1857 readToken (table);
1858 readToken (token);
1859 }
1860
1861 while (! (isKeyword (token, KEYWORD_begin) ||
1862 (isKeyword (token, KEYWORD_call)) ||
1863 ( isCmdTerm (token))) )
1864 {
1865 if ( isKeyword (token, KEYWORD_declare) )
1866 {
1867 addToScope(token, name->string);
1868 parseDeclare(token, TRUE);
1869 vStringClear(token->scope);
1870 }
1871 else
1872 readToken (token);
1873 }
1874
1875 if ( isKeyword (token, KEYWORD_begin) ||
1876 isKeyword (token, KEYWORD_call) )
1877 {
1878 addToScope(name, table->string);
1879 makeSqlTag (name, SQLTAG_TRIGGER);
1880 addToScope(token, table->string);
1881 if ( isKeyword (token, KEYWORD_begin) )
1882 {
1883 parseBlock (token, TRUE);
1884 }
1885 vStringClear(token->scope);
1886 }
1887 }
1888
1889 findCmdTerm (token, TRUE);
1890 deleteToken (name);
1891 deleteToken (table);
1892}
1893
1894static void parsePublication (tokenInfo *const token)
1895{
1896 tokenInfo *const name = newToken ();
1897
1898 /*
1899 * This deals with these formats
1900 * create or replace publication pu1 ()
1901 * create publication "pu2" ()
1902 * create publication dba."pu3" ()
1903 * create publication "dba"."pu4" ()
1904 */
1905
1906 readToken (name);
1907 readToken (token);
1908 if (isType (token, TOKEN_PERIOD))
1909 {
1910 readToken (name);
1911 readToken (token);
1912 }
1913 if (isType (token, TOKEN_OPEN_PAREN))
1914 {
1915 if (isType (name, TOKEN_IDENTIFIER) ||
1916 isType (name, TOKEN_STRING))
1917 {
1918 makeSqlTag (name, SQLTAG_PUBLICATION);
1919 }
1920 }
1921 findCmdTerm (token, FALSE);
1922 deleteToken (name);
1923}
1924
1925static void parseService (tokenInfo *const token)
1926{
1927 tokenInfo *const name = newToken ();
1928
1929 /*
1930 * This deals with these formats
1931 * CREATE SERVICE s1 TYPE 'HTML'
1932 * AUTHORIZATION OFF USER DBA AS
1933 * SELECT *
1934 * FROM SYS.SYSTABLE;
1935 * CREATE SERVICE "s2" TYPE 'HTML'
1936 * AUTHORIZATION OFF USER DBA AS
1937 * CALL sp_Something();
1938 */
1939
1940 readToken (name);
1941 readToken (token);
1942 if (isKeyword (token, KEYWORD_type))
1943 {
1944 if (isType (name, TOKEN_IDENTIFIER) ||
1945 isType (name, TOKEN_STRING))
1946 {
1947 makeSqlTag (name, SQLTAG_SERVICE);
1948 }
1949 }
1950 findCmdTerm (token, FALSE);
1951 deleteToken (name);
1952}
1953
1954static void parseDomain (tokenInfo *const token)
1955{
1956 tokenInfo *const name = newToken ();
1957
1958 /*
1959 * This deals with these formats
1960 * CREATE DOMAIN|DATATYPE [AS] your_name ...;
1961 */
1962
1963 readToken (name);
1964 if (isKeyword (name, KEYWORD_is))
1965 {
1966 readToken (name);
1967 }
1968 readToken (token);
1969 if (isType (name, TOKEN_IDENTIFIER) ||
1970 isType (name, TOKEN_STRING))
1971 {
1972 makeSqlTag (name, SQLTAG_DOMAIN);
1973 }
1974 findCmdTerm (token, FALSE);
1975 deleteToken (name);
1976}
1977
1978static void parseDrop (tokenInfo *const token)
1979{
1980 /*
1981 * This deals with these formats
1982 * DROP TABLE|PROCEDURE|DOMAIN|DATATYPE name;
1983 *
1984 * Just simply skip over these statements.
1985 * They are often confused with PROCEDURE prototypes
1986 * since the syntax is similar, this effectively deals with
1987 * the issue for all types.
1988 */
1989
1990 findCmdTerm (token, FALSE);
1991}
1992
1993static void parseVariable (tokenInfo *const token)
1994{
1995 tokenInfo *const name = newToken ();
1996
1997 /*
1998 * This deals with these formats
1999 * create variable varname1 integer;
2000 * create variable @varname2 integer;
2001 * create variable "varname3" integer;
2002 * drop variable @varname3;
2003 */
2004
2005 readToken (name);
2006 readToken (token);
2007 if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
2008 && !isType (token, TOKEN_SEMICOLON) )
2009 {
2010 makeSqlTag (name, SQLTAG_VARIABLE);
2011 }
2012 findCmdTerm (token, TRUE);
2013
2014 deleteToken (name);
2015}
2016
2017static void parseSynonym (tokenInfo *const token)
2018{
2019 tokenInfo *const name = newToken ();
2020
2021 /*
2022 * This deals with these formats
2023 * create variable varname1 integer;
2024 * create variable @varname2 integer;
2025 * create variable "varname3" integer;
2026 * drop variable @varname3;
2027 */
2028
2029 readToken (name);
2030 readToken (token);
2031 if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
2032 && isKeyword (token, KEYWORD_for) )
2033 {
2034 makeSqlTag (name, SQLTAG_SYNONYM);
2035 }
2036 findCmdTerm (token, TRUE);
2037
2038 deleteToken (name);
2039}
2040
2041static void parseView (tokenInfo *const token)
2042{
2043 tokenInfo *const name = newToken ();
2044
2045 /*
2046 * This deals with these formats
2047 * create variable varname1 integer;
2048 * create variable @varname2 integer;
2049 * create variable "varname3" integer;
2050 * drop variable @varname3;
2051 */
2052
2053 readToken (name);
2054 readToken (token);
2055 if (isType (token, TOKEN_PERIOD))
2056 {
2057 readToken (name);
2058 readToken (token);
2059 }
2060 if ( isType (token, TOKEN_OPEN_PAREN) )
2061 {
2062 skipArgumentList(token);
2063
2064 }
2065
2066 while (!(isKeyword (token, KEYWORD_is) ||
2067 isType (token, TOKEN_SEMICOLON)
2068 ))
2069 {
2070 readToken (token);
2071 }
2072
2073 if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
2074 && isKeyword (token, KEYWORD_is) )
2075 {
2076 makeSqlTag (name, SQLTAG_VIEW);
2077 }
2078
2079 findCmdTerm (token, TRUE);
2080
2081 deleteToken (name);
2082}
2083
2084static void parseMLTable (tokenInfo *const token)
2085{
2086 tokenInfo *const version = newToken ();
2087 tokenInfo *const table = newToken ();
2088 tokenInfo *const event = newToken ();
2089
2090 /*
2091 * This deals with these formats
2092 * call dbo.ml_add_table_script( 'version', 'table_name', 'event',
2093 * 'some SQL statement'
2094 * );
2095 */
2096
2097 readToken (token);
2098 if ( isType (token, TOKEN_OPEN_PAREN) )
2099 {
2100 readToken (version);
2101 readToken (token);
2102 while (!(isType (token, TOKEN_COMMA) ||
2103 isType (token, TOKEN_CLOSE_PAREN)
2104 ))
2105 {
2106 readToken (token);
2107 }
2108
2109 if (isType (token, TOKEN_COMMA))
2110 {
2111 readToken (table);
2112 readToken (token);
2113 while (!(isType (token, TOKEN_COMMA) ||
2114 isType (token, TOKEN_CLOSE_PAREN)
2115 ))
2116 {
2117 readToken (token);
2118 }
2119
2120 if (isType (token, TOKEN_COMMA))
2121 {
2122 readToken (event);
2123
2124 if (isType (version, TOKEN_STRING) &&
2125 isType (table, TOKEN_STRING) &&
2126 isType (event, TOKEN_STRING) )
2127 {
2128 addToScope(version, table->string);
2129 addToScope(version, event->string);
2130 makeSqlTag (version, SQLTAG_MLTABLE);
2131 }
2132 }
2133 if( !isType (token, TOKEN_CLOSE_PAREN) )
2134 findToken (token, TOKEN_CLOSE_PAREN);
2135 }
2136 }
2137
2138 findCmdTerm (token, TRUE);
2139
2140 deleteToken (version);
2141 deleteToken (table);
2142 deleteToken (event);
2143}
2144
2145static void parseMLConn (tokenInfo *const token)
2146{
2147 tokenInfo *const version = newToken ();
2148 tokenInfo *const event = newToken ();
2149
2150 /*
2151 * This deals with these formats
2152 * call ml_add_connection_script( 'version', 'event',
2153 * 'some SQL statement'
2154 * );
2155 */
2156
2157 readToken (token);
2158 if ( isType (token, TOKEN_OPEN_PAREN) )
2159 {
2160 readToken (version);
2161 readToken (token);
2162 while (!(isType (token, TOKEN_COMMA) ||
2163 isType (token, TOKEN_CLOSE_PAREN)
2164 ))
2165 {
2166 readToken (token);
2167 }
2168
2169 if (isType (token, TOKEN_COMMA))
2170 {
2171 readToken (event);
2172
2173 if (isType (version, TOKEN_STRING) &&
2174 isType (event, TOKEN_STRING) )
2175 {
2176 addToScope(version, event->string);
2177 makeSqlTag (version, SQLTAG_MLCONN);
2178 }
2179 }
2180 if( !isType (token, TOKEN_CLOSE_PAREN) )
2181 findToken (token, TOKEN_CLOSE_PAREN);
2182
2183 }
2184
2185 findCmdTerm (token, TRUE);
2186
2187 deleteToken (version);
2188 deleteToken (event);
2189}
2190
2191static void parseMLProp (tokenInfo *const token)
2192{
2193 tokenInfo *const component = newToken ();
2194 tokenInfo *const prop_set_name = newToken ();
2195 tokenInfo *const prop_name = newToken ();
2196
2197 /*
2198 * This deals with these formats
2199 * ml_add_property (
2200 * 'comp_name',
2201 * 'prop_set_name',
2202 * 'prop_name',
2203 * 'prop_value'
2204 * )
2205 */
2206
2207 readToken (token);
2208 if ( isType (token, TOKEN_OPEN_PAREN) )
2209 {
2210 readToken (component);
2211 readToken (token);
2212 while (!(isType (token, TOKEN_COMMA) ||
2213 isType (token, TOKEN_CLOSE_PAREN)
2214 ))
2215 {
2216 readToken (token);
2217 }
2218
2219 if (isType (token, TOKEN_COMMA))
2220 {
2221 readToken (prop_set_name);
2222 readToken (token);
2223 while (!(isType (token, TOKEN_COMMA) ||
2224 isType (token, TOKEN_CLOSE_PAREN)
2225 ))
2226 {
2227 readToken (token);
2228 }
2229
2230 if (isType (token, TOKEN_COMMA))
2231 {
2232 readToken (prop_name);
2233
2234 if (isType (component, TOKEN_STRING) &&
2235 isType (prop_set_name, TOKEN_STRING) &&
2236 isType (prop_name, TOKEN_STRING) )
2237 {
2238 addToScope(component, prop_set_name->string);
2239 addToScope(component, prop_name->string);
2240 makeSqlTag (component, SQLTAG_MLPROP);
2241 }
2242 }
2243 if( !isType (token, TOKEN_CLOSE_PAREN) )
2244 findToken (token, TOKEN_CLOSE_PAREN);
2245 }
2246 }
2247
2248 findCmdTerm (token, TRUE);
2249
2250 deleteToken (component);
2251 deleteToken (prop_set_name);
2252 deleteToken (prop_name);
2253}
2254
2255static void parseComment (tokenInfo *const token)
2256{
2257 /*
2258 * This deals with this statement:
2259 * COMMENT TO PRESERVE FORMAT ON PROCEDURE "DBA"."test" IS
2260 * {create PROCEDURE DBA."test"()
2261 * BEGIN
2262 * signal dave;
2263 * END
2264 * }
2265 * ;
2266 * The comment can contain anything between the CURLY
2267 * braces
2268 * COMMENT ON USER "admin" IS
2269 * 'Administration Group'
2270 * ;
2271 * Or it could be a simple string with no curly braces
2272 */
2273 while (! isKeyword (token, KEYWORD_is))
2274 {
2275 readToken (token);
2276 }
2277 readToken (token);
2278 if ( isType(token, TOKEN_OPEN_CURLY) )
2279 {
2280 findToken (token, TOKEN_CLOSE_CURLY);
2281 }
2282
2283 findCmdTerm (token, TRUE);
2284}
2285
2286
2287static void parseKeywords (tokenInfo *const token)
2288{
2289 switch (token->keyword)
2290 {
2291 case KEYWORD_begin: parseBlock (token, FALSE); break;
2292 case KEYWORD_comment: parseComment (token); break;
2293 case KEYWORD_cursor: parseSimple (token, SQLTAG_CURSOR); break;
2294 case KEYWORD_datatype: parseDomain (token); break;
2295 case KEYWORD_declare: parseBlock (token, FALSE); break;
2296 case KEYWORD_domain: parseDomain (token); break;
2297 case KEYWORD_drop: parseDrop (token); break;
2298 case KEYWORD_event: parseEvent (token); break;
2299 case KEYWORD_function: parseSubProgram (token); break;
2300 case KEYWORD_if: parseStatements (token, FALSE); break;
2301 case KEYWORD_index: parseIndex (token); break;
2302 case KEYWORD_ml_table: parseMLTable (token); break;
2303 case KEYWORD_ml_table_lang: parseMLTable (token); break;
2304 case KEYWORD_ml_table_dnet: parseMLTable (token); break;
2305 case KEYWORD_ml_table_java: parseMLTable (token); break;
2306 case KEYWORD_ml_table_chk: parseMLTable (token); break;
2307 case KEYWORD_ml_conn: parseMLConn (token); break;
2308 case KEYWORD_ml_conn_lang: parseMLConn (token); break;
2309 case KEYWORD_ml_conn_dnet: parseMLConn (token); break;
2310 case KEYWORD_ml_conn_java: parseMLConn (token); break;
2311 case KEYWORD_ml_conn_chk: parseMLConn (token); break;
2312 case KEYWORD_ml_prop: parseMLProp (token); break;
2313 case KEYWORD_package: parsePackage (token); break;
2314 case KEYWORD_procedure: parseSubProgram (token); break;
2315 case KEYWORD_publication: parsePublication (token); break;
2316 case KEYWORD_service: parseService (token); break;
2317 case KEYWORD_subtype: parseSimple (token, SQLTAG_SUBTYPE); break;
2318 case KEYWORD_synonym: parseSynonym (token); break;
2319 case KEYWORD_table: parseTable (token); break;
2320 case KEYWORD_trigger: parseTrigger (token); break;
2321 case KEYWORD_type: parseType (token); break;
2322 case KEYWORD_variable: parseVariable (token); break;
2323 case KEYWORD_view: parseView (token); break;
2324 default: break;
2325 }
2326}
2327
2328static void parseSqlFile (tokenInfo *const token)
2329{
2330 do
2331 {
2332 readToken (token);
2333
2334 if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
2335 parseLabel (token);
2336 else
2337 parseKeywords (token);
2338 } while (! isKeyword (token, KEYWORD_end));
2339}
2340
2341static void initialize (const langType language)
2342{
2343 Assert (sizeof (SqlKinds) / sizeof (SqlKinds [0]) == SQLTAG_COUNT);
2344 Lang_sql = language;
2345 buildSqlKeywordHash ();
2346}
2347
2348static void findSqlTags (void)
2349{
2350 tokenInfo *const token = newToken ();
2351 exception_t exception = (exception_t) (setjmp (Exception));
2352
2353 while (exception == ExceptionNone)
2354 parseSqlFile (token);
2355
2356 deleteToken (token);
2357}
2358
2359extern parserDefinition* SqlParser (void)
2360{
2361 static const char *const extensions [] = { "sql", NULL };
2362 parserDefinition* def = parserNew ("SQL");
2363 def->kinds = SqlKinds;
2364 def->kindCount = KIND_COUNT (SqlKinds);
2365 def->extensions = extensions;
2366 def->parser = findSqlTags;
2367 def->initialize = initialize;
2368 return def;
2369}
2370
2371/* vi:set tabstop=4 shiftwidth=4 noexpandtab: */