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}