root/trunk/src/java/org/jcoderz/phoenix/sqlparser/SqlParser.java

Revision 1264, 39.8 kB (checked in by amandel, 3 years ago)

Added support for string based type attributes like in 'VARCHAR2(100 char)'.
Solved situation where a identifier is also a keyword (operator or sequence).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 *
4 * Copyright 2006, The jCoderZ.org Project. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are
8 * met:
9 *
10 *    * Redistributions of source code must retain the above copyright
11 *      notice, this list of conditions and the following disclaimer.
12 *    * Redistributions in binary form must reproduce the above
13 *      copyright notice, this list of conditions and the following
14 *      disclaimer in the documentation and/or other materials
15 *      provided with the distribution.
16 *    * Neither the name of the jCoderZ.org Project nor the names of
17 *      its contributors may be used to endorse or promote products
18 *      derived from this software without specific prior written
19 *      permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS
25 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
28 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
29 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
31 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33package org.jcoderz.phoenix.sqlparser;
34
35import java.io.FileInputStream;
36import java.util.ArrayList;
37import java.util.Iterator;
38import java.util.List;
39import java.util.Stack;
40import java.util.logging.Level;
41import java.util.logging.Logger;
42
43/**
44 * @author Albrecht Messner
45 */
46public class SqlParser
47{
48   private static final String CLASSNAME = SqlParser.class.getName();
49   private static final Logger logger = Logger.getLogger(CLASSNAME);
50
51   private static final SpecialColumnComment TIMESTAMP_SPECIAL_COMMENT
52         = new SpecialColumnComment();
53
54   private static final SpecialColumnComment DATE_SPECIAL_COMMENT
55         = new SpecialColumnComment();
56
57   static
58   {
59      Token t;
60      try
61      {
62         t = new Token(TokenType.COMMENT,
63               "-- @cmpgen.java-type=org.jcoderz.commons.types.Date");
64         TIMESTAMP_SPECIAL_COMMENT.parseComment(t);
65         DATE_SPECIAL_COMMENT.parseComment(t);
66
67         t = new Token(TokenType.COMMENT,
68               "-- @cmpgen.store-method=toSqlTimestamp()");
69         TIMESTAMP_SPECIAL_COMMENT.parseComment(t);
70         t = new Token(TokenType.COMMENT,
71               "-- @cmpgen.store-method=toSqlDate()");
72         DATE_SPECIAL_COMMENT.parseComment(t);
73
74         t = new Token(TokenType.COMMENT,
75               "-- @cmpgen.load-method=fromSqlTimestamp(java.sql.Timestamp)");
76         TIMESTAMP_SPECIAL_COMMENT.parseComment(t);
77         t = new Token(TokenType.COMMENT,
78               "-- @cmpgen.load-method=fromSqlDate(java.sql.Date)");
79         DATE_SPECIAL_COMMENT.parseComment(t);
80      }
81      catch (ParseException e)
82      {
83         throw new RuntimeException("This must not happen!", e);
84      }
85   }
86
87   private final ScannerInterface mScanner;
88
89   private final Stack mTokenStack = new Stack();
90
91   private SpecialColumnComment mSpecialColumnComment;
92   private SpecialAnnotationComment mColumnAnnotation;
93
94   private final List mSpecialStatementComments = new ArrayList();
95   private SpecialAnnotationComment mStatementAnnotation;
96
97   /**
98    * Create a new SQL Parser.
99    *
100    * @param scanner the SQL Scanner that delivers tokens to this parser
101    */
102   public SqlParser (ScannerInterface scanner)
103   {
104      mScanner = scanner;
105      mScanner.setReportWhitespace(false);
106   }
107
108   /**
109    * Wrapper method for ScannerInterface.nextToken().
110    *
111    * @return returns the next token from the stack of the scanner,
112    *       or null if the stack is empty,
113    *
114    * @throws ParseException
115    */
116   private Token getNextToken () throws ParseException
117   {
118      Token result;
119      if (mTokenStack.isEmpty())
120      {
121         result = mScanner.nextToken();
122      }
123      else
124      {
125         result = (Token) mTokenStack.pop();
126      }
127
128      if (logger.isLoggable(Level.FINER))
129      {
130         final Exception x = new Exception();
131         int index = 1;
132         final StackTraceElement caller = x.getStackTrace()[index];
133         StackTraceElement callerParent = null;
134         try
135         {
136            callerParent = x.getStackTrace()[++index];
137         }
138         catch (ArrayIndexOutOfBoundsException x2)
139         {
140            // ignore, the stack is simply not long enough
141         }
142
143         logger.finest("TOKEN: " + result + " to " + caller
144               + " called by " + callerParent);
145      }
146
147      return result;
148   }
149
150   /**
151    * gives parser a chance to return a token if it has read too much
152    *
153    * @param token
154    */
155   private void pushToken (Token token)
156   {
157      mTokenStack.push(token);
158   }
159
160   /**
161    * gives parser a chance to return a token if it has read too much,
162    * the returned token is converted into an identifier if possible.
163    *
164    * @param token
165    * @throws ParseException
166    */
167   private void pushTokenAsIdentifier (Token token) throws ParseException
168   {
169       Token pushToken = null;
170       if (token.getType() == TokenType.IDENTIFIER)
171       {
172           pushToken = token;
173       }
174       else if (token.getType() == TokenType.SEQUENCE)
175       {
176           pushToken = new Token(TokenType.IDENTIFIER, token.getValue());
177       }
178       else
179       {
180           unexpectedToken(token);
181       }
182      mTokenStack.push(pushToken);
183   }
184
185   /**
186    * Read tokens from the scanner until EOF and parse them.
187    *
188    * @return a list of parsed SQL statements
189    * @throws ParseException if an error occurs
190    */
191   public final List parse ()
192      throws ParseException
193   {
194      final List tableObjects = new ArrayList();
195      final List indexObjects = new ArrayList();
196      final List sequenceObjects = new ArrayList();
197
198      Token token;
199      while ((token = getNextToken()).getType() != TokenType.EOF)
200      {
201         final SqlStatement stmt = handleStartOfStatement(token);
202         if (stmt != null)
203         {
204            if (stmt instanceof CreateTableStatement)
205            {
206               tableObjects.add(stmt);
207            }
208            else if (stmt instanceof CreateIndexStatement)
209            {
210               indexObjects.add(stmt);
211            }
212            else if (stmt instanceof CreateSequenceStatement)
213            {
214               sequenceObjects.add(stmt);
215            }
216            logger.info("Parsed statement: " + stmt);
217         }
218      }
219
220      for (final Iterator it = indexObjects.iterator(); it.hasNext(); )
221      {
222         final CreateIndexStatement stmt = (CreateIndexStatement) it.next();
223         final String tname = stmt.getTableName();
224         boolean found = false;
225         for (final Iterator it2 = tableObjects.iterator(); it2.hasNext(); )
226         {
227            final CreateTableStatement stmt2
228                = (CreateTableStatement) it2.next();
229            if (stmt2.getTableName().equalsIgnoreCase(tname))
230            {
231               stmt2.addIndex(stmt);
232               found = true;
233               break;
234            }
235         }
236
237         if (! found)
238         {
239            throw new ParseException(
240                  "Index on table " + tname
241                  + " declared, but create statement for table " + tname
242                  + " not found", -1, -1);
243         }
244      }
245
246      final List result = new ArrayList();
247      result.addAll(tableObjects);
248      result.addAll(sequenceObjects);
249      return result;
250   }
251
252   private SqlStatement handleStartOfStatement (Token token)
253      throws ParseException
254   {
255      SqlStatement result = null;
256      if (token.getType() == TokenType.CREATE)
257      {
258         logger.finer("Start parsing CREATE statement");
259         result = handleCreateStatement();
260      }
261      else if (token.getType() == TokenType.ALTER
262            || token.getType() == TokenType.DROP
263            || token.getType() == TokenType.INSERT
264            || token.getType() == TokenType.DELETE
265            || token.getType() == TokenType.SELECT)
266      {
267         logger.info("Skipping " + token.getValue() + " statement");
268         eatUntilSemicolon();
269      }
270      else if (token.getType() == TokenType.COMMENT)
271      {
272         pushToken(token);
273         parseStatementComment();
274      }
275      else
276      {
277         logger.finer("Unable to handle token " + token);
278         throw new ParseException(
279            "Unexpected token",
280            mScanner.getLine(),
281            mScanner.getColumn());
282      }
283      return result;
284   }
285
286   private SqlStatement handleCreateStatement ()
287      throws ParseException
288   {
289      final String methodName = "handleCreateStatement()";
290      logger.entering(CLASSNAME, methodName);
291
292
293      Token token = getNextToken();
294      SqlStatement result = null;
295
296      final TokenType type = token.getType();
297
298      // we can only parse CREATE TABLE and CREATE INDEX statements
299      if (type == TokenType.TABLE)
300      {
301         token = getNextToken();
302         // sequence is a valid identifier...
303         if (token.getType().equals(TokenType.IDENTIFIER)
304             || token.getType().equals(TokenType.SEQUENCE))
305         {
306             result = new CreateTableStatement(token.getValue());
307         }
308         else
309         {
310             unexpectedToken(token);
311         }
312
313
314         assertNextToken(TokenType.OPEN_PAREN);
315
316         do
317         {
318            parseRelationalPropOrComment((CreateTableStatement) result);
319
320            token = getNextToken();
321            if (token.getType() == TokenType.CLOSE_PAREN)
322            {
323               // we've reached the end of the relational properties
324               break;
325            }
326            else if (token.getType() == TokenType.COMMENT)
327            {
328               pushToken(token);
329               continue;
330            }
331            else
332            {
333               pushToken(token);
334               continue;
335            }
336         }
337         while (true);
338
339         eatUntilSemicolon();
340
341         if (mSpecialStatementComments.size() > 0)
342         {
343            for (final Iterator it = mSpecialStatementComments.iterator();
344                  it.hasNext(); )
345            {
346               final SpecialStatementComment comment
347                     = (SpecialStatementComment) it.next();
348               if (comment.getType() == SpecialStatementComment.TYPE_BEAN_NAME)
349               {
350                  ((CreateTableStatement) result).setBeanName(
351                        comment.getContent());
352               }
353               else if (
354                     comment.getType() == SpecialStatementComment.TYPE_JAVADOC)
355               {
356                  ((CreateTableStatement) result).setAdditionalJavadoc(
357                        comment.getContent());
358               }
359               else if (comment.getType()
360                     == SpecialStatementComment.TYPE_OPTIMISTIC_VERSION_COUNT)
361               {
362                  ((CreateTableStatement) result).
363                        setOptimisticVersionCount(true);
364               }
365               else if (comment.getType()
366                     == SpecialStatementComment.TYPE_SKIP_APPSERVER_SUPPORT)
367               {
368                  ((CreateTableStatement) result).
369                        setSkipAppserverSupport(true);
370               }
371
372            }
373            mSpecialStatementComments.clear();
374         }
375      }
376      else if (type == TokenType.UNIQUE
377            || type == TokenType.BITMAP
378            || type == TokenType.INDEX)
379      {
380         boolean isUnique = false;
381         if (type == TokenType.UNIQUE)
382         {
383            assertNextToken(TokenType.INDEX);
384            isUnique = true;
385         }
386         else if (type == TokenType.BITMAP)
387         {
388            assertNextToken(TokenType.INDEX);
389         }
390
391         token = assertNextToken(TokenType.IDENTIFIER);
392         final CreateIndexStatement stmt
393             = new CreateIndexStatement(token.getValue());
394         stmt.setUnique(isUnique);
395         parseCreateIndexStatement(stmt);
396         result = stmt;
397         eatUntilSemicolon();
398      }
399      else if (type == TokenType.SEQUENCE)
400      {
401         token = assertNextToken(TokenType.IDENTIFIER);
402         final CreateSequenceStatement stmt
403               = new CreateSequenceStatement(token.getValue());
404
405         parseCreateSequenceStatement(stmt);
406
407         result = stmt;
408         eatUntilSemicolon();
409
410         logger.info("Parsed sequence: " + stmt);
411      }
412      else
413      {
414         logger.info("Can't parse create statement for "
415               + token.getValue() + ", skipping.");
416         eatUntilSemicolon();
417      }
418
419      if (mStatementAnnotation != null)
420      {
421         result.setAnnotation(mStatementAnnotation.getAnnotation());
422         mStatementAnnotation = null;
423      }
424
425      logger.exiting(CLASSNAME, methodName, result);
426      return result;
427   }
428
429   private void parseCreateSequenceStatement (CreateSequenceStatement stmt)
430         throws ParseException
431   {
432      Token token;
433      while ((token = getNextToken()).getType() != TokenType.SEMICOLON)
434      {
435         final TokenType type = token.getType();
436         if (type == TokenType.INCREMENT)
437         {
438            assertNextToken(TokenType.BY);
439            token = assertNextToken(TokenType.NUMERIC_LITERAL);
440            stmt.setIncrementBy(Long.parseLong(token.getValue()));
441         }
442         else if (type == TokenType.START)
443         {
444            assertNextToken(TokenType.WITH);
445            token = assertNextToken(TokenType.NUMERIC_LITERAL);
446            stmt.setStartWith(new Long(Long.parseLong(token.getValue())));
447         }
448         else if (type == TokenType.MAXVALUE)
449         {
450            token = assertNextToken(TokenType.NUMERIC_LITERAL);
451            stmt.setMaxValue(new Long(Long.parseLong(token.getValue())));
452         }
453         else if (type == TokenType.NOMAXVALUE)
454         {
455            stmt.setNoMaxValue(true);
456         }
457         else if (type == TokenType.MINVALUE)
458         {
459            token = assertNextToken(TokenType.NUMERIC_LITERAL);
460            stmt.setMinValue(new Long(Long.parseLong(token.getValue())));
461         }
462         else if (type == TokenType.NOMINVALUE)
463         {
464            stmt.setNoMinValue(true);
465         }
466         else if (type == TokenType.CYCLE)
467         {
468            stmt.setCycle(true);
469         }
470         else if (type == TokenType.NOCYCLE)
471         {
472            stmt.setCycle(false);
473         }
474         else if (type == TokenType.CACHE)
475         {
476            token = assertNextToken(TokenType.NUMERIC_LITERAL);
477            stmt.setCache(Long.parseLong(token.getValue()));
478         }
479         else if (type == TokenType.NOCACHE)
480         {
481            stmt.setCache(0);
482         }
483         else if (type == TokenType.ORDER)
484         {
485            stmt.setOrder(true);
486         }
487         else if (type == TokenType.NOORDER)
488         {
489            stmt.setOrder(false);
490         }
491         else
492         {
493            unexpectedToken(token);
494         }
495      }
496
497      // put the semicolon back on the stack
498      pushToken(token);
499   }
500
501   private void parseCreateIndexStatement (CreateIndexStatement result)
502         throws ParseException
503   {
504      // the CREATE (UNIQUE|BITMAP) INDEX <name> part has already been eaten.
505      assertNextToken(TokenType.ON);
506      Token token = assertNextToken(TokenType.IDENTIFIER);
507      result.setTableName(token.getValue());
508
509      assertNextToken(TokenType.OPEN_PAREN);
510
511      while (true)
512      {
513         token = assertNextToken(TokenType.IDENTIFIER);
514         result.addColumn(token.getValue());
515
516         token = getNextToken();
517         if (token.getType() == TokenType.CLOSE_PAREN)
518         {
519            break;
520         }
521         else if (! (token.getType() == TokenType.COMMA))
522         {
523            unexpectedToken(token);
524         }
525      }
526   }
527
528   /**
529    * Parse a single column spec, or an out-of-line constraint.
530    */
531   private void parseRelationalPropOrComment (CreateTableStatement createStmt)
532      throws ParseException
533   {
534      final Token token = getNextToken();
535      if (isStartOfOutOfLineConstraint(token))
536      {
537         // delegate
538         pushToken(token);
539         parseOutOfLineConstraint(createStmt);
540      }
541      // sequence is a valid identifier
542      else if (token.getType() == TokenType.IDENTIFIER
543          || token.getType() == TokenType.SEQUENCE)
544      {
545         // delegate
546         pushTokenAsIdentifier(token);
547         parseColumnSpec(createStmt);
548      }
549      else if (token.getType() == TokenType.COMMENT)
550      {
551         // delegate
552         pushToken(token);
553         parseColumnComment();
554      }
555      else
556      {
557         unexpectedToken(token);
558      }
559   }
560
561   private void parseColumnSpec (CreateTableStatement createStmt)
562         throws ParseException
563   {
564      final ColumnSpec colSpec = new ColumnSpec();
565
566      // column name
567      Token token = assertNextToken(TokenType.IDENTIFIER);
568      colSpec.setColumnName(token.getValue());
569
570      // data type
571      token = assertNextToken(TokenType.IDENTIFIER);
572      colSpec.setColumnType(token.getValue());
573
574      if (token.getValue().equalsIgnoreCase("TIMESTAMP")
575            && mSpecialColumnComment == null)
576      {
577         mSpecialColumnComment = TIMESTAMP_SPECIAL_COMMENT;
578      }
579      else if (token.getValue().equalsIgnoreCase("DATE")
580            && mSpecialColumnComment == null)
581      {
582         mSpecialColumnComment = DATE_SPECIAL_COMMENT;
583      }
584
585      token = getNextToken();
586      if (token.getType() == TokenType.OPEN_PAREN)
587      {
588         token = assertNextToken(TokenType.NUMERIC_LITERAL);
589         colSpec.addDatatypeAttribute(new NumericAttribute(token.getValue()));
590         token = getNextToken();
591         if (token.getType() == TokenType.IDENTIFIER)
592         {
593            colSpec.addDatatypeAttribute(
594                new StringAttribute(token.getValue()));
595            assertNextToken(TokenType.CLOSE_PAREN);
596         }
597         else if (token.getType() == TokenType.COMMA)
598         {
599            token = assertNextToken(TokenType.NUMERIC_LITERAL);
600            colSpec.addDatatypeAttribute(
601                    new NumericAttribute(token.getValue()));
602            assertNextToken(TokenType.CLOSE_PAREN);
603         }
604         else if (token.getType() != TokenType.CLOSE_PAREN)
605         {
606            throw new ParseException("Unexpected token: " + token,
607               mScanner.getLine(), mScanner.getColumn());
608         }
609      }
610      else
611      {
612         pushToken(token);
613      }
614
615      parseColumnAttributes(colSpec, createStmt);
616
617      token = getNextToken();
618      if (token.getType() != TokenType.COMMA)
619      {
620         pushToken(token);
621      }
622
623      // at this point the full colspec is parsed
624      if (mSpecialColumnComment != null)
625      {
626         mSpecialColumnComment.validate();
627         colSpec.setJavaType(mSpecialColumnComment.getJavaType());
628         colSpec.setLoadMethod(mSpecialColumnComment.getLoadMethod());
629         colSpec.setStoreMethod(mSpecialColumnComment.getStoreMethod());
630         colSpec.setCurrencyColumn(mSpecialColumnComment.getCurrencyColumn());
631
632         colSpec.setIsPeriodDefined(
633               mSpecialColumnComment.getPeriodFieldName() != null
634               && mSpecialColumnComment.getPeriodEndDateColumn() != null);
635         colSpec.setPeriodFieldName(
636               mSpecialColumnComment.getPeriodFieldName());
637         colSpec.setPeriodEndDateColumn(
638               mSpecialColumnComment.getPeriodEndDateColumn());
639         colSpec.setWeblogicColumnType(
640               mSpecialColumnComment.getWeblogicColumnType());
641
642         colSpec.setSkipInInterface(mSpecialColumnComment.isSkipInInterface());
643         mSpecialColumnComment = null;
644      }
645      if (mColumnAnnotation != null)
646      {
647         colSpec.setAnnotation(mColumnAnnotation.getAnnotation());
648         mColumnAnnotation = null;
649      }
650
651      createStmt.addColumn(colSpec);
652   }
653
654   private void parseStatementComment ()
655         throws ParseException
656   {
657      final Token token = assertNextToken(TokenType.COMMENT);
658      final String val = token.getValue();
659
660      if (val.indexOf("@cmpgen") != -1)
661      {
662         final SpecialStatementComment comment
663               = SpecialStatementComment.parseComment(token);
664         if (comment != null)
665         {
666            mSpecialStatementComments.add(comment);
667         }
668      }
669      else if (val.indexOf("@@annotation") != -1)
670      {
671         if (mStatementAnnotation == null)
672         {
673            mStatementAnnotation = new SpecialAnnotationComment();
674         }
675         mStatementAnnotation.parseComment(token);
676      }
677   }
678
679
680   private void parseColumnComment () throws ParseException
681   {
682      final Token token = assertNextToken(TokenType.COMMENT);
683      final String str = token.getValue();
684      if (str.indexOf("@cmpgen") != -1)
685      {
686         if (mSpecialColumnComment == null)
687         {
688            mSpecialColumnComment = new SpecialColumnComment();
689         }
690         mSpecialColumnComment.parseComment(token);
691      }
692      else if (str.indexOf("@@annotation") != -1)
693      {
694         if (mColumnAnnotation != null)
695         {
696            throw new ParseException("Only one annotation per column allowed",
697                  mScanner.getLine(), mScanner.getColumn());
698         }
699         mColumnAnnotation = new SpecialAnnotationComment();
700         mColumnAnnotation.parseComment(token);
701      }
702      else
703      {
704         logger.finer("Ignoring non-special comment " + token);
705      }
706   }
707
708   /**
709    * parse a set of column attributes.
710    *
711    * this is supposed to parse everything after the column name and data type,
712    * namely:
713    * <ul>
714    *    <li>a default value for the column</li>
715    *    <li>inline constraints</li>
716    * </ul>
717    */
718   private void parseColumnAttributes (
719         ColumnSpec colSpec, CreateTableStatement stmt)
720         throws ParseException
721   {
722      Token token;
723
724      token = getNextToken();
725      TokenType type = token.getType();
726      if (type == TokenType.COMMA
727            || type == TokenType.CLOSE_PAREN)
728      {
729         pushToken(token);
730      }
731      else if (type == TokenType.DEFAULT)
732      {
733         // default clause: DEFAULT expr
734         // read until end of EXPR, which is either a comma,
735         // a closing parentheses or the start of an inline constraint
736         final StringBuffer defltExpr = new StringBuffer();
737         mScanner.setReportWhitespace(true);
738         while (true)
739         {
740            token = getNextToken();
741            type = token.getType();
742            if (type == TokenType.OPEN_PAREN)
743            {
744               defltExpr.append(token.getValue());
745               defltExpr.append(eatUntilClosingParen());
746               defltExpr.append(
747                     assertNextToken(TokenType.CLOSE_PAREN).getValue());
748            }
749            else if (type == TokenType.COMMA
750                  || type == TokenType.CLOSE_PAREN
751                  || isStartOfInlineConstraint(token))
752            {
753               pushToken(token);
754               break;
755            }
756            else
757            {
758               defltExpr.append(token.getValue());
759            }
760         }
761         mScanner.setReportWhitespace(false);
762         final DefaultClause dfltClause
763             = new DefaultClause(defltExpr.toString());
764         colSpec.addAttribute(dfltClause);
765      }
766      else if (isStartOfInlineConstraint(token))
767      {
768         // return the token we just read,
769         // because it's the start of a constraint
770         pushToken(token);
771         parseInlineConstraint(colSpec, stmt);
772      }
773      else
774      {
775         unexpectedToken(token);
776      }
777
778      while (true)
779      {
780         token = getNextToken();
781         logger.finer("Checking for more inline constraints: got " + token);
782         pushToken(token);
783         if (isStartOfInlineConstraint(token))
784         {
785            parseInlineConstraint(colSpec, stmt);
786         }
787         else if (token.getType() == TokenType.COMMA
788               || token.getType() == TokenType.CLOSE_PAREN)
789         {
790            break;
791         }
792         else
793         {
794            unexpectedToken(token);
795         }
796      }
797   }
798
799   private void parseInlineConstraint (
800         ColumnSpec column, CreateTableStatement stmt)
801         throws ParseException
802   {
803      Token token;
804      TokenType type;
805
806      String constraintName = "unnamed";
807      token = getNextToken();
808      type = token.getType();
809
810      if (type == TokenType.CONSTRAINT)
811      {
812         token = assertNextToken(TokenType.IDENTIFIER);
813         constraintName = token.getValue();
814      }
815      else
816      {
817         pushToken(token);
818      }
819
820      token = getNextToken();
821      type = token.getType();
822      if (type == TokenType.NOT)
823      {
824         assertNextToken(TokenType.NULL);
825         column.setNotNull(true);
826         logger.finer("Parsed inline constraint " + constraintName + ": "
827               + column.getColumnName() + " is not nullable");
828      }
829      else if (type == TokenType.NULL)
830      {
831         column.setNotNull(false);
832         logger.finer("Parsed inline constraint " + constraintName + ": "
833               + column.getColumnName() + " is nullable");
834      }
835      else if (type == TokenType.UNIQUE)
836      {
837         column.setUnique(true);
838         logger.finer("Parsed inline constraint " + constraintName + ": "
839               + column.getColumnName() + " is unique");
840      }
841      else if (type == TokenType.PRIMARY)
842      {
843         assertNextToken(TokenType.KEY);
844         column.setPrimaryKey(true);
845         logger.finer("Parsed inline constraint " + constraintName + ": "
846               + column.getColumnName() + " is primary key");
847      }
848      else if (type == TokenType.CHECK)
849      {
850         final StringBuffer checkCondition = new StringBuffer();
851         assertNextToken(TokenType.OPEN_PAREN);
852         mScanner.setReportWhitespace(true);
853         checkCondition.append(eatUntilClosingParen());
854         mScanner.setReportWhitespace(false);
855         assertNextToken(TokenType.CLOSE_PAREN);
856         logger.finer("Parsed inline constraint " + constraintName + ": "
857               + column.getColumnName() + " check condition: "
858               + checkCondition);
859      }
860      else if (type == TokenType.REFERENCES)
861      {
862         final String refTable
863             = assertNextToken(TokenType.IDENTIFIER).getValue();
864         String refColumn = null;
865
866         token = getNextToken();
867         if (token.getType() == TokenType.OPEN_PAREN)
868         {
869            refColumn = assertNextToken(TokenType.IDENTIFIER).getValue();
870            assertNextToken(TokenType.CLOSE_PAREN);
871         }
872         else
873         {
874            refColumn = column.getColumnName();
875            pushToken(token);
876         }
877
878         String deleteSemantic = "default";
879
880         token = getNextToken();
881         type = token.getType();
882         if (type.equals(TokenType.ON))
883         {
884            assertNextToken(TokenType.DELETE);
885            token = getNextToken();
886            type = token.getType();
887            if (type == TokenType.SET)
888            {
889               assertNextToken(TokenType.NULL);
890               deleteSemantic  = "set null";
891            }
892            else
893            {
894               assertToken(token, TokenType.CASCADE);
895               deleteSemantic = "cascade";
896            }
897         }
898         else
899         {
900            // oops, no on delete clause
901            pushToken(token);
902         }
903
904         final FkConstraint constraint = new FkConstraint(
905               constraintName, column.getColumnName(), refTable, refColumn);
906         stmt.addFkConstraint(constraint);
907         logger.finer("Parsed inline constraint " + constraintName + ": "
908               + column.getColumnName() + " references "
909               + refTable
910               + (refColumn != null ? "." + refColumn : "")
911               + " with " + deleteSemantic + " delete semantic");
912      }
913   }
914
915   private boolean isStartOfInlineConstraint (Token token)
916   {
917      final TokenType type = token.getType();
918      return type == TokenType.NOT
919            || type == TokenType.NULL
920            || type == TokenType.UNIQUE
921            || type == TokenType.PRIMARY
922            || type == TokenType.CHECK
923            || type == TokenType.REFERENCES
924            || type == TokenType.CONSTRAINT;
925   }
926
927   private boolean isStartOfOutOfLineConstraint (Token token)
928   {
929      final TokenType type = token.getType();
930      return type == TokenType.CONSTRAINT
931            || type == TokenType.UNIQUE
932            || type == TokenType.PRIMARY
933            || type == TokenType.FOREIGN
934            || type == TokenType.CHECK;
935   }
936
937   private StringBuffer eatUntilClosingParen ()
938      throws ParseException
939   {
940      final StringBuffer sbuf = new StringBuffer();
941
942      Token token;
943      while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
944      {
945         sbuf.append(token.getValue());
946         if (token.getType() == TokenType.OPEN_PAREN)
947         {
948            final StringBuffer nested = eatUntilClosingParen();
949            sbuf.append(nested);
950            token = assertNextToken(TokenType.CLOSE_PAREN);
951            sbuf.append(token.getValue());
952         }
953         logger.finest("Skipping token " + token);
954      }
955      pushToken(token);
956
957      return sbuf;
958   }
959
960   private Token eatUntilSemicolon ()
961      throws ParseException
962   {
963      Token token;
964      while (true)
965      {
966         token = getNextToken();
967         if (token.getType() == TokenType.SEMICOLON)
968         {
969            break;
970         }
971         logger.fine("Skipping token " + token);
972      }
973      return token;
974   }
975
976   private void parseOutOfLineConstraint (CreateTableStatement statement)
977      throws ParseException
978   {
979      String constraintName = "unnamed";
980
981      Token token;
982      TokenType type;
983
984      // 1. parse "CONSTRAINT <name>"
985      token = getNextToken();
986      type = token.getType();
987      if (type == TokenType.CONSTRAINT)
988      {
989         token = assertNextToken(TokenType.IDENTIFIER);
990         constraintName = token.getValue();
991      }
992      else
993      {
994         pushToken(token);
995      }
996
997      // 2. parse the constraint itself
998      token = getNextToken();
999      type = token.getType();
1000
1001      if (type == TokenType.UNIQUE)
1002      {
1003         // UNIQUE ( column [, column...] )
1004         assertNextToken(TokenType.OPEN_PAREN);
1005         final List uniqueCols = new ArrayList();
1006         while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
1007         {
1008            if (token.getType() != TokenType.COMMA)
1009            {
1010               uniqueCols.add(token.getValue());
1011            }
1012         }
1013         logger.finer("Parsed OOL constraint "
1014               + constraintName + ": UNIQUE " + uniqueCols);
1015         addUniqueColumns(statement, uniqueCols);
1016      }
1017      else if (type == TokenType.PRIMARY)
1018      {
1019         // PRIMARY KEY ( column [, column...] )
1020         assertNextToken(TokenType.KEY);
1021         assertNextToken(TokenType.OPEN_PAREN);
1022         final List pkCols = new ArrayList();
1023         while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
1024         {
1025            if (token.getType() != TokenType.COMMA)
1026            {
1027               pkCols.add(token.getValue());
1028            }
1029         }
1030         logger.finer("Parsed OOL constraint "
1031               + constraintName + ": PRIMARY KEY " + pkCols);
1032         addPkColumns(statement, pkCols);
1033      }
1034      else if (type == TokenType.FOREIGN)
1035      {
1036         parseOolForeignKey(constraintName, statement);
1037      }
1038      else if (type == TokenType.CHECK)
1039      {
1040         token = assertNextToken(TokenType.OPEN_PAREN);
1041         final StringBuffer checkCondition = new StringBuffer();
1042         checkCondition.append(token.getValue());
1043         mScanner.setReportWhitespace(true);
1044         checkCondition.append(eatUntilClosingParen());
1045         mScanner.setReportWhitespace(false);
1046         token = assertNextToken(TokenType.CLOSE_PAREN);
1047         checkCondition.append(token.getValue());
1048         logger.finer("Parsed OOL constraint "
1049            + constraintName + ": CHECK " + checkCondition);
1050      }
1051
1052      parseConstraintState();
1053
1054      token = getNextToken();
1055      if (token.getType() != TokenType.COMMA)
1056      {
1057         pushToken(token);
1058      }
1059   }
1060
1061   /**
1062    * Method to parse an oracle constraint state.
1063    *
1064    * This actually just parses a subset of an constraint state,
1065    * namely the ENABLE/DISABLE state.
1066    *
1067    * @throws ParseException
1068    */
1069   private void parseConstraintState ()
1070         throws ParseException
1071   {
1072      final Token token = getNextToken();
1073      final TokenType type = token.getType();
1074      if (type == TokenType.ENABLE
1075            || type == TokenType.DISABLE)
1076      {
1077         logger.finer("Parsed constraint state: " + token);
1078      }
1079      else if (type == TokenType.COMMA
1080            || type == TokenType.CLOSE_PAREN
1081            || type == TokenType.COMMENT)
1082      {
1083         // end of constraint reached
1084         pushToken(token);
1085      }
1086      else
1087      {
1088         unexpectedToken(token);
1089      }
1090   }
1091
1092   private void parseOolForeignKey (String constraintName,
1093         CreateTableStatement stmt)
1094         throws ParseException
1095   {
1096      Token token;
1097      TokenType type;
1098
1099      // FOREIGN KEY "(" column [, column...] ")"
1100      //    REFERENCES schema.object [ "(" column [, column...] ")" ]
1101      //    [ ON DELETE ( CASCADE | SET NULL ) ]
1102      //    [ (ENABLE | DISABLE) ]
1103      assertNextToken(TokenType.KEY);
1104      assertNextToken(TokenType.OPEN_PAREN);
1105      final List fkCols = new ArrayList();
1106      while ((token = getNextToken()).getType() != TokenType.CLOSE_PAREN)
1107      {
1108         if (token.getType() != TokenType.COMMA)
1109         {
1110            fkCols.add(token.getValue());
1111         }
1112      }
1113      assertNextToken(TokenType.REFERENCES);
1114      token = assertNextToken(TokenType.IDENTIFIER);
1115      final String refTable = token.getValue();
1116      final List refColumns = new ArrayList();
1117      token = getNextToken();
1118      if (token.getType() == TokenType.OPEN_PAREN)
1119      {
1120         while (true)
1121         {
1122            token = assertNextToken(TokenType.IDENTIFIER);
1123            refColumns.add(token.getValue());
1124
1125            token = getNextToken();
1126
1127            if (token.getType() == TokenType.CLOSE_PAREN)
1128            {
1129               break;
1130            }
1131            else if (token.getType() == TokenType.COMMA)
1132            {
1133               // expected
1134               continue;
1135            }
1136            else
1137            {
1138               unexpectedToken(token);
1139            }
1140         }
1141      }
1142      else
1143      {
1144         refColumns.addAll(fkCols);
1145         pushToken(token);
1146      }
1147
1148      token = getNextToken();
1149      type = token.getType();
1150      String deleteSemantic = "default";
1151      if (type == TokenType.ON)
1152      {
1153         assertNextToken(TokenType.DELETE);
1154         token = getNextToken();
1155         if (token.getType() == TokenType.SET)
1156         {
1157            assertNextToken(TokenType.NULL);
1158            deleteSemantic = "set null";
1159         }
1160         else if (token.getType() == TokenType.CASCADE)
1161         {
1162            deleteSemantic = "cascade";
1163         }
1164         else
1165         {
1166            unexpectedToken(token);
1167         }
1168      }
1169      else
1170      {
1171         pushToken(token);
1172      }
1173
1174      // constraint state
1175      String constraintState = null;
1176      token = getNextToken();
1177      type = token.getType();
1178      if (type == TokenType.ENABLE
1179            || type == TokenType.DISABLE)
1180      {
1181         constraintState = token.getValue();
1182      }
1183      else
1184      {
1185         pushToken(token);
1186      }
1187
1188      token = getNextToken();
1189      type = token.getType();
1190      if (type == TokenType.COMMA
1191            || type == TokenType.CLOSE_PAREN
1192            || type == TokenType.COMMENT)
1193      {
1194         // uff, we're done
1195         pushToken(token);
1196
1197         final FkConstraint constraint = new FkConstraint(
1198               constraintName, fkCols, refTable, refColumns);
1199         stmt.addFkConstraint(constraint);
1200         logger.finer("Parsed OOL constraint "
1201               + constraintName + ": FOREIGN KEY: " + fkCols
1202               + " reference " + refTable
1203               + "(" + refColumns + ")"
1204               + " with " + deleteSemantic + " delete semantic"
1205               + (constraintState != null
1206                  ? (" and constraint state " + constraintState)
1207                  : ""));
1208      }
1209      else
1210      {
1211         unexpectedToken(token);
1212      }
1213   }
1214
1215   private void addUniqueColumns (CreateTableStatement statement, List ukCols)
1216         throws ParseException
1217   {
1218      for (final Iterator it = ukCols.iterator(); it.hasNext(); )
1219      {
1220         final String colName = (String) it.next();
1221         final ColumnSpec col = statement.getColumnByName(colName);
1222         if (col == null)
1223         {
1224            throw new ParseException("Column " + colName + " not found",
1225                  mScanner.getLine(), mScanner.getColumn());
1226         }
1227         col.setUnique(true);
1228      }
1229   }
1230
1231   /**
1232    * convenience method to add a set of columns as PK columns to
1233    * a create statement.
1234    *
1235    * @param pkCols list of column names (Strings) that should be PK
1236    */
1237   private void addPkColumns (CreateTableStatement statement, List pkCols)
1238         throws ParseException
1239   {
1240      // first check that no primary key exists
1241      checkForPrimaryKey(statement);
1242
1243      for (final Iterator it = pkCols.iterator(); it.hasNext(); )
1244      {
1245         final String colName = (String) it.next();
1246         final ColumnSpec col = statement.getColumnByName(colName);
1247         if (col == null)
1248         {
1249            throw new ParseException("Column " + colName + " not found",
1250                  mScanner.getLine(), mScanner.getColumn());
1251         }
1252         col.setPrimaryKey(true);
1253      }
1254   }
1255
1256   /**
1257    * checks if the statement already has a column with PK
1258    *
1259    * @param statement a SQL create table statement
1260    * @throws ParseException if a primary key exists in the statement
1261    */
1262   private void checkForPrimaryKey (CreateTableStatement statement)
1263      throws ParseException
1264   {
1265      for (final Iterator it = statement.getColumns().iterator();
1266          it.hasNext(); )
1267      {
1268         final ColumnSpec col = (ColumnSpec) it.next();
1269         if (col.isPrimaryKey())
1270         {
1271            throw new ParseException(col.getColumnName()
1272                  + " is already primary key",
1273                  mScanner.getLine(), mScanner.getColumn());
1274         }
1275      }
1276   }
1277
1278   /**
1279    * assert that the next token is of the expected type return next token
1280    * @param expectedType
1281    * @throws ParseException
1282    */
1283   private Token assertNextToken (TokenType expectedType)
1284      throws ParseException
1285   {
1286      // final String methodName = "assertNextToken(TokenType)";
1287      // logger.entering(CLASSNAME, methodName, expectedType);
1288      final Token nextToken = getNextToken();
1289      if (nextToken.getType() != expectedType)
1290      {
1291         throw new ParseException(
1292            "Unexpected token: expected "
1293               + expectedType
1294               + " but got "
1295               + nextToken.getType(),
1296            mScanner.getLine(),
1297            mScanner.getColumn());
1298      }
1299
1300      // logger.exiting(CLASSNAME, methodName, nextToken);
1301      return nextToken;
1302   }
1303
1304   private void unexpectedToken (Token token) throws ParseException
1305   {
1306      throw new ParseException(
1307            "Unexpected token: " + token,
1308            mScanner.getLine(),
1309            mScanner.getColumn());
1310   }
1311
1312   private void assertToken (Token token, TokenType expectedType)
1313         throws ParseException
1314   {
1315      if (token.getType() != expectedType)
1316      {
1317         throw new ParseException(
1318            "Unexpected token: expected "
1319               + expectedType
1320               + " but got "
1321               + token.getType(),
1322            mScanner.getLine(),
1323            mScanner.getColumn());
1324      }
1325   }
1326
1327   /**
1328    * Main method is just to test parser.
1329    *
1330    * @param args command line arguments
1331    * @throws Exception if something goes wrong
1332    */
1333   public static void main (String[] args)
1334      throws Exception
1335   {
1336      final FileInputStream fin = new FileInputStream(args[0]);
1337      final SqlScanner scanner = new SqlScanner(fin);
1338      final SqlParser parser = new SqlParser(scanner);
1339      parser.parse();
1340   }
1341}
Note: See TracBrowser for help on using the browser.