File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\table\TableFilter.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.table;
8
9 import java.util.ArrayList;
10 import org.h2.command.Parser;
11 import org.h2.command.dml.Select;
12 import org.h2.constant.SysProperties;
13 import org.h2.engine.Right;
14 import org.h2.engine.Session;
15 import org.h2.engine.UndoLogRecord;
16 import org.h2.expression.Comparison;
17 import org.h2.expression.ConditionAndOr;
18 import org.h2.expression.Expression;
19 import org.h2.expression.ExpressionColumn;
20 import org.h2.index.Index;
21 import org.h2.index.IndexCondition;
22 import org.h2.index.IndexCursor;
23 import org.h2.message.DbException;
24 import org.h2.result.Row;
25 import org.h2.result.SearchRow;
26 import org.h2.util.New;
27 import org.h2.util.StatementBuilder;
28 import org.h2.util.StringUtils;
29 import org.h2.value.Value;
30 import org.h2.value.ValueLong;
31 import org.h2.value.ValueNull;
32
33 /**
34 * A table filter represents a table that is used in a query. There is one such
35 * object whenever a table (or view) is used in a query. For example the
36 * following query has 2 table filters: SELECT * FROM TEST T1, TEST T2.
37 */
38 public class TableFilter implements ColumnResolver {
39
40 private static final int BEFORE_FIRST = 0, FOUND = 1, AFTER_LAST = 2, NULL_ROW = 3;
41
42 /**
43 * Whether this is a direct or indirect (nested) outer join
44 */
45 protected boolean joinOuterIndirect;
46
47 private Session session;
48
49 private final Table table;
50 private final Select select;
51 private String alias;
52 private Index index;
53 private int scanCount;
54 private boolean evaluatable;
55
56 /**
57 * Indicates that this filter is used in the plan.
58 */
59 private boolean used;
60
61 /**
62 * The filter used to walk through the index.
63 */
64 private final IndexCursor cursor;
65
66 /**
67 * The index conditions used for direct index lookup (start or end).
68 */
69 private final ArrayList<IndexCondition> indexConditions = New.arrayList();
70
71 /**
72 * Additional conditions that can't be used for index lookup, but for row
73 * filter for this table (ID=ID, NAME LIKE '%X%')
74 */
75 private Expression filterCondition;
76
77 /**
78 * The complete join condition.
79 */
80 private Expression joinCondition;
81
82 private SearchRow currentSearchRow;
83 private Row current;
84 private int state;
85
86 /**
87 * The joined table (if there is one).
88 */
89 private TableFilter join;
90
91 /**
92 * Whether this is an outer join.
93 */
94 private boolean joinOuter;
95
96 /**
97 * The nested joined table (if there is one).
98 */
99 private TableFilter nestedJoin;
100
101 private ArrayList<Column> naturalJoinColumns;
102 private boolean foundOne;
103 private Expression fullCondition;
104 private final int hashCode;
105
106 /**
107 * Create a new table filter object.
108 *
109 * @param session the session
110 * @param table the table from where to read data
111 * @param alias the alias name
112 * @param rightsChecked true if rights are already checked
113 * @param select the select statement
114 */
115 public TableFilter(Session session, Table table, String alias, boolean rightsChecked, Select select) {
116 this.session = session;
117 this.table = table;
118 this.alias = alias;
119 this.select = select;
120 this.cursor = new IndexCursor(this);
121 if (!rightsChecked) {
122 session.getUser().checkRight(table, Right.SELECT);
123 }
124 hashCode = session.nextObjectId();
125 }
126
127 public Select getSelect() {
128 return select;
129 }
130
131 public Table getTable() {
132 return table;
133 }
134
135 /**
136 * Lock the table. This will also lock joined tables.
137 *
138 * @param s the session
139 * @param exclusive true if an exclusive lock is required
140 * @param force lock even in the MVCC mode
141 */
142 public void lock(Session s, boolean exclusive, boolean force) {
143 table.lock(s, exclusive, force);
144 if (join != null) {
145 join.lock(s, exclusive, force);
146 }
147 }
148
149 /**
150 * Get the best plan item (index, cost) to use use for the current join
151 * order.
152 *
153 * @param s the session
154 * @param level 1 for the first table in a join, 2 for the second, and so on
155 * @return the best plan item
156 */
157 public PlanItem getBestPlanItem(Session s, int level) {
158 PlanItem item;
159 if (indexConditions.size() == 0) {
160 item = new PlanItem();
161 item.setIndex(table.getScanIndex(s));
162 item.cost = item.getIndex().getCost(s, null);
163 } else {
164 int len = table.getColumns().length;
165 int[] masks = new int[len];
166 for (IndexCondition condition : indexConditions) {
167 if (condition.isEvaluatable()) {
168 if (condition.isAlwaysFalse()) {
169 masks = null;
170 break;
171 }
172 int id = condition.getColumn().getColumnId();
173 if (id >= 0) {
174 masks[id] |= condition.getMask(indexConditions);
175 }
176 }
177 }
178 item = table.getBestPlanItem(s, masks);
179 // The more index conditions, the earlier the table.
180 // This is to ensure joins without indexes run quickly:
181 // x (x.a=10); y (x.b=y.b) - see issue 113
182 item.cost -= item.cost * indexConditions.size() / 100 / level;
183 }
184 if (nestedJoin != null) {
185 setEvaluatable(nestedJoin);
186 item.setNestedJoinPlan(nestedJoin.getBestPlanItem(s, level));
187 // TODO optimizer: calculate cost of a join: should use separate
188 // expected row number and lookup cost
189 item.cost += item.cost * item.getNestedJoinPlan().cost;
190 }
191 if (join != null) {
192 setEvaluatable(join);
193 item.setJoinPlan(join.getBestPlanItem(s, level));
194 // TODO optimizer: calculate cost of a join: should use separate
195 // expected row number and lookup cost
196 item.cost += item.cost * item.getJoinPlan().cost;
197 }
198 return item;
199 }
200
201 private void setEvaluatable(TableFilter join) {
202 if (session.getDatabase().getSettings().nestedJoins) {
203 setEvaluatable(true);
204 return;
205 }
206 // this table filter is now evaluatable - in all sub-joins
207 do {
208 Expression e = join.getJoinCondition();
209 if (e != null) {
210 e.setEvaluatable(this, true);
211 }
212 TableFilter n = join.getNestedJoin();
213 if (n != null) {
214 setEvaluatable(n);
215 }
216 join = join.getJoin();
217 } while (join != null);
218 }
219
220 /**
221 * Set what plan item (index, cost) to use use.
222 *
223 * @param item the plan item
224 */
225 public void setPlanItem(PlanItem item) {
226 if (item == null) {
227 // invalid plan, most likely because a column wasn't found
228 // this will result in an exception later on
229 return;
230 }
231 setIndex(item.getIndex());
232 if (nestedJoin != null) {
233 if (item.getNestedJoinPlan() != null) {
234 nestedJoin.setPlanItem(item.getNestedJoinPlan());
235 }
236 }
237 if (join != null) {
238 if (item.getJoinPlan() != null) {
239 join.setPlanItem(item.getJoinPlan());
240 }
241 }
242 }
243
244 /**
245 * Prepare reading rows. This method will remove all index conditions that
246 * can not be used, and optimize the conditions.
247 */
248 public void prepare() {
249 // forget all unused index conditions
250 // the indexConditions list may be modified here
251 for (int i = 0; i < indexConditions.size(); i++) {
252 IndexCondition condition = indexConditions.get(i);
253 if (!condition.isAlwaysFalse()) {
254 Column col = condition.getColumn();
255 if (col.getColumnId() >= 0) {
256 if (index.getColumnIndex(col) < 0) {
257 indexConditions.remove(i);
258 i--;
259 }
260 }
261 }
262 }
263 if (nestedJoin != null) {
264 if (SysProperties.CHECK && nestedJoin == this) {
265 DbException.throwInternalError("self join");
266 }
267 nestedJoin.prepare();
268 }
269 if (join != null) {
270 if (SysProperties.CHECK && join == this) {
271 DbException.throwInternalError("self join");
272 }
273 join.prepare();
274 }
275 if (filterCondition != null) {
276 filterCondition = filterCondition.optimize(session);
277 }
278 if (joinCondition != null) {
279 joinCondition = joinCondition.optimize(session);
280 }
281 }
282
283 /**
284 * Start the query. This will reset the scan counts.
285 *
286 * @param s the session
287 */
288 public void startQuery(Session s) {
289 this.session = s;
290 scanCount = 0;
291 if (nestedJoin != null) {
292 nestedJoin.startQuery(s);
293 }
294 if (join != null) {
295 join.startQuery(s);
296 }
297 }
298
299 /**
300 * Reset to the current position.
301 */
302 public void reset() {
303 if (nestedJoin != null) {
304 nestedJoin.reset();
305 }
306 if (join != null) {
307 join.reset();
308 }
309 state = BEFORE_FIRST;
310 foundOne = false;
311 }
312
313 /**
314 * Check if there are more rows to read.
315 *
316 * @return true if there are
317 */
318 public boolean next() {
319 if (state == AFTER_LAST) {
320 return false;
321 } else if (state == BEFORE_FIRST) {
322 cursor.find(session, indexConditions);
323 if (!cursor.isAlwaysFalse()) {
324 if (nestedJoin != null) {
325 nestedJoin.reset();
326 }
327 if (join != null) {
328 join.reset();
329 }
330 }
331 } else {
332 // state == FOUND || NULL_ROW
333 // the last row was ok - try next row of the join
334 if (join != null && join.next()) {
335 return true;
336 }
337 }
338 while (true) {
339 // go to the next row
340 if (state == NULL_ROW) {
341 break;
342 }
343 if (cursor.isAlwaysFalse()) {
344 state = AFTER_LAST;
345 } else if (nestedJoin != null) {
346 if (state == BEFORE_FIRST) {
347 state = FOUND;
348 }
349 } else {
350 if ((++scanCount & 4095) == 0) {
351 checkTimeout();
352 }
353 if (cursor.next()) {
354 currentSearchRow = cursor.getSearchRow();
355 current = null;
356 state = FOUND;
357 } else {
358 state = AFTER_LAST;
359 }
360 }
361 if (nestedJoin != null && state == FOUND) {
362 if (!nestedJoin.next()) {
363 state = AFTER_LAST;
364 if (joinOuter && !foundOne) {
365 // possibly null row
366 } else {
367 continue;
368 }
369 }
370 }
371 // if no more rows found, try the null row (for outer joins only)
372 if (state == AFTER_LAST) {
373 if (joinOuter && !foundOne) {
374 setNullRow();
375 } else {
376 break;
377 }
378 }
379 if (!isOk(filterCondition)) {
380 continue;
381 }
382 boolean joinConditionOk = isOk(joinCondition);
383 if (state == FOUND) {
384 if (joinConditionOk) {
385 foundOne = true;
386 } else {
387 continue;
388 }
389 }
390 if (join != null) {
391 join.reset();
392 if (!join.next()) {
393 continue;
394 }
395 }
396 // check if it's ok
397 if (state == NULL_ROW || joinConditionOk) {
398 return true;
399 }
400 }
401 state = AFTER_LAST;
402 return false;
403 }
404
405 /**
406 * Set the state of this and all nested tables to the NULL row.
407 */
408 protected void setNullRow() {
409 state = NULL_ROW;
410 current = table.getNullRow();
411 currentSearchRow = current;
412 if (nestedJoin != null) {
413 nestedJoin.visit(new TableFilterVisitor() {
414 public void accept(TableFilter f) {
415 f.setNullRow();
416 }
417 });
418 }
419 }
420
421 private void checkTimeout() {
422 session.checkCanceled();
423 // System.out.println(this.alias+ " " + table.getName() + ": " +
424 // scanCount);
425 }
426
427 private boolean isOk(Expression condition) {
428 if (condition == null) {
429 return true;
430 }
431 return Boolean.TRUE.equals(condition.getBooleanValue(session));
432 }
433
434 /**
435 * Get the current row.
436 *
437 * @return the current row, or null
438 */
439 public Row get() {
440 if (current == null && currentSearchRow != null) {
441 current = cursor.get();
442 }
443 return current;
444 }
445
446 /**
447 * Set the current row.
448 *
449 * @param current the current row
450 */
451 public void set(Row current) {
452 // this is currently only used so that check constraints work - to set
453 // the current (new) row
454 this.current = current;
455 this.currentSearchRow = current;
456 }
457
458 /**
459 * Get the table alias name. If no alias is specified, the table name is
460 * returned.
461 *
462 * @return the alias name
463 */
464 public String getTableAlias() {
465 if (alias != null) {
466 return alias;
467 }
468 return table.getName();
469 }
470
471 /**
472 * Add an index condition.
473 *
474 * @param condition the index condition
475 */
476 public void addIndexCondition(IndexCondition condition) {
477 indexConditions.add(condition);
478 }
479
480 /**
481 * Add a filter condition.
482 *
483 * @param condition the condition
484 * @param isJoin if this is in fact a join condition
485 */
486 public void addFilterCondition(Expression condition, boolean isJoin) {
487 if (isJoin) {
488 if (joinCondition == null) {
489 joinCondition = condition;
490 } else {
491 joinCondition = new ConditionAndOr(ConditionAndOr.AND, joinCondition, condition);
492 }
493 } else {
494 if (filterCondition == null) {
495 filterCondition = condition;
496 } else {
497 filterCondition = new ConditionAndOr(ConditionAndOr.AND, filterCondition, condition);
498 }
499 }
500 }
501
502 /**
503 * Add a joined table.
504 *
505 * @param filter the joined table filter
506 * @param outer if this is an outer join
507 * @param nested if this is a nested join
508 * @param on the join condition
509 */
510 public void addJoin(TableFilter filter, boolean outer, boolean nested, final Expression on) {
511 if (on != null) {
512 on.mapColumns(this, 0);
513 if (session.getDatabase().getSettings().nestedJoins) {
514 visit(new TableFilterVisitor() {
515 public void accept(TableFilter f) {
516 on.mapColumns(f, 0);
517 }
518 });
519 filter.visit(new TableFilterVisitor() {
520 public void accept(TableFilter f) {
521 on.mapColumns(f, 0);
522 }
523 });
524 }
525 }
526 if (nested && session.getDatabase().getSettings().nestedJoins) {
527 if (nestedJoin != null) {
528 throw DbException.throwInternalError();
529 }
530 nestedJoin = filter;
531 filter.joinOuter = outer;
532 if (outer) {
533 visit(new TableFilterVisitor() {
534 public void accept(TableFilter f) {
535 f.joinOuterIndirect = true;
536 }
537 });
538 }
539 if (on != null) {
540 filter.mapAndAddFilter(on);
541 }
542 } else {
543 if (join == null) {
544 join = filter;
545 filter.joinOuter = outer;
546 if (session.getDatabase().getSettings().nestedJoins) {
547 if (outer) {
548 filter.visit(new TableFilterVisitor() {
549 public void accept(TableFilter f) {
550 f.joinOuterIndirect = true;
551 }
552 });
553 }
554 } else {
555 if (outer) {
556 // convert all inner joins on the right hand side to outer joins
557 TableFilter f = filter.join;
558 while (f != null) {
559 f.joinOuter = true;
560 f = f.join;
561 }
562 }
563 }
564 if (on != null) {
565 filter.mapAndAddFilter(on);
566 }
567 } else {
568 join.addJoin(filter, outer, nested, on);
569 }
570 }
571 }
572
573 /**
574 * Map the columns and add the join condition.
575 *
576 * @param on the condition
577 */
578 public void mapAndAddFilter(Expression on) {
579 on.mapColumns(this, 0);
580 addFilterCondition(on, true);
581 on.createIndexConditions(session, this);
582 if (nestedJoin != null) {
583 on.mapColumns(nestedJoin, 0);
584 on.createIndexConditions(session, nestedJoin);
585 }
586 if (join != null) {
587 join.mapAndAddFilter(on);
588 }
589 }
590
591 public TableFilter getJoin() {
592 return join;
593 }
594
595 /**
596 * Whether this is an outer joined table.
597 *
598 * @return true if it is
599 */
600 public boolean isJoinOuter() {
601 return joinOuter;
602 }
603
604 /**
605 * Whether this is indirectly an outer joined table (nested within an inner
606 * join).
607 *
608 * @return true if it is
609 */
610 public boolean isJoinOuterIndirect() {
611 return joinOuterIndirect;
612 }
613
614 /**
615 * Get the query execution plan text to use for this table filter.
616 *
617 * @param isJoin if this is a joined table
618 * @return the SQL statement snippet
619 */
620 public String getPlanSQL(boolean isJoin) {
621 StringBuilder buff = new StringBuilder();
622 if (isJoin) {
623 if (joinOuter) {
624 buff.append("LEFT OUTER JOIN ");
625 } else {
626 buff.append("INNER JOIN ");
627 }
628 }
629 if (nestedJoin != null) {
630 StringBuffer buffNested = new StringBuffer();
631 TableFilter n = nestedJoin;
632 do {
633 buffNested.append(n.getPlanSQL(n != nestedJoin));
634 buffNested.append('\n');
635 n = n.getJoin();
636 } while (n != null);
637 String nested = buffNested.toString();
638 boolean enclose = !nested.startsWith("(");
639 if (enclose) {
640 buff.append("(\n");
641 }
642 buff.append(StringUtils.indent(nested, 4, false));
643 if (enclose) {
644 buff.append(')');
645 }
646 if (isJoin) {
647 buff.append(" ON ");
648 if (joinCondition == null) {
649 // need to have a ON expression,
650 // otherwise the nesting is unclear
651 buff.append("1=1");
652 } else {
653 buff.append(StringUtils.unEnclose(joinCondition.getSQL()));
654 }
655 }
656 return buff.toString();
657 }
658 buff.append(table.getSQL());
659 if (alias != null) {
660 buff.append(' ').append(Parser.quoteIdentifier(alias));
661 }
662 if (index != null) {
663 buff.append('\n');
664 StatementBuilder planBuff = new StatementBuilder();
665 planBuff.append(index.getPlanSQL());
666 if (indexConditions.size() > 0) {
667 planBuff.append(": ");
668 for (IndexCondition condition : indexConditions) {
669 planBuff.appendExceptFirst("\n AND ");
670 planBuff.append(condition.getSQL());
671 }
672 }
673 String plan = StringUtils.quoteRemarkSQL(planBuff.toString());
674 if (plan.indexOf('\n') >= 0) {
675 plan += "\n";
676 }
677 buff.append(StringUtils.indent("/* " + plan + " */", 4, false));
678 }
679 if (isJoin) {
680 buff.append("\n ON ");
681 if (joinCondition == null) {
682 // need to have a ON expression, otherwise the nesting is
683 // unclear
684 buff.append("1=1");
685 } else {
686 buff.append(StringUtils.unEnclose(joinCondition.getSQL()));
687 }
688 }
689 if (filterCondition != null) {
690 buff.append('\n');
691 String condition = StringUtils.unEnclose(filterCondition.getSQL());
692 condition = "/* WHERE " + StringUtils.quoteRemarkSQL(condition) + "\n*/";
693 buff.append(StringUtils.indent(condition, 4, false));
694 }
695 if (scanCount > 0) {
696 buff.append("\n /* scanCount: ").append(scanCount).append(" */");
697 }
698 return buff.toString();
699 }
700
701 /**
702 * Remove all index conditions that are not used by the current index.
703 */
704 void removeUnusableIndexConditions() {
705 // the indexConditions list may be modified here
706 for (int i = 0; i < indexConditions.size(); i++) {
707 IndexCondition cond = indexConditions.get(i);
708 if (!cond.isEvaluatable()) {
709 indexConditions.remove(i--);
710 }
711 }
712 }
713
714 public Index getIndex() {
715 return index;
716 }
717
718 public void setIndex(Index index) {
719 this.index = index;
720 cursor.setIndex(index);
721 }
722
723 public void setUsed(boolean used) {
724 this.used = used;
725 }
726
727 public boolean isUsed() {
728 return used;
729 }
730
731 /**
732 * Set the session of this table filter.
733 *
734 * @param session the new session
735 */
736 void setSession(Session session) {
737 this.session = session;
738 }
739
740 /**
741 * Remove the joined table
742 */
743 public void removeJoin() {
744 this.join = null;
745 }
746
747 public Expression getJoinCondition() {
748 return joinCondition;
749 }
750
751 /**
752 * Remove the join condition.
753 */
754 public void removeJoinCondition() {
755 this.joinCondition = null;
756 }
757
758 public Expression getFilterCondition() {
759 return filterCondition;
760 }
761
762 /**
763 * Remove the filter condition.
764 */
765 public void removeFilterCondition() {
766 this.filterCondition = null;
767 }
768
769 public void setFullCondition(Expression condition) {
770 this.fullCondition = condition;
771 if (join != null) {
772 join.setFullCondition(condition);
773 }
774 }
775
776 /**
777 * Optimize the full condition. This will add the full condition to the
778 * filter condition.
779 *
780 * @param fromOuterJoin if this method was called from an outer joined table
781 */
782 void optimizeFullCondition(boolean fromOuterJoin) {
783 if (fullCondition != null) {
784 fullCondition.addFilterConditions(this, fromOuterJoin || joinOuter);
785 if (nestedJoin != null) {
786 nestedJoin.optimizeFullCondition(fromOuterJoin || joinOuter);
787 }
788 if (join != null) {
789 join.optimizeFullCondition(fromOuterJoin || joinOuter);
790 }
791 }
792 }
793
794 /**
795 * Update the filter and join conditions of this and all joined tables with
796 * the information that the given table filter and all nested filter can now
797 * return rows or not.
798 *
799 * @param filter the table filter
800 * @param b the new flag
801 */
802 public void setEvaluatable(TableFilter filter, boolean b) {
803 filter.setEvaluatable(b);
804 if (filterCondition != null) {
805 filterCondition.setEvaluatable(filter, b);
806 }
807 if (joinCondition != null) {
808 joinCondition.setEvaluatable(filter, b);
809 }
810 if (nestedJoin != null) {
811 // don't enable / disable the nested join filters
812 // if enabling a filter in a joined filter
813 if (this == filter) {
814 nestedJoin.setEvaluatable(nestedJoin, b);
815 }
816 }
817 if (join != null) {
818 join.setEvaluatable(filter, b);
819 }
820 }
821
822 public void setEvaluatable(boolean evaluatable) {
823 this.evaluatable = evaluatable;
824 }
825
826 public String getSchemaName() {
827 return table.getSchema().getName();
828 }
829
830 public Column[] getColumns() {
831 return table.getColumns();
832 }
833
834 /**
835 * Get the system columns that this table understands. This is used for
836 * compatibility with other databases. The columns are only returned if the
837 * current mode supports system columns.
838 *
839 * @return the system columns
840 */
841 public Column[] getSystemColumns() {
842 if (!session.getDatabase().getMode().systemColumns) {
843 return null;
844 }
845 Column[] sys = new Column[3];
846 sys[0] = new Column("oid", Value.INT);
847 sys[0].setTable(table, 0);
848 sys[1] = new Column("ctid", Value.STRING);
849 sys[1].setTable(table, 0);
850 sys[2] = new Column("CTID", Value.STRING);
851 sys[2].setTable(table, 0);
852 return sys;
853 }
854
855 public Column getRowIdColumn() {
856 if (session.getDatabase().getSettings().rowId) {
857 return table.getRowIdColumn();
858 }
859 return null;
860 }
861
862 public Value getValue(Column column) {
863 if (currentSearchRow == null) {
864 return null;
865 }
866 int columnId = column.getColumnId();
867 if (columnId == -1) {
868 return ValueLong.get(currentSearchRow.getKey());
869 }
870 if (current == null) {
871 Value v = currentSearchRow.getValue(columnId);
872 if (v != null) {
873 return v;
874 }
875 current = cursor.get();
876 if (current == null) {
877 return ValueNull.INSTANCE;
878 }
879 }
880 return current.getValue(columnId);
881 }
882
883 public TableFilter getTableFilter() {
884 return this;
885 }
886
887 public void setAlias(String alias) {
888 this.alias = alias;
889 }
890
891 public Expression optimize(ExpressionColumn expressionColumn, Column column) {
892 return expressionColumn;
893 }
894
895 public String toString() {
896 return alias != null ? alias : table.toString();
897 }
898
899 /**
900 * Add a column to the natural join key column list.
901 *
902 * @param c the column to add
903 */
904 public void addNaturalJoinColumn(Column c) {
905 if (naturalJoinColumns == null) {
906 naturalJoinColumns = New.arrayList();
907 }
908 naturalJoinColumns.add(c);
909 }
910
911 /**
912 * Check if the given column is a natural join column.
913 *
914 * @param c the column to check
915 * @return true if this is a joined natural join column
916 */
917 public boolean isNaturalJoinColumn(Column c) {
918 return naturalJoinColumns != null && naturalJoinColumns.indexOf(c) >= 0;
919 }
920
921 public int hashCode() {
922 return hashCode;
923 }
924
925 /**
926 * Are there any index conditions that involve IN(...).
927 *
928 * @return whether there are IN(...) comparisons
929 */
930 public boolean hasInComparisons() {
931 for (IndexCondition cond : indexConditions) {
932 int compareType = cond.getCompareType();
933 if (compareType == Comparison.IN_QUERY || compareType == Comparison.IN_LIST) {
934 return true;
935 }
936 }
937 return false;
938 }
939
940 /**
941 * Add the current row to the array, if there is a current row.
942 *
943 * @param rows the rows to lock
944 */
945 public void lockRowAdd(ArrayList<Row> rows) {
946 if (state == FOUND) {
947 rows.add(get());
948 }
949 }
950
951 /**
952 * Lock the given rows.
953 *
954 * @param forUpdateRows the rows to lock
955 */
956 public void lockRows(ArrayList<Row> forUpdateRows) {
957 for (Row row : forUpdateRows) {
958 Row newRow = row.getCopy();
959 table.removeRow(session, row);
960 session.log(table, UndoLogRecord.DELETE, row);
961 table.addRow(session, newRow);
962 session.log(table, UndoLogRecord.INSERT, newRow);
963 }
964 }
965
966 public TableFilter getNestedJoin() {
967 return nestedJoin;
968 }
969
970 /**
971 * Visit this and all joined or nested table filters.
972 *
973 * @param visitor the visitor
974 */
975 public void visit(TableFilterVisitor visitor) {
976 TableFilter f = this;
977 do {
978 visitor.accept(f);
979 TableFilter n = f.nestedJoin;
980 if (n != null) {
981 n.visit(visitor);
982 }
983 f = f.join;
984 } while (f != null);
985 }
986
987 public boolean isEvaluatable() {
988 return evaluatable;
989 }
990
991 public Session getSession() {
992 return session;
993 }
994
995 /**
996 * A visitor for table filters.
997 */
998 public interface TableFilterVisitor {
999
1000 /**
1001 * This method is called for each nested or joined table filter.
1002 *
1003 * @param f the filter
1004 */
1005 void accept(TableFilter f);
1006 }
1007
1008 }
1009