001package gudusoft.gsqlparser;
002
003import gudusoft.gsqlparser.nodes.TObjectName;
004import gudusoft.gsqlparser.nodes.TParseTreeNode;
005
006
007import java.util.Stack;
008
009/**
010 * Represents a source token which is the basic syntactic unit of SQL.
011 * A token can be a key word, an identifier, a quoted identifier, a literal (or constant), or a special character symbol.
012 * Tokens are normally separated by whitespace (space, tab, newline), but need not be if there is no ambiguity
013 * (which is generally only the case if a special character is adjacent to some other token type).
014 * <p>
015 * The parse tree node consists of source tokens.
016 * <p>
017 * <br>A list of source token will be available after parse or tokenize the input SQL.
018 * <pre>
019 * {@code
020 *
021 *   TGSqlParser sqlparser = new TGSqlParser(EDbVendor.dbvoracle);
022 *   sqlparser.sqltext = "select col from t";
023 *   int ret = sqlparser.parse();
024 *   if (ret == 0){
025 *    for(int i=0;i<sqlparser.sourcetokenlist.size();i++){
026 *      TSourceToken st =  sqlparser.sourcetokenlist.get(i);
027 *      System.out.println(st.tokentype.toString()+" "+st.toString());
028 *    }
029 *   }else{
030 *     System.out.println(sqlparser.getErrormessage());
031 *   }
032 *
033 * }
034 * </pre>
035 * Get a list of source tokens after call the method {@link gudusoft.gsqlparser.TGSqlParser#parse() }
036 * or just call {@link gudusoft.gsqlparser.TGSqlParser#tokenizeSqltext()} if you only
037 * need to access tokens of input SQL without generating the full parse tree.
038 * <br><br>
039 * {@link #tokencode} is the unique id represents the type of token,
040 * some typical tokens are: whitespace, return, keyword, identifier. This value is mainly used by the parser internally.
041 * <p>
042 * {@link #tokentype} uniquely identify the token type in a more meaningful way. It's more easier to use
043 * this field in your program than tokencode.
044 */
045
046public class TSourceToken implements Cloneable {
047
048    public TSourceToken clone(){
049        TSourceToken cloneObject = new TSourceToken();
050        cloneObject.tokencode = this.tokencode;
051        cloneObject.tokenstatus = this.tokenstatus;
052        cloneObject.tokentype = this.tokentype;
053        cloneObject.dbObjectType = this.dbObjectType;
054        cloneObject.astext = this.astext;
055        cloneObject.lineNo = this.lineNo;
056        cloneObject.columnNo = this.columnNo;
057
058        return cloneObject;
059    }
060
061    public ESqlClause location = ESqlClause.unknown;
062
063    public static void  concatInChain(TSourceToken st1, TSourceToken st2){
064        st1.setNextTokenInChain(st2);
065        st2.setPrevTokenInChain(st1);
066    }
067
068    public void insertANewTokenAfterMe(TSourceToken newToken){
069        newToken.setNextTokenInChain(this.getNextTokenInChain());
070        if (this.getNextTokenInChain() != null){
071            this.getNextTokenInChain().setPrevTokenInChain(newToken);
072        }
073
074        newToken.setPrevTokenInChain(this);
075        this.setNextTokenInChain(newToken);
076    }
077
078    public void insertANewTokenBeforeMe(TSourceToken newToken){
079        newToken.setPrevTokenInChain(this.getPrevTokenInChain());
080        if (this.getPrevTokenInChain() != null){
081            this.getPrevTokenInChain().setNextTokenInChain(newToken);
082        }
083
084        newToken.setNextTokenInChain(this);
085        this.setPrevTokenInChain(newToken);
086    }
087
088    public  void updateNodeEndWithThisToken(){
089        for(int i=0;i<this.getNodesEndWithThisToken().size();i++){
090            TParseTreeNode node = this.getNodesEndWithThisToken().get(i);
091                if (this.getPrevTokenInChain() != null){
092                    node.setEndTokenDirectly(this.getPrevTokenInChain());
093                    this.getPrevTokenInChain().getNodesEndWithThisToken().push(node);
094                }
095        }
096    }
097
098    public void updateNodeStartWithThisToken(){
099        for(int i=0;i<this.getNodesStartFromThisToken().size();i++){
100            TParseTreeNode node = this.getNodesStartFromThisToken().get(i);
101            if (this.getNextTokenInChain() != null){
102                node.setStartTokenDirectly(this.getNextTokenInChain());
103                this.getNextTokenInChain().getNodesStartFromThisToken().push(node);
104            }
105        }
106    }
107
108    public void removeFromChain(){
109        if (this.getPrevTokenInChain() != null){
110            this.getPrevTokenInChain().setNextTokenInChain(this.getNextTokenInChain());
111            if (this.getNextTokenInChain() != null){
112                this.getNextTokenInChain().setPrevTokenInChain(this.getPrevTokenInChain());
113            }
114        }
115    }
116    private TSourceToken prevTokenInChain = null, nextTokenInChain = null;
117
118    public void setPrevTokenInChain(TSourceToken prevTokenInChain) {
119        this.prevTokenInChain = prevTokenInChain;
120    }
121
122    public void setNextTokenInChain(TSourceToken nextTokenInChain) {
123        this.nextTokenInChain = nextTokenInChain;
124    }
125
126    public TSourceToken getPrevTokenInChain() {
127        return prevTokenInChain;
128    }
129
130    public TSourceToken getNextTokenInChain() {
131        return nextTokenInChain;
132    }
133
134    public ETokenStatus getTokenstatus() {
135        return tokenstatus;
136    }
137
138    public int getQuoteSymbolLength(){
139        String pstr = this.toString();
140        if (pstr.startsWith("'")){
141            return 1;
142        }else if (pstr.startsWith("$")){
143            return this.dolqstart.length();
144        }else {
145            return 0;
146        }
147    }
148    public String getQuotedString(){
149        String pstr = this.toString();
150        if (pstr.startsWith("'")){
151            return pstr.substring(1,pstr.length()-1);
152        }else if (pstr.startsWith("$")){
153            return pstr.substring(this.dolqstart.length(),pstr.length()-  this.dolqstart.length());
154        }else {
155            return "";
156        }
157    }
158
159    /**
160     * String literal in SQL usually inside ``.
161     * Quoted identifier in SQL usually surrounded by [ and ],
162     * This method only returns the text inside those surroundings.
163     *
164     * @return text inside `` and []
165     */
166    public String getTextWithoutQuoted(){
167        if ((toString().startsWith("'"))||(toString().startsWith("["))||(toString().startsWith("\""))){
168            return  toString().substring(1, toString().length() - 1);
169        }else
170          return  toString();
171    }
172
173    /**
174     * Class constructor
175     */
176    public  TSourceToken(){
177    }
178
179    /**
180     * Class constructor, set a string value.
181     *
182     * @param s the string value this toke represent for.
183     */
184    public TSourceToken(String s){
185        astext = s;
186    }
187
188    /**
189     * The string text of this token
190     *
191     * @return the string text of this token
192     */
193    public String toScript(){
194        return astext;
195    }
196
197
198    public int prevTokenCode = 0;
199    /**
200     * Unique id of this token used by parser internally.
201     * check available value start from {@link gudusoft.gsqlparser.TBaseType#cmtslashstar}
202     */
203    public int tokencode;
204
205    /**
206     * the line number of the first character in this token
207     */
208    public long lineNo;
209
210    /**
211     * the column number of the first character in this token
212     */
213    public long columnNo;
214
215    /**
216     * Token's offset from the beginning of the input query.
217     * <pre>
218     *     {@code
219     *    public void testOffset(){
220     *        TGSqlParser sqlparser = new TGSqlParser(EDbVendor.dbvoracle);
221     *        sqlparser.sqltext = "select f from t\n" +
222     *                "where f>1\n";
223     *        assertTrue(sqlparser.parse() == 0);
224     *        for (int i=0;i<sqlparser.sourcetokenlist.size();i++){
225     *            TSourceToken st = sqlparser.sourcetokenlist.get(i);
226     *            String textFromOffset = sqlparser.sqltext.toString().substring((int)st.offset,(int)st.offset+st.toString().length());
227     *            assertTrue(st.toString().equalsIgnoreCase(textFromOffset));
228     *        }
229     *    }
230     *     }
231     * </pre>
232     */
233    public long offset;
234
235    /**
236     * Uniquely identify the token type in a more meaningful way. It's more easier to use
237     * this field in your program than tokencode.
238     * check available value in {@link gudusoft.gsqlparser.ETokenType ETokenType}
239     */
240    public ETokenType tokentype;
241
242    /**
243     * Container for this token which is a list of source token, this is the reference to {@link gudusoft.gsqlparser.TGSqlParser#sourcetokenlist}
244     */
245    public TSourceTokenList container;
246    
247    /**
248     * Index of this token in the {@link #container}, start from 0
249     *
250     * <pre>
251     *     {@code
252     *    public void testPosinList(){
253     *        TGSqlParser sqlparser = new TGSqlParser(EDbVendor.dbvoracle);
254     *        sqlparser.sqltext = "select f from t\n" +
255     *                "where f>1\n";
256     *        assertTrue(sqlparser.parse() == 0);
257     *        for (int i=0;i<sqlparser.sourcetokenlist.size();i++){
258     *            assertTrue(i == sqlparser.sourcetokenlist.get(i).posinlist);
259     *        }
260     *    }
261     *     }
262     * </pre>
263     */
264    public int posinlist;
265
266    /**
267     * Text representation for this token.
268     */
269    public String astext;
270
271    public void appendText(TSourceToken st){
272
273        if (this.toString().startsWith(".")){
274            // dataset_id.3_ST_LEAK_ORDERS, .3_ST_LEAK_ORDERS 是由 .3 和_ST_LEAK_ORDERS组成
275            // 在拼接 .3 和_ST_LEAK_ORDERS 时,需要把 .3 中的 . 去掉
276            this.astext = this.toString().substring(1,this.toString().length());
277            this.insertANewTokenBeforeMe(new TSourceToken("."));
278        }
279        if (st.toString().endsWith(".")){
280            this.astext = this.astext+st.toString().substring(0,st.toString().length()-1);
281            //st.tokenstatus = ETokenStatus.tsdeleted; // make sure this token don't appear in toString()
282
283            // 以下代码确保 920778. 后面的 . 在 TObjectName.toString() 中出现
284            //  prod-gcp-data-lakehouse-920778.EXTERNALDATA.SMS_LOCATION
285
286            st.tokencode = '.';
287            st.astext = ".";
288            st.tokentype = ETokenType.ttperiod;
289        }else{
290            this.astext = this.astext+st.toString();
291            st.tokenstatus = ETokenStatus.tsdeleted;
292        }
293    }
294
295    public void appendText(String text){
296        this.astext = this.astext+text;
297    }
298
299    public void insertText(TSourceToken st){
300        this.astext = st.toString()+this.astext;
301        st.tokenstatus = ETokenStatus.tsdeleted;
302    }
303    public void setTextWithBackup(String newText){
304
305        prevsourcecode = astext;
306        tag = TBaseType.tag_token_value_changed_in_on_canonical;
307
308        astext = newText;
309    }
310
311    public boolean isChangedInAsCanonical(){
312        return tag == TBaseType.tag_token_value_changed_in_on_canonical;
313    }
314
315    public void setTokenstatus(ETokenStatus tokenstatus) {
316        this.tokenstatus = tokenstatus;
317    }
318
319    // private ETokenStatus statusForRewrite = ETokenStatus.tsoriginal;
320
321    /**
322     * Maintenance the status of this token during lex and parsing.
323     * Used by the parser internally.
324     */
325    public ETokenStatus tokenstatus;
326
327    /**
328     * Start part of Dollar-quoted String Constants of PostgreSQL.
329     *
330     * While the standard syntax for specifying string constants is usually convenient,
331     * it can be difficult to understand when the desired string contains many single quotes or backslashes,
332     * since each of those must be doubled. To allow more readable queries in such situations,
333     * PostgreSQL provides another way, called "dollar quoting", to write string constants.
334     * A dollar-quoted string constant consists of a dollar sign ($), an optional "tag" of zero or more characters,
335     * another dollar sign, an arbitrary sequence of characters that makes up the string content, a dollar sign,
336     * the same tag that began this dollar quote, and a dollar sign.
337     * <p></p>
338     * For example, here are two different ways to specify the string "Dianne's horse" using dollar quoting:
339     * <pre>
340     *     $$Dianne's horse$$
341     *     $SomeTag$Dianne's horse$SomeTag$
342     * </pre>
343     *
344     * This field will return $$ and $SomeTag$ accordingly.
345     */
346    public String  dolqstart;//postgresql, start part of Dollar-quoted String Constants
347
348    private String prevsourcecode;
349
350    public void restoreText(){
351        astext = prevsourcecode;
352        tag = 0;
353    }
354
355    boolean insqlpluscmd;
356
357    /*
358     * source token can be end token of one or more parse tree nodes
359     * NodesEndWithThisToken includes all those parse tree nodes
360     */
361    private Stack<TParseTreeNode> NodesEndWithThisToken = null;
362
363    /**
364     *
365     *
366     * A list of nodes whose end token is this token.
367     *
368     * @return A list of nodes whose end token is this token.
369     */
370    public Stack<TParseTreeNode> getNodesEndWithThisToken() {
371        if (this.NodesEndWithThisToken == null){
372            this.NodesEndWithThisToken = new Stack<TParseTreeNode>();
373        }
374        return NodesEndWithThisToken;
375    }
376
377    /*
378     * source token can be start token of one or more parse tree nodes,
379     * NodesStartFromThisToken includes those parse tree nodes.
380     */
381    private Stack<TParseTreeNode> NodesStartFromThisToken = null;
382
383    /**
384     *
385     * A list of node whose start token is this token
386     *
387     * @return A list of node whose start token is this token
388     */
389    public Stack<TParseTreeNode> getNodesStartFromThisToken() {
390        if (this.NodesStartFromThisToken == null){
391            this.NodesStartFromThisToken = new Stack<TParseTreeNode>();
392        }
393        return NodesStartFromThisToken;
394    }
395
396
397    /**
398     * SQL statement that owns this token.
399     */
400    public TCustomSqlStatement stmt;
401
402    /**
403     * Space to save a value for temporary use
404     */
405    public int tag;
406
407    /**
408     * @deprecated use {@link #setDbObjectType} instead.
409     *
410     * @param dbObjType the database object type
411     */
412    public void setDbObjType(int dbObjType) {
413        this.dbObjType = dbObjType;
414    }
415
416    /**
417     * Token in a {@link TObjectName} has the same database object type as the objectName.
418     * Please use {@link gudusoft.gsqlparser.nodes.TObjectName#getDbObjectType} instead of this method if possible.
419     *
420     * @return the type of the database object
421     */
422    public int getDbObjType() {
423
424        return dbObjType;
425    }
426
427    /**
428     * Set the database object type of this token
429     *
430     * @param dbObjectType database object type
431     */
432    public void setDbObjectType(EDbObjectType dbObjectType) {
433        this.dbObjectType = dbObjectType;
434    }
435
436    /**
437     * Token in a {@link TObjectName} has the same database object type as the objectName.
438     * Please use {@link gudusoft.gsqlparser.nodes.TObjectName#getDbObjectType} instead of this method if possible.
439     *
440     * @return the type of the database object
441     */
442    public EDbObjectType getDbObjectType() {
443
444        return dbObjectType;
445    }
446
447    private EDbObjectType dbObjectType = EDbObjectType.unknown;
448    private int dbObjType = TObjectName.ttobjUnknown;
449
450
451
452    /**
453     * The database vendor which the SQL script includes this token will run against
454     *
455     * @param dbvendor the database vendor such as Oracle, DB2 and so on.
456     */
457    public void setDbvendor(EDbVendor dbvendor) {
458        this.dbvendor = dbvendor;
459    }
460
461    /**
462     * The database vendor which the SQL script includes this token will run against
463     * @return dbvendor the database vendor such as Oracle, DB2 and so on.
464     */
465    public EDbVendor getDbvendor() {
466
467        return dbvendor;
468    }
469
470    private EDbVendor dbvendor;
471
472    /**
473     * set new string of this token
474     *
475     * @param str the new string text
476     */
477    public void setString(String str){
478        astext = str;
479    }
480
481    /**
482     * The original string text for this token.
483     * @return  the string text
484     */
485    public String toString(){
486      return astext;
487    }
488
489    /**
490     * String text with the debug information such as coordinate, token code, token type
491     *
492     * @return the string value with full debug info
493     */
494    public String toStringDebug(){
495      String ret = lineNo +","+ columnNo +","+ astext.length()+","+offset+","+tokencode+" "+tokentype;
496      if (tokencode == TBaseType.cmtslashstar)
497        {ret = ret +" multi line comment";}
498      else if (tokencode == TBaseType.cmtdoublehyphen)
499        {ret = ret +" single line comment";}
500      else if (tokencode == TBaseType.lexspace)
501        {ret = ret +" space"; }
502      else if (tokencode == TBaseType.lexnewline)
503        {ret = ret +" newline"; }
504      else
505        {ret = ret +" "+ astext;}
506      return ret;
507    }
508
509    /**
510     * Space, return, comments are treated as non-solid token by default
511     *
512     * @param tokentype token type
513     * @return true if token type is not one of ttwhitespace,ttreturn,ttsimplecomment,ttbracketedcomment
514     */
515    public  static boolean isnonsolidtoken(ETokenType tokentype){
516        return ( (tokentype == ETokenType.ttwhitespace) || (tokentype == ETokenType.ttreturn)
517                ||(tokentype == ETokenType.ttsimplecomment)||(tokentype == ETokenType.ttbracketedcomment));
518    }
519
520    /**
521     *  Is this token a solid token or not.
522     *
523     * @return true if it's a non-solid token.
524     */
525    public  boolean isnonsolidtoken(){
526        return !issolidtoken();
527    }
528
529    /**
530     * Is this token a non-solid token or not.
531     *
532     * @return true if it's a solid token.
533     */
534    public boolean issolidtoken(){
535        return !isnonsolidtoken(this.tokentype);
536    }
537
538    private TSourceTokenList tokensBefore = null;
539    private TSourceTokenList tokensAfter = null;
540
541    /**
542     * Used in sql formatter package only.
543     *
544     * @return source token list
545     */
546    public TSourceTokenList getTokensAfter() {
547        if (this.tokensAfter == null){
548            this.tokensAfter = new TSourceTokenList();
549        }
550        return tokensAfter;
551    }
552
553    /**
554     * Used in sql formatter package only
555     *
556     * @return source token list
557     */
558    public TSourceTokenList getTokensBefore() {
559        if (this.tokensBefore == null){
560            this.tokensBefore = new TSourceTokenList();
561        }
562        return tokensBefore;
563    }
564
565    private TSourceToken replaceToken = null;
566
567    /**
568     * Used in sql formatter package only
569     *
570     * @param replaceToken replaced token
571     */
572    public void setReplaceToken(TSourceToken replaceToken) {
573        this.replaceToken = replaceToken;
574    }
575
576    /**
577     * Used in sql formatter package only
578     *
579     * @return replaced token
580     */
581    public TSourceToken getReplaceToken() {
582
583        return replaceToken;
584    }
585
586    public  TSourceToken nextToken(){
587        TSourceToken ret = null;
588        if (this.container == null) return ret;
589        if (this.posinlist >= this.container.size() - 1) return null;
590        return this.container.get(this.posinlist+1);
591    }
592
593
594    public TSourceToken searchTokenAtTheEndOfSameLine(){
595        TSourceToken ret = null;
596        if (this.container == null) return ret;
597        int i = this.container.searchLastTokenAtTheSameLine(this.posinlist);
598        if (i == -1) return null;
599        return this.container.get(i);
600    }
601    /**
602     * Search a token before or after this token in the same source token list.
603     * The result token should has the tokencode equals to the targetTokenCode
604     * in a specified range.
605     *
606     * @param targetTokenCode, the token code need to be searched
607     * @param range, &gt; 0, search token start from the next token and forward,
608     * = 0, just compare with this token,
609     * &lt; 0, search from the previous token and backword.
610     * @return the token with the same token code, otherwise, return null.
611     */
612    public TSourceToken searchToken(int targetTokenCode,int range, int stopTokenCode, boolean stopAtSemiColon){
613        TSourceToken ret = null;
614        if (this.container == null) return ret;
615        return this.container.searchToken(targetTokenCode,"",this,range,stopTokenCode,stopAtSemiColon);
616    }
617
618    public TSourceToken searchToken(int targetTokenCode,int range){
619        return searchToken(targetTokenCode,range,0,false);
620    }
621
622    /**
623     * Search a token before or after this token in the same source token list.
624     * The result token should has the string text equals to the targetTokenText
625     * in a specified range.
626     *
627     * @param targetTokenText, the target string text
628     * @param range, &gt; 0, search token start from the next token and forward,
629     * = 0, just compare with this token,
630     * &lt; 0, search from the previous token and backword.
631     * @return the token with the same token code, otherwise, return null.
632     */
633    public TSourceToken searchToken(String targetTokenText,int range, int stopTokenCode, boolean stopAtSemiColon){
634        TSourceToken ret = null;
635        if (this.container == null) return ret;
636        return this.container.searchToken(0,targetTokenText,this,range,stopTokenCode,stopAtSemiColon);
637    }
638
639    public TSourceToken searchToken(String targetTokenText,int range) {
640            return searchToken(targetTokenText,range,0,false);
641    }
642    /**
643     * Search the first non-solid token after the next objectName.
644     * <p></p>
645     * Take this SQL for example:
646     * <pre>
647     * return new scott.func(x1);
648     * </pre>
649     * If this token is <code>new</code>, then call searchTokenAfterObjectName will return <code>(</code> token.
650     *
651     * @return solid token after the next objectName
652     */
653    public TSourceToken searchTokenAfterObjectName(){
654        TSourceToken ret = null;
655        if (this.container == null) return ret;
656        int i = container.nextObjectNameToken(posinlist,1,false);
657        if (i == -1) return  ret;
658        return container.get(i).nextSolidToken();
659    }
660
661    public  TSourceToken nextSolidToken(int pstep){
662        TSourceToken ret = null;
663        if (this.container == null) return ret;
664        return this.container.nextsolidtoken(this,pstep,false);
665    }
666
667
668    /**
669     * The next token whose {@link #tokentype} is not ttreturn,ttwhitespace,ttsimplecomment and ttbracketedcomment.
670     *
671     * @return the next solid token, returns null if not found.
672     */
673    public  TSourceToken nextSolidToken(){
674        TSourceToken ret = null;
675        if (this.container == null) return ret;
676        return this.container.nextsolidtoken(this,1,false);
677    }
678
679    /**
680     * The next token whose {@link #tokentype} is not ttreturn,ttwhitespace,ttsimplecomment and ttbracketedcomment.
681     *
682     * @param treatCommentAsSolidToken, set to true will treat comment token as a solid token
683     * @return the solid token if found, otherwise, return null
684     */
685    public  TSourceToken nextSolidToken(boolean treatCommentAsSolidToken){
686        TSourceToken ret = null;
687        if (this.container == null) return ret;
688        return this.container.nextsolidtoken(this,1,treatCommentAsSolidToken);
689    }
690
691
692    /**
693     * The previous token whose {@link #tokentype} is not ttreturn,ttwhitespace,ttsimplecomment and ttbracketedcomment.
694     *
695     * @return the solid token if found, otherwise, return null
696     */
697    public  TSourceToken prevSolidToken(){
698        TSourceToken ret = null;
699        if (this.container == null) return ret;
700        return this.container.nextsolidtoken(this,-1,false);
701    }
702
703    /**
704     * The previous token whose {@link #tokentype} is not ttreturn,ttwhitespace,ttsimplecomment and ttbracketedcomment
705     *
706     * @param treatCommentAsSolidToken set to true will treat comment token as a solid token
707     * @return the solid token if found, otherwise, return null
708     */
709    public  TSourceToken prevSolidToken(boolean treatCommentAsSolidToken){
710        TSourceToken ret = null;
711        if (this.container == null) return ret;
712        return this.container.nextsolidtoken(this,-1,treatCommentAsSolidToken);
713    }
714
715    /**
716     * Check to see if this token is the first token in a line of the input SQL
717     *
718     * @return true if this is the first token of a line, otherwise, return false
719     */
720    public boolean isFirstTokenOfLine(){
721        TSourceToken st = prevSolidToken();
722        if (st == null) return true;
723        return  (st.lineNo != this.lineNo);
724    }
725
726    /**
727     * Check to see if this token is the last token of in a line in the input SQL
728     *
729     * @return true if this is the last token of a line, otherwise, return false
730     */
731    public boolean isLastTokenOfLine(){
732        TSourceToken st = nextSolidToken();
733        if (st == null) return true;
734        return  (st.lineNo != this.lineNo);
735    }
736
737
738    private TSourceToken linkToken = null;
739
740    /**
741     * Create a link between two tokens. Make it easy to access another linked token.
742     * Usually, those are two tokens like the parenthesis in this SQL: <code>(select * from t)</code>
743     *
744     * @param linkToken the token need to be linked
745     */
746    public void setLinkToken(TSourceToken linkToken) {
747        this.linkToken = linkToken;
748        linkToken.linkToken = this;
749    }
750
751    /**
752     * Gets the linked token.
753     * Take this SQL for example:
754     * <code>(select * from t)</code>, if this token is '(', then you call this method will return ')' token.
755     *
756     * @return the paired parenthesis in select, expression
757     */
758    public TSourceToken getLinkToken() {
759
760        return linkToken;
761    }
762
763    /**
764     * Remove double quote <code>""</code>, bracket quote <code>[]</code> , left/right brace <code>{}</code>
765     * from a delimited identifier and return string text of this identifier.
766     *
767     * @return string text of this token
768     *
769     * @deprecated since 2.5.3.4
770     */
771    public String toUnQuotedString(){
772        if ((tokentype == ETokenType.ttdqstring)
773                ||(tokentype == ETokenType.ttdbstring)
774                ||(tokentype == ETokenType.ttbrstring)
775        ){
776                  return toString().substring(1,toString().length() - 1);
777        }else if ((dbvendor == EDbVendor.dbvmysql)&&(toString().startsWith("`"))){
778            return toString().substring(1,toString().length() - 1);
779        }else {
780            return toString();
781        }
782    }
783
784}