001package gudusoft.gsqlparser.nodes; 002 003import gudusoft.gsqlparser.*; 004import gudusoft.gsqlparser.scriptWriter.TScriptGenerator; 005import gudusoft.gsqlparser.sqlenv.TSQLEnv; 006import gudusoft.gsqlparser.util.gspMD5Utils; 007 008import java.util.ArrayList; 009import java.util.Iterator; 010 011import static java.nio.charset.StandardCharsets.UTF_8; 012 013/** 014 * <h1>TParseTreeNode - Core Parse Tree Node Implementation</h1> 015 * 016 * <h2>Overview</h2> 017 * This class is the root class for all syntax/parse tree nodes in the GSP SQL Parser. 018 * A parse tree node represents a syntactic element of a SQL statement, composed of a sequence 019 * of tokens generated by the lexer. The node maintains references to its start and end tokens 020 * in a double-linked token chain, enabling both traversal and modification of the SQL text. 021 * 022 * <h2>Architecture and Design</h2> 023 * 024 * <h3>Token Chain Structure</h3> 025 * The parse tree is built on a double-linked list of {@link TSourceToken} objects. Each node: 026 * <ul> 027 * <li>Has a {@code startToken} and {@code endToken} that define its boundaries</li> 028 * <li>Can iterate through its tokens using the token chain links</li> 029 * <li>Maintains relationships with other nodes that may share the same tokens</li> 030 * </ul> 031 * 032 * <h3>Critical: Node Nesting and Token Sharing</h3> 033 * <b>Nodes in the parse tree are nested and share tokens.</b> This is fundamental to the design: 034 * <ul> 035 * <li>A WHERE clause node contains an expression node, both sharing the same tokens</li> 036 * <li>A SELECT statement contains multiple clauses, all sharing tokens from the same chain</li> 037 * <li>Each token maintains stacks of nodes that start/end on it (via {@code nodesStartFromThisToken} 038 * and {@code nodesEndWithThisToken})</li> 039 * </ul> 040 * 041 * <h4>Token Sharing Example:</h4> 042 * <pre> 043 * SQL: "SELECT * FROM table WHERE id = 1" 044 * 045 * - SelectStatement node: [SELECT...1] 046 * - SelectList node: [*] 047 * - FromClause node: [FROM table] 048 * - WhereClause node: [WHERE id = 1] 049 * - Expression node: [id = 1] 050 * - LeftOperand: [id] 051 * - Operator: [=] 052 * - RightOperand: [1] 053 * </pre> 054 * 055 * <h3>Modification Strategy for Nested Nodes</h3> 056 * 057 * <h4>Core Rules:</h4> 058 * <ol> 059 * <li><b>Leaf-First Modification:</b> Always modify the most specific (leaf) node when possible</li> 060 * <li><b>Status Propagation:</b> When a node is modified, all overlapping nodes are updated: 061 * <ul> 062 * <li>{@code nsDetached}: Node's tokens were partially modified</li> 063 * <li>{@code nsPartitial}: Node contains modified sub-nodes</li> 064 * <li>{@code nsRemoved}: Node's tokens were completely removed</li> 065 * </ul> 066 * </li> 067 * <li><b>Chain Integrity:</b> The token chain must remain valid after any modification</li> 068 * </ol> 069 * 070 * <h4>Modification Scenarios and Handling:</h4> 071 * 072 * <b>Scenario 1: Modifying a Leaf Node</b> 073 * <pre> 074 * // Original: WHERE id = 1 075 * // Modify the right operand from "1" to "100" 076 * rightOperandNode.setString("100"); 077 * 078 * Result: 079 * - RightOperand node: replaced with new token "100" 080 * - Expression node: status = nsPartitial (contains modified child) 081 * - WhereClause node: status = nsPartitial (contains modified descendant) 082 * </pre> 083 * 084 * <b>Scenario 2: Modifying a Parent Node</b> 085 * <pre> 086 * // Original: WHERE id = 1 087 * // Replace entire WHERE clause 088 * whereClauseNode.setString("WHERE status = 'active'"); 089 * 090 * Result: 091 * - All child nodes (Expression, operands): status = nsDetached 092 * - WhereClause node: completely replaced with new tokens 093 * - SelectStatement: updated to reference new WHERE tokens 094 * </pre> 095 * 096 * <b>Scenario 3: Removing a Node</b> 097 * <pre> 098 * // Remove WHERE clause entirely 099 * whereClauseNode.removeTokens(); 100 * 101 * Result: 102 * - WhereClause and all children: status = nsRemoved 103 * - Tokens removed from chain 104 * - Parent SelectStatement: adjusted boundaries 105 * </pre> 106 * 107 * <h3>Best Practices for API Users</h3> 108 * 109 * <ol> 110 * <li><b>Check Node Status Before Modification:</b> 111 * <pre> 112 * if (node.getNodeStatus() == ENodeStatus.nsNormal) { 113 * // Safe to modify 114 * node.setString(newText); 115 * } 116 * </pre> 117 * </li> 118 * 119 * <li><b>Prefer Leaf Node Modifications:</b> 120 * <pre> 121 * // GOOD: Modify specific value 122 * tableNameNode.setString("new_table"); 123 * 124 * // AVOID: Replacing entire FROM clause when only table name needs change 125 * fromClause.setString("FROM new_table"); 126 * </pre> 127 * </li> 128 * 129 * <li><b>Use toScript() for Complex Modifications:</b> 130 * <pre> 131 * // After multiple modifications, use script generator 132 * if (statement.isChanged()) { 133 * String result = statement.toScript(); 134 * } 135 * </pre> 136 * </li> 137 * 138 * <li><b>Understand Cascading Effects:</b> 139 * <pre> 140 * // Modifying a node affects all overlapping nodes 141 * expression.setString("new_condition"); 142 * // Parent WHERE clause is now marked as modified 143 * assert whereClause.getNodeStatus() == ENodeStatus.nsPartitial; 144 * </pre> 145 * </li> 146 * </ol> 147 * 148 * <h3>Text Modification Mechanism</h3> 149 * The node supports dynamic text modification through the {@link #setString(String)} method: 150 * <ol> 151 * <li><b>Tokenization:</b> New text is tokenized using {@link TSingletonParser} to create new tokens</li> 152 * <li><b>Chain Replacement:</b> Old tokens are removed from the chain and replaced with new ones</li> 153 * <li><b>Relationship Updates:</b> All nodes sharing the modified tokens are updated: 154 * <ul> 155 * <li>{@code updateNodeWithTheSameStartToken()}: Updates nodes starting at modified position</li> 156 * <li>{@code updateMeNodeWithTheSameEndToken()}: Updates nodes ending at modified position</li> 157 * <li>{@code updateStatusOfNodeShareSameTokens()}: Propagates status changes</li> 158 * </ul> 159 * </li> 160 * <li><b>Status Tracking:</b> Node status changes to track modifications</li> 161 * </ol> 162 * 163 * <h3>String Reconstruction ({@link #toString()})</h3> 164 * The toString() method uses a sophisticated two-pass algorithm when {@code doubleLinkedTokenListToString} is true: 165 * <ul> 166 * <li><b>First Pass:</b> Detects chain modifications and marks redundant newlines for deletion</li> 167 * <li><b>Second Pass:</b> Builds the final string by concatenating non-deleted tokens</li> 168 * </ul> 169 * This approach cleanly handles both original parsed text and user modifications. 170 * 171 * <h2>Current Implementation Trade-offs</h2> 172 * 173 * <h3>Design Decisions (Simplicity over Features)</h3> 174 * <ul> 175 * <li><b>Re-tokenization on Every Modification:</b> Simple but potentially expensive. 176 * Accepted trade-off for reliability and consistency.</li> 177 * <li><b>No Partial Token Modification:</b> Cannot modify part of a token; must replace entire token. 178 * Keeps token integrity and chain management simple.</li> 179 * <li><b>Status-based Conflict Resolution:</b> When nodes overlap, status flags determine behavior. 180 * Simple rule-based system rather than complex conflict resolution.</li> 181 * <li><b>Single Token Chain:</b> All nodes reference the same global chain. 182 * Simpler than managing multiple chains but requires careful synchronization.</li> 183 * </ul> 184 * 185 * <h3>Limitations by Design</h3> 186 * <ul> 187 * <li><b>Cannot Preserve Comments in Modified Nodes:</b> When a node is replaced via setString(), 188 * internal comments are lost. This is acceptable for most use cases.</li> 189 * <li><b>No Undo/Redo:</b> Modifications are permanent. Applications must manage their own undo stack.</li> 190 * <li><b>Thread Safety:</b> Not thread-safe by design. Simpler implementation, caller must synchronize.</li> 191 * <li><b>Memory vs Speed:</b> Keeps all tokens in memory for fast access. Not optimized for very large SQLs.</li> 192 * </ul> 193 * 194 * <h2>Implementation Details</h2> 195 * 196 * <h3>Token Sharing Management</h3> 197 * When modifying a node with shared tokens: 198 * <ol> 199 * <li>Calculate token count for all affected nodes ({@code refreshAllNodesTokenCount()})</li> 200 * <li>Identify nodes with same start/end tokens</li> 201 * <li>Update node boundaries based on token count comparison: 202 * <ul> 203 * <li>Same count = sibling node, needs same update</li> 204 * <li>Larger count = parent node, needs boundary adjustment</li> 205 * <li>Smaller count = child node, may become detached</li> 206 * </ul> 207 * </li> 208 * <li>Splice new tokens into chain maintaining prev/next links</li> 209 * </ol> 210 * 211 * <h3>Status Flags and Their Meanings</h3> 212 * <ul> 213 * <li>{@code nsNormal}: Unmodified node with original tokens</li> 214 * <li>{@code nsFlattened}: Node created from user text (no original tokens)</li> 215 * <li>{@code nsPartitial}: Contains modified child nodes but own tokens unchanged</li> 216 * <li>{@code nsDetached}: Tokens modified by parent/sibling operation</li> 217 * <li>{@code nsRemoved}: Tokens removed from chain, node is defunct</li> 218 * </ul> 219 * 220 * <h2>Proposed Future Improvements</h2> 221 * 222 * <h3>1. Decouple Lexer Reference</h3> 223 * <pre> 224 * // Instead of token->lexer reference, use a TokenContext: 225 * class TokenContext { 226 * EDbVendor vendor; 227 * SourceBuffer sourceBuffer; 228 * int sourceId; // Identifies original vs modified source 229 * } 230 * </pre> 231 * 232 * <h3>2. Implement SourceBuffer System</h3> 233 * <pre> 234 * class SourceBuffer { 235 * List<TextSegment> segments; 236 * 237 * class TextSegment { 238 * String text; 239 * SourceType type; // ORIGINAL, USER_MODIFIED, GENERATED 240 * int startOffset; 241 * int endOffset; 242 * } 243 * 244 * String getText(int segmentId, int start, int end); 245 * int addSegment(String text, SourceType type); 246 * } 247 * </pre> 248 * 249 * <h3>3. Token Source Tracking</h3> 250 * <pre> 251 * class TSourceToken { 252 * int sourceBufferId; 253 * int segmentId; 254 * int startOffset; 255 * int endOffset; 256 * SourceType sourceType; 257 * 258 * String getText() { 259 * return SourceBufferManager.getBuffer(sourceBufferId) 260 * .getText(segmentId, startOffset, endOffset); 261 * } 262 * } 263 * </pre> 264 * 265 * <h3>4. Lazy Token Creation</h3> 266 * <ul> 267 * <li>Don't re-tokenize for simple text replacements</li> 268 * <li>Create synthetic tokens only when necessary</li> 269 * <li>Reuse existing tokens when possible</li> 270 * </ul> 271 * 272 * <h3>5. Unified Modification API</h3> 273 * <pre> 274 * class NodeModification { 275 * ModificationType type; // REPLACE, INSERT, DELETE 276 * String newText; 277 * boolean preserveFormatting; 278 * boolean retokenize; 279 * } 280 * 281 * public void modifyNode(NodeModification mod) { 282 * // Single entry point for all modifications 283 * } 284 * </pre> 285 * 286 * <h2>Critical Methods Documentation</h2> 287 * 288 * <h3>{@link #setString(String)}</h3> 289 * Main entry point for modifying node text. Process: 290 * <ol> 291 * <li>Tokenizes new text using TSingletonParser</li> 292 * <li>Creates new token chain from tokenized text</li> 293 * <li>Replaces old tokens in the chain</li> 294 * <li>Updates all affected node relationships</li> 295 * </ol> 296 * 297 * <h3>{@link #toString()}</h3> 298 * Reconstructs text from token chain. Two-pass algorithm: 299 * <ol> 300 * <li><b>Pass 1:</b> Detect modifications and mark redundant formatting</li> 301 * <li><b>Pass 2:</b> Build string from valid tokens</li> 302 * </ol> 303 * 304 * <h3>{@link #setText(TSourceToken, TSourceToken)}</h3> 305 * Low-level token replacement. Core of the modification system: 306 * <ul> 307 * <li>Updates token chain links</li> 308 * <li>Maintains node relationships</li> 309 * <li>Handles edge cases (null tokens, chain boundaries)</li> 310 * </ul> 311 * 312 * <h2>Usage Patterns</h2> 313 * 314 * <h3>Reading Original Text</h3> 315 * <pre> 316 * TParseTreeNode node = ...; 317 * String originalText = node.toString(); // Get text from token chain 318 * </pre> 319 * 320 * <h3>Modifying Node Text</h3> 321 * <pre> 322 * node.setString("new SQL text"); // Replace node's text 323 * String modifiedText = node.toString(); // Get modified text 324 * </pre> 325 * 326 * <h3>Checking Modifications</h3> 327 * <pre> 328 * if (node.isChanged()) { 329 * String newText = node.toScript(); // Use script generator for complex mods 330 * } 331 * </pre> 332 * 333 * <h2>Thread Safety</h2> 334 * This class is NOT thread-safe. Concurrent modifications to the token chain 335 * or node structure may result in inconsistent state or corruption. 336 * 337 * <h2>Performance Considerations</h2> 338 * <ul> 339 * <li>Token chain traversal is O(n) where n is number of tokens</li> 340 * <li>Text modification requires re-tokenization (expensive)</li> 341 * <li>toString() performs two passes over tokens</li> 342 * <li>Node relationship updates can cascade to many nodes</li> 343 * </ul> 344 * 345 * @see TSourceToken 346 * @see TSingletonParser 347 * @see TScriptGenerator 348 * @since 1.0 349 */ 350public abstract class TParseTreeNode implements Visitable,Iterator<TSourceToken>{ 351 352 public void setEvaluateDatatype(Class<?> evaluateDatatype) { 353 this.evaluateDatatype = evaluateDatatype; 354 } 355 356 /** 357 * Support String, Integer, Double and Boolean type 358 * 359 * @return 360 */ 361 public Class<?> getEvaluateDatatype() { 362 return evaluateDatatype; 363 } 364 365 private Class<?> evaluateDatatype; 366 private Object evalValue; 367 368 public void setEvalValue(Object evalValue) { 369 this.evalValue = evalValue; 370 } 371 372 public Object getEvalValue() { 373 return evalValue; 374 } 375 376 private String plainText = null; 377 378 public void setPlainText(String plainText) { 379 this.plainText = plainText; 380 } 381 382 public String getPlainText() { 383 if (plainText == null){ 384 return toString(); 385 }else{ 386 return plainText; 387 } 388 } 389 390 private ESqlClause location = ESqlClause.unknown; 391 392 public void setLocation(ESqlClause location) { 393 this.location = location; 394 } 395 396 /** 397 * SQL clause that include this objectName such as select list, from clause, set clause 398 * 399 * @return SQL clause that include this objectName 400 */ 401 public ESqlClause getLocation() { 402 return location; 403 } 404 405 private String compactString = null; 406 407 /** 408 * representation of this node by removing all spaces, return and comments 409 * @return string representation of this node by removing all spaces, return and comments 410 */ 411 public String getCompactString(){ 412 if (compactString != null) return compactString; 413 414 StringBuilder sb = new StringBuilder(); 415 TSourceToken st = null; 416 currentIterateToken = null; 417 while (this.hasNext()){ 418 st = next(); 419 if ((st.tokencode == TBaseType.lexspace)||(st.tokencode == TBaseType.lexnewline) 420 ||(st.tokencode == TBaseType.cmtslashstar)||(st.tokencode == TBaseType.cmtdoublehyphen) 421 ) { 422 continue; 423 }else{ 424 sb.append(st.toString()); 425 } 426 } 427 428 compactString = sb.toString(); 429 return compactString; 430 } 431 432 private String md5 = null; 433 /** 434 * md5 hash code of the string representation of this node. 435 * If the string of this node is empty, return the md5 value of the class name of this node. 436 * 437 * 1. remove all spaces, return, comments inside the node<br> 438 * 2. turn number constant in where clause into ? character. <br> 439 * 3. turn string constant in where clause into ?? character. <br> 440 * 4. turn all string of the token into uppercase if it's not a delimited identifier. 441 * 442 * @return md5 hash code of the string representation of this node 443 */ 444 public String getMd5(){ 445 if (md5 != null) return md5; 446 447 StringBuilder sb = new StringBuilder(); 448 TSourceToken st = null; 449 currentIterateToken = null; 450 while (this.hasNext()){ 451 st = next(); 452 // cmtslashstar lexspace lexnewline cmtdoublehyphen 453 if ((st.tokencode == TBaseType.lexspace)||(st.tokencode == TBaseType.lexnewline) 454 ||(st.tokencode == TBaseType.cmtslashstar)||(st.tokencode == TBaseType.cmtdoublehyphen) 455 ){ 456 continue; 457 } else if ((st.tokencode == TBaseType.fconst)||(st.tokencode == TBaseType.iconst)){ 458 if (st.location == ESqlClause.where){ 459 sb.append("?"); 460 }else{ 461 sb.append(st.toString()); 462 } 463 } else if (st.tokencode == TBaseType.sconst){ 464 if (st.location == ESqlClause.where){ 465 sb.append("??"); 466 }else{ 467 sb.append(st.toString()); 468 } 469 } else if (TSQLEnv.isDelimitedIdentifier(this.dbvendor,st.toString())){ 470 sb.append(st.toString()); 471 }else { 472 sb.append(st.toString().toUpperCase()); 473 } 474 } 475 byte[] md5InBytes; 476 if (sb.toString().length() > 0){ 477 md5InBytes = gspMD5Utils.digest(sb.toString().getBytes(UTF_8)); 478 }else{ 479 md5InBytes = gspMD5Utils.digest(this.getClass().getName() .getBytes(UTF_8)); 480 } 481 md5 = gspMD5Utils.bytesToHex(md5InBytes); 482 return md5; 483 484 } 485 486 public TParseTreeNode getParentObjectName() { 487 return parent; 488 } 489 490 public void setParent(TParseTreeNode parent) { 491 this.parent = parent; 492 } 493 494 private TParseTreeNode parent = null; 495 496// public void appendAToken(TSourceToken st){ 497// // this token is not belonged to this node 498// this.getEndToken() 499// } 500 public static boolean doubleLinkedTokenListToString = true; 501 502// TSourceToken st = getStartToken(); 503// TSourceToken et = getEndToken(); 504 public void insertAfterAToken(TSourceToken anchorToken){ 505 if ((anchorToken == null)||(this.getEndToken() == null)||(this.getStartToken() == null) )return; 506 this.getEndToken().setNextTokenInChain(anchorToken.getNextTokenInChain()); 507 if (anchorToken.getNextTokenInChain() != null){ 508 anchorToken.getNextTokenInChain().setPrevTokenInChain(this.getEndToken()); 509 }else{ 510 if (anchorToken.getNodesEndWithThisToken().size() > 0){ 511 for(Object o:anchorToken.getNodesEndWithThisToken()){ 512 TParseTreeNode node = (TParseTreeNode)o; 513 node.setEndToken(this.getEndToken()); 514 } 515 } 516 } 517 anchorToken.setNextTokenInChain(this.getStartToken()); 518 this.getStartToken().setPrevTokenInChain(anchorToken); 519 520 } 521 522 public void insertNewNodeBeforeMe(TParseTreeNode newNode, boolean needCommaBefore){ 523 524 updateNodeWithTheSameStartToken(nodeActionInsert,newNode.getStartToken()); 525 526 newNode.getStartToken().setPrevTokenInChain(this.getStartToken().getPrevTokenInChain()); 527 if (this.getStartToken().getPrevTokenInChain() != null){ 528 this.getStartToken().getPrevTokenInChain().setNextTokenInChain(newNode.getStartToken()); 529 } 530 531 newNode.getEndToken().setNextTokenInChain(this.getStartToken()); 532 this.getStartToken().setPrevTokenInChain(newNode.getEndToken()); 533 534 if (needCommaBefore) { 535 TSourceToken commaToken = new TSourceToken(","); 536 newNode.getEndToken().insertANewTokenAfterMe(commaToken); 537 } 538 539 } 540 541 public void appendNewNode(TParseTreeNode newNode, boolean needCommaBefore){ 542 // the new node must be share the same parent node with this node 543 544 refreshAllNodesTokenCount(); 545 updateMeNodeWithTheSameEndToken(nodeActionAppend,newNode.getEndToken()); 546 547 newNode.getEndToken().setNextTokenInChain(this.getEndToken().getNextTokenInChain()); 548 if (this.getEndToken().getNextTokenInChain() != null){ 549 this.getEndToken().getNextTokenInChain().setPrevTokenInChain(newNode.getEndToken()); 550 } 551 552 newNode.getStartToken().setPrevTokenInChain(this.getEndToken()); 553 this.getEndToken().setNextTokenInChain(newNode.getStartToken()); 554 555 if (needCommaBefore) { 556 TSourceToken commaToken = new TSourceToken(","); 557 this.getEndToken().insertANewTokenAfterMe(commaToken); 558 } 559 560 } 561 562 public void replaceWithNewNode(TParseTreeNode newNode){ 563 if ((getStartToken() == null)||(getEndToken() == null)) return; 564 if ((newNode.getStartToken() == null)||(newNode.getEndToken() == null)) return; 565 566 if (getStartToken().getPrevTokenInChain() == null) return; 567 568 refreshAllNodesTokenCount(); 569 updateNodeWithTheSameStartToken(nodeActionUpdate,newNode.startToken); 570 updateMeNodeWithTheSameEndToken(nodeActionUpdate,newNode.endToken); 571 572 getStartToken().getPrevTokenInChain().setNextTokenInChain(newNode.getStartToken()); 573 newNode.getStartToken().setPrevTokenInChain(getStartToken().getPrevTokenInChain()); 574 575 newNode.getEndToken().setNextTokenInChain(getEndToken().getNextTokenInChain()); 576 if (getEndToken().getNextTokenInChain() != null){ 577 getEndToken().getNextTokenInChain().setPrevTokenInChain(newNode.getEndToken()); 578 } 579 580 } 581 582 583 584 TSourceToken currentIterateToken = null; 585 586 587 public void resetIterator(){ 588 currentIterateToken = null; 589 } 590 591 @Override 592 public boolean hasNext() { 593 if ((getStartToken() != null)&&(getEndToken() != null)&&(currentIterateToken != getEndToken())){ 594 return true; 595 } 596 else{ 597 return false; 598 } 599 600 } 601 602 @Override 603 public TSourceToken next() { 604 if (this.hasNext()){ 605 if (currentIterateToken == null){ 606 currentIterateToken = getStartToken(); 607 }else { 608 currentIterateToken = currentIterateToken.getNextTokenInChain(); 609 } 610 611 return currentIterateToken; 612 } 613 else 614 return null; 615 } 616 617 @Override 618 public void remove() { 619 throw new UnsupportedOperationException(); 620 } 621 622// @Override 623// public Iterator<TSourceToken> iterator() { 624// return new TokenSetIterator(); 625// } 626// 627// private class TokenSetIterator implements Iterator { 628// TSourceToken st = getStartToken(); 629// TSourceToken et = getEndToken(); 630// TSourceToken currentToken = null; 631// 632// public boolean hasNext() { 633// if ((st != null)&&(et != null)&&(currentToken != et)){ 634// return true; 635// } 636// else 637// return false; 638// } 639// 640// public TSourceToken next() { 641// if (this.hasNext()){ 642// if (currentToken == null){ 643// currentToken = st; 644// }else { 645// currentToken = currentToken.getNextTokenInChain(); 646// } 647// 648// return currentToken; 649// } 650// else 651// return null; 652// } 653// 654// @Override 655// public void remove() { 656// 657// } 658// } 659 660 public void setNodeStatus(ENodeStatus nodeStatus) { 661 this.nodeStatus = nodeStatus; 662 } 663 664 public ENodeStatus getNodeStatus() { 665 666 return nodeStatus; 667 } 668 669 private ENodeStatus nodeStatus = ENodeStatus.nsNormal; 670 671 672 673 public void addToTokenChain(TSourceToken anchorToken, boolean beforeAnchorToken){ 674 675 }; 676 677 private int tokenCount = -1; 678 679 public int getTokenCount() { 680 if (tokenCount == -1){ 681 calculateTokenCount(); 682 } 683 return tokenCount; 684 } 685 686 public void refreshAllNodesTokenCount(){ 687 if (this.startToken != null){ 688 for(int i=0;i<this.startToken.getNodesStartFromThisToken().size();i++){ 689 TParseTreeNode node = this.startToken.getNodesStartFromThisToken().get(i); 690 node.calculateTokenCount(); 691 } 692 } 693 694 if (this.endToken != null){ 695 for(int i=0;i<this.endToken.getNodesEndWithThisToken().size();i++){ 696 TParseTreeNode node = this.endToken.getNodesEndWithThisToken().get(i); 697 node.calculateTokenCount(); 698 } 699 } 700 701 } 702 703 public void calculateTokenCount(){ 704 int ret = 0; 705 TSourceToken prevst = null; 706 this.resetIterator(); 707 while (this.hasNext()){ 708 ret++; 709 TSourceToken st = this.next(); 710 if (st == null){ 711 if (TBaseType.DEBUG){ 712 System.out.println("==========ERROR: start and token is not in the same chain ===============\n"); 713 System.out.println("Start Token:"+this.getStartToken().toString()); 714 System.out.println("End Token:"+this.getEndToken().toString()); 715 if (prevst != null){ 716 System.out.println("Prev token, code:"+prevst.tokencode+", Text:"+prevst.toString()); 717 }else{ 718 System.out.println("Prev token is null"); 719 } 720 System.out.println("============================\n"); 721 } 722 break; 723 } 724 prevst = st; 725 } 726 this.resetIterator(); 727 this.tokenCount = ret; 728 } 729 730 public final static int nodeActionUnknown = 0; 731 public final static int nodeActionRemove = 1; 732 public final static int nodeActionInsert = 2; 733 public final static int nodeActionUpdate = 3; 734 public final static int nodeActionUpdateText = 4; 735 public final static int nodeActionAppend = 5; 736 public final static int nodeChangeStartToken = 6; 737 public final static int nodeChangeEndToken = 7; 738 739 void updateStatusOfNodeShareSameTokens(int nodeAction){ 740 TSourceToken startToken = getStartToken(); 741 TSourceToken endToken = getEndToken(); 742 if (endToken == null) return; 743 if (startToken == null) return; 744 745 // System.out.println("node token count:"+thisNodeTokenCount); 746 this.resetIterator(); 747 while(this.hasNext()){ 748 TSourceToken st = this.next(); 749 // System.out.println(st.toString()); 750 if (st == null) break; 751 752 for(int i=0;i<st.getNodesStartFromThisToken().size();i++){ 753 TParseTreeNode node = st.getNodesStartFromThisToken().get(i); 754 if (st == startToken){ 755 if (this == node){ 756 switch (nodeAction){ 757 case nodeActionRemove: 758 node.nodeStatus = ENodeStatus.nsRemoved; 759 break; 760 default: 761 node.nodeStatus = ENodeStatus.nsDetached; 762 break; 763 } 764 }else if (node.getTokenCount() == this.getTokenCount()){ 765 switch (nodeAction){ 766 case nodeActionRemove: 767 node.nodeStatus = ENodeStatus.nsRemoved; 768 break; 769 default: 770 node.nodeStatus = ENodeStatus.nsDetached; 771 break; 772 } 773 }else if (node.getTokenCount() > this.getTokenCount()){ 774 node.nodeStatus = ENodeStatus.nsPartitial; 775 }else{ 776 node.nodeStatus = ENodeStatus.nsDetached; 777 } 778 }else{ 779 node.nodeStatus = ENodeStatus.nsDetached; 780 } 781 } 782 783 for(int i=0;i<st.getNodesEndWithThisToken().size();i++){ 784 TParseTreeNode node = st.getNodesEndWithThisToken().get(i); 785 if (st == endToken){ 786 if (this == node){ 787 switch (nodeAction){ 788 case nodeActionRemove: 789 node.nodeStatus = ENodeStatus.nsRemoved; 790 break; 791 default: 792 node.nodeStatus = ENodeStatus.nsDetached; 793 break; 794 } 795 }else if (node.getTokenCount() == this.getTokenCount()){ 796 switch (nodeAction){ 797 case nodeActionRemove: 798 node.nodeStatus = ENodeStatus.nsRemoved; 799 break; 800 default: 801 node.nodeStatus = ENodeStatus.nsDetached; 802 break; 803 } 804 }else if (node.getTokenCount() > this.getTokenCount()){ 805 node.nodeStatus = ENodeStatus.nsPartitial; 806 }else{ 807 switch (nodeAction){ 808 case nodeActionRemove: 809 node.nodeStatus = ENodeStatus.nsRemoved; 810 break; 811 default: 812 node.nodeStatus = ENodeStatus.nsDetached; 813 break; 814 } 815 } 816 }else{ 817 switch (nodeAction){ 818 case nodeActionRemove: 819 node.nodeStatus = ENodeStatus.nsRemoved; 820 break; 821 default: 822 node.nodeStatus = ENodeStatus.nsDetached; 823 break; 824 } 825 } 826 } 827 828 } 829 this.resetIterator(); 830 } 831 832 void updateNodeWithTheSameStartToken(int nodeAction, TSourceToken newStartToken){ 833 TSourceToken oldStartToken = this.getStartToken(); 834 for(int i=0;i<oldStartToken.getNodesStartFromThisToken().size();i++){ 835 TParseTreeNode node = oldStartToken.getNodesStartFromThisToken().get(i); 836 if (node == this){ // myself 837 switch (nodeAction){ 838 case nodeActionRemove: 839 node.startToken = null; 840 break; 841 case nodeActionUpdate: 842 case nodeActionUpdateText: 843 case nodeChangeStartToken: 844 node.startToken = newStartToken; 845 newStartToken.getNodesStartFromThisToken().push(node); 846 default: 847 break; 848 } 849 }else if((node.getTokenCount() == this.getTokenCount())){ // node with same tokens 850 switch (nodeAction){ 851 case nodeActionRemove: 852 node.startToken = null; 853 break; 854 case nodeActionUpdate: 855 case nodeActionUpdateText: 856 case nodeChangeStartToken: 857 node.startToken = newStartToken; 858 newStartToken.getNodesStartFromThisToken().push(node); 859 default: 860 break; 861 } 862 } 863 else if((node.getTokenCount() > this.getTokenCount())) { // parent node 864 node.startToken = newStartToken; 865 newStartToken.getNodesStartFromThisToken().push(node); 866 }else{ // sub node 867 868 } 869 } 870 } 871 872 void updateMeNodeWithTheSameEndToken(int nodeAction, TSourceToken newEndToken){ 873 TSourceToken oldEndToken = this.getEndToken(); 874 for(int i=0;i<oldEndToken.getNodesEndWithThisToken().size();i++){ 875 TParseTreeNode node = oldEndToken.getNodesEndWithThisToken().get(i); 876 if (node == this){ // myself 877 switch (nodeAction){ 878 case nodeActionRemove: 879 node.endToken = null; 880 break; 881 case nodeActionUpdate: 882 case nodeActionUpdateText: 883 case nodeChangeEndToken: 884 node.endToken = newEndToken; 885 newEndToken.getNodesEndWithThisToken().push(node); 886 default: 887 break; 888 } 889 }else if((node.getTokenCount() == this.getTokenCount())){ // node with same tokens 890 switch (nodeAction){ 891 case nodeActionRemove: 892 node.endToken = null; 893 break; 894 case nodeActionUpdate: 895 case nodeActionUpdateText: 896 case nodeChangeEndToken: 897 node.endToken = newEndToken; 898 newEndToken.getNodesEndWithThisToken().push(node); 899 default: 900 break; 901 } 902 } 903 else if((node.getTokenCount() > this.getTokenCount())) { // parent node 904 node.endToken = newEndToken; 905 newEndToken.getNodesEndWithThisToken().push(node); 906 }else{ // sub node 907 908 } 909 } 910 } 911 912 /** 913 * both begin and end token will be removed from the chain 914 * 915 * @param startToken 916 * @param endToken 917 */ 918 public static void removeTokensBetweenToken(TSourceToken startToken, TSourceToken endToken){ 919 if ((startToken == null)||(endToken == null)) return; 920 startToken.removeFromChain(); 921 while (startToken != endToken){ 922 startToken = startToken.getNextTokenInChain(); 923 if (startToken == null) break; 924 startToken.removeFromChain(); 925 } 926 } 927 928 public static void removeTokensBetweenNodes(TParseTreeNode startNode, TParseTreeNode endNode){ 929 if ((startNode == null)||(endNode == null)) return; 930 if ((startNode.getEndToken() == null)||(endNode.getStartToken() == null)) return; 931 TSourceToken startToken = startNode.getEndToken().getNextTokenInChain(); 932 TSourceToken endToken = endNode.getStartToken().getPrevTokenInChain(); 933 if ((startToken == null)||(endToken == null)) return; 934 935 startToken.removeFromChain(); 936 while (startToken != endToken){ 937 startToken = startToken.getNextTokenInChain(); 938 if (startToken == null) break; 939 startToken.removeFromChain(); 940 } 941 } 942 943 public void removeTokens(){ 944 if (this.nodeStatus == ENodeStatus.nsRemoved) return; 945 946 TSourceToken oldStartToken = getStartToken(); 947 TSourceToken oldEndToken = getEndToken(); 948 if (oldEndToken == null) return; 949 if (oldStartToken == null) return; 950 951 refreshAllNodesTokenCount(); 952 updateStatusOfNodeShareSameTokens(nodeActionRemove); 953 954 if (oldStartToken.getPrevTokenInChain() != null){ 955 updateMeNodeWithTheSameEndToken(nodeActionRemove,oldStartToken.getPrevTokenInChain()); 956 oldStartToken.getPrevTokenInChain().setNextTokenInChain(oldEndToken.getNextTokenInChain()); 957 } 958 if (oldEndToken.getNextTokenInChain() != null){ 959 updateNodeWithTheSameStartToken(nodeActionRemove,oldEndToken.getNextTokenInChain()); 960 oldEndToken.getNextTokenInChain().setPrevTokenInChain(oldStartToken.getPrevTokenInChain()); 961 } 962 963 if (this.nodeStatus == ENodeStatus.nsNormal){ 964 this.nodeStatus = ENodeStatus.nsRemoved; 965 } 966 967 this.setStartTokenDirectly(null); 968 this.setEndTokenDirectly(null); 969 970 } 971 972 973 974 /** 975 * SQL dialect of this statement. 976 */ 977 public EDbVendor dbvendor = EDbVendor.dbvgeneric; 978 979 /** 980 * The parser that generate this node. 981 * 982 * @return parser that generate this node 983 */ 984 public TGSqlParser getGsqlparser() { 985 if (gsqlparser == null){ 986 if (getStartToken() != null){ 987 // TODO removed in 2.6.8.1, to avoid memory leak in TTypeName 988 // gsqlparser = getStartToken().getGsqlparser(); 989 } 990 } 991 return gsqlparser; 992 } 993 994 boolean includingComment = true; 995 996 997 public void setIncludingComment(boolean includingComment) { 998 this.includingComment = includingComment; 999 } 1000 1001 private TGSqlParser gsqlparser = null; 1002 1003 public void setGsqlparser(TGSqlParser gsqlparser) { 1004 this.gsqlparser = gsqlparser; 1005 if (gsqlparser != null){ 1006 this.dbvendor = gsqlparser.getDbVendor(); 1007 } 1008 } 1009 1010 private int dummyTag = 0; 1011 1012 public void setDummyTag(int dummyTag) { 1013 this.dummyTag = dummyTag; 1014 } 1015 1016 /** 1017 * A temporary value can be used for any purposes 1018 * 1019 * @return a temporary value used for any purposes 1020 */ 1021 public int getDummyTag() { 1022 return dummyTag; 1023 } 1024 1025 1026 private TSourceToken startToken; 1027 1028 /** 1029 * The first token in this parse tree node 1030 * 1031 * @return the first token of node 1032 */ 1033 public TSourceToken getStartToken() { 1034 return startToken; 1035 } 1036 1037 /** 1038 * The last token of the node 1039 * 1040 * @return the last token of node 1041 */ 1042 public TSourceToken getEndToken() { 1043 return endToken; 1044 } 1045 1046 private TSourceToken endToken; 1047 private long lineNo = -1; 1048 1049 /** 1050 * Column position of the first token of this node 1051 * 1052 * @return column position 1053 */ 1054 public long getColumnNo() { 1055 if (this.getStartToken() != null){ 1056 columnNo = this.getStartToken().columnNo; 1057 } 1058 return columnNo; 1059 } 1060 1061 /** 1062 * Line number of the first token of this node 1063 * 1064 * @return line number 1065 */ 1066 public long getLineNo() { 1067 if (this.getStartToken() != null){ 1068 lineNo = this.getStartToken().lineNo; 1069 } 1070 return lineNo; 1071 } 1072 1073 private long columnNo = -1; 1074 1075 private int nodeType; 1076 /** 1077 * Set the node type for this node. 1078 * 1079 * @param nodeType The node type. 1080 */ 1081 public void setNodeType(int nodeType) 1082 { 1083 this.nodeType = nodeType; 1084 } 1085 1086 /** 1087 * A unique value to distinguish this node from others 1088 * 1089 * @return enum value of node type 1090 * @see gudusoft.gsqlparser.nodes.ENodeType 1091 */ 1092 public int getNodeType() 1093 { 1094 return nodeType; 1095 } 1096 1097 String getastext(){ return "";} 1098 String getasprettytext(){ return "";} 1099 1100 /** 1101 * Initialize a query tree node. 1102 * Used internally 1103 * 1104 * @param arg1 first argument 1105 */ 1106 public void init(Object arg1) 1107 { 1108 } 1109 1110 public void init(Object arg1, Object arg2) 1111 { 1112 } 1113 1114 public void init(Object arg1, Object arg2, Object arg3) 1115 { 1116 } 1117 1118 public void init(Object arg1, Object arg2, Object arg3, Object arg4) 1119 { 1120 } 1121 1122 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) 1123 { 1124 } 1125 1126 public void init(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) 1127 { 1128 } 1129 1130 /** 1131 * Analyze the sub-nodes inside this node. 1132 * Such as build the relationship between table and column. 1133 * 1134 * @param psql SQL statement this node belongs to 1135 * @param plocation SQL clause this node belongs to 1136 */ 1137 public void doParse(TCustomSqlStatement psql, ESqlClause plocation){ 1138 1139 } 1140 1141 public void setStartToken(ArrayList startNode) { 1142 if (startNode == null) return; 1143 if (startNode.size() == 0) return; 1144 TParseTreeNode parseTreeNode = (TParseTreeNode)startNode.get(0); 1145 this.setStartToken(parseTreeNode.getStartToken()); 1146 } 1147 1148 public void setEndToken(ArrayList endNode) { 1149 if (endNode == null) return; 1150 if (endNode.size() == 0 ) return; 1151 TParseTreeNode parseTreeNode = (TParseTreeNode)endNode.get(endNode.size()-1); 1152 this.setEndToken(parseTreeNode.getEndToken()); 1153 } 1154 1155 public void setStartToken(TSourceToken newStartToken) { 1156 if (newStartToken == null) return; 1157 TSourceToken oldStartToken = this.startToken; 1158 if (oldStartToken != null){ 1159 if (oldStartToken.getNodesStartFromThisToken().peek() == this){ 1160 oldStartToken.getNodesStartFromThisToken().pop(); 1161 } 1162 } 1163 if (newStartToken.getNodesStartFromThisToken().indexOf(this) == -1) 1164 { 1165 newStartToken.getNodesStartFromThisToken().push(this); 1166 } 1167 1168 this.startToken = newStartToken; 1169 } 1170 1171 public void setStartToken(TSourceTokenList startTokenList) { 1172 if (startTokenList == null) return; 1173 this.setStartToken(startTokenList.get(0)); 1174 } 1175 1176 public void setStartToken(TParseTreeNode startNode) { 1177 if (startNode == null) return; 1178 this.setStartToken(startNode.getStartToken()); 1179 } 1180 1181 public void setStartTokenDirectly(TSourceToken newStartToken) { 1182 this.startToken = newStartToken; 1183 } 1184 public void setEndTokenDirectly(TSourceToken newEndToken) { 1185 this.endToken = newEndToken; 1186 } 1187 1188 public void setEndToken(TSourceToken newEndToken) { 1189 if (newEndToken == null) return; 1190 TSourceToken oldEndToken = this.getEndToken(); 1191 1192 if (oldEndToken != null){ 1193 if (oldEndToken.getNodesEndWithThisToken().peek() == this){ 1194 oldEndToken.getNodesEndWithThisToken().pop(); 1195 } 1196 } 1197 1198 if (newEndToken.getNodesEndWithThisToken().indexOf(this) == -1){ 1199 newEndToken.getNodesEndWithThisToken().push(this); 1200 } 1201 1202 this.endToken = newEndToken; 1203 } 1204 1205 public void setEndToken(TSourceTokenList endTokenList) { 1206 if (endTokenList == null) return; 1207 this.setEndToken(endTokenList.get(endTokenList.size()-1)); 1208 } 1209 1210// public void setEndToken(TParseTreeNodeList endNodeList) { 1211// if (endNodeList == null) return; 1212// this.setEndToken(endNodeList.getEndToken()); 1213// } 1214 1215 1216 1217 public void setEndToken(TFromTableList endNode) { 1218 if (endNode == null) return; 1219 this.setEndToken(endNode.getEndToken()); 1220 } 1221 1222 public void setEndToken(TParseTreeNode endNode) { 1223 if (endNode == null) return; 1224 this.setEndToken(endNode.getEndToken()); 1225 } 1226 1227 private void setStartTokenToNull(){ 1228 this.startToken = null; 1229 } 1230 1231 private void setEndTokenToNull(){ 1232 this.endToken = null; 1233 } 1234 1235 private void resetStartAndEndTokenBeforeRemoveTokens(TSourceTokenList stList, int start, int end,boolean isReplace,TSourceToken relpacedStartToken,TSourceToken replacedEndToken){ 1236 1237 for(int m = start; m<=end;m++){ 1238 if ((m>stList.size()-1) ||(m<0)) break; 1239 TSourceToken deletedToken = stList.get(m); 1240 for(int i=0;i<deletedToken.getNodesStartFromThisToken().size();i++){ 1241 TParseTreeNode node = deletedToken.getNodesStartFromThisToken().get(i); 1242 TSourceToken EndSt = node.getEndToken(); 1243 1244 if (isReplace&&((m==start)||(m==end))){ 1245 node.setStartToken(relpacedStartToken); 1246 }else{ 1247 if (EndSt == null){ 1248 node.setStartTokenToNull(); 1249 }else{ 1250 if (EndSt.posinlist > end){ 1251 node.setStartToken(stList.get(end+1)); 1252 }else if (EndSt.posinlist < start){ 1253 node.setStartTokenToNull(); 1254 node.setEndTokenToNull(); 1255 }else{ 1256 node.setStartTokenToNull(); 1257 node.setEndTokenToNull(); 1258 } 1259 } 1260 } 1261 } // nodes start from this token 1262 1263 for (int i=0;i<deletedToken.getNodesEndWithThisToken().size();i++){ 1264 TParseTreeNode node = deletedToken.getNodesEndWithThisToken().get(i); 1265 TSourceToken startSt = node.getStartToken(); 1266 1267 if (isReplace&&((m==start)||(m==end))){ 1268 node.setEndToken(replacedEndToken); 1269 }else{ 1270 if (startSt == null){ 1271 node.setEndTokenToNull(); 1272 }else{ 1273 if (startSt.posinlist > end){ 1274 node.setStartTokenToNull(); 1275 node.setEndTokenToNull(); 1276 }else if (startSt.posinlist < start){ 1277 node.setEndToken(stList.get(start - 1)); 1278 }else{ 1279 node.setStartTokenToNull(); 1280 node.setEndTokenToNull(); 1281 } 1282 } 1283 } 1284 }// nodes end with this token 1285 1286 }//loop all tokens to be deleted in this parse tree nodes 1287 } 1288 1289 1290 1291 private void fastResetStartAndEndTokenBeforeRemoveTokens(TSourceTokenList stList, int start, int end,TSourceToken replacedToken){ 1292 int[] startEnd; 1293 if (start != end) { 1294 startEnd = new int[2]; 1295 startEnd[0] = start; 1296 startEnd[1] = end; 1297 } 1298 else { 1299 startEnd = new int[1]; 1300 startEnd[0] = start; 1301 } 1302 for(int m : startEnd){ 1303 TSourceToken deletedToken = stList.get(m); 1304 for(int i=0;i<deletedToken.getNodesStartFromThisToken().size();i++){ 1305 TParseTreeNode node = deletedToken.getNodesStartFromThisToken().get(i); 1306 node.setStartToken(replacedToken); 1307 } // nodes start from this token 1308 1309 for (int i=0;i<deletedToken.getNodesEndWithThisToken().size();i++){ 1310 TParseTreeNode node = deletedToken.getNodesEndWithThisToken().get(i); 1311 node.setEndToken(replacedToken); 1312 }// nodes end with this token 1313 1314 }//loop all tokens to be deleted in this parse tree nodes 1315 } 1316 1317 /** 1318 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1319 * Set the new string text of a node will destroy all the sub-node structure 1320 * 1321 * @param sqlSegment that override original text of this node. 1322 */ 1323 1324 /* 1325 * <p>1. new string will be tokenized into a list of source tokens: stlist 1326 * <p>2. link alternativetoken of start token of this node to the first token in stlist generated in step 1. 1327 * <p>3. link back alternative token of last token in stlist to the last token of this node. 1328 * <p>So, if you find a start token of parse tree node has an alternativetoken, then text of this node should be modified by using setString(String sqlSegment) 1329 * <p> if a parse tree node with an alias clause like: tablename as tablealias, 1330 * <p> TTable.setString() will replace whole text "tablename as tablealias" including alias clause. 1331 * 1332 * steps to set new string(sqlSegment) of parse tree node 1333 * length(sqlSegment)=0 or sqlSegment = one space,, means remove this node from parse tree 1334 * length(sqlSegment) > 0 , means replace source tokens of this node with new tokens 1335 * 1336 */ 1337 public void setString2(String sqlSegment){ 1338 1339// TGSqlParser l_sqlparser = new TGSqlParser(getGsqlparser().getDbVendor()); 1340// l_sqlparser.sqltext = sqlSegment; 1341// l_sqlparser.tokenizeSqltext(); 1342 1343 TSingletonParser singletonParser = TSingletonParser.getInstance(); 1344 1345 TSourceTokenList stList; 1346 if (this.dbvendor == EDbVendor.dbvgeneric){ 1347 stList = singletonParser.getTokens(TGSqlParser.currentDBVendor,sqlSegment); 1348 }else{ 1349 stList = singletonParser.getTokens(this.dbvendor,sqlSegment); 1350 } 1351 1352 1353 1354 if ((getStartToken() == null)&&(getEndToken() == null)){ 1355 // this is a manually created parse tree node, not created by yacc 1356// l_sqlparser.sourcetokenlist.get(0).container = l_sqlparser.sourcetokenlist; 1357// l_sqlparser.sourcetokenlist.get(l_sqlparser.sourcetokenlist.size()-1).container = l_sqlparser.sourcetokenlist; 1358 setStartToken(stList.get(0)); 1359 setEndToken(stList.get(stList.size()-1)); 1360 return; 1361 } 1362 1363 TSourceToken lcStartToken = getStartToken(); 1364 TSourceToken lcEndToken = getEndToken(); 1365 1366 // TSourceTokenList stList = lcEndToken.container; 1367 1368 int lcStartTokenPos = lcStartToken.posinlist; 1369 int lcEndTokenPos = lcEndToken.posinlist; 1370 1371 1372 if ((sqlSegment.length()==0) || (sqlSegment == " ")){ 1373 1374 //reset start and end token of those parse tree node 1375 resetStartAndEndTokenBeforeRemoveTokens(stList, lcStartTokenPos, lcEndTokenPos,false,null,null); 1376 1377 // remove token from list 1378 for(int i=lcEndTokenPos;i>=lcStartTokenPos;i--){ 1379 stList.remove(i); 1380 } 1381 1382 // reindex posinlist after remove token from container 1383 for(int i=lcStartTokenPos;i<stList.size();i++){ 1384 stList.get(i).posinlist = i; 1385 } 1386 1387 }else{ 1388 //replace 1389 //reset start and end token of those parse tree node 1390 TSourceToken lcReplacedStartToken = stList.get(0); 1391 TSourceToken lcReplacedEndToken = stList.get(stList.size()-1); 1392 resetStartAndEndTokenBeforeRemoveTokens(stList, lcStartTokenPos, lcEndTokenPos,true,lcReplacedStartToken,lcReplacedEndToken); 1393 1394 // remove token from list 1395 for(int i=lcEndTokenPos;i>=lcStartTokenPos;i--){ 1396 stList.remove(i); 1397 } 1398 1399 1400// for(int i = stList.size()-1; i>=0; i--){ 1401// TSourceToken st = stList.get(i); 1402// st.container = stList; 1403// stList.add(lcStartTokenPos,st); 1404// } 1405 1406 // reindex posinlist after remove token from container 1407 for(int i=lcStartTokenPos;i<stList.size();i++){ 1408 TSourceToken st = stList.get(i); 1409 st.posinlist = i; 1410 } 1411 1412 } 1413 1414 } 1415 1416 /** 1417 * Set the text of the node and update the corresponding source tokens synchronously. 1418 * This way, when the toString() method of the node itself or its parent node is called, 1419 * it will return the modified text. 1420 * 1421 * @param sqlSegment 1422 */ 1423 public void setString(String sqlSegment){ 1424 if (doubleLinkedTokenListToString){ 1425 setText(sqlSegment); 1426 }else{ 1427 setString2(sqlSegment); 1428 } 1429 } 1430 1431 public void fastSetString(String sqlSegment){ 1432 1433 TSourceToken replaceToken = new TSourceToken(sqlSegment); 1434 setText(replaceToken, replaceToken); 1435 } 1436 1437 1438 /** 1439 * return null if no comment is found before this node. 1440 * 1441 * @return the comment before this node 1442 */ 1443 public String getCommentBeforeNode(){ 1444 String ret = null; 1445 TSourceToken lcStartToken; 1446 1447 if (doubleLinkedTokenListToString){ 1448 lcStartToken = getStartToken(); 1449 if (lcStartToken == null) return null; 1450 1451 TSourceToken lcCurrentToken = lcStartToken.getPrevTokenInChain(); 1452 while (lcCurrentToken != null){ 1453 if ((lcCurrentToken.tokentype == ETokenType.ttreturn)||(lcCurrentToken.tokentype == ETokenType.ttwhitespace)) 1454 { 1455 if (ret != null){ 1456 ret = lcCurrentToken.toString() + ret ; 1457 } 1458 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1459 continue; 1460 } 1461 else if (lcCurrentToken.toString().trim().length() == 0) { 1462 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1463 continue; 1464 } 1465 if ((lcCurrentToken.tokencode == TBaseType.cmtslashstar) ||(lcCurrentToken.tokencode == TBaseType.cmtdoublehyphen) 1466 || lcCurrentToken.toString().startsWith("--") || lcCurrentToken.toString().startsWith("/*") 1467 ){ 1468 if (ret == null){ 1469 ret = lcCurrentToken.toString(); 1470 }else{ 1471 ret = lcCurrentToken.toString()+ ret; 1472 } 1473 1474 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1475 continue; 1476 } 1477 1478 break; 1479 } 1480 }else{ 1481 lcStartToken = getStartToken(); 1482 if (lcStartToken == null) return null; 1483 1484 TSourceTokenList stList = lcStartToken.container; 1485 if (stList == null) return null; 1486 1487 int b = lcStartToken.posinlist; 1488 1489 for(int i = b - 1 ; i>=0;i--){ 1490 TSourceToken st = stList.get(i); 1491 if ((st.tokencode == TBaseType.lexspace)|| (st.tokencode == TBaseType.lexnewline)) continue; 1492 if (st.getAstext().trim().length() == 0) continue; 1493 if ((st.tokencode == TBaseType.cmtslashstar) ||(st.tokencode == TBaseType.cmtdoublehyphen)){ 1494 if (ret == null){ 1495 ret = st.toString(); 1496 }else{ 1497 ret = st.toString()+TBaseType.newline + ret; 1498 } 1499 continue; 1500 } 1501 else if (st.getAstext().startsWith("--") || st.getAstext().startsWith("/*")){ 1502 if (ret == null){ 1503 ret = st.toString(); 1504 }else{ 1505 ret = st.toString()+TBaseType.newline + ret; 1506 } 1507 continue; 1508 } 1509 break; 1510 } 1511 } 1512 1513 return ret; 1514 } 1515 1516 public String getCommentAfterNode(){ 1517 String ret = null; 1518 TSourceToken lcEndToken; 1519 1520 if (!doubleLinkedTokenListToString) return ret; 1521 1522 lcEndToken = getEndToken(); 1523 if (lcEndToken == null) return null; 1524 1525 TSourceToken lcCurrentToken = lcEndToken; 1526 while (lcCurrentToken != null){ 1527 if ((lcCurrentToken.tokentype == ETokenType.ttreturn)||(lcCurrentToken.tokentype == ETokenType.ttwhitespace)) 1528 { 1529 if (ret != null){ 1530 ret = lcCurrentToken.toString() + ret ; 1531 } 1532 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1533 continue; 1534 } 1535 else if (lcCurrentToken.getAstext().trim().length() == 0) { 1536 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1537 continue; 1538 } 1539 if ((lcCurrentToken.tokencode == TBaseType.cmtslashstar) ||(lcCurrentToken.tokencode == TBaseType.cmtdoublehyphen) 1540 || lcCurrentToken.getAstext().startsWith("--") || lcCurrentToken.getAstext().startsWith("/*") 1541 ){ 1542 if (ret == null){ 1543 ret = lcCurrentToken.toString(); 1544 }else{ 1545 ret = lcCurrentToken.toString() +ret ; 1546 } 1547 1548 lcCurrentToken = lcCurrentToken.getPrevTokenInChain(); 1549 continue; 1550 } 1551 1552 break; 1553 } 1554 1555 return ret.trim(); // 移调第一个space or return token if any 1556 1557 } 1558 1559 /** 1560 * String representation of this parse tree node. 1561 * <p> 1562 * This string was generated by collecting text from the start token of this node to the end token of this node. 1563 * If this node was modified, then use {@link #toScript()} to get string representation of this node. 1564 * 1565 * @return string representation of this parse tree node 1566 */ 1567 public String toString2(){ 1568 String ret = null; 1569 TSourceToken lcStartToken = getStartToken(); 1570 if (lcStartToken == null) return null; 1571 1572 TSourceToken lcEndToken = getEndToken(); 1573 if (lcEndToken == null) return null; 1574 1575 TSourceTokenList stList = lcStartToken.container; 1576 if (stList == null) return null; 1577 1578 int b = lcStartToken.posinlist; 1579 int e = lcEndToken.posinlist; 1580 1581 StringBuffer sb = new StringBuffer(""); 1582 1583 for(int i= b ; i<=e;i++){ 1584 if(!includingComment && ((stList.get(i).tokencode == TBaseType.cmtslashstar) ||(stList.get(i).tokencode == TBaseType.cmtdoublehyphen))){ 1585 continue; 1586 } 1587 sb.append(stList.get(i).toString()); 1588 } 1589 1590 return sb.toString(); 1591 } 1592 1593 /** 1594 * <p> 1595 * 将一个语法树节点({@code TParseTreeNode})转换回它对应的原始SQL字符串。 1596 * 它通过遍历一个由 {@code startToken}(起始词法单元)和 {@code endToken}(结束词法单元)界定的双向链表来实现这一功能。 1597 * </p> 1598 * <p> 1599 * 这个方法的设计采用了“两遍扫描”(Two-Pass)的策略,也就是使用了两个 {@code while} 循环。 1600 * 第一个循环是预处理阶段,用于清理格式;第二个循环则负责构建最终的字符串。 1601 * </p> 1602 * <h3>第一个 {@code while} 循环</h3> 1603 * <p> 1604 * 这个循环的主要目标是识别并“软删除”多余的换行符(newline tokens)。这种情况尤其在SQL被程序动态修改后容易出现。 1605 * 这个循环本身不构建字符串,只负责分析和更新词法单元的状态。 1606 * 其工作原理是: 1607 * <ol> 1608 * <li><b>遍历:</b> 它从 {@code startToken} 开始,通过 {@code getNextTokenInChain()} 方法遍历到 {@code endToken}。</li> 1609 * <li><b>检测链表变化:</b> 它通过比较当前和前一个词法单元的 {@code posinlist}(词法单元在原始完整列表中的位置索引)来判断链表是否“自然”。 1610 * 如果不连续,意味着词法单元被插入或重排了,此时 {@code isChainModified} 标志位会被设为 {@code true}。</li> 1611 * <li><b>标记换行符:</b> 当遇到第一个换行符时,它会设置一个标志。如果紧接着又遇到了另一个换行符,并且此时链表已经被修改过, 1612 * 那么它会将这个多余的换行符标记为待删除状态。这个逻辑能有效地将代码被修改后可能产生的多个连续换行压缩成一个。</li> 1613 * </ol> 1614 * 简而言之,第一个循环是一个格式清理过程,它为后续生成整洁的字符串做准备。 1615 * </p> 1616 * <h3>第二个 {@code while} 循环</h3> 1617 * <p> 1618 * 这个循环负责从(经过第一步清理后的)词法单元流中实际地构建最终的输出字符串。 1619 * 其工作原理是: 1620 * <ol> 1621 * <li><b>遍历:</b> 它再次遍历完全相同的词法单元序列。</li> 1622 * <li><b>构建字符串:</b> 在将每个词法单元追加到 {@code StringBuffer} 之前,它会进行检查。</li> 1623 * <li><b>跳过特定单元:</b> 如果一个词法单元在第一个循环中被标记为待删除,或者它是一个注释(且配置为不包含注释),那么它将被跳过。</li> 1624 * <li><b>拼接字符串:</b> 对于所有未被跳过的词法单元,它会将其字符串值追加到 {@code StringBuffer} 中。</li> 1625 * </ol> 1626 * 当这个循环结束后,{@code StringBuffer} 就包含了该语法树节点的最终、重构后的SQL文本。 1627 * </p> 1628 * @return 节点对应的SQL字符串。 1629 */ 1630 public String toString(){ 1631 if (doubleLinkedTokenListToString){ 1632 TSourceToken startToken = getStartToken(); 1633 TSourceToken endToken = getEndToken(); 1634 1635 // Return null if boundary tokens are missing 1636 if (startToken == null || endToken == null) { 1637 return null; 1638 } 1639 1640 // First pass: Mark redundant newlines for deletion 1641 // This cleans up formatting issues caused by programmatic SQL modifications 1642 TSourceToken currentToken = startToken; 1643 TSourceToken previousToken = null; 1644 boolean shouldIgnoreNextNewline = false; 1645 boolean tokenChainWasModified = false; 1646 1647 while (currentToken != null) { 1648 // Detect if the token chain has been modified by checking position continuity 1649 if (!tokenChainWasModified && previousToken != null) { 1650 tokenChainWasModified = (currentToken.posinlist - previousToken.posinlist != 1); 1651 } 1652 1653 // Handle newline tokens - mark redundant ones for deletion 1654 if (currentToken.tokentype == ETokenType.ttreturn) { 1655 boolean isRedundantNewline = shouldIgnoreNextNewline && tokenChainWasModified; 1656 1657 if (isRedundantNewline) { 1658 currentToken.tokenstatus = ETokenStatus.tsdeleted; 1659 } else { 1660 shouldIgnoreNextNewline = true; 1661 } 1662 } else if (currentToken.tokentype != ETokenType.ttwhitespace) { 1663 // Reset newline flag when encountering non-whitespace tokens 1664 shouldIgnoreNextNewline = false; 1665 } 1666 1667 previousToken = currentToken; 1668 1669 // Move to next token or break if we've reached the end 1670 if (currentToken.equals(endToken)) { 1671 break; 1672 } 1673 currentToken = currentToken.getNextTokenInChain(); 1674 } 1675 1676 // Second pass: Build the final string by concatenating valid tokens 1677 StringBuffer result = new StringBuffer(); 1678 currentToken = startToken; 1679 1680 while (currentToken != null) { 1681 boolean shouldSkipToken = currentToken.tokenstatus == ETokenStatus.tsdeleted || 1682 (!includingComment && (currentToken.tokencode == TBaseType.cmtslashstar || 1683 currentToken.tokencode == TBaseType.cmtdoublehyphen) 1684 ); 1685 1686 if (!shouldSkipToken) { 1687 result.append(currentToken.toString()); 1688 } 1689 1690 // Move to next token or break if we've reached the end 1691 if (currentToken.equals(endToken)) { 1692 break; 1693 } 1694 currentToken = currentToken.getNextTokenInChain(); 1695 } 1696 1697 return result.toString(); 1698 } else { 1699 return toString2(); 1700 } 1701 } 1702 1703 1704 void setText(String nodeText, EDbVendor dbVendor){ 1705 this.dbvendor = dbVendor; 1706 setText(nodeText); 1707 } 1708 1709 private boolean tokensInChain = true; 1710 1711 public boolean isTokensInChain() { 1712 return tokensInChain; 1713 } 1714 1715 void setText(TSourceToken startToken, TSourceToken endToken){ 1716 TSourceToken newStartToken = startToken; 1717 TSourceToken newEndToken = endToken; 1718 1719 TSourceToken oldStartToken = getStartToken(); 1720 TSourceToken oldEndToken = getEndToken(); 1721 tokensInChain = false; 1722 if ((oldStartToken == null)||(oldEndToken == null)){ 1723 setStartToken(newStartToken); 1724 setEndToken(newEndToken); 1725 nodeStatus = ENodeStatus.nsFlattened; 1726 return; 1727 } 1728 1729 refreshAllNodesTokenCount(); 1730 1731 updateStatusOfNodeShareSameTokens(nodeActionUpdateText); 1732 1733 updateNodeWithTheSameStartToken(nodeActionUpdateText,newStartToken); 1734 updateMeNodeWithTheSameEndToken(nodeActionUpdateText,newEndToken); 1735 1736 if (oldStartToken.getPrevTokenInChain() != null){ 1737 oldStartToken.getPrevTokenInChain().setNextTokenInChain(newStartToken); 1738 newStartToken.setPrevTokenInChain(oldStartToken.getPrevTokenInChain()); 1739 } 1740 1741 if (oldEndToken.getNextTokenInChain() != null){ 1742 oldEndToken.getNextTokenInChain().setPrevTokenInChain(newEndToken); 1743 newEndToken.setNextTokenInChain(oldEndToken.getNextTokenInChain()); 1744 } 1745 1746 tokensInChain = true; 1747 } 1748 /** 1749 * set text of this node, if the original token of this node 1750 * is in a token chain, then merge the new token into the chain as well. 1751 * 1752 * use double linked list 1753 * 1754 * @param nodeText node text 1755 */ 1756 void setText(String nodeText){ 1757 if ((this.getNodeStatus() == ENodeStatus.nsRemoved)||(nodeText.length() == 0)) return; 1758 // if ((nodeText.length() == 0)) return; 1759 1760// TGSqlParser sqlParser = new TGSqlParser(TGSqlParser.currentDBVendor); 1761// sqlParser.sqltext = nodeText; 1762// sqlParser.tokenizeSqltext(); 1763// TSourceToken newStartToken = sqlParser.getSourcetokenlist().get(0); 1764// TSourceToken newEndToken = sqlParser.getSourcetokenlist().get(sqlParser.getSourcetokenlist().size() - 1); 1765// setText(newStartToken, newEndToken); 1766 1767 TSingletonParser singletonParser = TSingletonParser.getInstance(); 1768 1769 TSourceTokenList stList; 1770 if (this.dbvendor == EDbVendor.dbvgeneric){ 1771 stList = singletonParser.getTokens(TGSqlParser.currentDBVendor,nodeText); 1772 }else{ 1773 stList = singletonParser.getTokens(this.dbvendor,nodeText); 1774 } 1775 1776 1777 TSourceToken newStartToken = stList.get(0); 1778 TSourceToken newEndToken = stList.get(stList.size() - 1); 1779 setText(newStartToken, newEndToken); 1780 1781 } 1782 1783 1784 /** 1785 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1786 * Inserts tokens(from start token to end token ) of this parse tree node at the specified position in this 1787 * list. Shifts the element currently at that position (if any) and 1788 * any subsequent elements to the right . 1789 1790 * @param targetList the new list of source tokens 1791 * @param index the position insert the targetList 1792 * @return length of the source token list after insert targetList 1793 */ 1794 public int addAllMyTokensToTokenList(TSourceTokenList targetList, int index){ 1795 int ret = -1; 1796 1797 TSourceToken lcStartToken = getStartToken(); 1798 if (lcStartToken == null) return ret; 1799 1800 TSourceToken lcEndToken = getEndToken(); 1801 if (lcEndToken == null) return ret; 1802 1803 TSourceTokenList stList = lcStartToken.container; 1804 if (stList == null) return ret; 1805 1806 for(int i=lcEndToken.posinlist;i>=lcStartToken.posinlist;i--){ 1807 targetList.add(index,stList.get(i)); 1808 stList.get(i).container = targetList; 1809 } 1810 1811 // reindex posinlist after remove token from container 1812 for(int i=index;i<targetList.size();i++){ 1813 targetList.get(i).posinlist = i; 1814 } 1815 1816 return lcEndToken.posinlist - lcStartToken.posinlist + 1; 1817 } 1818 1819 /** 1820 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1821 * 1822 * @param additionalToken usually was comma before or after this parse tree node that also need to be deleted 1823 * @return length of the source token list after remove tokens 1824 */ 1825 protected int removeAllMyTokensFromTokenList(TSourceToken additionalToken){ 1826 1827 int ret = -1; 1828 TSourceToken lcStartToken = getStartToken(); 1829 if (lcStartToken == null) return ret; 1830 int lcStartTokenPos = lcStartToken.posinlist; 1831 1832 1833 TSourceToken lcEndToken = getEndToken(); 1834 if (lcEndToken == null) return ret; 1835 int lcEndTokenPos = lcEndToken.posinlist; 1836 1837 1838 TSourceTokenList stList = lcStartToken.container; 1839 if (stList == null) return ret; 1840 1841 if ((additionalToken != null)&&(additionalToken.tokentype != ETokenType.ttRemoved)){ 1842 if (additionalToken.posinlist < lcStartTokenPos){ 1843 lcStartTokenPos = additionalToken.posinlist; 1844 additionalToken.tokentype = ETokenType.ttRemoved; 1845 }else if(additionalToken.posinlist > lcEndTokenPos){ 1846 lcEndTokenPos = additionalToken.posinlist; 1847 additionalToken.tokentype = ETokenType.ttRemoved; 1848 } 1849 } 1850 1851 //reset start and end token of those parse tree node 1852 resetStartAndEndTokenBeforeRemoveTokens(stList, lcStartTokenPos, lcEndTokenPos,false,null,null); 1853 1854 // remove token from list 1855 for(int i=lcEndTokenPos;i>=lcStartTokenPos;i--){ 1856 if ((i>stList.size()-1) ||(i<0)) break; 1857 stList.remove(i); 1858 } 1859 1860 // reindex posinlist after remove token from container 1861 for(int i=lcStartTokenPos;i<stList.size();i++){ 1862 if ((i>stList.size()-1) ||(i<0)) break; 1863 stList.get(i).posinlist = i; 1864 } 1865 1866 return lcStartTokenPos - lcEndTokenPos +1; 1867 } 1868 1869 /** 1870 * @deprecated since v1.8.8.0, use scriptWriter technology to modify the node 1871 * parse string to tokens, then add those tokens to the end of this parse tree node 1872 * 1873 * @param sqlSegment 1874 */ 1875 1876 1877 /** 1878 * Accept a visitor 1879 * 1880 * @param v visitor is a descendant class of {@link TParseTreeVisitor} 1881 */ 1882 public void accept(TParseTreeVisitor v) 1883 { 1884 1885 } 1886 1887 /** 1888 * Accept a visitor to iterate this class and sub-nodes of this class 1889 * 1890 * @param v visitor is a descendant class of {@link TParseTreeVisitor} 1891 */ 1892 public void acceptChildren(TParseTreeVisitor v) 1893 { 1894 1895 } 1896 1897 private TScriptGenerator scriptGenerator = null; 1898 1899 /** 1900 * Return the text string of this node, the return value is the same as {@link #toString()} if this node is not modified manually 1901 * after created by parser. 1902 * <br> 1903 * If this node is modified, then use this method to get string representation instead of the {@link #toString()} method. 1904 * 1905 * @return text string of this node 1906 */ 1907 public String toScript(){ 1908 if (scriptGenerator == null){ 1909 scriptGenerator = new TScriptGenerator(); 1910 } 1911 return scriptGenerator.generateScript(this); 1912 } 1913 1914 public void setChanged(){ 1915 // set the changed status to the first token of this node 1916 if (getStartToken() == null) return; 1917 getStartToken().setTokenstatus(ETokenStatus.tschanged); 1918 } 1919 1920 /** 1921 * Detect wether this node is modified by checking all tokens included in this node. 1922 * @return true if this node is modified. 1923 */ 1924 public boolean isChanged(){ 1925 boolean ret = false; 1926 TSourceToken lcStartToken = getStartToken(); 1927 if (lcStartToken == null) return ret; 1928 1929 TSourceToken lcEndToken = getEndToken(); 1930 if (lcEndToken == null) return ret; 1931 1932 TSourceTokenList stList = lcStartToken.container; 1933 if (stList == null) return ret; 1934 1935 int b = lcStartToken.posinlist; 1936 int e = lcEndToken.posinlist; 1937 1938 for(int i= b ; i<=e;i++){ 1939 if (stList.get(i).getTokenstatus() == ETokenStatus.tschanged){ 1940 ret = true; 1941 break; 1942 } 1943 } 1944 return ret; 1945 } 1946 1947 protected void doAppendNewNode( TParseTreeNode newNode, TParseTreeNode anchorNode,boolean needCommaBefore){ 1948 if (anchorNode != null){ 1949 anchorNode.appendNewNode(newNode,needCommaBefore); 1950 }else{ 1951 if (!newNode.isTokensInChain()){ 1952 this.appendNewNode(newNode,false); 1953 this.setEndToken(newNode.getEndToken()); 1954 } 1955 } 1956 } 1957 1958 private TParseTreeNode anchorNode = null; 1959 1960 public void setAnchorNode(TParseTreeNode anchorNode) { 1961 this.anchorNode = anchorNode; 1962 } 1963 1964 public TParseTreeNode getAnchorNode() { 1965 return anchorNode; 1966 } 1967 1968 public void setNewSubNode( TParseTreeNode oldSubNode, TParseTreeNode newSubNode,TParseTreeNode anchorNode){ 1969 if (newSubNode == null){ 1970 //remove the old node 1971 if (oldSubNode != null){ 1972 oldSubNode.removeTokens(); 1973 } 1974 }else { 1975 if (oldSubNode == null){ 1976 // add a total new where clause, we need to find an anchor 1977 if (newSubNode.getNodeStatus()!=ENodeStatus.nsNormal){ 1978 doAppendNewNode(newSubNode,anchorNode,false); 1979 } 1980 1981 }else{ 1982 //replace old where clause 1983 if (newSubNode.getNodeStatus()!=ENodeStatus.nsNormal){ 1984 oldSubNode.replaceWithNewNode(newSubNode); 1985 } 1986 } 1987 } 1988 } 1989 1990 public static boolean subNodeInNode(TParseTreeNode subNode, TParseTreeNode wholeNode){ 1991 if (wholeNode == null) return false; 1992 TSourceToken startToken = wholeNode.getStartToken(); 1993 if (startToken == null) return false; 1994 TSourceToken endToken = wholeNode.getEndToken(); 1995 if (endToken == null) return false; 1996 1997 TSourceToken startTokenOfSubNode = subNode.getStartToken(); 1998 if (startTokenOfSubNode == null) return false; 1999 if (startTokenOfSubNode.lineNo < startToken.lineNo) return false; 2000 if ((startTokenOfSubNode.lineNo == startToken.lineNo) && (startTokenOfSubNode.columnNo < startToken.columnNo)) return false; 2001 2002 TSourceToken endTokenOfSubNode = subNode.getEndToken(); 2003 if (endTokenOfSubNode == null) return false; 2004 2005 if (endTokenOfSubNode.lineNo > endToken.lineNo) return false; 2006 if ((endTokenOfSubNode.lineNo == endToken.lineNo)&&(endTokenOfSubNode.columnNo > endToken.columnNo)) return false; 2007 2008 return true; 2009 } 2010 2011// public int setText(EDbVendor dbVendor, String sqlText){ 2012// TGSqlParser sqlParser = new TGSqlParser(dbvendor); 2013// sqlParser.sqltext = sqlText; 2014// sqlParser.tokenizeSqltext(); 2015// 2016// } 2017 2018} 2019