001/*
002 * ModeShape (http://www.modeshape.org)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *       http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.modeshape.sequencer.ddl.datatype;
017
018import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_ARRAY_DIMENSIONS;
019import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_LENGTH;
020import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_NAME;
021import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_PRECISION;
022import static org.modeshape.sequencer.ddl.StandardDdlLexicon.DATATYPE_SCALE;
023import java.math.BigInteger;
024import java.util.ArrayList;
025import java.util.List;
026import org.modeshape.common.logging.Logger;
027import org.modeshape.common.text.ParsingException;
028import org.modeshape.common.text.TokenStream;
029import org.modeshape.sequencer.ddl.DdlConstants;
030import org.modeshape.sequencer.ddl.DdlTokenStream;
031import org.modeshape.sequencer.ddl.node.AstNode;
032
033/**
034 * A parser for SQL data types.
035 */
036public class DataTypeParser implements DdlConstants {
037
038    private static Logger LOGGER = Logger.getLogger(DataTypeParser.class);
039
040    private static List<String[]> basicCharStringTypes = new ArrayList<String[]>();
041    private static List<String[]> basicNationalCharStringTypes = new ArrayList<String[]>();
042    private static List<String[]> basicBitStringTypes = new ArrayList<String[]>();
043    private static List<String[]> basicExactNumericTypes = new ArrayList<String[]>();
044    private static List<String[]> basicApproxNumericStringTypes = new ArrayList<String[]>();
045    private static List<String[]> basicDateTimeTypes = new ArrayList<String[]>();
046    private static List<String[]> basicMiscTypes = new ArrayList<String[]>();
047
048    private int defaultLength = 255;
049    private int defaultPrecision = 0;
050    private int defaultScale = 0;
051
052    public DataTypeParser() {
053        super();
054
055        initialize();
056    }
057
058    private void initialize() {
059
060        basicCharStringTypes.add(DataTypes.DTYPE_CHARACTER);
061        basicCharStringTypes.add(DataTypes.DTYPE_CHAR);
062        basicCharStringTypes.add(DataTypes.DTYPE_CHARACTER_VARYING);
063        basicCharStringTypes.add(DataTypes.DTYPE_CHAR_VARYING);
064        basicCharStringTypes.add(DataTypes.DTYPE_VARCHAR);
065
066        basicNationalCharStringTypes.add(DataTypes.DTYPE_NCHAR);
067        basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHARACTER);
068        basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING);
069        basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHAR);
070        basicNationalCharStringTypes.add(DataTypes.DTYPE_NATIONAL_CHAR_VARYING);
071        basicNationalCharStringTypes.add(DataTypes.DTYPE_NCHAR_VARYING);
072
073        basicBitStringTypes.add(DataTypes.DTYPE_BIT);
074        basicBitStringTypes.add(DataTypes.DTYPE_BIT_VARYING);
075
076        basicExactNumericTypes.add(DataTypes.DTYPE_NUMERIC);
077        basicExactNumericTypes.add(DataTypes.DTYPE_DEC);
078        basicExactNumericTypes.add(DataTypes.DTYPE_DECIMAL);
079        basicExactNumericTypes.add(DataTypes.DTYPE_INTEGER);
080        basicExactNumericTypes.add(DataTypes.DTYPE_INT);
081        basicExactNumericTypes.add(DataTypes.DTYPE_SMALLINT);
082
083        basicApproxNumericStringTypes.add(DataTypes.DTYPE_FLOAT);
084        basicApproxNumericStringTypes.add(DataTypes.DTYPE_REAL);
085        basicApproxNumericStringTypes.add(DataTypes.DTYPE_DOUBLE_PRECISION);
086
087        basicDateTimeTypes.add(DataTypes.DTYPE_DATE);
088        basicDateTimeTypes.add(DataTypes.DTYPE_TIME);
089        basicDateTimeTypes.add(DataTypes.DTYPE_TIMESTAMP);
090
091        basicMiscTypes.add(DataTypes.DTYPE_INTERVAL);
092
093    }
094
095    /**
096     * Method determines if the next set of tokens matches one of the registered data type token sets.
097     * 
098     * @param tokens
099     * @return is registered data type
100     * @throws ParsingException
101     */
102    public final boolean isDatatype( DdlTokenStream tokens ) throws ParsingException {
103        // Loop through the registered statement start string arrays and look for exact matches.
104
105        for (String[] stmts : basicCharStringTypes) {
106            if (tokens.matches(stmts)) return true;
107        }
108
109        for (String[] stmts : basicNationalCharStringTypes) {
110            if (tokens.matches(stmts)) return true;
111        }
112
113        for (String[] stmts : basicBitStringTypes) {
114            if (tokens.matches(stmts)) return true;
115        }
116
117        for (String[] stmts : basicExactNumericTypes) {
118            if (tokens.matches(stmts)) return true;
119        }
120
121        for (String[] stmts : basicApproxNumericStringTypes) {
122            if (tokens.matches(stmts)) return true;
123        }
124
125        for (String[] stmts : basicDateTimeTypes) {
126            if (tokens.matches(stmts)) return true;
127        }
128
129        for (String[] stmts : basicMiscTypes) {
130            if (tokens.matches(stmts)) return true;
131        }
132
133        // If no type is found, assume it's a custom type
134        return isCustomDataType(tokens);
135    }
136
137    /**
138     * Method determines if the next set of tokens matches one of the registered data type token sets.
139     * 
140     * @param tokens
141     * @param type
142     * @return is registered data type
143     * @throws ParsingException
144     */
145    private boolean isDatatype( DdlTokenStream tokens,
146                                int type ) throws ParsingException {
147        // Loop through the registered statement start string arrays and look for exact matches.
148
149        switch (type) {
150            case DataTypes.DTYPE_CODE_CHAR_STRING: {
151                for (String[] stmts : basicCharStringTypes) {
152                    if (tokens.matches(stmts)) return true;
153                }
154            }
155                break;
156            case DataTypes.DTYPE_CODE_NCHAR_STRING: {
157                for (String[] stmts : basicNationalCharStringTypes) {
158                    if (tokens.matches(stmts)) return true;
159                }
160            }
161                break;
162            case DataTypes.DTYPE_CODE_BIT_STRING: {
163                for (String[] stmts : basicBitStringTypes) {
164                    if (tokens.matches(stmts)) return true;
165                }
166            }
167                break;
168            case DataTypes.DTYPE_CODE_EXACT_NUMERIC: {
169                for (String[] stmts : basicExactNumericTypes) {
170                    if (tokens.matches(stmts)) return true;
171                }
172            }
173                break;
174            case DataTypes.DTYPE_CODE_APROX_NUMERIC: {
175                for (String[] stmts : basicApproxNumericStringTypes) {
176                    if (tokens.matches(stmts)) return true;
177                }
178            }
179                break;
180            case DataTypes.DTYPE_CODE_DATE_TIME: {
181                for (String[] stmts : basicDateTimeTypes) {
182                    if (tokens.matches(stmts)) return true;
183                }
184            }
185                break;
186            case DataTypes.DTYPE_CODE_MISC: {
187                for (String[] stmts : basicMiscTypes) {
188                    if (tokens.matches(stmts)) return true;
189                }
190            }
191                break;
192        }
193
194        return false;
195    }
196
197    /**
198     * Method to determine of next tokens represent a custom data type. Subclasses should override this method and perform token
199     * checks for any non-SQL92 spec'd data types.
200     * 
201     * @param tokens
202     * @return is custom data type
203     * @throws ParsingException
204     */
205    protected boolean isCustomDataType( DdlTokenStream tokens ) throws ParsingException {
206        return false;
207    }
208
209    /**
210     * Method which performs the actual parsing of the data type name and applicable values (i.e. VARCHAR(20)) if data type is
211     * found.
212     * 
213     * @param tokens
214     * @return the {@link DataType}
215     * @throws ParsingException
216     */
217    public DataType parse( DdlTokenStream tokens ) throws ParsingException {
218        DataType result = null;
219
220        if (isDatatype(tokens, DataTypes.DTYPE_CODE_CHAR_STRING)) {
221            result = parseCharStringType(tokens);
222        } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_NCHAR_STRING)) {
223            result = parseNationalCharStringType(tokens);
224        } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_BIT_STRING)) {
225            result = parseBitStringType(tokens);
226        } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_EXACT_NUMERIC)) {
227            result = parseExactNumericType(tokens);
228        } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_APROX_NUMERIC)) {
229            result = parseApproxNumericType(tokens);
230        } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_DATE_TIME)) {
231            result = parseDateTimeType(tokens);
232        } else if (isDatatype(tokens, DataTypes.DTYPE_CODE_MISC)) {
233            result = parseMiscellaneousType(tokens);
234        } else {
235            result = parseCustomType(tokens);
236        }
237
238        /*
239         * (FROM http://www.postgresql.org/docs/8.4/static/arrays.html) 
240        8.14.1. Declaration of Array Types
241
242        To illustrate the use of array types, we create this table:
243
244        CREATE TABLE sal_emp (
245            name            text,
246            pay_by_quarter  integer[],
247            schedule        text[][]
248        );
249
250        As shown, an array data type is named by appending square brackets ([]) to the data type name of the array elements. 
251        The above command will create a table named sal_emp with a column of type text (name), a one-dimensional array of type 
252        integer (pay_by_quarter), which represents the employee's salary by quarter, and a two-dimensional array of text (schedule), 
253        which represents the employee's weekly schedule.
254
255        The syntax for CREATE TABLE allows the exact size of arrays to be specified, for example:
256
257        CREATE TABLE tictactoe (
258            squares   integer[3][3]
259        );
260
261        However, the current implementation ignores any supplied array size limits, i.e., the behavior is the same as for 
262        arrays of unspecified length.
263
264        The current implementation does not enforce the declared number of dimensions either. Arrays of a particular element 
265        type are all considered to be of the same type, regardless of size or number of dimensions. So, declaring the array size 
266        or number of dimensions in CREATE TABLE is simply documentation; it does not affect run-time behavior.
267
268        An alternative syntax, which conforms to the SQL standard by using the keyword ARRAY, can be used for one-dimensional 
269        arrays. pay_by_quarter could have been defined as:
270
271            pay_by_quarter  integer ARRAY[4],
272
273        Or, if no array size is to be specified:
274
275            pay_by_quarter  integer ARRAY,
276        */
277
278        if (tokens.canConsume('[')) {
279            if (!tokens.canConsume(']')) {
280                // assume integer value
281                tokens.consume();
282                tokens.consume(']');
283            }
284
285            if (tokens.canConsume('[')) {
286                if (!tokens.canConsume(']')) {
287                    // assume integer value
288                    tokens.consume();
289                    tokens.consume(']');
290                }
291            }
292        }
293
294        return result;
295    }
296
297    /**
298     * Parses SQL-92 Character string data types. <character string type> ::= CHARACTER [ <left paren> <length> <right paren> ] |
299     * CHAR [ <left paren> <length> <right paren> ] | CHARACTER VARYING <left paren> <length> <right paren> | CHAR VARYING <left
300     * paren> <length> <right paren> | VARCHAR <left paren> <length> <right paren>
301     * 
302     * @param tokens
303     * @return the {@link DataType}
304     * @throws ParsingException
305     */
306    protected DataType parseCharStringType( DdlTokenStream tokens ) throws ParsingException {
307        DataType dataType = null;
308        String typeName = null;
309
310        if (tokens.matches(DataTypes.DTYPE_VARCHAR)) {
311            typeName = getStatementTypeName(DataTypes.DTYPE_VARCHAR);
312            dataType = new DataType(typeName);
313            consume(tokens, dataType, false, DataTypes.DTYPE_VARCHAR);
314            long length = parseBracketedLong(tokens, dataType);
315            dataType.setLength(length);
316        } else if (tokens.matches(DataTypes.DTYPE_CHAR_VARYING)) {
317            typeName = getStatementTypeName(DataTypes.DTYPE_CHAR_VARYING);
318            dataType = new DataType(typeName);
319            consume(tokens, dataType, false, DataTypes.DTYPE_CHAR_VARYING);
320            long length = parseBracketedLong(tokens, dataType);
321            dataType.setLength(length);
322        } else if (tokens.matches(DataTypes.DTYPE_CHARACTER_VARYING)) {
323            typeName = getStatementTypeName(DataTypes.DTYPE_CHARACTER_VARYING);
324            dataType = new DataType(typeName);
325            consume(tokens, dataType, false, DataTypes.DTYPE_CHARACTER_VARYING);
326            long length = parseBracketedLong(tokens, dataType);
327            dataType.setLength(length);
328        } else if (tokens.matches(DataTypes.DTYPE_CHAR) || tokens.matches(DataTypes.DTYPE_CHARACTER)) {
329            dataType = new DataType();
330            typeName = consume(tokens, dataType, false); // "CHARACTER", "CHAR",
331            dataType.setName(typeName);
332            long length = getDefaultLength();
333            if (tokens.matches(L_PAREN)) {
334                length = parseBracketedLong(tokens, dataType);
335            }
336            dataType.setLength(length);
337        }
338
339        return dataType;
340    }
341
342    /**
343     * Parses SQL-92 National Character string data types. <national character string type> ::= NATIONAL CHARACTER [ <left paren>
344     * <length> <right paren> ] | NATIONAL CHAR [ <left paren> <length> <right paren> ] | NCHAR [ <left paren> <length> <right
345     * paren> ] | NATIONAL CHARACTER VARYING <left paren> <length> <right paren> | NATIONAL CHAR VARYING <left paren> <length>
346     * <right paren> | NCHAR VARYING <left paren> <length> <right paren>
347     * 
348     * @param tokens
349     * @return the {@link DataType}
350     * @throws ParsingException
351     */
352    protected DataType parseNationalCharStringType( DdlTokenStream tokens ) throws ParsingException {
353        DataType dataType = null;
354        String typeName = null;
355
356        if (tokens.matches(DataTypes.DTYPE_NCHAR_VARYING)) {
357            typeName = getStatementTypeName(DataTypes.DTYPE_NCHAR_VARYING);
358            dataType = new DataType(typeName);
359            consume(tokens, dataType, false, DataTypes.DTYPE_NCHAR_VARYING);
360            long length = parseBracketedLong(tokens, dataType);
361
362            dataType.setLength(length);
363        } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHAR_VARYING)) {
364            typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHAR_VARYING);
365            dataType = new DataType(typeName);
366            consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHAR_VARYING);
367            long length = parseBracketedLong(tokens, dataType);
368
369            dataType.setLength(length);
370        } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING)) {
371            typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING);
372            dataType = new DataType(typeName);
373            consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHARACTER_VARYING);
374            long length = parseBracketedLong(tokens, dataType);
375
376            dataType.setLength(length);
377        } else if (tokens.matches(DataTypes.DTYPE_NCHAR)) {
378            typeName = getStatementTypeName(DataTypes.DTYPE_NCHAR);
379            dataType = new DataType(typeName);
380            consume(tokens, dataType, false, DataTypes.DTYPE_NCHAR);
381            long length = getDefaultLength();
382            if (tokens.matches(L_PAREN)) {
383                length = parseBracketedLong(tokens, dataType);
384            }
385            dataType.setLength(length);
386        } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHAR)) {
387            typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHAR);
388            dataType = new DataType(typeName);
389            consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHAR);
390            long length = getDefaultLength();
391            if (tokens.matches(L_PAREN)) {
392                length = parseBracketedLong(tokens, dataType);
393            }
394            dataType.setLength(length);
395        } else if (tokens.matches(DataTypes.DTYPE_NATIONAL_CHARACTER)) {
396            typeName = getStatementTypeName(DataTypes.DTYPE_NATIONAL_CHARACTER);
397            dataType = new DataType(typeName);
398            consume(tokens, dataType, false, DataTypes.DTYPE_NATIONAL_CHARACTER);
399            long length = getDefaultLength();
400            if (tokens.matches(L_PAREN)) {
401                length = parseBracketedLong(tokens, dataType);
402            }
403            dataType.setLength(length);
404        }
405
406        return dataType;
407    }
408
409    /**
410     * Parses SQL-92 Bit string data types. <bit string type> ::= BIT [ <left paren> <length> <right paren> ] | BIT VARYING <left
411     * paren> <length> <right paren>
412     * 
413     * @param tokens
414     * @return the {@link DataType}
415     * @throws ParsingException
416     */
417    protected DataType parseBitStringType( DdlTokenStream tokens ) throws ParsingException {
418        DataType dataType = null;
419        String typeName = null;
420
421        if (tokens.matches(DataTypes.DTYPE_BIT_VARYING)) {
422            typeName = getStatementTypeName(DataTypes.DTYPE_BIT_VARYING);
423            dataType = new DataType(typeName);
424            consume(tokens, dataType, false, DataTypes.DTYPE_BIT_VARYING);
425            long length = parseBracketedLong(tokens, dataType);
426
427            dataType.setLength(length);
428        } else if (tokens.matches(DataTypes.DTYPE_BIT)) {
429            typeName = getStatementTypeName(DataTypes.DTYPE_BIT);
430            dataType = new DataType(typeName);
431            consume(tokens, dataType, false, DataTypes.DTYPE_BIT);
432            long length = getDefaultLength();
433            if (tokens.matches(L_PAREN)) {
434                length = parseBracketedLong(tokens, dataType);
435            }
436            dataType.setLength(length);
437        }
438
439        return dataType;
440    }
441
442    /**
443     * Parses SQL-92 Exact numeric data types. <exact numeric type> ::= NUMERIC [ <left paren> <precision> [ <comma> <scale> ]
444     * <right paren> ] | DECIMAL [ <left paren> <precision> [ <comma> <scale> ] <right paren> ] | DEC [ <left paren> <precision> [
445     * <comma> <scale> ] <right paren> ] | INTEGER | INT | SMALLINT
446     * 
447     * @param tokens
448     * @return the {@link DataType}
449     * @throws ParsingException
450     */
451    protected DataType parseExactNumericType( DdlTokenStream tokens ) throws ParsingException {
452        DataType dataType = null;
453        String typeName = null;
454
455        if (tokens.matchesAnyOf("INTEGER", "INT", "SMALLINT")) {
456            dataType = new DataType();
457            typeName = consume(tokens, dataType, false);
458            dataType.setName(typeName);
459        } else if (tokens.matchesAnyOf("NUMERIC", "DECIMAL", "DEC")) {
460            dataType = new DataType();
461            typeName = consume(tokens, dataType, false);
462            dataType.setName(typeName);
463
464            int precision = 0;
465            int scale = 0;
466
467            if (tokens.matches(L_PAREN)) {
468                consume(tokens, dataType, false, L_PAREN);
469                precision = (int)parseLong(tokens, dataType);
470                if (canConsume(tokens, dataType, false, COMMA)) {
471                    scale = (int)parseLong(tokens, dataType);
472                } else {
473                    scale = getDefaultScale();
474                }
475                consume(tokens, dataType, false, R_PAREN);
476            } else {
477                precision = getDefaultPrecision();
478                scale = getDefaultScale();
479            }
480            dataType.setPrecision(precision);
481            dataType.setScale(scale);
482        }
483
484        return dataType;
485    }
486
487    /**
488     * Parses SQL-92 Approximate numeric data types. <approximate numeric type> ::= FLOAT [ <left paren> <precision> <right paren>
489     * ] | REAL | DOUBLE PRECISION
490     * 
491     * @param tokens
492     * @return the {@link DataType}
493     * @throws ParsingException
494     */
495    protected DataType parseApproxNumericType( DdlTokenStream tokens ) throws ParsingException {
496        DataType dataType = null;
497        String typeName = null;
498
499        if (tokens.matches(DataTypes.DTYPE_REAL)) {
500            dataType = new DataType();
501            typeName = consume(tokens, dataType, false, DataTypes.DTYPE_REAL);
502            dataType.setName(typeName);
503        } else if (tokens.matches(DataTypes.DTYPE_DOUBLE_PRECISION)) {
504            dataType = new DataType();
505            typeName = consume(tokens, dataType, false, DataTypes.DTYPE_DOUBLE_PRECISION);
506            dataType.setName(typeName);
507        } else if (tokens.matches(DataTypes.DTYPE_FLOAT)) {
508            dataType = new DataType();
509            typeName = consume(tokens, dataType, false, DataTypes.DTYPE_FLOAT);
510            dataType.setName(typeName);
511            int precision = 0;
512            if (tokens.matches(L_PAREN)) {
513                precision = (int)parseBracketedLong(tokens, dataType);
514            }
515            dataType.setPrecision(precision);
516        }
517
518        return dataType;
519    }
520
521    /**
522     * Parses SQL-92 Date and Time data types. <datetime type> ::= DATE | TIME [ <left paren> <time precision> <right paren> ] [
523     * WITH TIME ZONE ] | TIMESTAMP [ <left paren> <timestamp precision> <right paren> ] [ WITH TIME ZONE ]
524     * 
525     * @param tokens
526     * @return the {@link DataType}
527     * @throws ParsingException
528     */
529    protected DataType parseDateTimeType( DdlTokenStream tokens ) throws ParsingException {
530        DataType dataType = null;
531        String typeName = null;
532
533        if (tokens.matches(DataTypes.DTYPE_DATE)) {
534            dataType = new DataType();
535            typeName = consume(tokens, dataType, false, DataTypes.DTYPE_DATE);
536            dataType.setName(typeName);
537        } else if (tokens.matches(DataTypes.DTYPE_TIME)) {
538            dataType = new DataType();
539            typeName = consume(tokens, dataType, false, DataTypes.DTYPE_TIME);
540            dataType.setName(typeName);
541
542            int precision = 0;
543            if (tokens.matches(L_PAREN)) {
544                precision = (int)parseBracketedLong(tokens, dataType);
545            }
546            dataType.setPrecision(precision);
547
548            canConsume(tokens, dataType, true, "WITH", "TIME", "ZONE");
549        } else if (tokens.matches(DataTypes.DTYPE_TIMESTAMP)) {
550            dataType = new DataType();
551            typeName = consume(tokens, dataType, false, DataTypes.DTYPE_TIMESTAMP);
552            dataType.setName(typeName);
553
554            int precision = 0;
555            if (tokens.matches(L_PAREN)) {
556                precision = (int)parseBracketedLong(tokens, dataType);
557            }
558            dataType.setPrecision(precision);
559
560            canConsume(tokens, dataType, true, "WITH", "TIME", "ZONE");
561        }
562
563        return dataType;
564    }
565
566    /**
567     * Parses SQL-92 Misc data types. <interval type> ::= INTERVAL <interval qualifier> <interval qualifier> ::= <start field> TO
568     * <end field> | <single datetime field> <start field> ::= <non-second datetime field> [ <left paren> <interval leading field
569     * precision> <right paren> ] <non-second datetime field> ::= YEAR | MONTH | DAY | HOUR | MINUTE <interval leading field
570     * precision> ::= <unsigned integer> <end field> ::= <non-second datetime field> | SECOND [ <left paren> <interval fractional
571     * seconds precision> <right paren> ] <interval fractional seconds precision> ::= <unsigned integer> <single datetime field>
572     * ::= <non-second datetime field> [ <left paren> <interval leading field precision> <right paren> ] | SECOND [ <left paren>
573     * <interval leading field precision> [ <comma> <interval fractional seconds precision> ] <right paren> ]
574     * 
575     * @param tokens
576     * @return the {@link DataType}
577     * @throws ParsingException
578     */
579    protected DataType parseMiscellaneousType( DdlTokenStream tokens ) throws ParsingException {
580        DataType dataType = null;
581        String typeName = null;
582
583        if (tokens.matches(DataTypes.DTYPE_INTERVAL)) {
584            dataType = new DataType();
585            typeName = consume(tokens, dataType, false, DataTypes.DTYPE_INTERVAL);
586            dataType.setName(typeName);
587            // <non-second datetime field> TO <end field>
588            //
589            // CASE 2a: { YEAR | MONTH | DAY | HOUR | MINUTE } [ [ <left paren> <interval leading field precision> <right paren> ]
590            // CASE 2b: SECOND [ <left paren> <interval leading field precision> [ <comma> <interval fractional seconds precision>
591            // ] <right paren> ]
592
593            // CASE 1: { YEAR | MONTH | DAY | HOUR | MINUTE } TO { YEAR | MONTH | DAY | HOUR | MINUTE }
594            if (tokens.matchesAnyOf("YEAR", "MONTH", "DAY", "HOUR", "MINUTE")) {
595                // Consume first
596                consume(tokens, dataType, true);
597
598                if (canConsume(tokens, dataType, true, "TO")) {
599                    // CASE 1:
600                    // assume "YEAR | MONTH | DAY | HOUR | MINUTE" and consume
601                    consume(tokens, dataType, true);
602                } else if (tokens.matches(L_PAREN, TokenStream.ANY_VALUE, R_PAREN)) {
603                    // CASE 2a:
604                    consume(tokens, dataType, true, L_PAREN);
605                    consume(tokens, dataType, true);
606                    consume(tokens, dataType, true, R_PAREN);
607                } else {
608                    LOGGER.debug("  WARNING:  PROBLEM parsing INTERVAL data type. Check your DDL for incomplete statement.");
609                }
610            } else if (canConsume(tokens, dataType, true, "SECOND")) {
611                // CASE 2b:
612                if (canConsume(tokens, dataType, true, L_PAREN)) {
613
614                    consume(tokens, dataType, true); // PRECISION
615                    if (canConsume(tokens, dataType, true, COMMA)) {
616                        consume(tokens, dataType, true); // fractional seconds precision
617                    }
618                    canConsume(tokens, dataType, true, R_PAREN);
619                } else {
620                    LOGGER.debug("  WARNING:  PROBLEM parsing INTERVAL data type. Check your DDL for incomplete statement.");
621                }
622            } else {
623                LOGGER.debug("  WARNING:  PROBLEM parsing INTERVAL data type. Check your DDL for incomplete statement.");
624            }
625        }
626
627        return dataType;
628    }
629
630    /**
631     * General catch-all data type parsing method that sub-classes can override to parse database-specific data types.
632     * 
633     * @param tokens
634     * @return the {@link DataType}
635     * @throws ParsingException
636     */
637    protected DataType parseCustomType( DdlTokenStream tokens ) throws ParsingException {
638        return null;
639    }
640
641    /**
642     * @return integer default value for length
643     */
644    public int getDefaultLength() {
645        return defaultLength;
646    }
647
648    /**
649     * @param defaultLength
650     */
651    public void setDefaultLength( int defaultLength ) {
652        this.defaultLength = defaultLength;
653    }
654
655    /**
656     * @return integer default value for precision
657     */
658    public int getDefaultPrecision() {
659        return defaultPrecision;
660    }
661
662    /**
663     * @param defaultPrecision
664     */
665    public void setDefaultPrecision( int defaultPrecision ) {
666        this.defaultPrecision = defaultPrecision;
667    }
668
669    /**
670     * @return integer default value for scale
671     */
672    public int getDefaultScale() {
673        return defaultScale;
674    }
675
676    /**
677     * @param defaultScale
678     */
679    public void setDefaultScale( int defaultScale ) {
680        this.defaultScale = defaultScale;
681    }
682
683    /**
684     * Returns a long value from the input token stream assuming the long is not bracketed with parenthesis.
685     * 
686     * @param tokens
687     * @param dataType
688     * @return the long value
689     */
690    protected long parseLong( DdlTokenStream tokens,
691                              DataType dataType ) {
692        String value = consume(tokens, dataType, false);
693        return parseLong(value);
694    }
695
696    /**
697     * Returns a long value from the input token stream assuming the long is bracketed with parenthesis.
698     * 
699     * @param tokens
700     * @param dataType
701     * @return the long value
702     */
703    protected long parseBracketedLong( DdlTokenStream tokens,
704                                       DataType dataType ) {
705        consume(tokens, dataType, false, L_PAREN);
706        String value = consume(tokens, dataType, false);
707        consume(tokens, dataType, false, R_PAREN);
708        return parseLong(value);
709    }
710
711    /**
712     * Returns the integer value of the input string. Handles both straight integer string or complex KMG (CLOB or BLOB) value.
713     * 
714     * @param value
715     * @return integer value
716     * @throws NumberFormatException if a valid integer is not found
717     */
718    protected long parseLong( String value ) {
719        long factor = 1;
720        if (value.endsWith("K")) {
721            factor = KILO;
722        } else if (value.endsWith("M")) {
723            factor = MEGA;
724        } else if (value.endsWith("G")) {
725            factor = GIGA;
726        }
727        if (factor > 1) {
728            value = value.substring(0, value.length() - 1);
729        }
730        return new BigInteger(value).longValue() * factor;
731    }
732
733    /**
734     * @param tokens
735     * @param dataType
736     * @param addSpacePrefix
737     * @return consumed String value
738     * @throws ParsingException
739     */
740    protected String consume( DdlTokenStream tokens,
741                              DataType dataType,
742                              boolean addSpacePrefix ) throws ParsingException {
743        String value = tokens.consume();
744
745        dataType.appendSource(addSpacePrefix, value);
746
747        return value;
748    }
749
750    /**
751     * @param tokens
752     * @param dataType
753     * @param addSpacePrefix
754     * @param str
755     * @return consumed string value
756     * @throws ParsingException
757     */
758    protected String consume( DdlTokenStream tokens,
759                              DataType dataType,
760                              boolean addSpacePrefix,
761                              String str ) throws ParsingException {
762        tokens.consume(str);
763
764        dataType.appendSource(addSpacePrefix, str);
765
766        return str;
767    }
768
769    /**
770     * @param tokens
771     * @param dataType
772     * @param addSpacePrefix
773     * @param initialStr
774     * @param additionalStrs
775     * @return the consumed String
776     * @throws ParsingException
777     */
778    protected String consume( DdlTokenStream tokens,
779                              DataType dataType,
780                              boolean addSpacePrefix,
781                              String initialStr,
782                              String... additionalStrs ) throws ParsingException {
783        tokens.consume(initialStr, additionalStrs);
784        StringBuilder value = new StringBuilder(initialStr);
785        dataType.appendSource(addSpacePrefix, initialStr);
786
787        for (String str : additionalStrs) {
788            value.append(SPACE).append(str);
789            dataType.appendSource(addSpacePrefix, str);
790        }
791
792        return value.toString();
793    }
794
795    protected String consume( DdlTokenStream tokens,
796                              DataType dataType,
797                              boolean addSpacePrefix,
798                              String[] additionalStrs ) throws ParsingException {
799
800        tokens.consume(additionalStrs);
801
802        StringBuilder value = new StringBuilder(100);
803
804        int i = 0;
805
806        for (String str : additionalStrs) {
807            if (i == 0) {
808                value.append(str);
809            } else {
810                value.append(SPACE).append(str);
811            }
812            dataType.appendSource(addSpacePrefix, str);
813            i++;
814        }
815
816        return value.toString();
817    }
818
819    /**
820     * @param tokens
821     * @param dataType
822     * @param addSpacePrefix
823     * @param initialStr
824     * @param additionalStrs
825     * @return did consume
826     * @throws ParsingException
827     */
828    protected boolean canConsume( DdlTokenStream tokens,
829                                  DataType dataType,
830                                  boolean addSpacePrefix,
831                                  String initialStr,
832                                  String... additionalStrs ) throws ParsingException {
833        if (tokens.canConsume(initialStr, additionalStrs)) {
834            dataType.appendSource(addSpacePrefix, initialStr);
835
836            for (String str : additionalStrs) {
837                dataType.appendSource(addSpacePrefix, str);
838            }
839            return true;
840        }
841
842        return false;
843    }
844
845    /**
846     * @param tokens
847     * @param dataType
848     * @param addSpacePrefix
849     * @param additionalStrs
850     * @return did consume
851     * @throws ParsingException
852     */
853    protected boolean canConsume( DdlTokenStream tokens,
854                                  DataType dataType,
855                                  boolean addSpacePrefix,
856                                  String[] additionalStrs ) throws ParsingException {
857        if (tokens.canConsume(additionalStrs)) {
858
859            for (String str : additionalStrs) {
860                dataType.appendSource(addSpacePrefix, str);
861            }
862            return true;
863        }
864
865        return false;
866    }
867
868    /**
869     * @param tokens
870     * @param dataType
871     * @param addSpacePrefix
872     * @param type
873     * @return consumed String value
874     * @throws ParsingException
875     */
876    protected boolean canConsume( DdlTokenStream tokens,
877                                  DataType dataType,
878                                  boolean addSpacePrefix,
879                                  int type ) throws ParsingException {
880        if (tokens.matches(type)) {
881            dataType.appendSource(addSpacePrefix, tokens.consume());
882            return true;
883        }
884
885        return false;
886    }
887
888    /**
889     * @param tokens
890     * @param dataType
891     * @param addSpacePrefix
892     * @param initialStr
893     * @param additionalStrs
894     * @return did consume any
895     * @throws ParsingException
896     */
897    protected boolean canConsumeAnyOf( DdlTokenStream tokens,
898                                       DataType dataType,
899                                       boolean addSpacePrefix,
900                                       String initialStr,
901                                       String... additionalStrs ) throws ParsingException {
902        if (tokens.canConsume(initialStr)) {
903            dataType.appendSource(addSpacePrefix, initialStr);
904            return true;
905        }
906        for (String str : additionalStrs) {
907            dataType.appendSource(addSpacePrefix, str);
908            return true;
909        }
910
911        return false;
912    }
913
914    /**
915     * @param stmtPhrase
916     * @return concatenated name
917     */
918    public String getStatementTypeName( String[] stmtPhrase ) {
919        StringBuilder sb = new StringBuilder(100);
920        for (int i = 0; i < stmtPhrase.length; i++) {
921            if (i == 0) {
922                sb.append(stmtPhrase[0]);
923            } else {
924                sb.append(SPACE).append(stmtPhrase[i]);
925            }
926        }
927
928        return sb.toString();
929    }
930
931    public void setPropertiesOnNode( AstNode columnNode,
932                                     DataType datatype ) {
933        columnNode.setProperty(DATATYPE_NAME, datatype.getName());
934        if (datatype.getLength() >= 0) {
935            columnNode.setProperty(DATATYPE_LENGTH, datatype.getLength());
936        }
937        if (datatype.getPrecision() >= 0) {
938            columnNode.setProperty(DATATYPE_PRECISION, datatype.getPrecision());
939        }
940        if (datatype.getScale() >= 0) {
941            columnNode.setProperty(DATATYPE_SCALE, datatype.getScale());
942        }
943        if (datatype.getArrayDimensions() >= 0) {
944            columnNode.setProperty(DATATYPE_ARRAY_DIMENSIONS, datatype.getArrayDimensions());
945        }
946    }
947}