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