001: /* Copyright (c) 2001-2005, The HSQL Development Group
002: * All rights reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * Redistributions of source code must retain the above copyright notice, this
008: * list of conditions and the following disclaimer.
009: *
010: * Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * Neither the name of the HSQL Development Group nor the names of its
015: * contributors may be used to endorse or promote products derived from this
016: * software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
020: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
021: * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
022: * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025: * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
026: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package org.hsqldb;
032:
033: import org.hsqldb.HsqlNameManager.HsqlName;
034:
035: /**
036: * A simple structure class for holding the products of
037: * statement compilation for later execution.
038: *
039: * @author boucherb@users
040: * @version 1.7.2
041: * @since 1.7.2
042: */
043:
044: // fredt@users 20040404 - patch 1.7.2 - fixed type resolution for parameters
045: // boucherb@users 200404xx - patch 1.7.2 - changed parameter naming scheme for SQLCI client usability/support
046: // fredt@users 20050609 - 1.8.0 - fixed EXPLAIN PLAN by implementing describe(Session)
047: final class CompiledStatement {
048:
049: static final String PCOL_PREFIX = "@p";
050: static final String RETURN_COLUMN_NAME = "@p0";
051: static final int UNKNOWN = 0;
052:
053: // enumeration of allowable CompiledStatement types
054: static final int INSERT_VALUES = 1;
055: static final int INSERT_SELECT = 2;
056: static final int UPDATE = 3;
057: static final int DELETE = 4;
058: static final int SELECT = 5;
059: static final int SELECT_INTO = 6;
060: static final int CALL = 7;
061:
062: // enumeration of catagories
063: static final int DML = 7;
064: static final int DQL = 8;
065: static final int DDL = 9;
066:
067: /** id in CompiledStatementManager */
068: int id;
069:
070: /** false when cleared */
071: boolean isValid = true;
072:
073: /** target table for INSERT_XXX, UPDATE and DELETE */
074: Table targetTable;
075:
076: /** table filter for UPDATE and DELETE */
077: TableFilter targetFilter;
078:
079: /** condition expression for UPDATE and DELETE */
080: Expression condition;
081:
082: /** column map for INSERT_XXX, UPDATE */
083: int[] columnMap;
084:
085: /** Column value Expressions for INSERT_VALUES and UPDATE. */
086: Expression[] columnValues;
087:
088: /**
089: * Flags indicating which columns' values will/will not be
090: * explicitly set.
091: */
092: boolean[] checkColumns;
093:
094: /** Expression to be evaluated when this is a CALL statement. */
095: Expression expression;
096:
097: /**
098: * Select to be evaluated when this is an INSERT_SELECT or
099: * SELECT statement
100: */
101: Select select;
102:
103: /**
104: * Parse-order array of Expression objects, all of iType == PARAM ,
105: * involved in some way in any INSERT_XXX, UPDATE, DELETE, SELECT or
106: * CALL CompiledStatement
107: */
108: Expression[] parameters;
109:
110: /**
111: * int[] contains type of each parameter
112: */
113: int[] paramTypes;
114:
115: /**
116: * Subqueries inverse parse depth order
117: */
118: SubQuery[] subqueries;
119:
120: /**
121: * The type of this CompiledStatement. <p>
122: *
123: * One of: <p>
124: *
125: * <ol>
126: * <li>UNKNOWN
127: * <li>INSERT_VALUES
128: * <li>INSERT_SELECT
129: * <li>UPDATE
130: * <li>DELETE
131: * <li>SELECT
132: * <li>CALL
133: * <li>DDL
134: * </ol>
135: */
136: int type;
137:
138: /**
139: * The SQL string that produced this compiled statement
140: */
141: String sql;
142:
143: /**
144: * The default schema name used to resolve names in the sql
145: */
146: final HsqlName schemaHsqlName;
147:
148: /**
149: * Creates a new instance of CompiledStatement for DDL
150: *
151: */
152: CompiledStatement(HsqlName schema) {
153:
154: parameters = new Expression[0];
155: paramTypes = new int[0];
156: subqueries = new SubQuery[0];
157: type = DDL;
158: schemaHsqlName = schema;
159: }
160:
161: /**
162: * Initializes this as a DELETE statement
163: *
164: * @param targetFilter
165: * @param deleteCondition
166: * @param parameters
167: */
168: CompiledStatement(Session session, Database database,
169: HsqlName schema, TableFilter targetFilter,
170: Expression deleteCondition, SubQuery[] subqueries,
171: Expression[] params) throws HsqlException {
172:
173: schemaHsqlName = schema;
174: this .targetFilter = targetFilter;
175: targetTable = targetFilter.filterTable;
176:
177: if (deleteCondition != null) {
178: condition = new Expression(deleteCondition);
179:
180: condition.resolveTables(targetFilter);
181: condition.resolveTypes(session);
182: targetFilter.setConditions(session, condition);
183: }
184:
185: setParameters(params);
186: setSubqueries(subqueries);
187:
188: type = DELETE;
189: }
190:
191: /**
192: * Instantiate this as an UPDATE statement.
193: *
194: * @param targetTable
195: * @param columnMap
196: * @param columnValues
197: * @param updateCondition
198: * @param params
199: */
200: CompiledStatement(Session session, Database database,
201: HsqlName schema, TableFilter targetFilter, int[] columnMap,
202: Expression[] columnValues, Expression updateCondition,
203: SubQuery[] subqueries, Expression[] params)
204: throws HsqlException {
205:
206: schemaHsqlName = schema;
207: this .targetFilter = targetFilter;
208: targetTable = targetFilter.filterTable;
209: this .columnMap = columnMap;
210: this .columnValues = columnValues;
211:
212: for (int i = 0; i < columnValues.length; i++) {
213: Expression cve = columnValues[i];
214:
215: if (cve.isParam()) {
216: cve.setTableColumnAttributes(targetTable, columnMap[i]);
217: } else {
218: cve.resolveTables(targetFilter);
219: cve.resolveTypes(session);
220: }
221: }
222:
223: if (updateCondition != null) {
224: condition = new Expression(updateCondition);
225:
226: condition.resolveTables(targetFilter);
227: condition.resolveTypes(session);
228: targetFilter.setConditions(session, condition);
229: }
230:
231: setParameters(params);
232: setSubqueries(subqueries);
233:
234: type = UPDATE;
235: }
236:
237: /**
238: * Instantiate this as an INSERT_VALUES statement.
239: *
240: * @param targetTable
241: * @param columnMap
242: * @param columnValues
243: * @param checkColumns
244: * @param params
245: */
246: CompiledStatement(HsqlName schema, Table targetTable,
247: int[] columnMap, Expression[] columnValues,
248: boolean[] checkColumns, SubQuery[] subqueries,
249: Expression[] params) throws HsqlException {
250:
251: schemaHsqlName = schema;
252: this .targetTable = targetTable;
253: this .columnMap = columnMap;
254: this .checkColumns = checkColumns;
255: this .columnValues = columnValues;
256:
257: for (int i = 0; i < columnValues.length; i++) {
258: Expression cve = columnValues[i];
259:
260: // If its not a param, it's already been resolved in
261: // Parser.getColumnValueExpressions
262: if (cve.isParam()) {
263: cve.setTableColumnAttributes(targetTable, columnMap[i]);
264: }
265: }
266:
267: setParameters(params);
268: setSubqueries(subqueries);
269:
270: type = INSERT_VALUES;
271: }
272:
273: /**
274: * Instantiate this as an INSERT_SELECT statement.
275: *
276: * @param targetTable
277: * @param columnMap
278: * @param checkColumns
279: * @param select
280: * @param params
281: */
282: CompiledStatement(Session session, Database database,
283: HsqlName schema, Table targetTable, int[] columnMap,
284: boolean[] checkColumns, Select select,
285: SubQuery[] subqueries, Expression[] params)
286: throws HsqlException {
287:
288: schemaHsqlName = schema;
289: this .targetTable = targetTable;
290: this .columnMap = columnMap;
291: this .checkColumns = checkColumns;
292: this .select = select;
293:
294: // resolve any parameters in SELECT
295: resolveInsertParameterTypes();
296:
297: // set select result metadata etc.
298: select.prepareResult(session);
299: setParameters(params);
300: setSubqueries(subqueries);
301:
302: type = INSERT_SELECT;
303: }
304:
305: /**
306: * Instantiate this as a SELECT statement.
307: *
308: * @param select
309: * @param params
310: */
311: CompiledStatement(Session session, Database database,
312: HsqlName schema, Select select, SubQuery[] subqueries,
313: Expression[] params) throws HsqlException {
314:
315: schemaHsqlName = schema;
316: this .select = select;
317:
318: // resolve any parameters in SELECT as VARCHAR
319: for (int i = 0; i < select.iResultLen; i++) {
320: Expression colexpr = select.exprColumns[i];
321:
322: if (colexpr.getDataType() == Types.NULL) {
323: colexpr.setDataType(Types.VARCHAR);
324: }
325: }
326:
327: // set select result metadata etc.
328: select.prepareResult(session);
329: setParameters(params);
330: setSubqueries(subqueries);
331:
332: type = SELECT;
333: }
334:
335: /**
336: * Instantiate this as a CALL statement.
337: *
338: * @param expression
339: * @param params
340: */
341: CompiledStatement(Session session, Database database,
342: HsqlName schema, Expression expression,
343: SubQuery[] subqueries, Expression[] params)
344: throws HsqlException {
345:
346: schemaHsqlName = schema;
347: this .expression = expression;
348:
349: expression.resolveTypes(session);
350:
351: expression.paramMode = Expression.PARAM_OUT;
352:
353: setParameters(params);
354: setSubqueries(subqueries);
355:
356: type = CALL;
357: }
358:
359: /**
360: * For parameters in INSERT_VALUES and INSERT_SELECT lists
361: */
362: private void resolveInsertParameterTypes() {
363:
364: for (int i = 0; i < select.iResultLen; i++) {
365: Expression colexpr = select.exprColumns[i];
366:
367: if (colexpr.getDataType() == Types.NULL) {
368: Column col = targetTable.getColumn(columnMap[i]);
369:
370: colexpr.setDataType(col.getType());
371: }
372: }
373: }
374:
375: private void setParameters(Expression[] params) {
376:
377: this .parameters = params;
378:
379: int[] types = new int[parameters.length];
380:
381: for (int i = 0; i < parameters.length; i++) {
382: types[i] = parameters[i].getDataType();
383: }
384:
385: this .paramTypes = types;
386: }
387:
388: private void setSubqueries(SubQuery[] subqueries) {
389: this .subqueries = subqueries;
390: }
391:
392: void materializeSubQueries(Session session) throws HsqlException {
393:
394: for (int i = 0; i < subqueries.length; i++) {
395: SubQuery sq = subqueries[i];
396:
397: // VIEW working table contents are filled only once per query and reused
398: if (sq.isMaterialised) {
399: continue;
400: }
401:
402: if (sq.isResolved) {
403: sq.populateTable(session);
404:
405: sq.isMaterialised = true;
406: }
407: }
408: }
409:
410: void dematerializeSubQueries(Session session) {
411:
412: if (subqueries == null) {
413: return;
414: }
415:
416: for (int i = 0; i < subqueries.length; i++) {
417: subqueries[i].table.clearAllRows(session);
418:
419: subqueries[i].isMaterialised = false;
420: }
421: }
422:
423: void clearVariables() {
424:
425: isValid = false;
426: targetTable = null;
427: targetFilter = null;
428: condition = null;
429: columnMap = null;
430: columnValues = null;
431: checkColumns = null;
432: expression = null;
433: select = null;
434: parameters = null;
435: paramTypes = null;
436: subqueries = null;
437: }
438:
439: boolean canExecute(Session session) throws HsqlException {
440:
441: switch (type) {
442:
443: case CALL: {
444: }
445: case SELECT:
446: for (int i = 0; i < select.tFilter.length; i++) {
447: HsqlName name = select.tFilter[i].filterTable.getName();
448:
449: session.check(name, UserManager.SELECT);
450: }
451: break;
452:
453: case INSERT_SELECT:
454: break;
455:
456: case DELETE:
457: session.check(targetTable.getName(), UserManager.DELETE);
458: break;
459:
460: case INSERT_VALUES:
461: session.check(targetTable.getName(), UserManager.INSERT);
462: break;
463:
464: case UPDATE:
465: session.check(targetTable.getName(), UserManager.UPDATE);
466: break;
467:
468: case DDL:
469: }
470:
471: return true;
472: }
473:
474: void checkTableWriteAccess(Session session, Table table)
475: throws HsqlException {
476:
477: // session level user rights
478: session.checkReadWrite();
479:
480: // object type
481: if (table.isView()) {
482: throw Trace.error(Trace.NOT_A_TABLE, table.getName().name);
483: }
484:
485: // object readonly
486: table.checkDataReadOnly();
487: }
488:
489: private static final Result updateCountResult = new Result(
490: ResultConstants.UPDATECOUNT);
491:
492: Result describeResult() {
493:
494: switch (type) {
495:
496: case CALL: {
497:
498: // TODO:
499: //
500: // 1.) standard to register metadata for columns of
501: // the primary result set, if any, generated by call
502: //
503: // 2.) Represent the return value, if any (which is
504: // not, in truth, a result set), as an OUT parameter
505: //
506: // For now, I've reverted a bunch of code I had in place
507: // and instead simply reflect things as the are, describing
508: // a single column result set that communicates
509: // the return value. If the expression generating the
510: // return value has a void return type, a result set
511: // is described whose single column is of type NULL
512: Expression e;
513: Result r;
514:
515: e = expression;
516: r = Result.newSingleColumnResult(
517: CompiledStatement.RETURN_COLUMN_NAME, e
518: .getDataType());
519: r.metaData.classNames[0] = e.getValueClassName();
520:
521: // no more setup for r; all the defaults apply
522: return r;
523: }
524: case SELECT:
525: return select.sIntoTable == null ? select.describeResult()
526: : updateCountResult;
527:
528: case DELETE:
529: case INSERT_SELECT:
530: case INSERT_VALUES:
531: case UPDATE:
532: case DDL:
533:
534: // will result in
535: return updateCountResult;
536:
537: default:
538: return new Result(Trace.runtimeError(
539: Trace.UNSUPPORTED_INTERNAL_OPERATION,
540: "CompiledStatement.describeResult()"), null);
541: }
542: }
543:
544: Result describeParameters() {
545:
546: Result out;
547: Expression e;
548: int outlen;
549: int offset;
550: int idx;
551: boolean hasReturnValue;
552:
553: outlen = parameters.length;
554: offset = 0;
555:
556: // NO: Not yet
557: // hasReturnValue = (type == CALL && !expression.isProcedureCall());
558: //
559: // if (hasReturnValue) {
560: // outlen++;
561: // offset = 1;
562: // }
563: out = Result.newParameterDescriptionResult(outlen);
564:
565: // NO: Not yet
566: // if (hasReturnValue) {
567: // e = expression;
568: // out.sName[0] = DIProcedureInfo.RETURN_COLUMN_NAME;
569: // out.sClassName[0] = e.getValueClassName();
570: // out.colType[0] = e.getDataType();
571: // out.colSize[0] = e.getColumnSize();
572: // out.colScale[0] = e.getColumnScale();
573: // out.nullability[0] = e.nullability;
574: // out.isIdentity[0] = false;
575: // out.paramMode[0] = expression.PARAM_OUT;
576: // }
577: for (int i = 0; i < parameters.length; i++) {
578: e = parameters[i];
579: idx = i + offset;
580:
581: // always i + 1. We currently use the convention of @p0 to name the
582: // return value OUT parameter
583: out.metaData.colNames[idx] = CompiledStatement.PCOL_PREFIX
584: + (i + 1);
585:
586: // sLabel is meaningless in this context.
587: out.metaData.classNames[idx] = e.getValueClassName();
588: out.metaData.colTypes[idx] = e.getDataType();
589: out.metaData.colSizes[idx] = e.getColumnSize();
590: out.metaData.colScales[idx] = e.getColumnScale();
591: out.metaData.colNullable[idx] = e.nullability;
592: out.metaData.isIdentity[idx] = e.isIdentity;
593:
594: // currently will always be Expression.PARAM_IN
595: out.metaData.paramMode[idx] = e.paramMode;
596: }
597:
598: return out;
599: }
600:
601: /**
602: * Retrieves a String representation of this object.
603: *
604: * @return the String representation of this object
605: */
606: public String describe(Session session) {
607:
608: try {
609: return describeImpl(session);
610: } catch (Exception e) {
611: return e.toString();
612: }
613: }
614:
615: /**
616: * Provides the toString() implementation.
617: *
618: * @throws Exception if a database access or io error occurs
619: * @return the String representation of this object
620: */
621: private String describeImpl(Session session) throws Exception {
622:
623: StringBuffer sb;
624:
625: sb = new StringBuffer();
626:
627: switch (type) {
628:
629: case SELECT: {
630: sb.append(select.describe(session));
631: appendParms(sb).append('\n');
632: appendSubqueries(sb);
633:
634: return sb.toString();
635: }
636: case INSERT_VALUES: {
637: sb.append("INSERT VALUES");
638: sb.append('[').append('\n');
639: appendColumns(sb).append('\n');
640: appendTable(sb).append('\n');
641: appendParms(sb).append('\n');
642: appendSubqueries(sb).append(']');
643:
644: return sb.toString();
645: }
646: case INSERT_SELECT: {
647: sb.append("INSERT SELECT");
648: sb.append('[').append('\n');
649: appendColumns(sb).append('\n');
650: appendTable(sb).append('\n');
651: sb.append(select.describe(session)).append('\n');
652: appendParms(sb).append('\n');
653: appendSubqueries(sb).append(']');
654:
655: return sb.toString();
656: }
657: case UPDATE: {
658: sb.append("UPDATE");
659: sb.append('[').append('\n');
660: appendColumns(sb).append('\n');
661: appendTable(sb).append('\n');
662: appendCondition(session, sb);
663: sb.append(targetFilter.describe(session)).append('\n');
664: appendParms(sb).append('\n');
665: appendSubqueries(sb).append(']');
666:
667: return sb.toString();
668: }
669: case DELETE: {
670: sb.append("DELETE");
671: sb.append('[').append('\n');
672: appendTable(sb).append('\n');
673: appendCondition(session, sb);
674: sb.append(targetFilter.describe(session)).append('\n');
675: appendParms(sb).append('\n');
676: appendSubqueries(sb).append(']');
677:
678: return sb.toString();
679: }
680: case CALL: {
681: sb.append("CALL");
682: sb.append('[');
683: sb.append(expression.describe(session)).append('\n');
684: appendParms(sb).append('\n');
685: appendSubqueries(sb).append(']');
686:
687: return sb.toString();
688: }
689: default: {
690: return "UNKNOWN";
691: }
692: }
693: }
694:
695: private StringBuffer appendSubqueries(StringBuffer sb) {
696:
697: sb.append("SUBQUERIES[");
698:
699: for (int i = 0; i < subqueries.length; i++) {
700: sb.append("\n[level=").append(subqueries[i].level).append(
701: '\n').append("hasParams=").append(
702: subqueries[i].hasParams).append('\n');
703:
704: if (subqueries[i].select != null) {
705: sb.append("org.hsqldb.Select@").append(
706: Integer.toHexString(subqueries[i].select
707: .hashCode()));
708: }
709:
710: sb.append("]");
711: }
712:
713: sb.append(']');
714:
715: return sb;
716: }
717:
718: private StringBuffer appendTable(StringBuffer sb) {
719:
720: sb.append("TABLE[").append(targetTable.getName().name).append(
721: ']');
722:
723: return sb;
724: }
725:
726: private StringBuffer appendColumns(StringBuffer sb) {
727:
728: sb.append("COLUMNS=[");
729:
730: for (int i = 0; i < columnMap.length; i++) {
731: sb
732: .append('\n')
733: .append(columnMap[i])
734: .append(':')
735: .append(' ')
736: .append(
737: targetTable.getColumn(columnMap[i]).columnName.name)
738: .append('[').append(columnValues[i]).append(']');
739: }
740:
741: sb.append(']');
742:
743: return sb;
744: }
745:
746: private StringBuffer appendParms(StringBuffer sb) {
747:
748: sb.append("PARAMETERS=[");
749:
750: for (int i = 0; i < parameters.length; i++) {
751: sb.append('\n').append('@').append(i).append('[').append(
752: parameters[i]).append(']');
753: }
754:
755: sb.append(']');
756:
757: return sb;
758: }
759:
760: private StringBuffer appendCondition(Session session,
761: StringBuffer sb) {
762:
763: return condition == null ? sb.append("CONDITION[]\n") : sb
764: .append("CONDITION[").append(
765: condition.describe(session)).append("]\n");
766: }
767: }
|