1 /*
2  *	$Id: sql.c 745 2009-10-27 02:42:55Z dfishburn $
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 
53 typedef enum eException { ExceptionNone, ExceptionEOF } exception_t;
54 
55 /*
56  * Used to specify type of keyword.
57  */
58 typedef 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  */
138 typedef struct sKeywordDesc {
139 	const char *name;
140 	keywordId id;
141 } keywordDesc;
142 
143 typedef 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 
168 typedef 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 	long filePosition;
176 } tokenInfo;
177 
178 /*
179  *	DATA DEFINITIONS
180  */
181 
182 static langType Lang_sql;
183 
184 static jmp_buf Exception;
185 
186 typedef 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 
213 static 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 
239 static 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 */
321 static void parseBlock (tokenInfo *const token, const boolean local);
322 static void parseDeclare (tokenInfo *const token, const boolean local);
323 static void parseKeywords (tokenInfo *const token);
324 static void parseSqlFile (tokenInfo *const token);
325 
326 /*
327  *	 FUNCTION DEFINITIONS
328  */
329 
isIdentChar1(const int c)330 static 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 
isIdentChar(const int c)342 static boolean isIdentChar (const int c)
343 {
344 	return (boolean)
345 		(isalpha (c) || isdigit (c) || c == '$' ||
346 		 c == '@' || c == '_' || c == '#');
347 }
348 
isCmdTerm(tokenInfo * const token)349 static 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 
isMatchedEnd(tokenInfo * const token,int nest_lvl)379 static 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 
buildSqlKeywordHash(void)408 static 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 
newToken(void)420 static 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 
deleteToken(tokenInfo * const token)435 static 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 
makeConstTag(tokenInfo * const token,const sqlKind kind)446 static 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 
makeSqlTag(tokenInfo * const token,const sqlKind kind)463 static 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 
parseString(vString * const string,const int delimiter)491 static 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 */
parseIdentifier(vString * const string,const int firstChar)516 static 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 
readToken(tokenInfo * const token)530 static 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 
538 getNextChar:
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 
addToScope(tokenInfo * const token,vString * const extra)697 static 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 
findToken(tokenInfo * const token,const tokenType type)711 static void findToken (tokenInfo *const token, const tokenType type)
712 {
713 	while (! isType (token, type))
714 	{
715 		readToken (token);
716 	}
717 }
718 
findCmdTerm(tokenInfo * const token,const boolean check_first)719 static 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 
skipToMatched(tokenInfo * const token)734 static 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 
skipArgumentList(tokenInfo * const token)786 static void skipArgumentList (tokenInfo *const token)
787 {
788 	/*
789 	 * Other databases can have arguments with fully declared
790 	 * datatypes:
791 	 *	 (	name varchar(30), text binary(10)  )
792 	 * So we must check for nested open and closing parentheses
793 	 */
794 
795 	if (isType (token, TOKEN_OPEN_PAREN))	/* arguments? */
796 	{
797 		skipToMatched (token);
798 	}
799 }
800 
parseSubProgram(tokenInfo * const token)801 static void parseSubProgram (tokenInfo *const token)
802 {
803 	tokenInfo *const name  = newToken ();
804 
805 	/*
806 	 * This must handle both prototypes and the body of
807 	 * the procedures.
808 	 *
809 	 * Prototype:
810 	 *	   FUNCTION func_name RETURN integer;
811 	 *	   PROCEDURE proc_name( parameters );
812 	 * Procedure
813 	 *	   FUNCTION GET_ML_USERNAME RETURN VARCHAR2
814 	 *	   IS
815 	 *	   BEGIN
816 	 *		   RETURN v_sync_user_id;
817 	 *	   END GET_ML_USERNAME;
818 	 *
819 	 *	   PROCEDURE proc_name( parameters )
820 	 *		   IS
821 	 *		   BEGIN
822 	 *		   END;
823 	 *	   CREATE PROCEDURE proc_name( parameters )
824 	 *		   EXTERNAL NAME ... ;
825 	 *	   CREATE PROCEDURE proc_name( parameters )
826 	 *		   BEGIN
827 	 *		   END;
828 	 *
829 	 *	   CREATE FUNCTION f_GetClassName(
830 	 *		   IN @object VARCHAR(128)
831 	 *		  ,IN @code   VARCHAR(128)
832 	 *	   )
833 	 *	   RETURNS VARCHAR(200)
834 	 *	   DETERMINISTIC
835 	 *	   BEGIN
836 	 *
837 	 *		   IF( @object = 'user_state' ) THEN
838 	 *			   SET something = something;
839 	 *		   END IF;
840 	 *
841 	 *		   RETURN @name;
842 	 *	   END;
843 	 */
844 	const sqlKind kind = isKeyword (token, KEYWORD_function) ?
845 		SQLTAG_FUNCTION : SQLTAG_PROCEDURE;
846 	Assert (isKeyword (token, KEYWORD_function) ||
847 			isKeyword (token, KEYWORD_procedure));
848 	readToken (name);
849 	readToken (token);
850 	if (isType (token, TOKEN_PERIOD))
851 	{
852 		readToken (name);
853 		readToken (token);
854 	}
855 	if (isType (token, TOKEN_OPEN_PAREN))
856 	{
857 		/* Reads to the next token after the TOKEN_CLOSE_PAREN */
858 		skipArgumentList(token);
859 	}
860 
861 	if (kind == SQLTAG_FUNCTION)
862 	{
863 		if (isKeyword (token, KEYWORD_return) || isKeyword (token, KEYWORD_returns))
864 		{
865 			/* Read datatype */
866 			readToken (token);
867 			/*
868 			 * Read token after which could be the
869 			 * command terminator if a prototype
870 			 * or an open parenthesis
871 			 */
872 			readToken (token);
873 			if (isType (token, TOKEN_OPEN_PAREN))
874 			{
875 				/* Reads to the next token after the TOKEN_CLOSE_PAREN */
876 				skipArgumentList(token);
877 			}
878 		}
879 	}
880 	if( isCmdTerm (token) )
881 	{
882 		makeSqlTag (name, SQLTAG_PROTOTYPE);
883 	}
884 	else
885 	{
886 		while (!(isKeyword (token, KEYWORD_is) ||
887 					isKeyword (token, KEYWORD_begin) ||
888 					isKeyword (token, KEYWORD_at) ||
889 					isKeyword (token, KEYWORD_internal) ||
890 					isKeyword (token, KEYWORD_external) ||
891 					isKeyword (token, KEYWORD_url) ||
892 					isType (token, TOKEN_EQUAL) ||
893 					isCmdTerm (token)
894 				)
895 			  )
896 		{
897 			if ( isKeyword (token, KEYWORD_result) )
898 			{
899 				readToken (token);
900 				if (isType (token, TOKEN_OPEN_PAREN))
901 				{
902 					/* Reads to the next token after the TOKEN_CLOSE_PAREN */
903 					skipArgumentList(token);
904 				}
905 			} else {
906 				readToken (token);
907 			}
908 		}
909 		if (isKeyword (token, KEYWORD_at) ||
910 				isKeyword (token, KEYWORD_url) ||
911 				isKeyword (token, KEYWORD_internal) ||
912 				isKeyword (token, KEYWORD_external) )
913 		{
914 			addToScope(token, name->string);
915 			if (isType (name, TOKEN_IDENTIFIER) ||
916 					isType (name, TOKEN_STRING) ||
917 					!isKeyword (token, KEYWORD_NONE)
918 			   )
919 				makeSqlTag (name, kind);
920 
921 			vStringClear (token->scope);
922 		}
923 		if ( isType (token, TOKEN_EQUAL) )
924 			readToken (token);
925 
926 		if ( isKeyword (token, KEYWORD_declare) )
927 			parseDeclare (token, FALSE);
928 
929 		if (isKeyword (token, KEYWORD_is) ||
930 				isKeyword (token, KEYWORD_begin) )
931 		{
932 			addToScope(token, name->string);
933 			if (isType (name, TOKEN_IDENTIFIER) ||
934 					isType (name, TOKEN_STRING) ||
935 					!isKeyword (token, KEYWORD_NONE)
936 			   )
937 				makeSqlTag (name, kind);
938 
939 			parseBlock (token, TRUE);
940 			vStringClear (token->scope);
941 		}
942 	}
943 	deleteToken (name);
944 }
945 
parseRecord(tokenInfo * const token)946 static void parseRecord (tokenInfo *const token)
947 {
948 	/*
949 	 * Make it a bit forgiving, this is called from
950 	 * multiple functions, parseTable, parseType
951 	 */
952 	if (!isType (token, TOKEN_OPEN_PAREN))
953 		readToken (token);
954 
955 	Assert (isType (token, TOKEN_OPEN_PAREN));
956 	do
957 	{
958 		if ( isType (token, TOKEN_COMMA) || isType (token, TOKEN_OPEN_PAREN) )
959 			readToken (token);
960 
961 		/*
962 		 * Create table statements can end with various constraints
963 		 * which must be excluded from the SQLTAG_FIELD.
964 		 *	  create table t1 (
965 		 *		  c1 integer,
966 		 *		  c2 char(30),
967 		 *		  c3 numeric(10,5),
968 		 *		  c4 integer,
969 		 *		  constraint whatever,
970 		 *		  primary key(c1),
971 		 *		  foreign key (),
972 		 *		  check ()
973 		 *	  )
974 		 */
975 		if (! (isKeyword(token, KEYWORD_primary) ||
976 					isKeyword(token, KEYWORD_references) ||
977 					isKeyword(token, KEYWORD_unique) ||
978 					isKeyword(token, KEYWORD_check) ||
979 					isKeyword(token, KEYWORD_constraint) ||
980 					isKeyword(token, KEYWORD_foreign) ) )
981 		{
982 			if (isType (token, TOKEN_IDENTIFIER) ||
983 					isType (token, TOKEN_STRING))
984 				makeSqlTag (token, SQLTAG_FIELD);
985 		}
986 
987 		while (!(isType (token, TOKEN_COMMA) ||
988 					isType (token, TOKEN_CLOSE_PAREN) ||
989 					isType (token, TOKEN_OPEN_PAREN)
990 				))
991 		{
992 			readToken (token);
993 			/*
994 			 * A table structure can look like this:
995 			 *	  create table t1 (
996 			 *		  c1 integer,
997 			 *		  c2 char(30),
998 			 *		  c3 numeric(10,5),
999 			 *		  c4 integer
1000 			 *	  )
1001 			 * We can't just look for a COMMA or CLOSE_PAREN
1002 			 * since that will not deal with the numeric(10,5)
1003 			 * case.  So we need to skip the argument list
1004 			 * when we find an open paren.
1005 			 */
1006 			if (isType (token, TOKEN_OPEN_PAREN))
1007 			{
1008 				/* Reads to the next token after the TOKEN_CLOSE_PAREN */
1009 				skipArgumentList(token);
1010 			}
1011 		}
1012 	} while (! isType (token, TOKEN_CLOSE_PAREN));
1013 }
1014 
parseType(tokenInfo * const token)1015 static void parseType (tokenInfo *const token)
1016 {
1017 	tokenInfo *const name = newToken ();
1018 	vString * saveScope = vStringNew ();
1019 
1020 	vStringCopy(saveScope, token->scope);
1021 	/* If a scope has been set, add it to the name */
1022 	addToScope (name, token->scope);
1023 	readToken (name);
1024 	if (isType (name, TOKEN_IDENTIFIER))
1025 	{
1026 		readToken (token);
1027 		if (isKeyword (token, KEYWORD_is))
1028 		{
1029 			readToken (token);
1030 			addToScope (token, name->string);
1031 			switch (token->keyword)
1032 			{
1033 				case KEYWORD_record:
1034 				case KEYWORD_object:
1035 					makeSqlTag (name, SQLTAG_RECORD);
1036 					parseRecord (token);
1037 					break;
1038 
1039 				case KEYWORD_table:
1040 					makeSqlTag (name, SQLTAG_TABLE);
1041 					break;
1042 
1043 				case KEYWORD_ref:
1044 					readToken (token);
1045 					if (isKeyword (token, KEYWORD_cursor))
1046 						makeSqlTag (name, SQLTAG_CURSOR);
1047 					break;
1048 
1049 				default: break;
1050 			}
1051 			vStringClear (token->scope);
1052 		}
1053 	}
1054 	vStringCopy(token->scope, saveScope);
1055 	deleteToken (name);
1056 	vStringDelete(saveScope);
1057 }
1058 
parseSimple(tokenInfo * const token,const sqlKind kind)1059 static void parseSimple (tokenInfo *const token, const sqlKind kind)
1060 {
1061 	/* This will simply make the tagname from the first word found */
1062 	readToken (token);
1063 	if (isType (token, TOKEN_IDENTIFIER) ||
1064 			isType (token, TOKEN_STRING))
1065 		makeSqlTag (token, kind);
1066 }
1067 
parseDeclare(tokenInfo * const token,const boolean local)1068 static void parseDeclare (tokenInfo *const token, const boolean local)
1069 {
1070 	/*
1071 	 * PL/SQL declares are of this format:
1072 	 *	  IS|AS
1073 	 *	  [declare]
1074 	 *		 CURSOR curname ...
1075 	 *		 varname1 datatype;
1076 	 *		 varname2 datatype;
1077 	 *		 varname3 datatype;
1078 	 *	  begin
1079 	 */
1080 
1081 	if (isKeyword (token, KEYWORD_declare))
1082 		readToken (token);
1083 	while (! isKeyword (token, KEYWORD_begin) && ! isKeyword (token, KEYWORD_end))
1084 	{
1085 		switch (token->keyword)
1086 		{
1087 			case KEYWORD_cursor:	parseSimple (token, SQLTAG_CURSOR); break;
1088 			case KEYWORD_function:	parseSubProgram (token); break;
1089 			case KEYWORD_procedure: parseSubProgram (token); break;
1090 			case KEYWORD_subtype:	parseSimple (token, SQLTAG_SUBTYPE); break;
1091 			case KEYWORD_trigger:	parseSimple (token, SQLTAG_TRIGGER); break;
1092 			case KEYWORD_type:		parseType (token); break;
1093 
1094 			default:
1095 				if (isType (token, TOKEN_IDENTIFIER))
1096 				{
1097 					if (local)
1098 					{
1099 						makeSqlTag (token, SQLTAG_LOCAL_VARIABLE);
1100 					}
1101 					else
1102 					{
1103 						makeSqlTag (token, SQLTAG_VARIABLE);
1104 					}
1105 				}
1106 				break;
1107 		}
1108 		findToken (token, TOKEN_SEMICOLON);
1109 		readToken (token);
1110 	}
1111 }
1112 
parseDeclareANSI(tokenInfo * const token,const boolean local)1113 static void parseDeclareANSI (tokenInfo *const token, const boolean local)
1114 {
1115 	tokenInfo *const type = newToken ();
1116 	/*
1117 	 * ANSI declares are of this format:
1118 	 *	 BEGIN
1119 	 *		 DECLARE varname1 datatype;
1120 	 *		 DECLARE varname2 datatype;
1121 	 *		 ...
1122 	 *
1123 	 * This differ from PL/SQL where DECLARE preceeds the BEGIN block
1124 	 * and the DECLARE keyword is not repeated.
1125 	 */
1126 	while (isKeyword (token, KEYWORD_declare))
1127 	{
1128 		readToken (token);
1129 		readToken (type);
1130 
1131 		if (isKeyword (type, KEYWORD_cursor))
1132 			makeSqlTag (token, SQLTAG_CURSOR);
1133 		else if (isKeyword (token, KEYWORD_local) &&
1134 				isKeyword (type, KEYWORD_temporary))
1135 		{
1136 			/*
1137 			 * DECLARE LOCAL TEMPORARY TABLE table_name (
1138 			 *	  c1 int,
1139 			 *	  c2 int
1140 			 * );
1141 			 */
1142 			readToken (token);
1143 			if (isKeyword (token, KEYWORD_table))
1144 			{
1145 				readToken (token);
1146 				if (isType(token, TOKEN_IDENTIFIER) ||
1147 						isType(token, TOKEN_STRING) )
1148 				{
1149 					makeSqlTag (token, SQLTAG_TABLE);
1150 				}
1151 			}
1152 		}
1153 		else if (isType (token, TOKEN_IDENTIFIER) ||
1154 				isType (token, TOKEN_STRING))
1155 		{
1156 			if (local)
1157 				makeSqlTag (token, SQLTAG_LOCAL_VARIABLE);
1158 			else
1159 				makeSqlTag (token, SQLTAG_VARIABLE);
1160 		}
1161 		findToken (token, TOKEN_SEMICOLON);
1162 		readToken (token);
1163 	}
1164 	deleteToken (type);
1165 }
1166 
parseLabel(tokenInfo * const token)1167 static void parseLabel (tokenInfo *const token)
1168 {
1169 	/*
1170 	 * A label has this format:
1171 	 *	   <<tobacco_dependency>>
1172 	 *	   DECLARE
1173 	 *		  v_senator VARCHAR2(100) := 'THURMOND, JESSE';
1174 	 *	   BEGIN
1175 	 *		  IF total_contributions (v_senator, 'TOBACCO') > 25000
1176 	 *		  THEN
1177 	 *			 <<alochol_dependency>>
1178 	 *			 DECLARE
1179 	 *				v_senator VARCHAR2(100) := 'WHATEVERIT, TAKES';
1180 	 *			 BEGIN
1181 	 *				...
1182 	 */
1183 
1184 	Assert (isType (token, TOKEN_BLOCK_LABEL_BEGIN));
1185 	readToken (token);
1186 	if (isType (token, TOKEN_IDENTIFIER))
1187 	{
1188 		makeSqlTag (token, SQLTAG_BLOCK_LABEL);
1189 		readToken (token);		  /* read end of label */
1190 	}
1191 }
1192 
parseStatements(tokenInfo * const token,const boolean exit_on_endif)1193 static void parseStatements (tokenInfo *const token, const boolean exit_on_endif )
1194 {
1195 	boolean isAnsi   = TRUE;
1196 	boolean stmtTerm = FALSE;
1197 	do
1198 	{
1199 
1200 		if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
1201 			parseLabel (token);
1202 		else
1203 		{
1204 			switch (token->keyword)
1205 			{
1206 				case KEYWORD_exception:
1207 					/*
1208 					 * EXCEPTION
1209 					 *	 <exception handler>;
1210 					 *
1211 					 * Where an exception handler could be:
1212 					 *	 BEGIN
1213 					 *		WHEN OTHERS THEN
1214 					 *			x := x + 3;
1215 					 *	 END;
1216 					 * In this case we need to skip this keyword and
1217 					 * move on to the next token without reading until
1218 					 * TOKEN_SEMICOLON;
1219 					 */
1220 					readToken (token);
1221 					continue;
1222 
1223 				case KEYWORD_when:
1224 					/*
1225 					 * WHEN statements can be used in exception clauses
1226 					 * and CASE statements.  The CASE statement should skip
1227 					 * these given below we skip over to an END statement.
1228 					 * But for an exception clause, we can have:
1229 					 *	   EXCEPTION
1230 					 *		   WHEN OTHERS THEN
1231 					 *		   BEGIN
1232 					 *				  x := x + 3;
1233 					 *		   END;
1234 					 * If we skip to the TOKEN_SEMICOLON, we miss the begin
1235 					 * of a nested BEGIN END block.  So read the next token
1236 					 * after the THEN and restart the LOOP.
1237 					 */
1238 					while (! isKeyword (token, KEYWORD_then))
1239 						readToken (token);
1240 
1241 					readToken (token);
1242 					continue;
1243 
1244 				case KEYWORD_if:
1245 					/*
1246 					 * We do not want to look for a ; since for an empty
1247 					 * IF block, it would skip over the END.
1248 					 *	IF...THEN
1249 					 *	END IF;
1250 					 *
1251 					 *	IF...THEN
1252 					 *	ELSE
1253 					 *	END IF;
1254 					 *
1255 					 *	IF...THEN
1256 					 *	ELSEIF...THEN
1257 					 *	ELSE
1258 					 *	END IF;
1259 					 *
1260 					 *	or non-ANSI
1261 					 *	IF ...
1262 					 *	BEGIN
1263 					 *	END
1264 					 */
1265 					while ( ! isKeyword (token, KEYWORD_then)  &&
1266 							! isKeyword (token, KEYWORD_begin) )
1267 					{
1268 						readToken (token);
1269 					}
1270 
1271 					if( isKeyword (token, KEYWORD_begin ) )
1272 					{
1273 						isAnsi = FALSE;
1274 						parseBlock(token, FALSE);
1275 
1276 						/*
1277 						 * Handle the non-Ansi IF blocks.
1278 						 * parseBlock consumes the END, so if the next
1279 						 * token in a command terminator (like GO)
1280 						 * we know we are done with this statement.
1281 						 */
1282 						if ( isCmdTerm (token) )
1283 							stmtTerm = TRUE;
1284 					}
1285 					else
1286 					{
1287 						readToken (token);
1288 
1289 						while( ! (isKeyword (token, KEYWORD_end ) ||
1290 						          isKeyword (token, KEYWORD_endif ) )
1291 								)
1292 						{
1293 							if ( isKeyword (token, KEYWORD_else) ||
1294 									isKeyword (token, KEYWORD_elseif)    )
1295 								readToken (token);
1296 
1297 							parseStatements (token, TRUE);
1298 
1299 							if ( isCmdTerm(token) )
1300 								readToken (token);
1301 
1302 						}
1303 
1304 						/*
1305 						 * parseStatements returns when it finds an END, an IF
1306 						 * should follow the END for ANSI anyway.
1307 						 *	IF...THEN
1308 						 *	END IF;
1309 						 */
1310 						if( isKeyword (token, KEYWORD_end ) )
1311 							readToken (token);
1312 
1313 						if( isKeyword (token, KEYWORD_if ) || isKeyword (token, KEYWORD_endif ) )
1314 						{
1315 							readToken (token);
1316 							if ( isCmdTerm(token) )
1317 								stmtTerm = TRUE;
1318 						}
1319 						else
1320 						{
1321 							/*
1322 							 * Well we need to do something here.
1323 							 * There are lots of different END statements
1324 							 * END;
1325 							 * END CASE;
1326 							 * ENDIF;
1327 							 * ENDCASE;
1328 							 */
1329 						}
1330 					}
1331 					break;
1332 
1333 				case KEYWORD_loop:
1334 				case KEYWORD_case:
1335 				case KEYWORD_for:
1336 					/*
1337 					 *	LOOP...
1338 					 *	END LOOP;
1339 					 *
1340 					 *	CASE
1341 					 *	WHEN '1' THEN
1342 					 *	END CASE;
1343 					 *
1344 					 *	FOR loop_name AS cursor_name CURSOR FOR ...
1345 					 *	DO
1346 					 *	END FOR;
1347 					 */
1348 					if( isKeyword (token, KEYWORD_for ) )
1349 					{
1350 						/* loop name */
1351 						readToken (token);
1352 						/* AS */
1353 						readToken (token);
1354 
1355 						while ( ! isKeyword (token, KEYWORD_is) )
1356 						{
1357 							/*
1358 							 * If this is not an AS keyword this is
1359 							 * not a proper FOR statement and should
1360 							 * simply be ignored
1361 							 */
1362 							return;
1363 						}
1364 
1365 						while ( ! isKeyword (token, KEYWORD_do) )
1366 							readToken (token);
1367 					}
1368 
1369 
1370 					readToken (token);
1371 					while( ! isKeyword (token, KEYWORD_end ) )
1372 					{
1373 						/*
1374 						if ( isKeyword (token, KEYWORD_else) ||
1375 								isKeyword (token, KEYWORD_elseif)    )
1376 							readToken (token);
1377 							*/
1378 
1379 						parseStatements (token, FALSE);
1380 
1381 						if ( isCmdTerm(token) )
1382 							readToken (token);
1383 					}
1384 
1385 
1386 					if( isKeyword (token, KEYWORD_end ) )
1387 						readToken (token);
1388 
1389 					/*
1390 					 * Typically ended with
1391 					 *    END LOOP [loop name];
1392 					 *    END CASE
1393 					 *    END FOR [loop name];
1394 					 */
1395 					if ( isKeyword (token, KEYWORD_loop) ||
1396 							isKeyword (token, KEYWORD_case) ||
1397 							isKeyword (token, KEYWORD_for)    )
1398 						readToken (token);
1399 
1400 					if ( isCmdTerm(token) )
1401 						stmtTerm = TRUE;
1402 
1403 					break;
1404 
1405 				case KEYWORD_create:
1406 					readToken (token);
1407 					parseKeywords(token);
1408 					break;
1409 
1410 				case KEYWORD_declare:
1411 				case KEYWORD_begin:
1412 					parseBlock (token, TRUE);
1413 					break;
1414 
1415 				case KEYWORD_end:
1416 					break;
1417 
1418 				default:
1419 					readToken (token);
1420 					break;
1421 			}
1422 			/*
1423 			 * Not all statements must end in a semi-colon
1424 			 *	   begin
1425 			 *		   if current publisher <> 'publish' then
1426 			 *			 signal UE_FailStatement
1427 			 *		   end if
1428 			 *	   end;
1429 			 * The last statement prior to an end ("signal" above) does
1430 			 * not need a semi-colon, nor does the end if, since it is
1431 			 * also the last statement prior to the end of the block.
1432 			 *
1433 			 * So we must read to the first semi-colon or an END block
1434 			 */
1435 			while ( ! stmtTerm                               &&
1436 					! (   isKeyword (token, KEYWORD_end)     ||
1437 						 (isCmdTerm(token))              )
1438 					)
1439 			{
1440 				if (   isKeyword (token, KEYWORD_endif)   &&
1441 						 exit_on_endif                   )
1442 					return;
1443 
1444 				if (isType (token, TOKEN_COLON) )
1445 				{
1446 					/*
1447 					 * A : can signal a loop name
1448 					 *    myloop:
1449 					 *    LOOP
1450 					 *        LEAVE myloop;
1451 					 *    END LOOP;
1452 					 * Unfortunately, labels do not have a
1453 					 * cmd terminator, therefore we have to check
1454 					 * if the next token is a keyword and process
1455 					 * it accordingly.
1456 					 */
1457 					readToken (token);
1458 					if ( isKeyword (token, KEYWORD_loop) ||
1459 							isKeyword (token, KEYWORD_while) ||
1460 							isKeyword (token, KEYWORD_for) )
1461 						/* parseStatements (token); */
1462 						return;
1463 				}
1464 
1465 				readToken (token);
1466 
1467 				if (isType (token, TOKEN_OPEN_PAREN)  ||
1468 				    isType (token, TOKEN_OPEN_CURLY)  ||
1469 				    isType (token, TOKEN_OPEN_SQUARE)  )
1470 						skipToMatched (token);
1471 
1472 				/*
1473 				 * Since we know how to parse various statements
1474 				 * if we detect them, parse them to completion
1475 				 */
1476 				if (isType (token, TOKEN_BLOCK_LABEL_BEGIN) ||
1477 						isKeyword (token, KEYWORD_exception) ||
1478 						isKeyword (token, KEYWORD_loop) ||
1479 						isKeyword (token, KEYWORD_case) ||
1480 						isKeyword (token, KEYWORD_for) ||
1481 						isKeyword (token, KEYWORD_begin)    )
1482 					parseStatements (token, FALSE);
1483 				else if (isKeyword (token, KEYWORD_if))
1484 					parseStatements (token, TRUE);
1485 
1486 			}
1487 		}
1488 		/*
1489 		 * We assumed earlier all statements ended with a command terminator.
1490 		 * See comment above, now, only read if the current token
1491 		 * is not a command terminator.
1492 		 */
1493 		if ( isCmdTerm(token) && ! stmtTerm )
1494 			stmtTerm = TRUE;
1495 
1496 	} while (! isKeyword (token, KEYWORD_end) &&
1497 			 ! (exit_on_endif && isKeyword (token, KEYWORD_endif) ) &&
1498 			 ! stmtTerm );
1499 }
1500 
parseBlock(tokenInfo * const token,const boolean local)1501 static void parseBlock (tokenInfo *const token, const boolean local)
1502 {
1503 	if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
1504 	{
1505 		parseLabel (token);
1506 		readToken (token);
1507 	}
1508 	if (! isKeyword (token, KEYWORD_begin))
1509 	{
1510 		readToken (token);
1511 		/*
1512 		 * These are Oracle style declares which generally come
1513 		 * between an IS/AS and BEGIN block.
1514 		 */
1515 		parseDeclare (token, local);
1516 	}
1517 	if (isKeyword (token, KEYWORD_begin))
1518 	{
1519 		readToken (token);
1520 		/*
1521 		 * Check for ANSI declarations which always follow
1522 		 * a BEGIN statement.  This routine will not advance
1523 		 * the token if none are found.
1524 		 */
1525 		parseDeclareANSI (token, local);
1526 		token->begin_end_nest_lvl++;
1527 		while (! isKeyword (token, KEYWORD_end))
1528 		{
1529 			parseStatements (token, FALSE);
1530 
1531 			if ( isCmdTerm(token) )
1532 				readToken (token);
1533 		}
1534 		token->begin_end_nest_lvl--;
1535 
1536 		/*
1537 		 * Read the next token (we will assume
1538 		 * it is the command delimiter)
1539 		 */
1540 		readToken (token);
1541 
1542 		/*
1543 		 * Check if the END block is terminated
1544 		 */
1545 		if ( !isCmdTerm (token)	)
1546 		{
1547 			/*
1548 			 * Not sure what to do here at the moment.
1549 			 * I think the routine that calls parseBlock
1550 			 * must expect the next token has already
1551 			 * been read since it is possible this
1552 			 * token is not a command delimiter.
1553 			 */
1554 			/* findCmdTerm (token, FALSE); */
1555 		}
1556 	}
1557 }
1558 
parsePackage(tokenInfo * const token)1559 static void parsePackage (tokenInfo *const token)
1560 {
1561 	/*
1562 	 * Packages can be specified in a number of ways:
1563 	 *		CREATE OR REPLACE PACKAGE pkg_name AS
1564 	 * or
1565 	 *		CREATE OR REPLACE PACKAGE owner.pkg_name AS
1566 	 * or by specifying a package body
1567 	 *	   CREATE OR REPLACE PACKAGE BODY pkg_name AS
1568 	 *	   CREATE OR REPLACE PACKAGE BODY owner.pkg_name AS
1569 	 */
1570 	tokenInfo *const name = newToken ();
1571 	readToken (name);
1572 	if (isKeyword (name, KEYWORD_body))
1573 	{
1574 		/*
1575 		 * Ignore the BODY tag since we will process
1576 		 * the body or prototypes in the same manner
1577 		 */
1578 		readToken (name);
1579 	}
1580 	/* Check for owner.pkg_name */
1581 	while (! isKeyword (token, KEYWORD_is))
1582 	{
1583 		readToken (token);
1584 		if ( isType(token, TOKEN_PERIOD) )
1585 		{
1586 			readToken (name);
1587 		}
1588 	}
1589 	if (isKeyword (token, KEYWORD_is))
1590 	{
1591 		if (isType (name, TOKEN_IDENTIFIER) ||
1592 				isType (name, TOKEN_STRING))
1593 			makeSqlTag (name, SQLTAG_PACKAGE);
1594 		parseBlock (token, FALSE);
1595 	}
1596 	findCmdTerm (token, FALSE);
1597 	deleteToken (name);
1598 }
1599 
parseTable(tokenInfo * const token)1600 static void parseTable (tokenInfo *const token)
1601 {
1602 	tokenInfo *const name = newToken ();
1603 
1604 	/*
1605 	 * This deals with these formats:
1606 	 *	   create table t1 (c1 int);
1607 	 *	   create global tempoary table t2 (c1 int);
1608 	 *	   create table "t3" (c1 int);
1609 	 *	   create table bob.t4 (c1 int);
1610 	 *	   create table bob."t5" (c1 int);
1611 	 *	   create table "bob"."t6" (c1 int);
1612 	 *	   create table bob."t7" (c1 int);
1613 	 * Proxy tables use this format:
1614 	 *	   create existing table bob."t7" AT '...';
1615 	 * SQL Server and Sybase formats
1616      *     create table OnlyTable (
1617      *     create table dbo.HasOwner (
1618      *     create table [dbo].[HasOwnerSquare] (
1619      *     create table master.dbo.HasDb (
1620      *     create table master..HasDbNoOwner (
1621      *     create table [master].dbo.[HasDbAndOwnerSquare] (
1622      *     create table [master]..[HasDbNoOwnerSquare] (
1623 	 */
1624 
1625 	/* This could be a database, owner or table name */
1626 	readToken (name);
1627 	if (isType (name, TOKEN_OPEN_SQUARE))
1628 	{
1629 		readToken (name);
1630 		/* Read close square */
1631 		readToken (token);
1632 	}
1633 	readToken (token);
1634 	if (isType (token, TOKEN_PERIOD))
1635 	{
1636 		/*
1637 		 * This could be a owner or table name.
1638 		 * But this is also a special case since the table can be
1639 		 * referenced with a blank owner:
1640 		 *     dbname..tablename
1641 		 */
1642 		readToken (name);
1643 		if (isType (name, TOKEN_OPEN_SQUARE))
1644 		{
1645 			readToken (name);
1646 			/* Read close square */
1647 			readToken (token);
1648 		}
1649 		/* Check if a blank name was provided */
1650 		if (isType (name, TOKEN_PERIOD))
1651 		{
1652 			readToken (name);
1653 			if (isType (name, TOKEN_OPEN_SQUARE))
1654 			{
1655 				readToken (name);
1656 				/* Read close square */
1657 				readToken (token);
1658 			}
1659 		}
1660 		readToken (token);
1661 		if (isType (token, TOKEN_PERIOD))
1662 		{
1663 			/* This can only be the table name */
1664 			readToken (name);
1665 			if (isType (name, TOKEN_OPEN_SQUARE))
1666 			{
1667 				readToken (name);
1668 				/* Read close square */
1669 				readToken (token);
1670 			}
1671 			readToken (token);
1672 		}
1673 	}
1674 	if (isType (token, TOKEN_OPEN_PAREN))
1675 	{
1676 		if (isType (name, TOKEN_IDENTIFIER) ||
1677 				isType (name, TOKEN_STRING))
1678 		{
1679 			makeSqlTag (name, SQLTAG_TABLE);
1680 			vStringCopy(token->scope, name->string);
1681 			parseRecord (token);
1682 			vStringClear (token->scope);
1683 		}
1684 	}
1685 	else if (isKeyword (token, KEYWORD_at))
1686 	{
1687 		if (isType (name, TOKEN_IDENTIFIER))
1688 		{
1689 			makeSqlTag (name, SQLTAG_TABLE);
1690 		}
1691 	}
1692 	findCmdTerm (token, FALSE);
1693 	deleteToken (name);
1694 }
1695 
parseIndex(tokenInfo * const token)1696 static void parseIndex (tokenInfo *const token)
1697 {
1698 	tokenInfo *const name  = newToken ();
1699 	tokenInfo *const owner = newToken ();
1700 
1701 	/*
1702 	 * This deals with these formats
1703 	 *	   create index i1 on t1(c1) create index "i2" on t1(c1)
1704 	 *	   create virtual unique clustered index "i3" on t1(c1)
1705 	 *	   create unique clustered index "i4" on t1(c1)
1706 	 *	   create clustered index "i5" on t1(c1)
1707 	 *	   create bitmap index "i6" on t1(c1)
1708 	 */
1709 
1710 	readToken (name);
1711 	readToken (token);
1712 	if (isType (token, TOKEN_PERIOD))
1713 	{
1714 		readToken (name);
1715 		readToken (token);
1716 	}
1717 	if ( isKeyword (token, KEYWORD_on) &&
1718 			(isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING) ) )
1719 	{
1720 		readToken (owner);
1721 		readToken (token);
1722 		if (isType (token, TOKEN_PERIOD))
1723 		{
1724 			readToken (owner);
1725 			readToken (token);
1726 		}
1727 		addToScope(name, owner->string);
1728 		makeSqlTag (name, SQLTAG_INDEX);
1729 	}
1730 	findCmdTerm (token, FALSE);
1731 	deleteToken (name);
1732 	deleteToken (owner);
1733 }
1734 
parseEvent(tokenInfo * const token)1735 static void parseEvent (tokenInfo *const token)
1736 {
1737 	tokenInfo *const name = newToken ();
1738 
1739 	/*
1740 	 * This deals with these formats
1741 	 *	   create event e1 handler begin end;
1742 	 *	   create event "e2" handler begin end;
1743 	 *	   create event dba."e3" handler begin end;
1744 	 *	   create event "dba"."e4" handler begin end;
1745 	 */
1746 
1747 	readToken (name);
1748 	readToken (token);
1749 	if (isType (token, TOKEN_PERIOD))
1750 	{
1751 		readToken (name);
1752 	}
1753 	while (! (isKeyword (token, KEYWORD_handler) ||
1754 				(isType (token, TOKEN_SEMICOLON)))	  )
1755 	{
1756 		readToken (token);
1757 	}
1758 
1759 	if ( isKeyword (token, KEYWORD_handler) ||
1760 			isType (token, TOKEN_SEMICOLON)   )
1761 	{
1762 		makeSqlTag (name, SQLTAG_EVENT);
1763 	}
1764 
1765 	if (isKeyword (token, KEYWORD_handler))
1766 	{
1767 		readToken (token);
1768 		if ( isKeyword (token, KEYWORD_begin) )
1769 		{
1770 			parseBlock (token, TRUE);
1771 		}
1772 		findCmdTerm (token, TRUE);
1773 	}
1774 	deleteToken (name);
1775 }
1776 
parseTrigger(tokenInfo * const token)1777 static void parseTrigger (tokenInfo *const token)
1778 {
1779 	tokenInfo *const name  = newToken ();
1780 	tokenInfo *const table = newToken ();
1781 
1782 	/*
1783 	 * This deals with these formats
1784 	 *	   create or replace trigger tr1 begin end;
1785 	 *	   create trigger "tr2" begin end;
1786 	 *	   drop trigger "droptr1";
1787 	 *	   create trigger "tr3" CALL sp_something();
1788 	 *	   create trigger "owner"."tr4" begin end;
1789 	 *	   create trigger "tr5" not valid;
1790 	 *	   create trigger "tr6" begin end;
1791 	 */
1792 
1793 	readToken (name);
1794 	readToken (token);
1795 	if (isType (token, TOKEN_PERIOD))
1796 	{
1797 		readToken (name);
1798 		readToken (token);
1799 	}
1800 
1801 	while ( !isKeyword (token, KEYWORD_on) &&
1802 			!isCmdTerm (token)		)
1803 	{
1804 		readToken (token);
1805 	}
1806 
1807 	/*if (! isType (token, TOKEN_SEMICOLON) ) */
1808 	if (! isCmdTerm (token) )
1809 	{
1810 		readToken (table);
1811 		readToken (token);
1812 		if (isType (token, TOKEN_PERIOD))
1813 		{
1814 			readToken (table);
1815 			readToken (token);
1816 		}
1817 
1818 		while (! (isKeyword (token, KEYWORD_begin) ||
1819 					(isKeyword (token, KEYWORD_call)) ||
1820 					(	isCmdTerm (token)))    )
1821 		{
1822 			if ( isKeyword (token, KEYWORD_declare) )
1823 			{
1824 				addToScope(token, name->string);
1825 				parseDeclare(token, TRUE);
1826 				vStringClear(token->scope);
1827 			}
1828 			else
1829 				readToken (token);
1830 		}
1831 
1832 		if ( isKeyword (token, KEYWORD_begin) ||
1833 				isKeyword (token, KEYWORD_call)   )
1834 		{
1835 			addToScope(name, table->string);
1836 			makeSqlTag (name, SQLTAG_TRIGGER);
1837 			addToScope(token, table->string);
1838 			if ( isKeyword (token, KEYWORD_begin) )
1839 			{
1840 				parseBlock (token, TRUE);
1841 			}
1842 			vStringClear(token->scope);
1843 		}
1844 	}
1845 
1846 	findCmdTerm (token, TRUE);
1847 	deleteToken (name);
1848 	deleteToken (table);
1849 }
1850 
parsePublication(tokenInfo * const token)1851 static void parsePublication (tokenInfo *const token)
1852 {
1853 	tokenInfo *const name = newToken ();
1854 
1855 	/*
1856 	 * This deals with these formats
1857 	 *	   create or replace publication pu1 ()
1858 	 *	   create publication "pu2" ()
1859 	 *	   create publication dba."pu3" ()
1860 	 *	   create publication "dba"."pu4" ()
1861 	 */
1862 
1863 	readToken (name);
1864 	readToken (token);
1865 	if (isType (token, TOKEN_PERIOD))
1866 	{
1867 		readToken (name);
1868 		readToken (token);
1869 	}
1870 	if (isType (token, TOKEN_OPEN_PAREN))
1871 	{
1872 		if (isType (name, TOKEN_IDENTIFIER) ||
1873 				isType (name, TOKEN_STRING))
1874 		{
1875 			makeSqlTag (name, SQLTAG_PUBLICATION);
1876 		}
1877 	}
1878 	findCmdTerm (token, FALSE);
1879 	deleteToken (name);
1880 }
1881 
parseService(tokenInfo * const token)1882 static void parseService (tokenInfo *const token)
1883 {
1884 	tokenInfo *const name = newToken ();
1885 
1886 	/*
1887 	 * This deals with these formats
1888 	 *	   CREATE SERVICE s1 TYPE 'HTML'
1889 	 *		   AUTHORIZATION OFF USER DBA AS
1890 	 *		   SELECT *
1891 	 *			 FROM SYS.SYSTABLE;
1892 	 *	   CREATE SERVICE "s2" TYPE 'HTML'
1893 	 *		   AUTHORIZATION OFF USER DBA AS
1894 	 *		   CALL sp_Something();
1895 	 */
1896 
1897 	readToken (name);
1898 	readToken (token);
1899 	if (isKeyword (token, KEYWORD_type))
1900 	{
1901 		if (isType (name, TOKEN_IDENTIFIER) ||
1902 				isType (name, TOKEN_STRING))
1903 		{
1904 			makeSqlTag (name, SQLTAG_SERVICE);
1905 		}
1906 	}
1907 	findCmdTerm (token, FALSE);
1908 	deleteToken (name);
1909 }
1910 
parseDomain(tokenInfo * const token)1911 static void parseDomain (tokenInfo *const token)
1912 {
1913 	tokenInfo *const name = newToken ();
1914 
1915 	/*
1916 	 * This deals with these formats
1917 	 *	   CREATE DOMAIN|DATATYPE [AS] your_name ...;
1918 	 */
1919 
1920 	readToken (name);
1921 	if (isKeyword (name, KEYWORD_is))
1922 	{
1923 		readToken (name);
1924 	}
1925 	readToken (token);
1926 	if (isType (name, TOKEN_IDENTIFIER) ||
1927 			isType (name, TOKEN_STRING))
1928 	{
1929 		makeSqlTag (name, SQLTAG_DOMAIN);
1930 	}
1931 	findCmdTerm (token, FALSE);
1932 	deleteToken (name);
1933 }
1934 
parseDrop(tokenInfo * const token)1935 static void parseDrop (tokenInfo *const token)
1936 {
1937 	/*
1938 	 * This deals with these formats
1939 	 *	   DROP TABLE|PROCEDURE|DOMAIN|DATATYPE name;
1940 	 *
1941 	 * Just simply skip over these statements.
1942 	 * They are often confused with PROCEDURE prototypes
1943 	 * since the syntax is similar, this effectively deals with
1944 	 * the issue for all types.
1945 	 */
1946 
1947 	findCmdTerm (token, FALSE);
1948 }
1949 
parseVariable(tokenInfo * const token)1950 static void parseVariable (tokenInfo *const token)
1951 {
1952 	tokenInfo *const name = newToken ();
1953 
1954 	/*
1955 	 * This deals with these formats
1956 	 *	   create variable varname1 integer;
1957 	 *	   create variable @varname2 integer;
1958 	 *	   create variable "varname3" integer;
1959 	 *	   drop   variable @varname3;
1960 	 */
1961 
1962 	readToken (name);
1963 	readToken (token);
1964 	if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
1965 			&& !isType (token, TOKEN_SEMICOLON) )
1966 	{
1967 		makeSqlTag (name, SQLTAG_VARIABLE);
1968 	}
1969 	findCmdTerm (token, TRUE);
1970 
1971 	deleteToken (name);
1972 }
1973 
parseSynonym(tokenInfo * const token)1974 static void parseSynonym (tokenInfo *const token)
1975 {
1976 	tokenInfo *const name = newToken ();
1977 
1978 	/*
1979 	 * This deals with these formats
1980 	 *	   create variable varname1 integer;
1981 	 *	   create variable @varname2 integer;
1982 	 *	   create variable "varname3" integer;
1983 	 *	   drop   variable @varname3;
1984 	 */
1985 
1986 	readToken (name);
1987 	readToken (token);
1988 	if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
1989 			&& isKeyword (token, KEYWORD_for) )
1990 	{
1991 		makeSqlTag (name, SQLTAG_SYNONYM);
1992 	}
1993 	findCmdTerm (token, TRUE);
1994 
1995 	deleteToken (name);
1996 }
1997 
parseView(tokenInfo * const token)1998 static void parseView (tokenInfo *const token)
1999 {
2000 	tokenInfo *const name = newToken ();
2001 
2002 	/*
2003 	 * This deals with these formats
2004 	 *	   create variable varname1 integer;
2005 	 *	   create variable @varname2 integer;
2006 	 *	   create variable "varname3" integer;
2007 	 *	   drop   variable @varname3;
2008 	 */
2009 
2010 	readToken (name);
2011 	readToken (token);
2012 	if (isType (token, TOKEN_PERIOD))
2013 	{
2014 		readToken (name);
2015 		readToken (token);
2016 	}
2017 	if ( isType (token, TOKEN_OPEN_PAREN) )
2018 	{
2019 		skipArgumentList(token);
2020 
2021 	}
2022 
2023 	while (!(isKeyword (token, KEYWORD_is) ||
2024 				isType (token, TOKEN_SEMICOLON)
2025 			))
2026 	{
2027 		readToken (token);
2028 	}
2029 
2030 	if ( (isType (name, TOKEN_IDENTIFIER) || isType (name, TOKEN_STRING))
2031 			&& isKeyword (token, KEYWORD_is) )
2032 	{
2033 		makeSqlTag (name, SQLTAG_VIEW);
2034 	}
2035 
2036 	findCmdTerm (token, TRUE);
2037 
2038 	deleteToken (name);
2039 }
2040 
parseMLTable(tokenInfo * const token)2041 static void parseMLTable (tokenInfo *const token)
2042 {
2043 	tokenInfo *const version = newToken ();
2044 	tokenInfo *const table	 = newToken ();
2045 	tokenInfo *const event	 = newToken ();
2046 
2047 	/*
2048 	 * This deals with these formats
2049 	 *	  call dbo.ml_add_table_script( 'version', 'table_name', 'event',
2050 	 *		   'some SQL statement'
2051 	 *		   );
2052 	 */
2053 
2054 	readToken (token);
2055 	if ( isType (token, TOKEN_OPEN_PAREN) )
2056 	{
2057 		readToken (version);
2058 		readToken (token);
2059 		while (!(isType (token, TOKEN_COMMA) ||
2060 					isType (token, TOKEN_CLOSE_PAREN)
2061 				))
2062 		{
2063 			readToken (token);
2064 		}
2065 
2066 		if (isType (token, TOKEN_COMMA))
2067 		{
2068 			readToken (table);
2069 			readToken (token);
2070 			while (!(isType (token, TOKEN_COMMA) ||
2071 						isType (token, TOKEN_CLOSE_PAREN)
2072 					))
2073 			{
2074 				readToken (token);
2075 			}
2076 
2077 			if (isType (token, TOKEN_COMMA))
2078 			{
2079 				readToken (event);
2080 
2081 				if (isType (version, TOKEN_STRING) &&
2082 						isType (table, TOKEN_STRING) &&
2083 						isType (event, TOKEN_STRING) )
2084 				{
2085 					addToScope(version, table->string);
2086 					addToScope(version, event->string);
2087 					makeSqlTag (version, SQLTAG_MLTABLE);
2088 				}
2089 			}
2090 			if( !isType (token, TOKEN_CLOSE_PAREN) )
2091 				findToken (token, TOKEN_CLOSE_PAREN);
2092 		}
2093 	}
2094 
2095 	findCmdTerm (token, TRUE);
2096 
2097 	deleteToken (version);
2098 	deleteToken (table);
2099 	deleteToken (event);
2100 }
2101 
parseMLConn(tokenInfo * const token)2102 static void parseMLConn (tokenInfo *const token)
2103 {
2104 	tokenInfo *const version = newToken ();
2105 	tokenInfo *const event	 = newToken ();
2106 
2107 	/*
2108 	 * This deals with these formats
2109 	 *	  call ml_add_connection_script( 'version', 'event',
2110 	 *		   'some SQL statement'
2111 	 *		   );
2112 	 */
2113 
2114 	readToken (token);
2115 	if ( isType (token, TOKEN_OPEN_PAREN) )
2116 	{
2117 		readToken (version);
2118 		readToken (token);
2119 		while (!(isType (token, TOKEN_COMMA) ||
2120 					isType (token, TOKEN_CLOSE_PAREN)
2121 				))
2122 		{
2123 			readToken (token);
2124 		}
2125 
2126 		if (isType (token, TOKEN_COMMA))
2127 		{
2128 			readToken (event);
2129 
2130 			if (isType (version, TOKEN_STRING) &&
2131 					isType (event, TOKEN_STRING) )
2132 			{
2133 				addToScope(version, event->string);
2134 				makeSqlTag (version, SQLTAG_MLCONN);
2135 			}
2136 		}
2137 		if( !isType (token, TOKEN_CLOSE_PAREN) )
2138 			findToken (token, TOKEN_CLOSE_PAREN);
2139 
2140 	}
2141 
2142 	findCmdTerm (token, TRUE);
2143 
2144 	deleteToken (version);
2145 	deleteToken (event);
2146 }
2147 
parseMLProp(tokenInfo * const token)2148 static void parseMLProp (tokenInfo *const token)
2149 {
2150 	tokenInfo *const component     = newToken ();
2151 	tokenInfo *const prop_set_name = newToken ();
2152 	tokenInfo *const prop_name     = newToken ();
2153 
2154 	/*
2155 	 * This deals with these formats
2156      *   ml_add_property (
2157      *       'comp_name',
2158      *       'prop_set_name',
2159      *       'prop_name',
2160      *       'prop_value'
2161      *   )
2162 	 */
2163 
2164 	readToken (token);
2165 	if ( isType (token, TOKEN_OPEN_PAREN) )
2166 	{
2167 		readToken (component);
2168 		readToken (token);
2169 		while (!(isType (token, TOKEN_COMMA) ||
2170 					isType (token, TOKEN_CLOSE_PAREN)
2171 				))
2172 		{
2173 			readToken (token);
2174 		}
2175 
2176 		if (isType (token, TOKEN_COMMA))
2177 		{
2178 			readToken (prop_set_name);
2179 			readToken (token);
2180 			while (!(isType (token, TOKEN_COMMA) ||
2181 						isType (token, TOKEN_CLOSE_PAREN)
2182 					))
2183 			{
2184 				readToken (token);
2185 			}
2186 
2187 			if (isType (token, TOKEN_COMMA))
2188 			{
2189 				readToken (prop_name);
2190 
2191 				if (isType (component, TOKEN_STRING) &&
2192 						isType (prop_set_name, TOKEN_STRING) &&
2193 						isType (prop_name, TOKEN_STRING) )
2194 				{
2195 					addToScope(component, prop_set_name->string);
2196 					addToScope(component, prop_name->string);
2197 					makeSqlTag (component, SQLTAG_MLPROP);
2198 				}
2199 			}
2200 			if( !isType (token, TOKEN_CLOSE_PAREN) )
2201 				findToken (token, TOKEN_CLOSE_PAREN);
2202 		}
2203 	}
2204 
2205 	findCmdTerm (token, TRUE);
2206 
2207 	deleteToken (component);
2208 	deleteToken (prop_set_name);
2209 	deleteToken (prop_name);
2210 }
2211 
parseComment(tokenInfo * const token)2212 static void parseComment (tokenInfo *const token)
2213 {
2214 	/*
2215 	 * This deals with this statement:
2216 	 *	   COMMENT TO PRESERVE FORMAT ON PROCEDURE "DBA"."test" IS
2217 	 *	   {create PROCEDURE DBA."test"()
2218 	 *	   BEGIN
2219 	 *		signal dave;
2220 	 *	   END
2221 	 *	   }
2222 	 *	   ;
2223 	 * The comment can contain anything between the CURLY
2224 	 * braces
2225 	 *	   COMMENT ON USER "admin" IS
2226 	 *			'Administration Group'
2227 	 *			;
2228 	 * Or it could be a simple string with no curly braces
2229 	 */
2230 	while (! isKeyword (token, KEYWORD_is))
2231 	{
2232 		readToken (token);
2233 	}
2234 	readToken (token);
2235 	if ( isType(token, TOKEN_OPEN_CURLY) )
2236 	{
2237 		findToken (token, TOKEN_CLOSE_CURLY);
2238 	}
2239 
2240 	findCmdTerm (token, TRUE);
2241 }
2242 
2243 
parseKeywords(tokenInfo * const token)2244 static void parseKeywords (tokenInfo *const token)
2245 {
2246 		switch (token->keyword)
2247 		{
2248 			case KEYWORD_begin:			parseBlock (token, FALSE); break;
2249 			case KEYWORD_comment:		parseComment (token); break;
2250 			case KEYWORD_cursor:		parseSimple (token, SQLTAG_CURSOR); break;
2251 			case KEYWORD_datatype:		parseDomain (token); break;
2252 			case KEYWORD_declare:		parseBlock (token, FALSE); break;
2253 			case KEYWORD_domain:		parseDomain (token); break;
2254 			case KEYWORD_drop:			parseDrop (token); break;
2255 			case KEYWORD_event:			parseEvent (token); break;
2256 			case KEYWORD_function:		parseSubProgram (token); break;
2257 			case KEYWORD_if:			parseStatements (token, FALSE); break;
2258 			case KEYWORD_index:			parseIndex (token); break;
2259 			case KEYWORD_ml_table:		parseMLTable (token); break;
2260 			case KEYWORD_ml_table_lang: parseMLTable (token); break;
2261 			case KEYWORD_ml_table_dnet: parseMLTable (token); break;
2262 			case KEYWORD_ml_table_java: parseMLTable (token); break;
2263 			case KEYWORD_ml_table_chk:  parseMLTable (token); break;
2264 			case KEYWORD_ml_conn:		parseMLConn (token); break;
2265 			case KEYWORD_ml_conn_lang:	parseMLConn (token); break;
2266 			case KEYWORD_ml_conn_dnet:	parseMLConn (token); break;
2267 			case KEYWORD_ml_conn_java:	parseMLConn (token); break;
2268 			case KEYWORD_ml_conn_chk:	parseMLConn (token); break;
2269 			case KEYWORD_ml_prop:		parseMLProp (token); break;
2270 			case KEYWORD_package:		parsePackage (token); break;
2271 			case KEYWORD_procedure:		parseSubProgram (token); break;
2272 			case KEYWORD_publication:	parsePublication (token); break;
2273 			case KEYWORD_service:		parseService (token); break;
2274 			case KEYWORD_subtype:		parseSimple (token, SQLTAG_SUBTYPE); break;
2275 			case KEYWORD_synonym:		parseSynonym (token); break;
2276 			case KEYWORD_table:			parseTable (token); break;
2277 			case KEYWORD_trigger:		parseTrigger (token); break;
2278 			case KEYWORD_type:			parseType (token); break;
2279 			case KEYWORD_variable:		parseVariable (token); break;
2280 			case KEYWORD_view:			parseView (token); break;
2281 			default:				    break;
2282 		}
2283 }
2284 
parseSqlFile(tokenInfo * const token)2285 static void parseSqlFile (tokenInfo *const token)
2286 {
2287 	do
2288 	{
2289 		readToken (token);
2290 
2291 		if (isType (token, TOKEN_BLOCK_LABEL_BEGIN))
2292 			parseLabel (token);
2293 		else
2294 			parseKeywords (token);
2295 	} while (! isKeyword (token, KEYWORD_end));
2296 }
2297 
initialize(const langType language)2298 static void initialize (const langType language)
2299 {
2300 	Assert (sizeof (SqlKinds) / sizeof (SqlKinds [0]) == SQLTAG_COUNT);
2301 	Lang_sql = language;
2302 	buildSqlKeywordHash ();
2303 }
2304 
findSqlTags(void)2305 static void findSqlTags (void)
2306 {
2307 	tokenInfo *const token = newToken ();
2308 	exception_t exception = (exception_t) (setjmp (Exception));
2309 
2310 	while (exception == ExceptionNone)
2311 		parseSqlFile (token);
2312 
2313 	deleteToken (token);
2314 }
2315 
SqlParser(void)2316 extern parserDefinition* SqlParser (void)
2317 {
2318 	static const char *const extensions [] = { "sql", NULL };
2319 	parserDefinition* def = parserNew ("SQL");
2320 	def->kinds		= SqlKinds;
2321 	def->kindCount	= KIND_COUNT (SqlKinds);
2322 	def->extensions = extensions;
2323 	def->parser		= findSqlTags;
2324 	def->initialize = initialize;
2325 	return def;
2326 }
2327 
2328 /* vi:set tabstop=4 shiftwidth=4 noexpandtab: */
2329