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