Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/hsqldb/Parser.java


1   /* Copyrights and Licenses
2    *
3    * This product includes Hypersonic SQL.
4    * Originally developed by Thomas Mueller and the Hypersonic SQL Group. 
5    *
6    * Copyright (c) 1995-2000 by the Hypersonic SQL Group. All rights reserved. 
7    * Redistribution and use in source and binary forms, with or without modification, are permitted
8    * provided that the following conditions are met: 
9    *     -  Redistributions of source code must retain the above copyright notice, this list of conditions
10   *         and the following disclaimer. 
11   *     -  Redistributions in binary form must reproduce the above copyright notice, this list of
12   *         conditions and the following disclaimer in the documentation and/or other materials
13   *         provided with the distribution. 
14   *     -  All advertising materials mentioning features or use of this software must display the
15   *        following acknowledgment: "This product includes Hypersonic SQL." 
16   *     -  Products derived from this software may not be called "Hypersonic SQL" nor may
17   *        "Hypersonic SQL" appear in their names without prior written permission of the
18   *         Hypersonic SQL Group. 
19   *     -  Redistributions of any form whatsoever must retain the following acknowledgment: "This
20   *          product includes Hypersonic SQL." 
21   * This software is provided "as is" and any expressed or implied warranties, including, but
22   * not limited to, the implied warranties of merchantability and fitness for a particular purpose are
23   * disclaimed. In no event shall the Hypersonic SQL Group or its contributors be liable for any
24   * direct, indirect, incidental, special, exemplary, or consequential damages (including, but
25   * not limited to, procurement of substitute goods or services; loss of use, data, or profits;
26   * or business interruption). However caused any on any theory of liability, whether in contract,
27   * strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this
28   * software, even if advised of the possibility of such damage. 
29   * This software consists of voluntary contributions made by many individuals on behalf of the
30   * Hypersonic SQL Group.
31   *
32   *
33   * For work added by the HSQL Development Group:
34   *
35   * Copyright (c) 2001-2002, The HSQL Development Group
36   * All rights reserved.
37   *
38   * Redistribution and use in source and binary forms, with or without
39   * modification, are permitted provided that the following conditions are met:
40   *
41   * Redistributions of source code must retain the above copyright notice, this
42   * list of conditions and the following disclaimer, including earlier
43   * license statements (above) and comply with all above license conditions.
44   *
45   * Redistributions in binary form must reproduce the above copyright notice,
46   * this list of conditions and the following disclaimer in the documentation
47   * and/or other materials provided with the distribution, including earlier
48   * license statements (above) and comply with all above license conditions.
49   *
50   * Neither the name of the HSQL Development Group nor the names of its
51   * contributors may be used to endorse or promote products derived from this
52   * software without specific prior written permission.
53   *
54   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
55   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
56   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
57   * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG, 
58   * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
59   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
60   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
61   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
62   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
63   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65   */
66  
67  
68  package org.hsqldb;
69  
70  import org.hsqldb.lib.StringUtil;
71  import java.sql.SQLException;
72  import java.sql.Types;
73  import java.util.Vector;
74  
75  // fredt@users 20020130 - patch 491987 by jimbag@users - made optional
76  // changes applied to different parts of this method
77  // fredt@users 20020215 - patch 1.7.0 by fredt - quoted identifiers
78  // support for sql standard quoted identifiers for column and table names
79  // fredt@users 20020218 - patch 1.7.0 by fredt - DEFAULT keyword
80  // support for default values for table columns
81  // fredt@users 20020425 - patch 548182 by skitt@users - DEFAULT enhancement
82  // thertz@users 20020320 - patch 473613 by thertz - outer join condition bug
83  // fredt@users 20020420 - patch 523880 by leptipre@users - VIEW support
84  // fredt@users 20020525 - patch 559914 by fredt@users - SELECT INTO logging
85  
86  /**
87   *  Class declaration
88   *
89   * @version    1.7.0
90   */
91  class Parser {
92  
93      private Database       dDatabase;
94      private Tokenizer      tTokenizer;
95      private Session        cSession;
96      private String         sTable;
97      private String         sToken;
98      private Object         oData;
99      private int            iType;
100     private int            iToken;
101     private static boolean sql_enforce_size;
102 
103     /**
104      *  Constructor declaration
105      *
106      * @param  db
107      * @param  t
108      * @param  session
109      */
110     Parser(Database db, Tokenizer t, Session session) {
111 
112         dDatabase  = db;
113         tTokenizer = t;
114         cSession   = session;
115     }
116 
117     /**
118      *  Sets the enforceSize attribute of the Parser class
119      *
120      * @param  value  The new enforceSize value
121      */
122     static void setEnforceSize(boolean value) {
123         sql_enforce_size = value;
124     }
125 
126     /**
127      *  Method declaration
128      *
129      * @return
130      * @throws  SQLException
131      */
132     Result processSelect() throws SQLException {
133 
134         Select select = parseSelect();
135 
136         if (select.sIntoTable == null) {
137             return select.getResult(cSession.getMaxRows());
138         } else {
139 
140 // fredt@users 20020215 - patch 497872 by Nitin Chauhan
141 // to require column labels in SELECT INTO TABLE
142             for (int i = 0; i < select.eColumn.length; i++) {
143                 if (select.eColumn[i].getAlias().length() == 0) {
144                     throw Trace.error(Trace.LABEL_REQUIRED);
145                 }
146             }
147 
148             if (dDatabase.findUserTable(select.sIntoTable.name, cSession)
149                     != null) {
150                 throw Trace.error(Trace.TABLE_ALREADY_EXISTS,
151                                   select.sIntoTable.name);
152             }
153 
154             Result r = select.getResult(0);
155 
156 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
157             Table t;
158 
159             if (select.intoType == Table.TEXT_TABLE) {
160                 t = new TextTable(dDatabase, select.sIntoTable,
161                                   select.intoType, cSession);
162             } else {
163                 t = new Table(dDatabase, select.sIntoTable, select.intoType,
164                               cSession);
165             }
166 
167             t.addColumns(r);
168             t.createPrimaryKey();
169             dDatabase.linkTable(t);
170 
171 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
172             if (select.intoType == Table.TEXT_TABLE) {
173                 try {
174 
175                     // Use default lowercase name "<table>.csv" (with invalid
176                     // char's converted to underscores):
177                     String src =
178                         StringUtil.toLowerSubset(select.sIntoTable.name, '_')
179                         + ".csv";
180 
181                     t.setDataSource(src, false, cSession);
182                     logTableDDL(t);
183                     t.insert(r, cSession);
184                 } catch (SQLException e) {
185                     dDatabase.dropTable(select.sIntoTable.name, false, false,
186                                         cSession);
187 
188                     throw (e);
189                 }
190             } else {
191                 logTableDDL(t);
192 
193                 // SELECT .. INTO can't fail because of constraint violation
194                 t.insert(r, cSession);
195             }
196 
197             int i = r.getSize();
198 
199             r              = new Result();
200             r.iUpdateCount = i;
201 
202             return r;
203         }
204     }
205 
206     /**
207      *  Logs the DDL for a table created with INTO.
208      *  Uses three dummy arguments for getTableDDL() as the new table has no
209      *  FK constraints.
210      *
211      * @throws  SQLException
212      */
213     void logTableDDL(Table t) throws SQLException {
214 
215         if (t.isTemp()) {
216             return;
217         }
218 
219         StringBuffer tableDDL = new StringBuffer();
220 
221         DatabaseScript.getTableDDL(dDatabase, t, 0, null, null, tableDDL);
222 
223         String sourceDDL = DatabaseScript.getDataSource(t);
224 
225         dDatabase.logger.writeToLog(cSession, tableDDL.toString());
226 
227         if (sourceDDL != null) {
228             dDatabase.logger.writeToLog(cSession, sourceDDL);
229         }
230     }
231 
232     /**
233      *  Method declaration
234      *
235      * @return
236      * @throws  SQLException
237      */
238     Result processCall() throws SQLException {
239 
240         Expression e = parseExpression();
241 
242         e.resolve(null);
243 
244         int    type = e.getDataType();
245         Object o    = e.getValue();
246         Result r    = new Result(1);
247 
248         r.sTable[0]  = "";
249         r.colType[0] = type;
250         r.sLabel[0]  = "";
251         r.sName[0]   = "";
252 
253         Object row[] = new Object[1];
254 
255         row[0] = o;
256 
257         r.add(row);
258 
259         return r;
260     }
261 
262     /**
263      *  Method declaration
264      *
265      * @return
266      * @throws  SQLException
267      */
268     Result processUpdate() throws SQLException {
269 
270         String token = tTokenizer.getString();
271 
272         cSession.checkReadWrite();
273         cSession.check(token, UserManager.UPDATE);
274 
275         Table       table  = dDatabase.getTable(token, cSession);
276         TableFilter filter = new TableFilter(table, null, false);
277 
278         if (table.isView()) {
279             throw Trace.error(Trace.NOT_A_TABLE, token);
280         }
281 
282         tTokenizer.getThis("SET");
283 
284         Vector vColumn = new Vector();
285         Vector eColumn = new Vector();
286         int    len     = 0;
287 
288         token = null;
289 
290         do {
291             len++;
292 
293             int i = table.getColumnNr(tTokenizer.getString());
294 
295             vColumn.addElement(new Integer(i));
296             tTokenizer.getThis("=");
297 
298             Expression e = parseExpression();
299 
300             e.resolve(filter);
301             eColumn.addElement(e);
302 
303             token = tTokenizer.getString();
304         } while (token.equals(","));
305 
306         Expression eCondition = null;
307 
308         if (token.equals("WHERE")) {
309             eCondition = parseExpression();
310 
311             eCondition.resolve(filter);
312             filter.setCondition(eCondition);
313         } else {
314             tTokenizer.back();
315         }
316 
317         // do the update
318         table.fireAll(TriggerDef.UPDATE_BEFORE);
319 
320         Expression exp[] = new Expression[len];
321 
322         eColumn.copyInto(exp);
323 
324         int col[]   = new int[len];
325         int type[]  = new int[len];
326         int csize[] = new int[len];
327 
328         for (int i = 0; i < len; i++) {
329             col[i] = ((Integer) vColumn.elementAt(i)).intValue();
330 
331             Column column = table.getColumn(col[i]);
332 
333             type[i]  = column.getType();
334             csize[i] = column.getSize();
335         }
336 
337         int count = 0;
338 
339         if (filter.findFirst()) {
340             Result del  = new Result();    // don't need column count and so on
341             Result ins  = new Result();
342             int    size = table.getColumnCount();
343 
344             do {
345                 if (eCondition == null || eCondition.test()) {
346                     Object nd[] = filter.oCurrentData;
347 
348                     del.add(nd);
349 
350                     Object ni[] = table.getNewRow();
351 
352 // fredt@users 20020130 - patch 1.7.0 by fredt
353                     System.arraycopy(nd, 0, ni, 0, size);
354 
355                     /*
356                      for (int i = 0; i < size; i++) {
357                      ni[i] = nd[i];
358                      }
359                      */
360 
361 // fredt@users 20020130 - patch 491987 by jimbag@users - made optional
362                     if (sql_enforce_size) {
363                         for (int i = 0; i < len; i++) {
364                             ni[col[i]] = enforceSize(exp[i].getValue(type[i]),
365                                                      type[i], csize[i], true);
366                         }
367                     } else {
368                         for (int i = 0; i < len; i++) {
369                             ni[col[i]] = exp[i].getValue(type[i]);
370                         }
371                     }
372 
373                     ins.add(ni);
374                 }
375             } while (filter.next());
376 
377             cSession.beginNestedTransaction();
378 
379             try {
380                 Record nd = del.rRoot;
381 
382                 while (nd != null) {
383                     table.fireAll(TriggerDef.UPDATE_BEFORE_ROW, nd.data);
384                     table.deleteNoCheck(nd.data, cSession, true);
385 
386                     nd = nd.next;
387                 }
388 
389                 Record ni = ins.rRoot;
390 
391                 while (ni != null) {
392                     table.insertNoCheck(ni.data, cSession, true);
393 
394                     ni = ni.next;
395 
396                     count++;
397                 }
398 
399                 table.checkUpdate(col, del, ins);
400 
401                 ni = ins.rRoot;
402 
403                 while (ni != null) {
404 
405                     // fire triggers now that update has been checked
406                     table.fireAll(TriggerDef.UPDATE_AFTER_ROW, ni.data);
407 
408                     ni = ni.next;
409                 }
410 
411                 cSession.endNestedTransaction(false);
412             } catch (SQLException e) {
413 
414                 // update failed (constraint violation)
415                 cSession.endNestedTransaction(true);
416 
417                 throw e;
418             }
419         }
420 
421         table.fireAll(TriggerDef.UPDATE_AFTER);
422 
423         Result r = new Result();
424 
425         r.iUpdateCount = count;
426 
427         return r;
428     }
429 
430     /**
431      *  Method declaration
432      *
433      * @return
434      * @throws  SQLException
435      */
436     Result processDelete() throws SQLException {
437 
438         tTokenizer.getThis("FROM");
439 
440         String token = tTokenizer.getString();
441 
442         cSession.checkReadWrite();
443         cSession.check(token, UserManager.DELETE);
444 
445         Table       table  = dDatabase.getTable(token, cSession);
446         TableFilter filter = new TableFilter(table, null, false);
447 
448         if (table.isView()) {
449             throw Trace.error(Trace.NOT_A_TABLE, token);
450         }
451 
452         token = tTokenizer.getString();
453 
454         Expression eCondition = null;
455 
456         if (token.equals("WHERE")) {
457             eCondition = parseExpression();
458 
459             eCondition.resolve(filter);
460             filter.setCondition(eCondition);
461         } else {
462             tTokenizer.back();
463         }
464 
465 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
466         Trace.check(!table.isDataReadOnly(), Trace.DATA_IS_READONLY);
467         table.fireAll(TriggerDef.DELETE_BEFORE);
468 
469         int count = 0;
470 
471         if (filter.findFirst()) {
472             Result del = new Result();    // don't need column count and so on
473 
474             do {
475                 if (eCondition == null || eCondition.test()) {
476                     del.add(filter.oCurrentData);
477                 }
478             } while (filter.next());
479 
480             Record n = del.rRoot;
481 
482             while (n != null) {
483                 table.delete(n.data, cSession);
484 
485                 count++;
486 
487                 n = n.next;
488             }
489         }
490 
491         table.fireAll(TriggerDef.DELETE_AFTER);
492 
493         Result r = new Result();
494 
495         r.iUpdateCount = count;
496 
497         return r;
498     }
499 
500     /**
501      *  Method declaration
502      *
503      * @return
504      * @throws  SQLException
505      */
506     Result processInsert() throws SQLException {
507 
508         tTokenizer.getThis("INTO");
509 
510         String token = tTokenizer.getString();
511 
512         cSession.checkReadWrite();
513         cSession.check(token, UserManager.INSERT);
514 
515         Table t = dDatabase.getTable(token, cSession);
516 
517         if (t.isView()) {
518             throw Trace.error(Trace.NOT_A_TABLE, token);
519         }
520 
521         token = tTokenizer.getString();
522 
523         Vector vcolumns = null;
524 
525         if (token.equals("(")) {
526             vcolumns = new Vector();
527 
528             int i = 0;
529 
530             while (true) {
531                 vcolumns.addElement(tTokenizer.getString());
532 
533                 i++;
534 
535                 token = tTokenizer.getString();
536 
537                 if (token.equals(")")) {
538                     break;
539                 }
540 
541                 if (!token.equals(",")) {
542                     throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
543                 }
544             }
545 
546             token = tTokenizer.getString();
547         }
548 
549         int count = 0;
550         int len;
551 
552         if (vcolumns == null) {
553             len = t.getColumnCount();
554         } else {
555             len = vcolumns.size();
556         }
557 
558 // fredt@users 20020218 - patch 1.7.0 by fredt - DEFAULT keyword
559         if (token.equals("VALUES")) {
560             tTokenizer.getThis("(");
561 
562             Object  row[]   = t.getNewRow();
563             boolean check[] = (vcolumns == null) ? null
564                                                  : new boolean[row.length];
565             int     i       = 0;
566 
567             while (true) {
568                 int colindex;
569 
570                 if (vcolumns == null) {
571                     colindex = i;
572 
573                     if (i == len) {
574 
575                         // fredt will be caught in Trace.check below
576                         break;
577                     }
578                 } else {
579                     colindex = t.getColumnNr((String) vcolumns.elementAt(i));
580                     check[colindex] = true;
581                 }
582 
583                 Column column = t.getColumn(colindex);
584 
585 // fredt@users 20020130 - patch 491987 by jimbag@users - made optional
586                 if (sql_enforce_size) {
587                     row[colindex] = enforceSize(getValue(column.getType()),
588                                                 column.getType(),
589                                                 column.getSize(), true);
590                 } else {
591                     row[colindex] = getValue(column.getType());
592                 }
593 
594                 i++;
595 
596                 token = tTokenizer.getString();
597 
598                 if (token.equals(")")) {
599                     break;
600                 }
601 
602                 if (!token.equals(",")) {
603                     throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
604                 }
605             }
606 
607             Trace.check(len == i, Trace.COLUMN_COUNT_DOES_NOT_MATCH);
608 
609             if (vcolumns != null) {
610                 for (i = 0; i < check.length; i++) {
611                     if (check[i] == false) {
612                         String def = t.getColumn(i).getDefaultString();
613 
614                         if (def != null) {
615                             row[i] = Column.convertObject(
616                                 def, t.getColumn(i).getType());
617                         }
618                     }
619                 }
620             }
621 
622             t.insert(row, cSession);
623 
624             count = 1;
625         } else if (token.equals("SELECT")) {
626             Result result = processSelect();
627             Record r      = result.rRoot;
628 
629             Trace.check(len == result.getColumnCount(),
630                         Trace.COLUMN_COUNT_DOES_NOT_MATCH);
631 
632             int col[]  = new int[len];
633             int type[] = new int[len];
634 
635             for (int i = 0; i < len; i++) {
636                 int j;
637 
638                 if (vcolumns == null) {
639                     j = i;
640                 } else {
641                     j = t.getColumnNr((String) vcolumns.elementAt(i));
642                 }
643 
644                 col[i]  = j;
645                 type[i] = t.getColumn(j).getType();
646             }
647 
648             cSession.beginNestedTransaction();
649 
650             try {
651                 while (r != null) {
652                     Object  row[]   = t.getNewRow();
653                     boolean check[] = new boolean[row.length];
654 
655                     for (int i = 0; i < len; i++) {
656                         check[col[i]] = true;
657 
658                         if (type[i] != result.colType[i]) {
659                             row[col[i]] = Column.convertObject(r.data[i],
660                                                                type[i]);
661                         } else {
662                             row[col[i]] = r.data[i];
663                         }
664                     }
665 
666                     // skitt@users - this is exactly the same loop as the
667                     // above - it probably should be in a separate method
668                     for (int i = 0; i < check.length; i++) {
669                         if (check[i] == false) {
670                             String def = t.getColumn(i).getDefaultString();
671 
672                             if (def != null) {
673                                 row[i] = Column.convertObject(
674                                     def, t.getColumn(i).getType());
675                             }
676                         }
677                     }
678 
679                     t.insert(row, cSession);
680 
681                     count++;
682 
683                     r = r.next;
684                 }
685 
686                 cSession.endNestedTransaction(false);
687             } catch (SQLException e) {
688 
689                 // insert failed (violation of primary key)
690                 cSession.endNestedTransaction(true);
691 
692                 throw e;
693             }
694         } else {
695             throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
696         }
697 
698         Result r = new Result();
699 
700         r.iUpdateCount = count;
701 
702         return r;
703     }
704 
705 // fredt@users 20020130 - patch 491987 by jimbag@users - modified
706 
707     /**
708      *  Check an object for type CHAR and VARCHAR and truncate/pad based on
709      *  the  size
710      *
711      * @param  obj   object to check
712      * @param  type  the object type
713      * @param  size  size to enforce
714      * @param  pad   pad strings
715      * @return       the altered object if the right type, else the object
716      *      passed in unaltered
717      */
718     static Object enforceSize(Object obj, int type, int size, boolean pad) {
719 
720         // todo: need to handle BINARY like this as well
721         if (size == 0 || obj == null) {
722             return obj;
723         }
724 
725         switch (type) {
726 
727             case Types.CHAR :
728                 return padOrTrunc((String) obj, size, pad);
729 
730             case Types.VARCHAR :
731                 if (((String) obj).length() > size) {
732 
733                     // Just truncate for VARCHAR type
734                     return ((String) obj).substring(0, size);
735                 }
736             default :
737                 return obj;
738         }
739     }
740 
741     /**
742      *  Pad or truncate a string to len size
743      *
744      * @param  s    the string to pad to truncate
745      * @param  len  the len to make the string
746      * @param pad   pad the string
747      * @return      the string of size len
748      */
749     static String padOrTrunc(String s, int len, boolean pad) {
750 
751         if (s.length() >= len) {
752             return s.substring(0, len);
753         }
754 
755         StringBuffer b = new StringBuffer(len);
756 
757         b.append(s);
758 
759         if (pad) {
760             for (int i = s.length(); i < len; i++) {
761                 b.append(' ');
762             }
763         }
764 
765         return b.toString();
766     }
767 
768     /**
769      *  Method declaration
770      *
771      * @return
772      * @throws  SQLException
773      */
774     Select parseSelect() throws SQLException {
775 
776         Select select = new Select();
777 
778 // fredt@users 20011010 - patch 471710 by fredt - LIMIT rewritten
779 // SELECT LIMIT n m DISTINCT ... queries and error message
780 // "SELECT LIMIT n m ..." creates the result set for the SELECT statement then
781 // discards the first n rows and returns m rows of the remaining result set
782 // "SELECT LIMIT 0 m" is equivalent to "SELECT TOP m" or "SELECT FIRST m"
783 // in other RDBMS's
784 // "SELECT LIMIT n 0" discards the first n rows and returns the remaining rows
785 // fredt@users 20020225 - patch 456679 by hiep256 - TOP keyword
786         String token = tTokenizer.getString();
787 
788         if (token.equals("LIMIT")) {
789             String limStart = tTokenizer.getString();
790             String limEnd   = tTokenizer.getString();
791 
792             try {
793                 select.limitStart = new Integer(limStart).intValue();
794                 select.limitCount = new Integer(limEnd).intValue();
795             } catch (NumberFormatException ex) {
796 
797                 // todo: add appropriate error type and message to Trace.java
798                 throw Trace.error(Trace.WRONG_DATA_TYPE, "LIMIT n m");
799             }
800 
801             token = tTokenizer.getString();
802         } else if (token.equals("TOP")) {
803             String limEnd = tTokenizer.getString();
804 
805             try {
806                 select.limitStart = 0;
807                 select.limitCount = new Integer(limEnd).intValue();
808             } catch (NumberFormatException ex) {
809 
810                 // todo: add appropriate error type and message to Trace.java
811                 throw Trace.error(Trace.WRONG_DATA_TYPE, "TOP m");
812             }
813 
814             token = tTokenizer.getString();
815         }
816 
817         if (token.equals("DISTINCT")) {
818             select.isDistinctSelect = true;
819         } else {
820             tTokenizer.back();
821         }
822 
823         // parse column list
824         Vector vcolumn = new Vector();
825 
826         do {
827             Expression e = parseExpression();
828 
829             token = tTokenizer.getString();
830 
831             if (token.equals("AS")) {
832                 e.setAlias(tTokenizer.getName(),
833                            tTokenizer.wasQuotedIdentifier());
834 
835                 token = tTokenizer.getString();
836             } else if (tTokenizer.wasName()) {
837                 e.setAlias(token, tTokenizer.wasQuotedIdentifier());
838 
839                 token = tTokenizer.getString();
840             }
841 
842             vcolumn.addElement(e);
843         } while (token.equals(","));
844 
845         if (token.equals("INTO")) {
846 
847 // fredt@users 20020221 - patch 513005 by sqlbob@users (RMP)
848             token = tTokenizer.getString();
849 
850             if (token.equals("CACHED")) {
851                 select.intoType = Table.CACHED_TABLE;
852                 select.sIntoTable =
853                     new HsqlName(tTokenizer.getString(),
854                                  tTokenizer.wasQuotedIdentifier());
855             } else if (token.equals("TEMP")) {
856                 select.intoType = Table.TEMP_TABLE;
857                 select.sIntoTable =
858                     new HsqlName(tTokenizer.getString(),
859                                  tTokenizer.wasQuotedIdentifier());
860             } else if (token.equals("TEXT")) {
861                 select.intoType = Table.TEXT_TABLE;
862                 select.sIntoTable =
863                     new HsqlName(tTokenizer.getString(),
864                                  tTokenizer.wasQuotedIdentifier());
865             } else {
866                 select.sIntoTable =
867                     new HsqlName(token, tTokenizer.wasQuotedIdentifier());
868             }
869 
870             token = tTokenizer.getString();
871         }
872 
873         if (!token.equals("FROM")) {
874             throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
875         }
876 
877         Expression condition = null;
878 
879         // parse table list
880         Vector vfilter = new Vector();
881 
882         vfilter.addElement(parseTableFilter(false));
883 
884         while (true) {
885             token = tTokenizer.getString();
886 
887             if (token.equals("LEFT")) {
888                 token = tTokenizer.getString();
889 
890                 if (token.equals("OUTER")) {
891                     token = tTokenizer.getString();
892                 }
893 
894                 Trace.check(token.equals("JOIN"), Trace.UNEXPECTED_TOKEN,
895                             token);
896                 vfilter.addElement(parseTableFilter(true));
897                 tTokenizer.getThis("ON");
898 
899 // thertz@users 20020320 - patch 473613 - outer join condition bug
900 // we now call parseJoinCondition() because a limitation of HSQLDB results
901 // in incorrect results for OUTER JOINS that have anything other than
902 // tableA.colA=tableB.colB type expressions
903                 //condition = addCondition(condition, parseExpression());
904                 condition = addCondition(condition,
905                                          parseOuterJoinCondition());
906             } else if (token.equals("INNER")) {
907                 tTokenizer.getThis("JOIN");
908                 vfilter.addElement(parseTableFilter(false));
909                 tTokenizer.getThis("ON");
910 
911                 condition = addCondition(condition, parseExpression());
912             } else if (token.equals(",")) {
913                 vfilter.addElement(parseTableFilter(false));
914             } else {
915                 break;
916             }
917         }
918 
919         tTokenizer.back();
920 
921         int         len      = vfilter.size();
922         TableFilter filter[] = new TableFilter[len];
923 
924         vfilter.copyInto(filter);
925 
926         select.tFilter = filter;
927 
928         // expand [table.]* columns
929         len = vcolumn.size();
930 
931         for (int i = 0; i < len; i++) {
932             Expression e = (Expression) (vcolumn.elementAt(i));
933 
934             if (e.getType() == Expression.ASTERIX) {
935                 int    current = i;
936                 Table  table   = null;
937                 String n       = e.getTableName();
938 
939                 for (int t = 0; t < filter.length; t++) {
940                     TableFilter f = filter[t];
941 
942                     e.resolve(f);
943 
944                     if (n != null &&!n.equals(f.getName())) {
945                         continue;
946                     }
947 
948                     table = f.getTable();
949 
950                     int col = table.getColumnCount();
951 
952                     for (int c = 0; c < col; c++) {
953                         Expression ins = new Expression(
954                             f.getName(), table.getColumn(c).columnName.name,
955                             table.getColumn(c).columnName.isNameQuoted);
956 
957                         vcolumn.insertElementAt(ins, current++);
958 
959                         // now there is one element more to parse
960                         len++;
961                     }
962                 }
963 
964                 Trace.check(table != null, Trace.TABLE_NOT_FOUND, n);
965 
966                 // minus the asterix element
967                 len--;
968 
969                 vcolumn.removeElementAt(current);
970             } else if (e.getType() == Expression.COLUMN) {
971                 if (e.getTableName() == null) {
972                     for (int filterIndex = 0; filterIndex < filter.length;
973                             filterIndex++) {
974                         e.resolve(filter[filterIndex]);
975                     }
976                 }
977             }
978         }
979 
980         select.iResultLen = len;
981 
982         // where
983         token = tTokenizer.getString();
984 
985         if (token.equals("WHERE")) {
986             condition = addCondition(condition, parseExpression());
987             token     = tTokenizer.getString();
988         }
989 
990         select.eCondition = condition;
991 
992 // fredt@users 20020215 - patch 1.7.0 by fredt
993 // to support GROUP BY with more than one column
994         if (token.equals("GROUP")) {
995             tTokenizer.getThis("BY");
996 
997             len = 0;
998 
999             do {
1000                Expression e = parseExpression();
1001
1002                e = doOrderGroup(e, vcolumn);
1003
1004                vcolumn.addElement(e);
1005
1006                token = tTokenizer.getString();
1007
1008                len++;
1009            } while (token.equals(","));
1010
1011            select.iGroupLen = len;
1012        }
1013
1014        if (token.equals("HAVING")) {
1015
1016            //fredt - not yet!
1017            Expression hcondition = null;
1018
1019            addCondition(hcondition, parseExpression());
1020
1021            select.havingCondition = hcondition;
1022            token                  = tTokenizer.getString();
1023
1024            throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED);
1025        }
1026
1027        if (token.equals("ORDER")) {
1028            tTokenizer.getThis("BY");
1029
1030            len = 0;
1031
1032            do {
1033                Expression e = parseExpression();
1034
1035                e     = doOrderGroup(e, vcolumn);
1036                token = tTokenizer.getString();
1037
1038                if (token.equals("DESC")) {
1039                    e.setDescending();
1040
1041                    token = tTokenizer.getString();
1042                } else if (token.equals("ASC")) {
1043                    token = tTokenizer.getString();
1044                }
1045
1046                vcolumn.addElement(e);
1047
1048                len++;
1049            } while (token.equals(","));
1050
1051            select.iOrderLen = len;
1052        }
1053
1054        len            = vcolumn.size();
1055        select.eColumn = new Expression[len];
1056
1057        vcolumn.copyInto(select.eColumn);
1058
1059        if (token.equals("UNION")) {
1060            token = tTokenizer.getString();
1061
1062            if (token.equals("ALL")) {
1063                select.iUnionType = Select.UNIONALL;
1064            } else {
1065                select.iUnionType = Select.UNION;
1066
1067                tTokenizer.back();
1068            }
1069
1070            tTokenizer.getThis("SELECT");
1071
1072            select.sUnion = parseSelect();
1073        } else if (token.equals("INTERSECT")) {
1074            tTokenizer.getThis("SELECT");
1075
1076            select.iUnionType = Select.INTERSECT;
1077            select.sUnion     = parseSelect();
1078        } else if (token.equals("EXCEPT") || token.equals("MINUS")) {
1079            tTokenizer.getThis("SELECT");
1080
1081            select.iUnionType = Select.EXCEPT;
1082            select.sUnion     = parseSelect();
1083        } else {
1084            tTokenizer.back();
1085        }
1086
1087        return select;
1088    }
1089
1090    /**
1091     *  Description of the Method
1092     *
1093     * @param  e                          Description of the Parameter
1094     * @param  vcolumn                    Description of the Parameter
1095     * @return                            Description of the Return Value
1096     * @exception  java.sql.SQLException  Description of the Exception
1097     */
1098    private Expression doOrderGroup(Expression e,
1099                                    Vector vcolumn)
1100                                    throws java.sql.SQLException {
1101
1102        if (e.getType() == Expression.VALUE) {
1103
1104            // order by 1,2,3
1105            if (e.getDataType() == Types.INTEGER) {
1106                int i = ((Integer) e.getValue()).intValue();
1107
1108                e = (Expression) vcolumn.elementAt(i - 1);
1109            }
1110        } else if (e.getType() == Expression.COLUMN
1111                   && e.getTableName() == null) {
1112
1113            // this could be an alias column
1114            String s = e.getColumnName();
1115
1116            for (int i = 0, vSize = vcolumn.size(); i < vSize; i++) {
1117                Expression ec = (Expression) vcolumn.elementAt(i);
1118
1119                if (s.equals(ec.getAlias())) {
1120                    e = ec;
1121
1122                    break;
1123                }
1124            }
1125        }
1126
1127        return e;
1128    }
1129
1130    /**
1131     *  Method declaration
1132     *
1133     * @param  outerjoin
1134     * @return
1135     * @throws  SQLException
1136     */
1137    private TableFilter parseTableFilter(boolean outerjoin)
1138    throws SQLException {
1139
1140        String token = tTokenizer.getString();
1141        Table  t     = null;
1142
1143        if (token.equals("(")) {
1144            tTokenizer.getThis("SELECT");
1145
1146            Select s = parseSelect();
1147            Result r = s.getResult(0);
1148
1149            // it's not a problem that this table has not a unique name
1150            t = new Table(dDatabase, new HsqlName("SYSTEM_SUBQUERY", false),
1151                          Table.SYSTEM_TABLE, null);
1152
1153            tTokenizer.getThis(")");
1154            t.addColumns(r);
1155            t.createPrimaryKey();
1156
1157            // subquery creation can't fail because constraint violation
1158            t.insert(r, cSession);
1159        } else {
1160            cSession.check(token, UserManager.SELECT);
1161
1162            t = dDatabase.getTable(token, cSession);
1163
1164// fredt@users 20020420 - patch523880 by leptipre@users - VIEW support
1165            if (t.isView()) {
1166                String Viewname    = token;
1167                int    CurrentPos  = tTokenizer.getPosition();
1168                int    sLength     = tTokenizer.getLength();
1169                int    TokenLength = token.length();
1170                int    NewCurPos   = CurrentPos;
1171
1172                token = tTokenizer.getString();
1173
1174                if (token.equals("AS")) {
1175                    Viewname  = tTokenizer.getName();
1176                    NewCurPos = tTokenizer.getPosition();
1177                } else if (tTokenizer.wasName()) {
1178                    Viewname  = token;
1179                    NewCurPos = tTokenizer.getPosition();
1180                } else {
1181                    tTokenizer.back();
1182                }
1183
1184                String sLeft = tTokenizer.getPart(0, CurrentPos
1185                                                  - TokenLength);
1186                String       sRight = tTokenizer.getPart(NewCurPos, sLength);
1187                View         v         = (View) t;
1188                String       sView     = v.getStatement();
1189                StringBuffer sFromView = new StringBuffer(128);
1190
1191                sFromView.append(sLeft);
1192                sFromView.append('(');
1193                sFromView.append(sView);
1194                sFromView.append(") ");
1195                sFromView.append(Viewname);
1196                sFromView.append(sRight);
1197                tTokenizer.setString(sFromView.toString(),
1198                                     CurrentPos - TokenLength + 1);
1199                tTokenizer.getThis("SELECT");
1200
1201                Select s = parseSelect();
1202                Result r = s.getResult(0);
1203
1204                // it's not a problem that this table has not a unique name
1205                t = new Table(dDatabase,
1206                              new HsqlName("SYSTEM_SUBQUERY", false),
1207                              Table.SYSTEM_TABLE, null);
1208
1209                tTokenizer.getThis(")");
1210                t.addColumns(r);
1211                t.createPrimaryKey();
1212
1213                // subquery creation can't fail because constraint violation
1214                t.insert(r, cSession);
1215            }
1216        }
1217
1218        String sAlias = null;
1219
1220        token = tTokenizer.getString();
1221
1222        if (token.equals("AS")) {
1223            sAlias = tTokenizer.getName();
1224        } else if (tTokenizer.wasName()) {
1225            sAlias = token;
1226        } else {
1227            tTokenizer.back();
1228        }
1229
1230        return new TableFilter(t, sAlias, outerjoin);
1231    }
1232
1233    /**
1234     *  Method declaration
1235     *
1236     * @param  e1
1237     * @param  e2
1238     * @return
1239     */
1240    private Expression addCondition(Expression e1, Expression e2) {
1241
1242        if (e1 == null) {
1243            return e2;
1244        } else if (e2 == null) {
1245            return e1;
1246        } else {
1247            return new Expression(Expression.AND, e1, e2);
1248        }
1249    }
1250
1251    /**
1252     *  Method declaration
1253     *
1254     * @param  type
1255     * @return
1256     * @throws  SQLException
1257     */
1258    private Object getValue(int type) throws SQLException {
1259
1260        Expression r = parseExpression();
1261
1262        r.resolve(null);
1263
1264        return r.getValue(type);
1265    }
1266
1267// thertz@users 20020320 - patch 473613 - outer join condition bug
1268
1269    /**
1270     * parses the expression that can be used behind a
1271     * [..] JOIN table ON (exp).
1272     * This expression should always be in the form "tab.col=tab2.col"
1273     * with optional brackets (to support automated query tools).<br>
1274     * this method is used from the parseSelect method
1275     *
1276     * @return the expression
1277     * @throws  SQLException if the syntax was not correct
1278     */
1279    private Expression parseOuterJoinCondition() throws SQLException {
1280
1281        boolean parens = false;
1282
1283        read();
1284
1285        if (iToken == Expression.OPEN) {
1286            parens = true;
1287
1288            read();
1289        }
1290
1291        Trace.check(iToken == Expression.COLUMN, Trace.OUTER_JOIN_CONDITION);
1292
1293        Expression left = new Expression(sTable, sToken);
1294
1295        read();
1296        Trace.check(iToken == Expression.EQUAL, Trace.OUTER_JOIN_CONDITION);
1297        read();
1298        Trace.check(iToken == Expression.COLUMN, Trace.OUTER_JOIN_CONDITION);
1299
1300        Expression right = new Expression(sTable, sToken);
1301
1302        if (parens) {
1303            read();
1304            Trace.check(iToken == Expression.CLOSE,
1305                        Trace.OUTER_JOIN_CONDITION);
1306        }
1307
1308        return new Expression(Expression.EQUAL, left, right);
1309    }
1310
1311    /**
1312     *  Method declaration
1313     *
1314     * @return
1315     * @throws  SQLException
1316     */
1317    private Expression parseExpression() throws SQLException {
1318
1319        read();
1320
1321        // todo: really this should be in readTerm
1322        // but then grouping is much more complex
1323        if (Expression.isAggregate(iToken)) {
1324            boolean distinct = false;
1325            int     type     = iToken;
1326
1327            read();
1328
1329            if (tTokenizer.getString().equals("DISTINCT")) {
1330                distinct = true;
1331            } else {
1332                tTokenizer.back();
1333            }
1334
1335            Expression r = new Expression(type, readOr(), null);
1336
1337            r.setDistinctAggregate(distinct);
1338            tTokenizer.back();
1339
1340            return r;
1341        }
1342
1343        Expression r = readOr();
1344
1345        tTokenizer.back();
1346
1347        return r;
1348    }
1349
1350    /**
1351     *  Method declaration
1352     *
1353     * @return
1354     * @throws  SQLException
1355     */
1356    private Expression readOr() throws SQLException {
1357
1358        Expression r = readAnd();
1359
1360        while (iToken == Expression.OR) {
1361            int        type = iToken;
1362            Expression a    = r;
1363
1364            read();
1365
1366            r = new Expression(type, a, readAnd());
1367        }
1368
1369        return r;
1370    }
1371
1372    /**
1373     *  Method declaration
1374     *
1375     * @return
1376     * @throws  SQLException
1377     */
1378    private Expression readAnd() throws SQLException {
1379
1380        Expression r = readCondition();
1381
1382        while (iToken == Expression.AND) {
1383            int        type = iToken;
1384            Expression a    = r;
1385
1386            read();
1387
1388            r = new Expression(type, a, readCondition());
1389        }
1390
1391        return r;
1392    }
1393
1394    /**
1395     *  Method declaration
1396     *
1397     * @return
1398     * @throws  SQLException
1399     */
1400    private Expression readCondition() throws SQLException {
1401
1402        if (iToken == Expression.NOT) {
1403            int type = iToken;
1404
1405            read();
1406
1407            return new Expression(type, readCondition(), null);
1408        } else if (iToken == Expression.EXISTS) {
1409            int type = iToken;
1410
1411            read();
1412            readThis(Expression.OPEN);
1413            Trace.check(iToken == Expression.SELECT, Trace.UNEXPECTED_TOKEN);
1414
1415            Expression s = new Expression(parseSelect());
1416
1417            read();
1418            readThis(Expression.CLOSE);
1419
1420            return new Expression(type, s, null);
1421        } else {
1422            Expression a   = readConcat();
1423            boolean    not = false;
1424
1425            if (iToken == Expression.NOT) {
1426                not = true;
1427
1428                read();
1429            }
1430
1431            if (iToken == Expression.LIKE) {
1432                read();
1433
1434                Expression b      = readConcat();
1435                char       escape = 0;
1436
1437                if (sToken.equals("ESCAPE")) {
1438                    read();
1439
1440                    Expression c = readTerm();
1441
1442                    Trace.check(c.getType() == Expression.VALUE,
1443                                Trace.INVALID_ESCAPE);
1444
1445                    String s = (String) c.getValue(Types.VARCHAR);
1446
1447                    if (s == null || s.length() < 1) {
1448                        throw Trace.error(Trace.INVALID_ESCAPE, s);
1449                    }
1450
1451                    escape = s.charAt(0);
1452                }
1453
1454                a = new Expression(Expression.LIKE, a, b);
1455
1456                a.setLikeEscape(escape);
1457            } else if (iToken == Expression.BETWEEN) {
1458                read();
1459
1460                Expression l = new Expression(Expression.BIGGER_EQUAL, a,
1461                                              readConcat());
1462
1463                readThis(Expression.AND);
1464
1465                Expression h = new Expression(Expression.SMALLER_EQUAL, a,
1466                                              readConcat());
1467
1468                a = new Expression(Expression.AND, l, h);
1469            } else if (iToken == Expression.IN) {
1470                int type = iToken;
1471
1472                read();
1473                readThis(Expression.OPEN);
1474
1475                Expression b = null;
1476
1477                if (iToken == Expression.SELECT) {
1478                    b = new Expression(parseSelect());
1479
1480                    read();
1481                } else {
1482                    tTokenizer.back();
1483
1484                    Vector v = new Vector();
1485
1486                    while (true) {
1487                        v.addElement(getValue(Types.VARCHAR));
1488                        read();
1489
1490                        if (iToken != Expression.COMMA) {
1491                            break;
1492                        }
1493                    }
1494
1495                    b = new Expression(v);
1496                }
1497
1498                readThis(Expression.CLOSE);
1499
1500                a = new Expression(type, a, b);
1501            } else {
1502                Trace.check(!not, Trace.UNEXPECTED_TOKEN);
1503
1504                if (Expression.isCompare(iToken)) {
1505                    int type = iToken;
1506
1507                    read();
1508
1509                    return new Expression(type, a, readConcat());
1510                }
1511
1512                return a;
1513            }
1514
1515            if (not) {
1516                a = new Expression(Expression.NOT, a, null);
1517            }
1518
1519            return a;
1520        }
1521    }
1522
1523    /**
1524     *  Method declaration
1525     *
1526     * @param  type
1527     * @throws  SQLException
1528     */
1529    private void readThis(int type) throws SQLException {
1530        Trace.check(iToken == type, Trace.UNEXPECTED_TOKEN);
1531        read();
1532    }
1533
1534    /**
1535     *  Method declaration
1536     *
1537     * @return
1538     * @throws  SQLException
1539     */
1540    private Expression readConcat() throws SQLException {
1541
1542        Expression r = readSum();
1543
1544        while (i