KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > h2 > command > dml > Select


1 /*
2  * Copyright 2004-2006 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
3  * Initial Developer: H2 Group
4  */

5 package org.h2.command.dml;
6
7 import java.sql.SQLException JavaDoc;
8 import java.util.HashMap JavaDoc;
9 import java.util.HashSet JavaDoc;
10
11 import org.h2.engine.Constants;
12 import org.h2.engine.Session;
13 import org.h2.expression.Comparison;
14 import org.h2.expression.ConditionAndOr;
15 import org.h2.expression.Expression;
16 import org.h2.expression.ExpressionColumn;
17 import org.h2.expression.ExpressionVisitor;
18 import org.h2.expression.ValueExpression;
19 import org.h2.expression.Wildcard;
20 import org.h2.index.Index;
21 import org.h2.message.Message;
22 import org.h2.result.LocalResult;
23 import org.h2.result.SortOrder;
24 import org.h2.table.Column;
25 import org.h2.table.ColumnResolver;
26 import org.h2.table.Table;
27 import org.h2.table.TableFilter;
28 import org.h2.util.ObjectArray;
29 import org.h2.util.StringUtils;
30 import org.h2.util.ValueHashMap;
31 import org.h2.value.Value;
32 import org.h2.value.ValueArray;
33 import org.h2.value.ValueInt;
34 import org.h2.value.ValueNull;
35
36
37 /**
38  * visibleColumnCount <= distinctColumnCount <= expressionCount
39  * Sortable count could include ORDER BY expressions that are not in the select list
40  * Expression count could include GROUP BY expressions
41  *
42  * init
43  * (maybe additional mapColumns if it's a subquery)
44  * prepare
45  */

46 public class Select extends Query {
47     private TableFilter topTableFilter;
48     private ObjectArray filters = new ObjectArray();
49     private ObjectArray topFilters = new ObjectArray();
50     private ObjectArray expressions;
51     private Expression having;
52     private Expression condition;
53     private int visibleColumnCount, distinctColumnCount;
54     private ObjectArray orderList;
55     private ObjectArray group;
56     private int[] groupIndex;
57     private boolean[] groupByExpression;
58     private boolean distinct;
59     private HashMap JavaDoc currentGroup;
60     private int havingIndex;
61     private boolean isGroupQuery;
62     private boolean isForUpdate;
63     private double cost;
64     private boolean isQuickQuery;
65     private boolean isPrepared, checkInit;
66     private SortOrder sort;
67
68     public Select(Session session) {
69         super(session);
70     }
71
72     public void addTableFilter(TableFilter filter, boolean isTop) {
73         // TODO compatibility: it seems oracle doesn't check on duplicate aliases; do other databases check it?
74
// String alias = filter.getAlias();
75
// if(filterNames.contains(alias)) {
76
// throw Message.getSQLException(Message.DUPLICATE_TABLE_ALIAS, alias);
77
// }
78
// filterNames.add(alias);
79
filters.add(filter);
80         if(isTop) {
81             topFilters.add(filter);
82         }
83     }
84
85     public ObjectArray getTopFilters() {
86         return topFilters;
87     }
88
89     public void setExpressions(ObjectArray expressions) {
90         this.expressions = expressions;
91     }
92
93     public void setGroupQuery() {
94         isGroupQuery = true;
95     }
96
97     public void setGroupBy(ObjectArray group) {
98         this.group = group;
99     }
100
101     public HashMap JavaDoc getCurrentGroup() {
102         return currentGroup;
103     }
104
105     public void setOrder(ObjectArray order) {
106         orderList = order;
107     }
108
109     public void addCondition(Expression cond) {
110         if(condition == null) {
111             condition = cond;
112         } else {
113             condition = new ConditionAndOr(ConditionAndOr.AND, cond, condition);
114         }
115     }
116
117     private void queryGroup(int columnCount, LocalResult result) throws SQLException JavaDoc {
118         ValueHashMap groups = new ValueHashMap(session.getDatabase());
119         int rowNumber = 0;
120         setCurrentRowNumber(0);
121         ValueArray defaultGroup = ValueArray.get(new Value[0]);
122         while (topTableFilter.next()) {
123             checkCancelled();
124             setCurrentRowNumber(rowNumber+1);
125             if (condition == null || Boolean.TRUE.equals(condition.getBooleanValue(session))) {
126                 Value key;
127                 rowNumber++;
128                 if(groupIndex == null) {
129                     key = defaultGroup;
130                 } else {
131                     Value[] keyValues = new Value[groupIndex.length];
132                     // update group
133
for (int i = 0; i < groupIndex.length; i++) {
134                         int idx = groupIndex[i];
135                         Expression expr = (Expression) expressions.get(idx);
136                         keyValues[i] = expr.getValue(session);
137                     }
138                     key = ValueArray.get(keyValues);
139                 }
140                 HashMap JavaDoc values = (HashMap JavaDoc)groups.get(key);
141                 if(values == null) {
142                     values = new HashMap JavaDoc();
143                     groups.put(key, values);
144                 }
145                 currentGroup = values;
146                 int len = columnCount;
147                 for (int i = 0; i < len; i++) {
148                     if(groupByExpression == null || !groupByExpression[i]) {
149                         Expression expr = (Expression) expressions.get(i);
150                         expr.updateAggregate(session);
151                     }
152                 }
153                 if(sampleSize > 0 && rowNumber >= sampleSize) {
154                     break;
155                 }
156             }
157         }
158         if(groupIndex == null && groups.size()==0) {
159             groups.put(defaultGroup, new HashMap JavaDoc());
160         }
161         ObjectArray keys = groups.keys();
162         for (int i=0; i<keys.size(); i++) {
163             ValueArray key = (ValueArray) keys.get(i);
164             currentGroup = (HashMap JavaDoc) groups.get(key);
165             Value[] keyValues = key.getList();
166             Value[] row = new Value[columnCount];
167             for (int j=0; groupIndex != null && j<groupIndex.length; j++) {
168                 row[groupIndex[j]] = keyValues[j];
169             }
170             for (int j = 0; j < columnCount; j++) {
171                 if(groupByExpression != null && groupByExpression[j]) {
172                     continue;
173                 }
174                 Expression expr = (Expression) expressions.get(j);
175                 row[j] = expr.getValue(session);
176             }
177             if (havingIndex > 0) {
178                 Value v = row[havingIndex];
179                 if (v == ValueNull.INSTANCE) {
180                     continue;
181                 }
182                 if (!Boolean.TRUE.equals(v.getBoolean())) {
183                     continue;
184                 }
185             }
186             if(columnCount != distinctColumnCount) {
187                 // remove columns so that 'distinct' can filter duplicate rows
188
Value[] r2 = new Value[distinctColumnCount];
189                 System.arraycopy(row, 0, r2, 0, distinctColumnCount);
190                 row = r2;
191             }
192             result.addRow(row);
193         }
194     }
195
196     private Index getSortIndex() throws SQLException JavaDoc {
197         if(sort == null) {
198             return null;
199         }
200         int[] sortTypes = sort.getSortTypes();
201         for(int i=0; i<sortTypes.length; i++) {
202             if((sortTypes[i] & (SortOrder.DESCENDING | SortOrder.NULLS_LAST)) != 0) {
203                 return null;
204             }
205         }
206         int[] indexes = sort.getIndexes();
207         ObjectArray sortColumns = new ObjectArray();
208         for(int i=0; i<indexes.length; i++) {
209             int idx = indexes[i];
210             if(idx < 0 || idx >= expressions.size()) {
211                 throw Message.getInvalidValueException("order by", ""+idx);
212             }
213             Expression expr = (Expression) expressions.get(idx);
214             expr = expr.getNonAliasExpression();
215             if(expr.isConstant()) {
216                 continue;
217             }
218             if(!(expr instanceof ExpressionColumn)) {
219                 return null;
220             }
221             Column col = ((ExpressionColumn) expr).getColumn();
222             if(col.getTable() != topTableFilter.getTable()) {
223                 return null;
224             }
225             sortColumns.add(col);
226         }
227         Column[] sortCols = new Column[sortColumns.size()];
228         sortColumns.toArray(sortCols);
229         if(sortCols.length == 0) {
230             // sort just on constants - can use scan index
231
return topTableFilter.getTable().getScanIndex(session);
232         }
233         ObjectArray list = topTableFilter.getTable().getIndexes();
234         for(int i=0; list != null && i<list.size(); i++) {
235             Index index = (Index) list.get(i);
236             if(index.getCreateSQL() == null) {
237                 // can't use the scan index
238
continue;
239             }
240             if(index.indexType.isHash()) {
241                 continue;
242             }
243             Column[] indexCols = index.getColumns();
244             if(indexCols.length < sortCols.length) {
245                 continue;
246             }
247             boolean ok = true;
248             for(int j=0; j<sortCols.length; j++) {
249                 // the index and the sort order must start with the exact same columns
250
if(indexCols[j] != sortCols[j]) {
251                     ok = false;
252                     break;
253                 }
254             }
255             if(ok) {
256                 return index;
257             }
258         }
259         return null;
260     }
261
262     private void queryFlat(int columnCount, LocalResult result) throws SQLException JavaDoc {
263         int limitRows;
264         if(limit == null) {
265             limitRows = 0;
266         } else {
267             limitRows = limit.getValue(session).getInt();
268             if(offset != null) {
269                 limitRows += offset.getValue(session).getInt();
270             }
271         }
272         int rowNumber = 0;
273         setCurrentRowNumber(0);
274         while (topTableFilter.next()) {
275             checkCancelled();
276             setCurrentRowNumber(rowNumber+1);
277             if (condition == null || Boolean.TRUE.equals(condition.getBooleanValue(session))) {
278                 Value[] row = new Value[columnCount];
279                 for (int i = 0; i < columnCount; i++) {
280                     Expression expr = (Expression) expressions.get(i);
281                     row[i] = expr.getValue(session);
282                 }
283                 result.addRow(row);
284                 rowNumber++;
285                 if(sort == null && limitRows != 0 && result.getRowCount() >= limitRows) {
286                     break;
287                 }
288                 if(sampleSize > 0 && rowNumber >= sampleSize) {
289                     break;
290                 }
291             }
292         }
293     }
294
295     private void queryQuick(int columnCount, LocalResult result) throws SQLException JavaDoc {
296         Value[] row = new Value[columnCount];
297         for (int i = 0; i < columnCount; i++) {
298             Expression expr = (Expression) expressions.get(i);
299             row[i] = expr.getValue(session);
300         }
301         result.addRow(row);
302     }
303
304     public LocalResult queryWithoutCache(int maxrows) throws SQLException JavaDoc {
305         if(maxrows != 0) {
306             if(limit != null) {
307                 maxrows = Math.min(limit.getValue(session).getInt(), maxrows);
308             }
309             limit = ValueExpression.get(ValueInt.get(maxrows));
310         }
311         int columnCount = expressions.size();
312         LocalResult result = new LocalResult(session, expressions, visibleColumnCount);
313         result.setSortOrder(sort);
314         if(distinct) {
315             result.setDistinct();
316         }
317         topTableFilter.startQuery();
318         topTableFilter.reset();
319         // TODO lock tables of subqueries
320
topTableFilter.lock(session, isForUpdate);
321         if(isQuickQuery) {
322             queryQuick(columnCount, result);
323         } else if(isGroupQuery) {
324             queryGroup(columnCount, result);
325         } else {
326             queryFlat(columnCount, result);
327         }
328         if(offset != null) {
329             result.setOffset(offset.getValue(session).getInt());
330         }
331         if(limit != null) {
332             result.setLimit(limit.getValue(session).getInt());
333         }
334         result.done();
335         return result;
336     }
337
338     private void expandColumnList() throws SQLException JavaDoc {
339         // TODO this works: select distinct count(*) from system_columns group by table
340
for (int i = 0; i < expressions.size(); i++) {
341             Expression expr = (Expression) expressions.get(i);
342             if (!expr.isWildcard()) {
343                 continue;
344             }
345             String JavaDoc tableAlias = expr.getTableAlias();
346             if (tableAlias == null) {
347                 int temp = i;
348                 expressions.remove(i);
349                 for (int j = 0; j < filters.size(); j++) {
350                     TableFilter filter = (TableFilter) filters.get(j);
351                     Wildcard c2 = new Wildcard(filter.getTable().getSchema().getName(), filter.getTableAlias());
352                     expressions.add(i++, c2);
353                 }
354                 i = temp - 1;
355             } else {
356                 TableFilter filter = null;
357                 for (int j = 0; j < filters.size(); j++) {
358                     TableFilter f = (TableFilter) filters.get(j);
359                     if (tableAlias.equals(f.getTableAlias())) {
360                         filter = f;
361                         break;
362                     }
363                 }
364                 if (filter == null) {
365                     throw Message.getSQLException(Message.TABLE_OR_VIEW_NOT_FOUND_1, tableAlias);
366                 }
367                 Table t = filter.getTable();
368                 String JavaDoc alias = filter.getTableAlias();
369                 expressions.remove(i);
370                 Column[] columns = t.getColumns();
371                 for (int j = 0; j < columns.length; j++) {
372                     Column c = columns[j];
373                     ExpressionColumn ec = new ExpressionColumn(session.getDatabase(), this, null, alias, c.getName());
374                     expressions.add(i++, ec);
375                 }
376                 i--;
377             }
378         }
379     }
380
381     public void init() throws SQLException JavaDoc {
382         if(Constants.CHECK && checkInit) {
383             throw Message.getInternalError();
384         }
385         checkInit = true;
386         expandColumnList();
387         visibleColumnCount = expressions.size();
388         if(orderList != null) {
389             sort = initOrder(expressions, orderList, visibleColumnCount, distinct);
390             orderList = null;
391         }
392         distinctColumnCount = expressions.size();
393
394         if(having != null) {
395             expressions.add(having);
396             havingIndex = expressions.size()-1;
397             having = null;
398         } else {
399             havingIndex = -1;
400         }
401
402         // first visible columns, then order by, then having, and then group by at the end
403
if(group != null) {
404             groupIndex = new int[group.size()];
405             ObjectArray expressionSQL = new ObjectArray();
406             for(int i=0; i<expressions.size(); i++) {
407                 Expression expr = (Expression) expressions.get(i);
408                 expr = expr.getNonAliasExpression();
409                 String JavaDoc sql = expr.getSQL();
410                 expressionSQL.add(sql);
411             }
412             for(int i=0; i<group.size(); i++) {
413                 Expression expr = (Expression) group.get(i);
414                 String JavaDoc sql = expr.getSQL();
415                 int found = -1;
416                 for(int j=0; j<expressionSQL.size(); j++) {
417                     String JavaDoc s2 = (String JavaDoc) expressionSQL.get(j);
418                     if(s2.equals(sql)) {
419                         found = j;
420                         break;
421                     }
422                 }
423                 if(found < 0) {
424                     int index = expressions.size();
425                     groupIndex[i] = index;
426                     expressions.add(expr);
427                 } else {
428                     groupIndex[i] = found;
429                 }
430             }
431             groupByExpression = new boolean[expressions.size()];
432             for(int i=0; i<groupIndex.length; i++) {
433                 groupByExpression[groupIndex[i]] = true;
434             }
435             group = null;
436         }
437         // map columns in select list and condition
438
for (int i = 0; i < filters.size(); i++) {
439             TableFilter f = (TableFilter) filters.get(i);
440             for (int j = 0; j < expressions.size(); j++) {
441                 Expression expr = (Expression) expressions.get(j);
442                 expr.mapColumns(f, 0);
443             }
444             if (condition != null) {
445                 condition.mapColumns(f, 0);
446             }
447         }
448     }
449
450     public void prepare() throws SQLException JavaDoc {
451         if(isPrepared) {
452             // TODO optimization: sometimes a subquery is prepared twice. why?
453
return;
454         }
455         if(Constants.CHECK && !checkInit) {
456             throw Message.getInternalError("already prepared");
457         }
458         isPrepared = true;
459         for(int i=0; i<expressions.size(); i++) {
460             Expression e = (Expression) expressions.get(i);
461             expressions.set(i, e.optimize(session));
462         }
463         if(condition != null) {
464             condition = condition.optimize(session);
465             for (int j = 0; j < filters.size(); j++) {
466                 TableFilter f = (TableFilter) filters.get(j);
467                 condition.createIndexConditions(f);
468             }
469         }
470         if(condition == null && isGroupQuery && groupIndex == null && havingIndex<0 && filters.size()==1) {
471             ExpressionVisitor optimizable = ExpressionVisitor.get(ExpressionVisitor.OPTIMIZABLE_MIN_MAX_COUNT_ALL);
472             optimizable.table = ((TableFilter)filters.get(0)).getTable();
473             isQuickQuery = isEverything(optimizable);
474         }
475         cost = preparePlan();
476         if(sort != null && !isQuickQuery && !isGroupQuery) {
477             Index index = getSortIndex();
478             Index current = topTableFilter.getIndex();
479             if(index != null && (current.indexType.isScan() || current == index)) {
480                 topTableFilter.setIndex(index);
481                 sort = null;
482             }
483         }
484     }
485
486     public double getCost() {
487         return cost;
488     }
489
490     public HashSet JavaDoc getTables() {
491         HashSet JavaDoc set = new HashSet JavaDoc();
492         for(int i=0; i<filters.size(); i++) {
493             TableFilter filter = (TableFilter) filters.get(i);
494             set.add(filter.getTable());
495         }
496         return set;
497     }
498
499     private double preparePlan() throws SQLException JavaDoc {
500
501         TableFilter[] topArray = new TableFilter[topFilters.size()];
502         topFilters.toArray(topArray);
503         for(int i=0; i<topArray.length; i++) {
504             topArray[i].setFullCondition(condition);
505         }
506
507         Optimizer optimizer = new Optimizer(topArray, condition, session);
508         optimizer.optimize();
509         topTableFilter = optimizer.getTopFilter();
510         double cost = optimizer.getCost();
511
512         TableFilter f = topTableFilter;
513         while(f != null) {
514             f.setEvaluatable(f, true);
515             if(condition != null) {
516                 condition.setEvaluatable(f, true);
517             }
518             Expression on = f.getJoinCondition();
519             if(on != null) {
520                 if(!on.isEverything(ExpressionVisitor.EVALUATABLE)) {
521                     f.removeJoinCondition();
522                     addCondition(on);
523                 }
524             }
525             on = f.getFilterCondition();
526             if(on != null) {
527                 if(!on.isEverything(ExpressionVisitor.EVALUATABLE)) {
528                     f.removeFilterCondition();
529                     addCondition(on);
530                 }
531             }
532             f = f.getJoin();
533         }
534
535         topTableFilter.prepare();
536         return cost;
537     }
538
539     public String JavaDoc getPlan() {
540         StringBuffer JavaDoc buff = new StringBuffer JavaDoc();
541         Expression[] exprList = new Expression[expressions.size()];
542         expressions.toArray(exprList);
543         buff.append("SELECT ");
544         if(distinct) {
545             buff.append("DISTINCT ");
546         }
547         for(int i=0; i<visibleColumnCount; i++) {
548             if(i>0) {
549                 buff.append(", ");
550             }
551             Expression expr = exprList[i];
552             buff.append(StringUtils.unEnclose(expr.getSQL()));
553         }
554         buff.append("\nFROM ");
555         TableFilter filter = topTableFilter;
556         boolean join = false;
557         int id=0;
558         do {
559             if(id > 0) {
560                 buff.append('\n');
561             }
562             id++;
563             buff.append(filter.getPlanSQL(join));
564             join = true;
565             filter = filter.getJoin();
566         } while(filter != null);
567         if(condition != null) {
568             buff.append("\nWHERE " + StringUtils.unEnclose(condition.getSQL()));
569         }
570         if(groupIndex != null) {
571             buff.append("\nGROUP BY ");
572             for(int i=0; i<groupIndex.length; i++) {
573                 Expression gro = exprList[groupIndex[i]];
574                 if(i>0) {
575                     buff.append(", ");
576                 }
577                 buff.append(StringUtils.unEnclose(gro.getSQL()));
578             }
579         }
580         if(havingIndex >= 0) {
581             Expression hav = exprList[havingIndex];
582             buff.append("\nHAVING " + StringUtils.unEnclose(hav.getSQL()));
583         }
584         if(sort != null) {
585             buff.append("\nORDER BY ");
586             buff.append(sort.getSQL(exprList, visibleColumnCount));
587         }
588         if(limit != null) {
589             buff.append("\nLIMIT ");
590             buff.append(StringUtils.unEnclose(limit.getSQL()));
591             if(offset != null) {
592                 buff.append(" OFFSET ");
593                 buff.append(StringUtils.unEnclose(offset.getSQL()));
594             }
595         }
596         if(isForUpdate) {
597             buff.append("\nFOR UPDATE");
598         }
599         return buff.toString();
600     }
601
602     public void setDistinct(boolean b) {
603         distinct = b;
604     }
605
606     public void setHaving(Expression having) {
607         this.having = having;
608     }
609
610     public int getColumnCount() {
611         return visibleColumnCount;
612     }
613
614     public TableFilter getTopTableFilter() {
615         return topTableFilter;
616     }
617
618     public ObjectArray getExpressions() {
619         return expressions;
620     }
621
622     public Expression getCondition() {
623         return condition;
624     }
625
626     public boolean isDistinct() {
627         return distinct;
628     }
629
630     public ObjectArray getGroupBy() {
631         return group;
632     }
633
634     public void setForUpdate(boolean b) {
635         this.isForUpdate = b;
636     }
637
638     public void mapColumns(ColumnResolver resolver, int level) throws SQLException JavaDoc {
639         for(int i=0; i<expressions.size(); i++) {
640             Expression e = (Expression) expressions.get(i);
641             e.mapColumns(resolver, level);
642         }
643         if(condition != null) {
644             condition.mapColumns(resolver, level);
645         }
646     }
647
648     public void setEvaluatable(TableFilter tableFilter, boolean b) {
649         for(int i=0; i<expressions.size(); i++) {
650             Expression e = (Expression) expressions.get(i);
651             e.setEvaluatable(tableFilter, b);
652         }
653         if(condition != null) {
654             condition.setEvaluatable(tableFilter, b);
655         }
656     }
657
658     public boolean isQuickQuery() {
659         return isQuickQuery;
660     }
661
662     public void addGlobalCondition(Expression expr, int columnId, int comparisonType) throws SQLException JavaDoc {
663         Expression col = (Expression)expressions.get(columnId);
664         Expression comp = new Comparison(session, comparisonType, col, expr);
665         comp = comp.optimize(session);
666         if(isGroupQuery) {
667             if(having == null) {
668                 having = comp;
669             } else {
670                 having = new ConditionAndOr(ConditionAndOr.AND, having, comp);
671             }
672         } else {
673             if(condition == null) {
674                 condition = comp;
675             } else {
676                 condition = new ConditionAndOr(ConditionAndOr.AND, condition, comp);
677             }
678         }
679     }
680
681     public boolean isEverything(ExpressionVisitor visitor) {
682         if(visitor.type == ExpressionVisitor.SET_MAX_DATA_MODIFICATION_ID) {
683             for(int i=0; i<filters.size(); i++) {
684                 TableFilter f = (TableFilter) filters.get(i);
685                 long m = f.getTable().getMaxDataModificationId();
686                 visitor.addDataModificationId(m);
687             }
688         }
689         if(visitor.type == ExpressionVisitor.EVALUATABLE) {
690             if(!Constants.OPTIMIZE_EVALUATABLE_SUBQUERIES) {
691                 return false;
692             }
693         }
694         if(visitor.type != ExpressionVisitor.EVALUATABLE) {
695             visitor.queryLevel(1);
696         }
697         boolean result = true;
698         for(int i=0; i<expressions.size(); i++) {
699             Expression e = (Expression) expressions.get(i);
700             if(!e.isEverything(visitor)) {
701                 result = false;
702                 break;
703             }
704         }
705         if(result && condition != null && !condition.isEverything(visitor)) {
706             result = false;
707         }
708         if(result && having != null && !having.isEverything(visitor)) {
709             result = false;
710         }
711         if(visitor.type != ExpressionVisitor.EVALUATABLE) {
712             visitor.queryLevel(-1);
713         }
714         return result;
715     }
716     
717     public boolean isReadOnly() {
718         return isEverything(ExpressionVisitor.READONLY);
719     }
720
721 }
722
Popular Tags