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