View Javadoc

1   /*
2    * ModeShape (http://www.modeshape.org)
3    * See the COPYRIGHT.txt file distributed with this work for information
4    * regarding copyright ownership.  Some portions may be licensed
5    * to Red Hat, Inc. under one or more contributor license agreements.
6    * See the AUTHORS.txt file in the distribution for a full listing of 
7    * individual contributors.
8    *
9    * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
10   * is licensed to you under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation; either version 2.1 of
12   * the License, or (at your option) any later version.
13   * 
14   * ModeShape is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17   * Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public
20   * License along with this software; if not, write to the Free
21   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
22   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
23   */
24  package org.modeshape.sequencer.ddl.datatype;
25  
26  import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_LENGTH;
27  import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_NAME;
28  import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_PRECISION;
29  import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_SCALE;
30  import java.math.BigInteger;
31  import java.util.ArrayList;
32  import java.util.List;
33  import org.modeshape.common.text.ParsingException;
34  import org.modeshape.sequencer.ddl.DdlConstants;
35  import org.modeshape.sequencer.ddl.DdlTokenStream;
36  import org.modeshape.sequencer.ddl.node.AstNode;
37  
38  /**
39   * A parser for SQL data types.
40   */
41  public class DataTypeParser implements DdlConstants {
42      private static List<String[]> basicCharStringTypes = new ArrayList<String[]>();
43      private static List<String[]> basicNationalCharStringTypes = new ArrayList<String[]>();
44      private static List<String[]> basicBitStringTypes = new ArrayList<String[]>();
45      private static List<String[]> basicExactNumericTypes = new ArrayList<String[]>();
46      private static List<String[]> basicApproxNumericStringTypes = new ArrayList<String[]>();
47      private static List<String[]> basicDateTimeTypes = new ArrayList<String[]>();
48      private static List<String[]> basicMiscTypes = new ArrayList<String[]>();
49  
50      private int defaultLength = 255;
51      private int defaultPrecision = 0;
52      private int defaultScale = 0;
53  
54      public DataTypeParser() {
55          super();
56  
57          initialize();
58      }
59  
60      private void initialize() {
61  
62          basicCharStringTypes.add(DataTypes.DTYPE_CHARACTER);
63          basicCharStringTypes.add(DataTypes.DTYPE_CHAR);
64          basicCharStringTypes.add(DataTypes.DTYPE_CHARACTER_VARYING);
65          basicCharStringTypes.add(DataTypes.DTYPE_CHAR_VARYING);
66          basicCharStringTypes.add(DataTypes.DTYPE_VARCHAR);
67  
68          basicNationalCharStringTypes.add(DataTypes.DTYPE_NCHAR);
69          basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHARACTER);
70          basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING);
71          basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHAR);
72          basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHAR_VARYING);
73          basicNationalCharStringTypes.add(DataTypes.DTYPE_NCHAR_VARYING);
74  
75          basicBitStringTypes.add(DataTypes.DTYPE_BIT);
76          basicBitStringTypes.add(DataTypes.DTYPE_BIT_VARYING);
77  
78          basicExactNumericTypes.add(DataTypes.DTYPE_NUMERIC);
79          basicExactNumericTypes.add(DataTypes.DTYPE_DEC);
80          basicExactNumericTypes.add(DataTypes.DTYPE_DECIMAL);
81          basicExactNumericTypes.add(DataTypes.DTYPE_INTEGER);
82          basicExactNumericTypes.add(DataTypes.DTYPE_INT);
83          basicExactNumericTypes.add(DataTypes.DTYPE_SMALLINT);
84  
85          basicApproxNumericStringTypes.add(DataTypes.DTYPE_FLOAT);
86          basicApproxNumericStringTypes.add(DataTypes.DTYPE_REAL);
87          basicApproxNumericStringTypes.add(DataTypes.DTYPE_DOUBLE_PRECISION);
88  
89          basicDateTimeTypes.add(DataTypes.DTYPE_DATE);
90          basicDateTimeTypes.add(DataTypes.DTYPE_TIME);
91          basicDateTimeTypes.add(DataTypes.DTYPE_TIMESTAMP);
92  
93          basicMiscTypes.add(DataTypes.DTYPE_INTERVAL);
94  
95      }
96  
97      /**
98       * Method determines if the next set of tokens matches one of the registered data type token sets.
99       * 
100      * @param tokens
101      * @return is registered data type
102      * @throws ParsingException
103      */
104     public final boolean isDatatype( DdlTokenStream tokens ) throws ParsingException {
105         // Loop through the registered statement start string arrays and look for exact matches.
106 
107         for (String[] stmts : basicCharStringTypes) {
108             if (tokens.matches(stmts)) return true;
109         }
110 
111         for (String[] stmts : basicNationalCharStringTypes) {
112             if (tokens.matches(stmts)) return true;
113         }
114 
115         for (String[] stmts : basicBitStringTypes) {
116             if (tokens.matches(stmts)) return true;
117         }
118 
119         for (String[] stmts : basicExactNumericTypes) {
120             if (tokens.matches(stmts)) return true;
121         }
122 
123         for (String[] stmts : basicApproxNumericStringTypes) {
124             if (tokens.matches(stmts)) return true;
125         }
126 
127         for (String[] stmts : basicDateTimeTypes) {
128             if (tokens.matches(stmts)) return true;
129         }
130 
131         for (String[] stmts : basicMiscTypes) {
132             if (tokens.matches(stmts)) return true;
133         }
134 
135         // If no type is found, assume it's a custom type
136         return isCustomDataType(tokens);
137     }
138 
139     /**
140      * Method determines if the next set of tokens matches one of the registered data type token sets.
141      * 
142      * @param tokens
143      * @param type
144      * @return is registered data type
145      * @throws ParsingException
146      */
147     private boolean isDatatype( DdlTokenStream tokens,
148                                 int type ) throws ParsingException {
149         // Loop through the registered statement start string arrays and look for exact matches.
150 
151         switch (type) {
152             case DataTypes.DTYPE_CODE_CHAR_STRING: {
153                 for (String[] stmts : basicCharStringTypes) {
154                     if (tokens.matches(stmts)) return true;
155                 }
156             }
157                 break;
158             case DataTypes.DTYPE_CODE_NCHAR_STRING: {
159                 for (String[] stmts : basicNationalCharStringTypes) {
160                     if (tokens.matches(stmts)) return true;
161                 }
162             }
163                 break;
164             case DataTypes.DTYPE_CODE_BIT_STRING: {
165                 for (String[] stmts : basicBitStringTypes) {
166                     if (tokens.matches(stmts)) return true;
167                 }
168             }
169                 break;
170             case DataTypes.DTYPE_CODE_EXACT_NUMERIC: {
171                 for (String[] stmts : basicExactNumericTypes) {
172                     if (tokens.matches(stmts)) return true;
173                 }
174             }
175                 break;
176             case DataTypes.DTYPE_CODE_APROX_NUMERIC: {
177                 for (String[] stmts : basicApproxNumericStringTypes) {
178                     if (tokens.matches(stmts)) return true;
179                 }
180             }
181                 break;
182             case DataTypes.DTYPE_CODE_DATE_TIME: {
183                 for (String[] stmts : basicDateTimeTypes) {
184                     if (tokens.matches(stmts)) return true;
185                 }
186             }
187                 break;
188             case DataTypes.DTYPE_CODE_MISC: {
189                 for (String[] stmts : basicMiscTypes) {
190                     if (tokens.matches(stmts)) return true;
191                 }
192             }
193                 break;
194         }
195 
196         return false;
197     }
198 
199     /**
200      * Method to determine of next tokens represent a custom data type. Subclasses should override this method and perform token
201      * checks for any non-SQL92 spec'd data types.
202      * 
203      * @param tokens
204      * @return is custom data type
205      * @throws ParsingException
206      */
207     protected boolean isCustomDataType( DdlTokenStream tokens ) throws ParsingException {
208         return false;
209     }
210 
211     /**
212      * Method which performs the actual parsing of the data type name and applicable values (i.e. VARCHAR(20)) if data type is
213      * found.
214      * 
215      * @param tokens
216      * @return the {@link DataType}
217      * @throws ParsingException
218      */
219     public DataType parse( DdlTokenStream tokens ) throws ParsingException {
220         DataType result = null;
221 
222         if (isDatatype(tokens, DataTypes.DTYPE_CODE_CHAR_STRING)) {
223             result = parseCharStringType(tokens);
224         } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_NCHAR_STRING)) {
225             result = parseNationalCharStringType(tokens);
226         } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_BIT_STRING)) {
227             result = parseBitStringType(tokens);
228         } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_EXACT_NUMERIC)) {
229             result = parseExactNumericType(tokens);
230         } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_APROX_NUMERIC)) {
231             result = parseApproxNumericType(tokens);
232         } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_DATE_TIME)) {
233             result = parseDateTimeType(tokens);
234         } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_MISC)) {
235             result = parseMiscellaneousType(tokens);
236         } else {
237             result = parseCustomType(tokens);
238         }
239 
240         /*
241          * (FROM http://www.postgresql.org/docs/8.4/static/arrays.html) 
242         8.14.1. Declaration of Array Types
243 
244         To illustrate the use of array types, we create this table:
245 
246         CREATE TABLE sal_emp (
247             name            text,
248             pay_by_quarter  integer[],
249             schedule        text[][]
250         );
251 
252         As shown, an array data type is named by appending square brackets ([]) to the data type name of the array elements. 
253         The above command will create a table named sal_emp with a column of type text (name), a one-dimensional array of type 
254         integer (pay_by_quarter), which represents the employee's salary by quarter, and a two-dimensional array of text (schedule), 
255         which represents the employee's weekly schedule.
256 
257         The syntax for CREATE TABLE allows the exact size of arrays to be specified, for example:
258 
259         CREATE TABLE tictactoe (
260             squares   integer[3][3]
261         );
262 
263         However, the current implementation ignores any supplied array size limits, i.e., the behavior is the same as for 
264         arrays of unspecified length.
265 
266         The current implementation does not enforce the declared number of dimensions either. Arrays of a particular element 
267         type are all considered to be of the same type, regardless of size or number of dimensions. So, declaring the array size 
268         or number of dimensions in CREATE TABLE is simply documentation; it does not affect run-time behavior.
269 
270         An alternative syntax, which conforms to the SQL standard by using the keyword ARRAY, can be used for one-dimensional 
271         arrays. pay_by_quarter could have been defined as:
272 
273             pay_by_quarter  integer ARRAY[4],
274 
275         Or, if no array size is to be specified:
276 
277             pay_by_quarter  integer ARRAY,
278         */
279 
280         if (tokens.canConsume('[')) {
281             if (!tokens.canConsume(']')) {
282                 // assume integer value
283                 tokens.consume();
284                 tokens.consume(']');
285             }
286 
287             if (tokens.canConsume('[')) {
288                 if (!tokens.canConsume(']')) {
289                     // assume integer value
290                     tokens.consume();
291                     tokens.consume(']');
292                 }
293             }
294         }
295 
296         return result;
297     }
298 
299     /**
300      * Parses SQL-92 Character string data types. <character string type> ::= CHARACTER [ <left paren> <length> <right paren> ] |
301      * CHAR [ <left paren> <length> <right paren> ] | CHARACTER VARYING <left paren> <length> <right paren> | CHAR VARYING <left
302      * paren> <length> <right paren> | VARCHAR <left paren> <length> <right paren>
303      * 
304      * @param tokens
305      * @return the {@link DataType}
306      * @throws ParsingException
307      */
308     protected DataType parseCharStringType( DdlTokenStream tokens ) throws ParsingException {
309         DataType dataType = null;
310         String typeName = null;
311 
312         if (tokens.matches(DataTypes.DTYPE_VARCHAR)) {
313             typeName = getStatementTypeName(DataTypes.DTYPE_VARCHAR);
314             dataType = new DataType(typeName);
315             consume(tokens, dataType, false, DataTypes.DTYPE_VARCHAR);
316             int length = parseBracketedInteger(tokens, dataType);
317             dataType.setLength(length);
318         } else if (tokens.matches(DataTypes.DTYPE_CHAR_VARYING)) {
319             typeName = getStatementTypeName(DataTypes.DTYPE_CHAR_VARYING);
320             dataType = new DataType(typeName);
321             consume(tokens, dataType, false, DataTypes.DTYPE_CHAR_VARYING);
322             int length = parseBracketedInteger(tokens, dataType);
323             dataType.setLength(length);
324         } else if (tokens.matches(DataTypes.DTYPE_CHARACTER_VARYING)) {
325             typeName = getStatementTypeName(DataTypes.DTYPE_CHARACTER_VARYING);
326             dataType = new DataType(typeName);
327             consume(tokens, dataType, false, DataTypes.DTYPE_CHARACTER_VARYING);
328             int length = parseBracketedInteger(tokens, dataType);
329             dataType.setLength(length);
330         } else if (tokens.matches(DataTypes.DTYPE_CHAR) || tokens.matches(DataTypes.DTYPE_CHARACTER)) {
331             dataType = new DataType();
332             typeName = consume(tokens, dataType, false); // "CHARACTER", "CHAR",
333             dataType.setName(typeName);
334             int length = getDefaultLength();
335             if (tokens.matches(L_PAREN)) {
336                 length = parseBracketedInteger(tokens, dataType);
337             }
338             dataType.setLength(length);
339         }
340 
341         return dataType;
342     }
343 
344     /**
345      * Parses SQL-92 National Character string data types. <national character string type> ::= NATIONAL CHARACTER [ <left paren>
346      * <length> <right paren> ] | NATIONAL CHAR [ <left paren> <length> <right paren> ] | NCHAR [ <left paren> <length> <right
347      * paren> ] | NATIONAL CHARACTER VARYING <left paren> <length> <right paren> | NATIONAL CHAR VARYING <left paren> <length>
348      * <right paren> | NCHAR VARYING <left paren> <length> <right paren>
349      * 
350      * @param tokens
351      * @return the {@link DataType}
352      * @throws ParsingException
353      */
354     protected DataType parseNationalCharStringType( DdlTokenStream tokens ) throws ParsingException {
355         DataType dataType = null;
356         String typeName = null;
357 
358         if (tokens.matches(DataTypes.DTYPE_NCHAR_VARYING)) {
359             typeName = getStatementTypeName(DataTypes.DTYPE_NCHAR_VARYING);
360             dataType = new DataType(typeName);
361             consume(tokens, dataType, false, DataTypes.DTYPE_NCHAR_VARYING);
362             int length = parseBracketedInteger(tokens, dataType);
363 
364             dataType.setLength(length);
365         } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHAR_VARYING)) {
366             typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHAR_VARYING);
367             dataType = new DataType(typeName);
368             consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHAR_VARYING);
369             int length = parseBracketedInteger(tokens, dataType);
370 
371             dataType.setLength(length);
372         } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING)) {
373             typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING);
374             dataType = new DataType(typeName);
375             consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING);
376             int length = parseBracketedInteger(tokens, dataType);
377 
378             dataType.setLength(length);
379         } else if (tokens.matches(DataTypes.DTYPE_NCHAR)) {
380             typeName = getStatementTypeName(DataTypes.DTYPE_NCHAR);
381             dataType = new DataType(typeName);
382             consume(tokens, dataType, false, DataTypes.DTYPE_NCHAR);
383             int length = getDefaultLength();
384             if (tokens.matches(L_PAREN)) {
385                 length = parseBracketedInteger(tokens, dataType);
386             }
387             dataType.setLength(length);
388         } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHAR)) {
389             typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHAR);
390             dataType = new DataType(typeName);
391             consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHAR);
392             int length = getDefaultLength();
393             if (tokens.matches(L_PAREN)) {
394                 length = parseBracketedInteger(tokens, dataType);
395             }
396             dataType.setLength(length);
397         } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHARACTER)) {
398             typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHARACTER);
399             dataType = new DataType(typeName);
400             consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHARACTER);
401             int length = getDefaultLength();
402             if (tokens.matches(L_PAREN)) {
403                 length = parseBracketedInteger(tokens, dataType);
404             }
405             dataType.setLength(length);
406         }
407 
408         return dataType;
409     }
410 
411     /**
412      * Parses SQL-92 Bit string data types. <bit string type> ::= BIT [ <left paren> <length> <right paren> ] | BIT VARYING <left
413      * paren> <length> <right paren>
414      * 
415      * @param tokens
416      * @return the {@link DataType}
417      * @throws ParsingException
418      */
419     protected DataType parseBitStringType( DdlTokenStream tokens ) throws ParsingException {
420         DataType dataType = null;
421         String typeName = null;
422 
423         if (tokens.matches(DataTypes.DTYPE_BIT_VARYING)) {
424             typeName = getStatementTypeName(DataTypes.DTYPE_BIT_VARYING);
425             dataType = new DataType(typeName);
426             consume(tokens, dataType, false, DataTypes.DTYPE_BIT_VARYING);
427             int length = parseBracketedInteger(tokens, dataType);
428 
429             dataType.setLength(length);
430         } else if (tokens.matches(DataTypes.DTYPE_BIT)) {
431             typeName = getStatementTypeName(DataTypes.DTYPE_BIT);
432             dataType = new DataType(typeName);
433             consume(tokens, dataType, false, DataTypes.DTYPE_BIT);
434             int length = getDefaultLength();
435             if (tokens.matches(L_PAREN)) {
436                 length = parseBracketedInteger(tokens, dataType);
437             }
438             dataType.setLength(length);
439         }
440 
441         return dataType;
442     }
443 
444     /**
445      * Parses SQL-92 Exact numeric data types. <exact numeric type> ::= NUMERIC [ <left paren> <precision> [ <comma> <scale> ]
446      * <right paren> ] | DECIMAL [ <left paren> <precision> [ <comma> <scale> ] <right paren> ] | DEC [ <left paren> <precision> [
447      * <comma> <scale> ] <right paren> ] | INTEGER | INT | SMALLINT
448      * 
449      * @param tokens
450      * @return the {@link DataType}
451      * @throws ParsingException
452      */
453     protected DataType parseExactNumericType( DdlTokenStream tokens ) throws ParsingException {
454         DataType dataType = null;
455         String typeName = null;
456 
457         if (tokens.matchesAnyOf("INTEGER", "INT", "SMALLINT")) {
458             dataType = new DataType();
459             typeName = consume(tokens, dataType, false);
460             dataType.setName(typeName);
461         } else if (tokens.matchesAnyOf("NUMERIC", "DECIMAL", "DEC")) {
462             dataType = new DataType();
463             typeName = consume(tokens, dataType, false);
464             dataType.setName(typeName);
465 
466             int precision = 0;
467             int scale = 0;
468 
469             if (tokens.matches(L_PAREN)) {
470                 consume(tokens, dataType, false, L_PAREN);
471                 precision = parseInteger(tokens, dataType);
472                 if (canConsume(tokens, dataType, false, COMMA)) {
473                     scale = parseInteger(tokens, dataType);
474                 } else {
475                     scale = getDefaultScale();
476                 }
477                 consume(tokens, dataType, false, R_PAREN);
478             } else {
479                 precision = getDefaultPrecision();
480                 scale = getDefaultScale();
481             }
482             dataType.setPrecision(precision);
483             dataType.setScale(scale);
484         }
485 
486         return dataType;
487     }
488 
489     /**
490      * Parses SQL-92 Approximate numeric data types. <approximate numeric type> ::= FLOAT [ <left paren> <precision> <right paren>
491      * ] | REAL | DOUBLE PRECISION
492      * 
493      * @param tokens
494      * @return the {@link DataType}
495      * @throws ParsingException
496      */
497     protected DataType parseApproxNumericType( DdlTokenStream tokens ) throws ParsingException {
498         DataType dataType = null;
499         String typeName = null;
500 
501         if (tokens.matches(DataTypes.DTYPE_REAL)) {
502             dataType = new DataType();
503             typeName = consume(tokens, dataType, false, DataTypes.DTYPE_REAL);
504             dataType.setName(typeName);
505         } else if (tokens.matches(DataTypes.DTYPE_DOUBLE_PRECISION)) {
506             dataType = new DataType();
507             typeName = consume(tokens, dataType, false, DataTypes.DTYPE_DOUBLE_PRECISION);
508             dataType.setName(typeName);
509         } else if (tokens.matches(DataTypes.DTYPE_FLOAT)) {
510             dataType = new DataType();
511             typeName = consume(tokens, dataType, false, DataTypes.DTYPE_FLOAT);
512             dataType.setName(typeName);
513             int precision = 0;
514             if (tokens.matches(L_PAREN)) {
515                 precision = parseBracketedInteger(tokens, dataType);
516             }
517             dataType.setPrecision(precision);
518         }
519 
520         return dataType;
521     }
522 
523     /**
524      * Parses SQL-92 Date and Time data types. <datetime type> ::= DATE | TIME [ <left paren> <time precision> <right paren> ] [
525      * WITH TIME ZONE ] | TIMESTAMP [ <left paren> <timestamp precision> <right paren> ] [ WITH TIME ZONE ]
526      * 
527      * @param tokens
528      * @return the {@link DataType}
529      * @throws ParsingException
530      */
531     protected DataType parseDateTimeType( DdlTokenStream tokens ) throws ParsingException {
532         DataType dataType = null;
533         String typeName = null;
534 
535         if (tokens.matches(DataTypes.DTYPE_DATE)) {
536             dataType = new DataType();
537             typeName = consume(tokens, dataType, false, DataTypes.DTYPE_DATE);
538             dataType.setName(typeName);
539         } else if (tokens.matches(DataTypes.DTYPE_TIME)) {
540             dataType = new DataType();
541             typeName = consume(tokens, dataType, false, DataTypes.DTYPE_TIME);
542             dataType.setName(typeName);
543 
544             int precision = 0;
545             if (tokens.matches(L_PAREN)) {
546                 precision = parseBracketedInteger(tokens, dataType);
547             }
548             dataType.setPrecision(precision);
549 
550             canConsume(tokens, dataType, true, "WITH", "TIME", "ZONE");
551         } else if (tokens.matches(DataTypes.DTYPE_TIMESTAMP)) {
552             dataType = new DataType();
553             typeName = consume(tokens, dataType, false, DataTypes.DTYPE_TIMESTAMP);
554             dataType.setName(typeName);
555 
556             int precision = 0;
557             if (tokens.matches(L_PAREN)) {
558                 precision = parseBracketedInteger(tokens, dataType);
559             }
560             dataType.setPrecision(precision);
561 
562             canConsume(tokens, dataType, true, "WITH", "TIME", "ZONE");
563         }
564 
565         return dataType;
566     }
567 
568     /**
569      * Parses SQL-92 Misc data types. <interval type> ::= INTERVAL <interval qualifier> <interval qualifier> ::= <start field> TO
570      * <end field> | <single datetime field> <start field> ::= <non-second datetime field> [ <left paren> <interval leading field
571      * precision> <right paren> ] <non-second datetime field> ::= YEAR | MONTH | DAY | HOUR | MINUTE <interval leading field
572      * precision> ::= <unsigned integer> <end field> ::= <non-second datetime field> | SECOND [ <left paren> <interval fractional
573      * seconds precision> <right paren> ] <interval fractional seconds precision> ::= <unsigned integer> <single datetime field>
574      * ::= <non-second datetime field> [ <left paren> <interval leading field precision> <right paren> ] | SECOND [ <left paren>
575      * <interval leading field precision> [ <comma> <interval fractional seconds precision> ] <right paren> ]
576      * 
577      * @param tokens
578      * @return the {@link DataType}
579      * @throws ParsingException
580      */
581     protected DataType parseMiscellaneousType( DdlTokenStream tokens ) throws ParsingException {
582         DataType dataType = null;
583         String typeName = null;
584 
585         if (tokens.matches(DataTypes.DTYPE_INTERVAL)) {
586             dataType = new DataType();
587             typeName = consume(tokens, dataType, false, DataTypes.DTYPE_INTERVAL);
588             dataType.setName(typeName);
589             // <non-second datetime field> TO <end field>
590             // 
591             // CASE 2a: { YEAR | MONTH | DAY | HOUR | MINUTE } [ [ <left paren> <interval leading field precision> <right paren> ]
592             // CASE 2b: SECOND [ <left paren> <interval leading field precision> [ <comma> <interval fractional seconds precision>
593             // ] <right paren> ]
594 
595             // CASE 1: { YEAR | MONTH | DAY | HOUR | MINUTE } TO { YEAR | MONTH | DAY | HOUR | MINUTE }
596             if (tokens.matchesAnyOf("YEAR", "MONTH", "DAY", "HOUR", "MINUTE")) {
597                 // Consume first
598                 consume(tokens, dataType, true);
599 
600                 if (canConsume(tokens, dataType, true, "TO")) {
601                     // CASE 1:
602                     // assume "YEAR | MONTH | DAY | HOUR | MINUTE" and consume
603                     consume(tokens, dataType, true);
604                 } else if (tokens.matches(L_PAREN, DdlTokenStream.ANY_VALUE, R_PAREN)) {
605                     // CASE 2a:
606                     consume(tokens, dataType, true, L_PAREN);
607                     consume(tokens, dataType, true);
608                     consume(tokens, dataType, true, R_PAREN);
609                 } else {
610                     System.out.println("  WARNING:  PROBLEM parsing INTERVAL data type. Check your DDL for incomplete statement.");
611                 }
612             } else if (canConsume(tokens, dataType, true, "SECOND")) {
613                 // CASE 2b:
614                 if (canConsume(tokens, dataType, true, L_PAREN)) {
615 
616                     consume(tokens, dataType, true); // PRECISION
617                     if (canConsume(tokens, dataType, true, COMMA)) {
618                         consume(tokens, dataType, true); // fractional seconds precision
619                     }
620                     canConsume(tokens, dataType, true, R_PAREN);
621                 } else {
622                     System.out.println("  WARNING:  PROBLEM parsing INTERVAL data type. Check your DDL for incomplete statement.");
623                 }
624             } else {
625                 System.out.println("  WARNING:  PROBLEM parsing INTERVAL data type. Check your DDL for incomplete statement.");
626             }
627         }
628 
629         return dataType;
630     }
631 
632     /**
633      * General catch-all data type parsing method that sub-classes can override to parse database-specific data types.
634      * 
635      * @param tokens
636      * @return the {@link DataType}
637      * @throws ParsingException
638      */
639     protected DataType parseCustomType( DdlTokenStream tokens ) throws ParsingException {
640         return null;
641     }
642 
643     /**
644      * @return integer default value for length
645      */
646     public int getDefaultLength() {
647         return defaultLength;
648     }
649 
650     /**
651      * @param defaultLength
652      */
653     public void setDefaultLength( int defaultLength ) {
654         this.defaultLength = defaultLength;
655     }
656 
657     /**
658      * @return integer default value for precision
659      */
660     public int getDefaultPrecision() {
661         return defaultPrecision;
662     }
663 
664     /**
665      * @param defaultPrecision
666      */
667     public void setDefaultPrecision( int defaultPrecision ) {
668         this.defaultPrecision = defaultPrecision;
669     }
670 
671     /**
672      * @return integer default value for scale
673      */
674     public int getDefaultScale() {
675         return defaultScale;
676     }
677 
678     /**
679      * @param defaultScale
680      */
681     public void setDefaultScale( int defaultScale ) {
682         this.defaultScale = defaultScale;
683     }
684 
685     /**
686      * Returns an integer value from the input token stream assuming the integer is not bracketed with parenthesis.
687      * 
688      * @param tokens
689      * @param dataType
690      * @return integer value
691      */
692     protected int parseInteger( DdlTokenStream tokens,
693                                 DataType dataType ) {
694         String value = consume(tokens, dataType, false);
695 
696         if (isKMGInteger(value)) {
697             value = value.substring(0, value.length() - 1);
698         }
699 
700         return new BigInteger(value).intValue();
701     }
702 
703     /**
704      * Returns an integer value from the input token stream assuming the integer is bracketed with parenthesis. Example: (255)
705      * 
706      * @param tokens
707      * @param dataType
708      * @return integer value
709      */
710     protected int parseBracketedInteger( DdlTokenStream tokens,
711                                          DataType dataType ) {
712         consume(tokens, dataType, false, L_PAREN);
713         int length = parseInteger(tokens, dataType);
714         consume(tokens, dataType, false, R_PAREN);
715 
716         return length;
717     }
718 
719     /**
720      * Returns the whether or not a tokenized string is a complex BLOB or CLOB length value ending in K, M or G. Example: BLOB
721      * (100M) or CLOB(64K)
722      * 
723      * @param value
724      * @return true if value ends with M, K or G
725      */
726     protected boolean isKMGInteger( String value ) {
727         return (value.endsWith("M") || value.endsWith("K") || value.endsWith("G"));
728     }
729 
730     /**
731      * Returns the integer value of the input string. Handles both straight integer string or complex KMG (CLOB or BLOB) value.
732      * 
733      * @param value
734      * @return integer value
735      * @throws NumberFormatException if a valid integer is not found
736      */
737     protected int parseInteger( String value ) {
738         if (isKMGInteger(value)) {
739             value = value.substring(0, value.length() - 1);
740         }
741 
742         return new BigInteger(value).intValue();
743     }
744 
745     /**
746      * Returns the K, M or G string value of a tokenized complex BLOB or CLOB length value Example: BLOB (100M) or CLOB(64K)
747      * 
748      * @param value
749      * @return K, M or G or NULL if not found.
750      */
751     protected String getKMG( String value ) {
752         if (value.endsWith("M")) return "M";
753         if (value.endsWith("K")) return "K";
754         if (value.endsWith("G")) return "G";
755 
756         return null;
757     }
758 
759     /**
760      * @param tokens
761      * @param dataType
762      * @param addSpacePrefix
763      * @return consumed String value
764      * @throws ParsingException
765      */
766     protected String consume( DdlTokenStream tokens,
767                               DataType dataType,
768                               boolean addSpacePrefix ) throws ParsingException {
769         String value = tokens.consume();
770 
771         dataType.appendSource(addSpacePrefix, value);
772 
773         return value;
774     }
775 
776     /**
777      * @param tokens
778      * @param dataType
779      * @param addSpacePrefix
780      * @param str
781      * @return consumed string value
782      * @throws ParsingException
783      */
784     protected String consume( DdlTokenStream tokens,
785                               DataType dataType,
786                               boolean addSpacePrefix,
787                               String str ) throws ParsingException {
788         tokens.consume(str);
789 
790         dataType.appendSource(addSpacePrefix, str);
791 
792         return str;
793     }
794 
795     /**
796      * @param tokens
797      * @param dataType
798      * @param addSpacePrefix
799      * @param initialStr
800      * @param additionalStrs
801      * @return the consumed String
802      * @throws ParsingException
803      */
804     protected String consume( DdlTokenStream tokens,
805                               DataType dataType,
806                               boolean addSpacePrefix,
807                               String initialStr,
808                               String... additionalStrs ) throws ParsingException {
809         tokens.consume(initialStr, additionalStrs);
810         StringBuffer value = new StringBuffer(initialStr);
811         dataType.appendSource(addSpacePrefix, initialStr);
812 
813         for (String str : additionalStrs) {
814             value.append(SPACE).append(str);
815             dataType.appendSource(addSpacePrefix, str);
816         }
817 
818         return value.toString();
819     }
820 
821     protected String consume( DdlTokenStream tokens,
822                               DataType dataType,
823                               boolean addSpacePrefix,
824                               String[] additionalStrs ) throws ParsingException {
825 
826         tokens.consume(additionalStrs);
827 
828         StringBuffer value = new StringBuffer(100);
829 
830         int i = 0;
831 
832         for (String str : additionalStrs) {
833             if (i == 0) {
834                 value.append(str);
835             } else {
836                 value.append(SPACE).append(str);
837             }
838             dataType.appendSource(addSpacePrefix, str);
839             i++;
840         }
841 
842         return value.toString();
843     }
844 
845     /**
846      * @param tokens
847      * @param dataType
848      * @param addSpacePrefix
849      * @param initialStr
850      * @param additionalStrs
851      * @return did consume
852      * @throws ParsingException
853      */
854     protected boolean canConsume( DdlTokenStream tokens,
855                                   DataType dataType,
856                                   boolean addSpacePrefix,
857                                   String initialStr,
858                                   String... additionalStrs ) throws ParsingException {
859         if (tokens.canConsume(initialStr, additionalStrs)) {
860             dataType.appendSource(addSpacePrefix, initialStr);
861 
862             for (String str : additionalStrs) {
863                 dataType.appendSource(addSpacePrefix, str);
864             }
865             return true;
866         }
867 
868         return false;
869     }
870 
871     /**
872      * @param tokens
873      * @param dataType
874      * @param addSpacePrefix
875      * @param additionalStrs
876      * @return did consume
877      * @throws ParsingException
878      */
879     protected boolean canConsume( DdlTokenStream tokens,
880                                   DataType dataType,
881                                   boolean addSpacePrefix,
882                                   String[] additionalStrs ) throws ParsingException {
883         if (tokens.canConsume(additionalStrs)) {
884 
885             for (String str : additionalStrs) {
886                 dataType.appendSource(addSpacePrefix, str);
887             }
888             return true;
889         }
890 
891         return false;
892     }
893 
894     /**
895      * @param tokens
896      * @param dataType
897      * @param addSpacePrefix
898      * @param type
899      * @return consumed String value
900      * @throws ParsingException
901      */
902     protected boolean canConsume( DdlTokenStream tokens,
903                                   DataType dataType,
904                                   boolean addSpacePrefix,
905                                   int type ) throws ParsingException {
906         if (tokens.matches(type)) {
907             dataType.appendSource(addSpacePrefix, tokens.consume());
908             return true;
909         }
910 
911         return false;
912     }
913 
914     /**
915      * @param tokens
916      * @param dataType
917      * @param addSpacePrefix
918      * @param initialStr
919      * @param additionalStrs
920      * @return did consume any
921      * @throws ParsingException
922      */
923     protected boolean canConsumeAnyOf( DdlTokenStream tokens,
924                                        DataType dataType,
925                                        boolean addSpacePrefix,
926                                        String initialStr,
927                                        String... additionalStrs ) throws ParsingException {
928         if (tokens.canConsume(initialStr)) {
929             dataType.appendSource(addSpacePrefix, initialStr);
930             return true;
931         }
932         for (String str : additionalStrs) {
933             dataType.appendSource(addSpacePrefix, str);
934             return true;
935         }
936 
937         return false;
938     }
939 
940     /**
941      * @param stmtPhrase
942      * @return concatenated name
943      */
944     public String getStatementTypeName( String[] stmtPhrase ) {
945         StringBuffer sb = new StringBuffer(100);
946         for (int i = 0; i < stmtPhrase.length; i++) {
947             if (i == 0) {
948                 sb.append(stmtPhrase[0]);
949             } else {
950                 sb.append(SPACE).append(stmtPhrase[i]);
951             }
952         }
953 
954         return sb.toString();
955     }
956 
957     public void setPropertiesOnNode( AstNode columnNode,
958                                      DataType datatype ) {
959         columnNode.setProperty(DATATYPE_NAME, datatype.getName());
960         if (datatype.getLength() >= 0) {
961             columnNode.setProperty(DATATYPE_LENGTH, datatype.getLength());
962         }
963         if (datatype.getPrecision() >= 0) {
964             columnNode.setProperty(DATATYPE_PRECISION, datatype.getPrecision());
965         }
966         if (datatype.getScale() >= 0) {
967             columnNode.setProperty(DATATYPE_SCALE, datatype.getScale());
968         }
969     }
970 }