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             long length = parseBracketedLong(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             long length = parseBracketedLong(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             long length = parseBracketedLong(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             long length = getDefaultLength();
335             if (tokens.matches(L_PAREN)) {
336                 length = parseBracketedLong(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             long length = parseBracketedLong(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             long length = parseBracketedLong(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             long length = parseBracketedLong(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             long length = getDefaultLength();
384             if (tokens.matches(L_PAREN)) {
385                 length = parseBracketedLong(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             long length = getDefaultLength();
393             if (tokens.matches(L_PAREN)) {
394                 length = parseBracketedLong(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             long length = getDefaultLength();
402             if (tokens.matches(L_PAREN)) {
403                 length = parseBracketedLong(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             long length = parseBracketedLong(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             long length = getDefaultLength();
435             if (tokens.matches(L_PAREN)) {
436                 length = parseBracketedLong(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 = (int)parseLong(tokens, dataType);
472                 if (canConsume(tokens, dataType, false, COMMA)) {
473                     scale = (int)parseLong(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 = (int)parseBracketedLong(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 = (int)parseBracketedLong(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 = (int)parseBracketedLong(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 a long value from the input token stream assuming the long is not bracketed with parenthesis.
687      * 
688      * @param tokens
689      * @param dataType
690      * @return the long value
691      */
692     protected long parseLong( DdlTokenStream tokens,
693                               DataType dataType ) {
694         String value = consume(tokens, dataType, false);
695         return parseLong(value);
696     }
697 
698     /**
699      * Returns a long value from the input token stream assuming the long is bracketed with parenthesis.
700      * 
701      * @param tokens
702      * @param dataType
703      * @return the long value
704      */
705     protected long parseBracketedLong( DdlTokenStream tokens,
706                                        DataType dataType ) {
707         consume(tokens, dataType, false, L_PAREN);
708         String value = consume(tokens, dataType, false);
709         consume(tokens, dataType, false, R_PAREN);
710         return parseLong(value);
711     }
712 
713     /**
714      * Returns the integer value of the input string. Handles both straight integer string or complex KMG (CLOB or BLOB) value.
715      * 
716      * @param value
717      * @return integer value
718      * @throws NumberFormatException if a valid integer is not found
719      */
720     protected long parseLong( String value ) {
721         long factor = 1;
722         if (value.endsWith("K")) {
723             factor = KILO;
724         } else if (value.endsWith("M")) {
725             factor = MEGA;
726         } else if (value.endsWith("G")) {
727             factor = GIGA;
728         }
729         if (factor > 1) {
730             value = value.substring(0, value.length() - 1);
731         }
732         return new BigInteger(value).longValue() * factor;
733     }
734 
735     /**
736      * @param tokens
737      * @param dataType
738      * @param addSpacePrefix
739      * @return consumed String value
740      * @throws ParsingException
741      */
742     protected String consume( DdlTokenStream tokens,
743                               DataType dataType,
744                               boolean addSpacePrefix ) throws ParsingException {
745         String value = tokens.consume();
746 
747         dataType.appendSource(addSpacePrefix, value);
748 
749         return value;
750     }
751 
752     /**
753      * @param tokens
754      * @param dataType
755      * @param addSpacePrefix
756      * @param str
757      * @return consumed string value
758      * @throws ParsingException
759      */
760     protected String consume( DdlTokenStream tokens,
761                               DataType dataType,
762                               boolean addSpacePrefix,
763                               String str ) throws ParsingException {
764         tokens.consume(str);
765 
766         dataType.appendSource(addSpacePrefix, str);
767 
768         return str;
769     }
770 
771     /**
772      * @param tokens
773      * @param dataType
774      * @param addSpacePrefix
775      * @param initialStr
776      * @param additionalStrs
777      * @return the consumed String
778      * @throws ParsingException
779      */
780     protected String consume( DdlTokenStream tokens,
781                               DataType dataType,
782                               boolean addSpacePrefix,
783                               String initialStr,
784                               String... additionalStrs ) throws ParsingException {
785         tokens.consume(initialStr, additionalStrs);
786         StringBuffer value = new StringBuffer(initialStr);
787         dataType.appendSource(addSpacePrefix, initialStr);
788 
789         for (String str : additionalStrs) {
790             value.append(SPACE).append(str);
791             dataType.appendSource(addSpacePrefix, str);
792         }
793 
794         return value.toString();
795     }
796 
797     protected String consume( DdlTokenStream tokens,
798                               DataType dataType,
799                               boolean addSpacePrefix,
800                               String[] additionalStrs ) throws ParsingException {
801 
802         tokens.consume(additionalStrs);
803 
804         StringBuffer value = new StringBuffer(100);
805 
806         int i = 0;
807 
808         for (String str : additionalStrs) {
809             if (i == 0) {
810                 value.append(str);
811             } else {
812                 value.append(SPACE).append(str);
813             }
814             dataType.appendSource(addSpacePrefix, str);
815             i++;
816         }
817 
818         return value.toString();
819     }
820 
821     /**
822      * @param tokens
823      * @param dataType
824      * @param addSpacePrefix
825      * @param initialStr
826      * @param additionalStrs
827      * @return did consume
828      * @throws ParsingException
829      */
830     protected boolean canConsume( DdlTokenStream tokens,
831                                   DataType dataType,
832                                   boolean addSpacePrefix,
833                                   String initialStr,
834                                   String... additionalStrs ) throws ParsingException {
835         if (tokens.canConsume(initialStr, additionalStrs)) {
836             dataType.appendSource(addSpacePrefix, initialStr);
837 
838             for (String str : additionalStrs) {
839                 dataType.appendSource(addSpacePrefix, str);
840             }
841             return true;
842         }
843 
844         return false;
845     }
846 
847     /**
848      * @param tokens
849      * @param dataType
850      * @param addSpacePrefix
851      * @param additionalStrs
852      * @return did consume
853      * @throws ParsingException
854      */
855     protected boolean canConsume( DdlTokenStream tokens,
856                                   DataType dataType,
857                                   boolean addSpacePrefix,
858                                   String[] additionalStrs ) throws ParsingException {
859         if (tokens.canConsume(additionalStrs)) {
860 
861             for (String str : additionalStrs) {
862                 dataType.appendSource(addSpacePrefix, str);
863             }
864             return true;
865         }
866 
867         return false;
868     }
869 
870     /**
871      * @param tokens
872      * @param dataType
873      * @param addSpacePrefix
874      * @param type
875      * @return consumed String value
876      * @throws ParsingException
877      */
878     protected boolean canConsume( DdlTokenStream tokens,
879                                   DataType dataType,
880                                   boolean addSpacePrefix,
881                                   int type ) throws ParsingException {
882         if (tokens.matches(type)) {
883             dataType.appendSource(addSpacePrefix, tokens.consume());
884             return true;
885         }
886 
887         return false;
888     }
889 
890     /**
891      * @param tokens
892      * @param dataType
893      * @param addSpacePrefix
894      * @param initialStr
895      * @param additionalStrs
896      * @return did consume any
897      * @throws ParsingException
898      */
899     protected boolean canConsumeAnyOf( DdlTokenStream tokens,
900                                        DataType dataType,
901                                        boolean addSpacePrefix,
902                                        String initialStr,
903                                        String... additionalStrs ) throws ParsingException {
904         if (tokens.canConsume(initialStr)) {
905             dataType.appendSource(addSpacePrefix, initialStr);
906             return true;
907         }
908         for (String str : additionalStrs) {
909             dataType.appendSource(addSpacePrefix, str);
910             return true;
911         }
912 
913         return false;
914     }
915 
916     /**
917      * @param stmtPhrase
918      * @return concatenated name
919      */
920     public String getStatementTypeName( String[] stmtPhrase ) {
921         StringBuffer sb = new StringBuffer(100);
922         for (int i = 0; i < stmtPhrase.length; i++) {
923             if (i == 0) {
924                 sb.append(stmtPhrase[0]);
925             } else {
926                 sb.append(SPACE).append(stmtPhrase[i]);
927             }
928         }
929 
930         return sb.toString();
931     }
932 
933     public void setPropertiesOnNode( AstNode columnNode,
934                                      DataType datatype ) {
935         columnNode.setProperty(DATATYPE_NAME, datatype.getName());
936         if (datatype.getLength() >= 0) {
937             columnNode.setProperty(DATATYPE_LENGTH, datatype.getLength());
938         }
939         if (datatype.getPrecision() >= 0) {
940             columnNode.setProperty(DATATYPE_PRECISION, datatype.getPrecision());
941         }
942         if (datatype.getScale() >= 0) {
943             columnNode.setProperty(DATATYPE_SCALE, datatype.getScale());
944         }
945     }
946 }