1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl.query;
18
19 import java.io.IOException;
20 import java.lang.System.Logger;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.Iterator;
25 import java.util.List;
26
27 import com.healthmarketscience.jackcess.PropertyMap;
28 import com.healthmarketscience.jackcess.RowId;
29 import com.healthmarketscience.jackcess.impl.DatabaseImpl;
30 import com.healthmarketscience.jackcess.impl.PropertyMaps;
31 import com.healthmarketscience.jackcess.impl.QueryNameMapImpl;
32 import com.healthmarketscience.jackcess.impl.RowIdImpl;
33 import com.healthmarketscience.jackcess.impl.RowImpl;
34 import com.healthmarketscience.jackcess.impl.ToStringBuilder;
35 import static com.healthmarketscience.jackcess.impl.query.QueryFormat.*;
36 import com.healthmarketscience.jackcess.query.Query;
37
38
39
40
41
42
43
44
45
46 public abstract class QueryImpl implements Query, PropertyMaps.Owner
47 {
48 protected static final Logger LOG = System.getLogger(QueryImpl.class.getName());
49
50 private static final Rowackcess/Row.html#Row">Row EMPTY_ROW = new Row();
51
52 private final String _name;
53 private final List<Row> _rows;
54 private final int _objectId;
55 private final Type _type;
56 private final int _objectFlag;
57
58
59 private DatabaseImpl _database;
60
61 private PropertyMaps _propertyMaps;
62
63 private QueryNameMapImpl _nameMap;
64
65 protected QueryImpl(String name, List<Row> rows, int objectId, int objectFlag,
66 Type type)
67 {
68 _name = name;
69 _rows = rows;
70 _objectId = objectId;
71 _type = type;
72 _objectFlag = objectFlag;
73
74 if(type != Type.UNKNOWN) {
75 short foundType = getShortValue(getQueryType(rows),
76 _type.getValue());
77 if(foundType != _type.getValue()) {
78 throw new IllegalStateException(withErrorContext(
79 "Unexpected query type " + foundType));
80 }
81 }
82 }
83
84
85
86
87 @Override
88 public String getName() {
89 return _name;
90 }
91
92
93
94
95 @Override
96 public Type getType() {
97 return _type;
98 }
99
100 @Override
101 public boolean isHidden() {
102 return((_objectFlag & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0);
103 }
104
105
106
107
108 @Override
109 public int getObjectId() {
110 return _objectId;
111 }
112
113 @Override
114 public int getObjectFlag() {
115 return _objectFlag;
116 }
117
118 @Override
119 public PropertyMap getProperties() throws IOException {
120 return getPropertyMaps().getDefault();
121 }
122
123
124
125
126
127 public PropertyMaps getPropertyMaps() throws IOException {
128 if(_propertyMaps == null) {
129 _propertyMaps = _database.getPropertiesForObject(_objectId, this);
130 }
131 return _propertyMaps;
132 }
133
134 @Override
135 public QueryNameMapImpl getNameMap() throws IOException {
136 if(_nameMap == null) {
137 byte[] bytes = (byte[])getProperties().getValue(
138 PropertyMap.QUERY_NAME_MAP_PROP);
139 if(bytes != null) {
140 _nameMap = QueryNameMapImpl.read(bytes, _database);
141 }
142 }
143 return _nameMap;
144 }
145
146 @Override
147 public void propertiesUpdated() throws IOException {
148
149 _nameMap = null;
150 }
151
152
153
154
155
156 public List<Row> getRows() {
157 return _rows;
158 }
159
160 protected List<Row> getRowsByAttribute(Byte attribute) {
161 return getRowsByAttribute(getRows(), attribute);
162 }
163
164 protected Row getRowByAttribute(Byte attribute) {
165 return getUniqueRow(getRowsByAttribute(getRows(), attribute));
166 }
167
168 public Row getTypeRow() {
169 return getRowByAttribute(TYPE_ATTRIBUTE);
170 }
171
172
173
174
175
176 protected String getTypeRowText() {
177 String text = getTypeRow().expression;
178 if(text == null) {
179 return null;
180 }
181 int end = text.length();
182 while((end > 0) && (text.charAt(end - 1) == '\0')) {
183 --end;
184 }
185 return text.substring(0, end);
186 }
187
188 protected List<Row> getParameterRows() {
189 return getRowsByAttribute(PARAMETER_ATTRIBUTE);
190 }
191
192 protected Row getFlagRow() {
193 return getRowByAttribute(FLAG_ATTRIBUTE);
194 }
195
196 protected Row getRemoteDatabaseRow() {
197 return getRowByAttribute(REMOTEDB_ATTRIBUTE);
198 }
199
200 protected List<Row> getTableRows() {
201 return getRowsByAttribute(TABLE_ATTRIBUTE);
202 }
203
204 protected List<Row> getColumnRows() {
205 return getRowsByAttribute(COLUMN_ATTRIBUTE);
206 }
207
208 protected List<Row> getJoinRows() {
209 return getRowsByAttribute(JOIN_ATTRIBUTE);
210 }
211
212 protected Row getWhereRow() {
213 return getRowByAttribute(WHERE_ATTRIBUTE);
214 }
215
216 protected List<Row> getGroupByRows() {
217 return getRowsByAttribute(GROUPBY_ATTRIBUTE);
218 }
219
220 protected Row getHavingRow() {
221 return getRowByAttribute(HAVING_ATTRIBUTE);
222 }
223
224 protected List<Row> getOrderByRows() {
225 return getRowsByAttribute(ORDERBY_ATTRIBUTE);
226 }
227
228 protected abstract void toSQLString(StringBuilder builder);
229
230 protected void toSQLParameterString(StringBuilder builder) {
231
232 List<String> params = getParameters();
233 if(!params.isEmpty()) {
234 builder.append("PARAMETERS ").append(params)
235 .append(';').append(NEWLINE);
236 }
237 }
238
239 @Override
240 public List<String> getParameters()
241 {
242 return (new RowFormatter(getParameterRows()) {
243 @Override protected void format(StringBuilder builder, Row row) {
244 String typeName = PARAM_TYPE_MAP.get(row.flag);
245 if(typeName == null) {
246 throw new IllegalStateException(withErrorContext(
247 "Unknown param type " + row.flag));
248 }
249
250 toParameterName(builder, row.name1).append(' ').append(typeName);
251 if((TEXT_FLAG.equals(row.flag)) && (getIntValue(row.extra, 0) > 0)) {
252 builder.append('(').append(row.extra).append(')');
253 }
254 }
255 }).format();
256 }
257
258 protected List<String> getFromTables()
259 {
260
261 List<TableSource> tableExprs = new ArrayList<>();
262 for(Row table : getTableRows()) {
263 StringBuilder builder = new StringBuilder();
264
265 if(table.name1 == null) {
266
267
268
269
270 if(table.expression != null) {
271 builder.append('(').append(table.expression).append(')');
272 }
273 } else {
274 if(table.expression != null) {
275 toQuotedExpr(builder, table.expression).append(IDENTIFIER_SEP_CHAR);
276 }
277 toOptionalQuotedExpr(builder, table.name1, true);
278 }
279 toAlias(builder, table.name2);
280
281 String key = ((table.name2 != null) ? table.name2 : table.name1);
282 tableExprs.add(new SimpleTable(key, builder.toString()));
283 }
284
285
286 List<Row> joins = getJoinRows();
287 for(Row joinRow : joins) {
288
289 String fromTable = joinRow.name1;
290 String toTable = joinRow.name2;
291
292 TableSource fromTs = null;
293 TableSource toTs = null;
294
295
296 for(Iterator<TableSource> joinIter = tableExprs.iterator();
297 (joinIter.hasNext() && ((fromTs == null) || (toTs == null))); ) {
298 TableSource ts = joinIter.next();
299
300 if((fromTs == null) && ts.containsTable(fromTable)) {
301 fromTs = ts;
302
303
304 if((toTs == null) && ts.containsTable(toTable)) {
305 toTs = ts;
306 break;
307 }
308
309 joinIter.remove();
310
311 } else if((toTs == null) && ts.containsTable(toTable)) {
312
313 toTs = ts;
314 joinIter.remove();
315 }
316 }
317
318 if(fromTs == null) {
319 fromTs = new SimpleTable(fromTable);
320 }
321 if(toTs == null) {
322 toTs = new SimpleTable(toTable);
323 }
324
325 if(fromTs == toTs) {
326
327 if(fromTs.addCondition(fromTable, toTable, joinRow.flag,
328 joinRow.expression)) {
329
330
331 continue;
332 }
333
334 throw new IllegalStateException(withErrorContext(
335 "Inconsistent join types for " + fromTable + " and " + toTable));
336 }
337
338
339 tableExprs.add(new Join(fromTs, toTs, joinRow.flag, joinRow.expression));
340 }
341
342
343 List<String> result = new AppendableList<>();
344 for(TableSource ts : tableExprs) {
345 result.add(ts.toString());
346 }
347
348 return result;
349 }
350
351 protected String getFromRemoteDbPath()
352 {
353 return getRemoteDatabaseRow().name1;
354 }
355
356 protected String getFromRemoteDbType()
357 {
358 return getRemoteDatabaseRow().expression;
359 }
360
361 protected String getWhereExpression()
362 {
363 return getWhereRow().expression;
364 }
365
366 protected List<String> getOrderings()
367 {
368 return (new RowFormatter(getOrderByRows()) {
369 @Override protected void format(StringBuilder builder, Row row) {
370 builder.append(row.expression);
371 if(DESCENDING_FLAG.equalsIgnoreCase(row.name1)) {
372 builder.append(" DESC");
373 }
374 }
375 }).format();
376 }
377
378
379
380
381
382
383 public String getSelectType()
384 {
385 StringBuilder builder = new StringBuilder();
386
387 if(hasFlag(DISTINCT_SELECT_TYPE)) {
388 builder.append("DISTINCT");
389 } else if(hasFlag(DISTINCT_ROW_SELECT_TYPE)) {
390 builder.append("DISTINCTROW");
391 }
392
393 if(hasFlag(TOP_SELECT_TYPE)) {
394 if(builder.length() > 0) {
395 builder.append(' ');
396 }
397 builder.append("TOP ").append(getFlagRow().name1);
398 if(hasFlag(PERCENT_SELECT_TYPE)) {
399 builder.append(" PERCENT");
400 }
401 }
402
403 return ((builder.length() > 0) ? builder.toString() : DEFAULT_TYPE);
404 }
405
406
407
408
409 protected void appendSelectType(StringBuilder builder)
410 {
411 String selectType = getSelectType();
412 if(!DEFAULT_TYPE.equals(selectType)) {
413 builder.append(selectType).append(' ');
414 }
415 }
416
417 @Override
418 public String getOwnerAccessType() {
419 return(hasFlag(OWNER_ACCESS_SELECT_TYPE) ?
420 "WITH OWNERACCESS OPTION" : DEFAULT_TYPE);
421 }
422
423 protected boolean hasFlag(int flagMask)
424 {
425 return hasFlag(getFlagRow(), flagMask);
426 }
427
428 protected boolean supportsStandardClauses() {
429 return true;
430 }
431
432
433
434
435 @Override
436 public String toSQLString()
437 {
438 StringBuilder builder = new StringBuilder();
439 if(supportsStandardClauses()) {
440 toSQLParameterString(builder);
441 }
442
443 toSQLString(builder);
444
445 if(supportsStandardClauses()) {
446
447 String accessType = getOwnerAccessType();
448 if(!DEFAULT_TYPE.equals(accessType)) {
449 builder.append(NEWLINE).append(accessType);
450 }
451
452 builder.append(';');
453 }
454 return builder.toString();
455 }
456
457 @Override
458 public String toString() {
459
460 return ToStringBuilder.builder(this)
461 .append("name", _name)
462 .append("objectFlag", _objectFlag)
463 .append("objectId", _objectId)
464 .append("rows", _rows)
465 .append("type", _type)
466 .toString();
467 }
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482 public static QueryImpl create(int objectFlag, String name, List<Row> rows,
483 int objectId)
484 {
485 return create(null, objectFlag, name, rows, objectId);
486 }
487
488
489
490
491
492
493
494
495
496
497
498
499
500 public static QueryImpl create(DatabaseImpl database, int objectFlag,
501 String name, List<Row> rows, int objectId)
502 {
503 QueryImpl query = createQuery(objectFlag, name, sortByOrder(rows),
504 objectId);
505 query._database = database;
506 return query;
507 }
508
509 private static QueryImpl createQuery(int objectFlag, String name,
510 List<Row> rows, int objectId)
511 {
512
513 int objTypeFlag = objectFlag & OBJECT_FLAG_MASK;
514
515 if(objTypeFlag == 0) {
516
517 short rowTypeFlag = getShortValue(getQueryType(rows), objTypeFlag);
518 Type rowType = TYPE_MAP.get(rowTypeFlag);
519 if((rowType != null) && (rowType.getObjectFlag() != objTypeFlag)) {
520
521 objTypeFlag = rowType.getObjectFlag();
522 }
523 }
524
525 try {
526 switch(objTypeFlag) {
527 case SELECT_QUERY_OBJECT_FLAG:
528 return new SelectQueryImpl(name, rows, objectId, objectFlag);
529 case MAKE_TABLE_QUERY_OBJECT_FLAG:
530 return new MakeTableQueryImpl(name, rows, objectId, objectFlag);
531 case APPEND_QUERY_OBJECT_FLAG:
532 return new AppendQueryImpl(name, rows, objectId, objectFlag);
533 case UPDATE_QUERY_OBJECT_FLAG:
534 return new UpdateQueryImpl(name, rows, objectId, objectFlag);
535 case DELETE_QUERY_OBJECT_FLAG:
536 return new DeleteQueryImpl(name, rows, objectId, objectFlag);
537 case CROSS_TAB_QUERY_OBJECT_FLAG:
538 return new CrossTabQueryImpl(name, rows, objectId, objectFlag);
539 case DATA_DEF_QUERY_OBJECT_FLAG:
540 return new DataDefinitionQueryImpl(name, rows, objectId, objectFlag);
541 case PASSTHROUGH_QUERY_OBJECT_FLAG:
542 return new PassthroughQueryImpl(name, rows, objectId, objectFlag);
543 case UNION_QUERY_OBJECT_FLAG:
544 return new UnionQueryImpl(name, rows, objectId, objectFlag);
545 default:
546
547 throw new IllegalStateException(withErrorContext(
548 "unknown query object flag " + objTypeFlag, name));
549 }
550 } catch(IllegalStateException e) {
551 LOG.log(Logger.Level.WARNING, withErrorContext("Failed parsing query", name), e);
552 }
553
554
555 return new UnknownQueryImpl(name, rows, objectId, objectFlag);
556 }
557
558
559
560
561
562
563
564
565
566
567 private static List<Row> sortByOrder(List<Row> rows)
568 {
569 for(Row row : rows) {
570 if(row.order == null) {
571 return rows;
572 }
573 }
574 List<Row> sorted = new ArrayList<>(rows);
575
576 sorted.sort(QueryImpl::compareOrder);
577 return sorted;
578 }
579
580
581
582
583
584
585 private static int compareOrder(Roww" href="../../../../../com/healthmarketscience/jackcess/Row.html#Row">Row r1, Row r2)
586 {
587 int cmp = Integer.compare(toAttribute(r1), toAttribute(r2));
588 return ((cmp != 0) ? cmp : Arrays.compareUnsigned(r1.order, r2.order));
589 }
590
591 private static int toAttribute(Row row)
592 {
593 return ((row.attribute != null) ? row.attribute : -1);
594 }
595
596 private static Short getQueryType(List<Row> rows)
597 {
598 return getFirstRowByAttribute(rows, TYPE_ATTRIBUTE).flag;
599 }
600
601 private static List<Row> getRowsByAttribute(List<Row> rows, Byte attribute) {
602 List<Row> result = new ArrayList<>();
603 for(Row row : rows) {
604 if(attribute.equals(row.attribute)) {
605 result.add(row);
606 }
607 }
608 return result;
609 }
610
611 private static Row getFirstRowByAttribute(List<Row> rows, Byte attribute) {
612 for(Row row : rows) {
613 if(attribute.equals(row.attribute)) {
614 return row;
615 }
616 }
617 return EMPTY_ROW;
618 }
619
620 protected Row getUniqueRow(List<Row> rows) {
621 if(rows.size() == 1) {
622 return rows.get(0);
623 }
624 if(rows.isEmpty()) {
625 return EMPTY_ROW;
626 }
627 throw new IllegalStateException(withErrorContext(
628 "Unexpected number of rows for" + rows));
629 }
630
631 protected static List<Row> filterRowsByFlag(
632 List<Row> rows, final short flag)
633 {
634 return new RowFilter() {
635 @Override protected boolean keep(Row row) {
636 return hasFlag(row, flag);
637 }
638 }.filter(rows);
639 }
640
641 protected static List<Row> filterRowsByNotFlag(
642 List<Row> rows, final short flag)
643 {
644 return new RowFilter() {
645 @Override protected boolean keep(Row row) {
646 return !hasFlag(row, flag);
647 }
648 }.filter(rows);
649 }
650
651 protected static boolean hasFlag(Row row, int flagMask)
652 {
653 return((getShortValue(row.flag, 0) & flagMask) != 0);
654 }
655
656 protected static short getShortValue(Short s, int def) {
657 return ((s != null) ? (short)s : (short)def);
658 }
659
660 protected static int getIntValue(Integer i, int def) {
661 return ((i != null) ? (int)i : def);
662 }
663
664 protected static StringBuilder toOptionalQuotedExpr(StringBuilder builder,
665 String fullExpr,
666 boolean isIdentifier)
667 {
668 String[] exprs = (isIdentifier ?
669 IDENTIFIER_SEP_PAT.split(fullExpr) :
670 new String[]{fullExpr});
671 for(int i = 0; i < exprs.length; ++i) {
672 String expr = exprs[i];
673 if(QUOTABLE_CHAR_PAT.matcher(expr).find()) {
674 toQuotedExpr(builder, expr);
675 } else {
676 builder.append(expr);
677 }
678 if(i < (exprs.length - 1)) {
679 builder.append(IDENTIFIER_SEP_CHAR);
680 }
681 }
682 return builder;
683 }
684
685 protected static StringBuilder toQuotedExpr(StringBuilder builder,
686 String expr)
687 {
688 return (!isQuoted(expr) ?
689 builder.append('[').append(expr).append(']') :
690 builder.append(expr));
691 }
692
693 protected static boolean isQuoted(String expr) {
694 return ((expr.length() >= 2) &&
695 (expr.charAt(0) == '[') && (expr.charAt(expr.length() - 1) == ']'));
696 }
697
698 protected static StringBuilder toRemoteDb(StringBuilder builder,
699 String remoteDbPath,
700 String remoteDbType) {
701 if((remoteDbPath != null) || (remoteDbType != null)) {
702
703 builder.append(" IN '");
704 if(remoteDbPath != null) {
705 builder.append(remoteDbPath);
706 }
707 builder.append('\'');
708 if(remoteDbType != null) {
709 builder.append(" [").append(remoteDbType).append(']');
710 }
711 }
712 return builder;
713 }
714
715
716
717
718
719
720
721
722
723
724 protected static StringBuilder toParameterName(StringBuilder builder,
725 String name)
726 {
727 if(name == null) {
728 return builder.append(name);
729 }
730
731 List<String> parts = new ArrayList<>();
732 List<Character> seps = new ArrayList<>();
733 boolean hasBang = false;
734 int start = 0;
735 boolean inBrackets = false;
736 for(int i = 0; i < name.length(); ++i) {
737 char c = name.charAt(i);
738 if(c == '[') {
739 inBrackets = true;
740 } else if(c == ']') {
741 inBrackets = false;
742 } else if(!inBrackets && ((c == '!') || (c == IDENTIFIER_SEP_CHAR))) {
743 hasBang |= (c == '!');
744 parts.add(name.substring(start, i));
745 seps.add(c);
746 start = i + 1;
747 }
748 }
749 parts.add(name.substring(start));
750
751
752
753 boolean isReference = hasBang;
754 for(String part : parts) {
755 if(part.isEmpty() || part.startsWith(" ") || part.endsWith(" ")) {
756 isReference = false;
757 }
758 }
759 if(!isReference) {
760 return toOptionalQuotedExpr(builder, name, false);
761 }
762
763 for(int i = 0; i < parts.size(); ++i) {
764 if(i > 0) {
765 builder.append(seps.get(i - 1));
766 }
767 toOptionalQuotedExpr(builder, parts.get(i), false);
768 }
769 return builder;
770 }
771
772 protected static StringBuilder toAlias(StringBuilder builder,
773 String alias) {
774 if(alias != null) {
775 toOptionalQuotedExpr(builder.append(" AS "), alias, false);
776 }
777 return builder;
778 }
779
780 private String withErrorContext(String msg) {
781 return withErrorContext(msg, getName());
782 }
783
784 private static String withErrorContext(String msg, String queryName) {
785 return msg + " (Query: " + queryName + ")";
786 }
787
788
789 private static final class UnknownQueryImpl extends QueryImpl
790 {
791 private UnknownQueryImpl(String name, List<Row> rows, int objectId,
792 int objectFlag)
793 {
794 super(name, rows, objectId, objectFlag, Type.UNKNOWN);
795 }
796
797 @Override
798 protected void toSQLString(StringBuilder builder) {
799 throw new UnsupportedOperationException();
800 }
801 }
802
803
804
805
806
807 public static final class Row
808 {
809 private final RowId _id;
810 public final Byte attribute;
811 public final String expression;
812 public final Short flag;
813 public final Integer extra;
814 public final String name1;
815 public final String name2;
816 public final Integer objectId;
817 public final byte[] order;
818
819 private Row() {
820 this._id = null;
821 this.attribute = null;
822 this.expression = null;
823 this.flag = null;
824 this.extra = null;
825 this.name1 = null;
826 this.name2= null;
827 this.objectId = null;
828 this.order = null;
829 }
830
831 public Row(com.healthmarketscience.jackcess.Row tableRow) {
832 this(tableRow.getId(),
833 tableRow.getByte(COL_ATTRIBUTE),
834 tableRow.getString(COL_EXPRESSION),
835 tableRow.getShort(COL_FLAG),
836 tableRow.getInt(COL_EXTRA),
837 tableRow.getString(COL_NAME1),
838 tableRow.getString(COL_NAME2),
839 tableRow.getInt(COL_OBJECTID),
840 tableRow.getBytes(COL_ORDER));
841 }
842
843 public Row(RowId id, Byte newAttribute, String newExpression,
844 Short newFlag, Integer newExtra, String newName1,
845 String newName2, Integer newObjectId, byte[] newOrder)
846 {
847 _id = id;
848 attribute = newAttribute;
849 expression = newExpression;
850 flag = newFlag;
851 extra = newExtra;
852 name1 = newName1;
853 name2 = newName2;
854 objectId = newObjectId;
855 order = newOrder;
856 }
857
858 public com.healthmarketscience.jackcess.Row toTableRow()
859 {
860 com.healthmarketscience.jackcess.Row tableRow = new RowImpl((RowIdImpl)_id);
861
862 tableRow.put(COL_ATTRIBUTE, attribute);
863 tableRow.put(COL_EXPRESSION, expression);
864 tableRow.put(COL_FLAG, flag);
865 tableRow.put(COL_EXTRA, extra);
866 tableRow.put(COL_NAME1, name1);
867 tableRow.put(COL_NAME2, name2);
868 tableRow.put(COL_OBJECTID, objectId);
869 tableRow.put(COL_ORDER, order);
870
871 return tableRow;
872 }
873
874 @Override
875 public String toString() {
876 return ToStringBuilder.builder(this).reflectionToString();
877 }
878 }
879
880 protected static abstract class RowFormatter
881 {
882 private final List<Row> _list;
883
884 protected RowFormatter(List<Row> list) {
885 _list = list;
886 }
887
888 public List<String> format() {
889 return format(new AppendableList<String>());
890 }
891
892 public List<String> format(List<String> strs) {
893 for(Row row : _list) {
894 StringBuilder builder = new StringBuilder();
895 format(builder, row);
896 strs.add(builder.toString());
897 }
898 return strs;
899 }
900
901 protected abstract void format(StringBuilder builder, Row row);
902 }
903
904 protected static abstract class RowFilter
905 {
906 protected RowFilter() {
907 }
908
909 public List<Row> filter(List<Row> list) {
910 for(Iterator<Row> iter = list.iterator(); iter.hasNext(); ) {
911 if(!keep(iter.next())) {
912 iter.remove();
913 }
914 }
915 return list;
916 }
917
918 protected abstract boolean keep(Row row);
919 }
920
921 protected static class AppendableList<E> extends ArrayList<E>
922 {
923 private static final long serialVersionUID = 0L;
924 private static final String DEF_SEP = ", ";
925
926 private final String _sep;
927
928 protected AppendableList() {
929 this(DEF_SEP);
930 }
931
932 protected AppendableList(String sep) {
933 _sep = sep;
934 }
935
936 protected AppendableList(Collection<? extends E> c) {
937 super(c);
938 _sep = DEF_SEP;
939 }
940
941 @Override
942 public String toString() {
943 StringBuilder builder = new StringBuilder();
944 for(Iterator<E> iter = iterator(); iter.hasNext(); ) {
945 builder.append(iter.next().toString());
946 if(iter.hasNext()) {
947 builder.append(_sep);
948 }
949 }
950 return builder.toString();
951 }
952 }
953
954
955
956
957 private static abstract class TableSource
958 {
959 @Override
960 public String toString() {
961 StringBuilder sb = new StringBuilder();
962 toString(sb, true);
963 return sb.toString();
964 }
965
966 protected abstract void toString(StringBuilder sb, boolean isTopLevel);
967
968 public abstract boolean containsTable(String table);
969
970
971
972
973
974
975
976
977
978
979
980 public abstract boolean addCondition(String table1, String table2,
981 short type, String on);
982 }
983
984
985
986
987 private static final class SimpleTable extends TableSource
988 {
989 private final String _tableName;
990 private final String _tableExpr;
991
992 private SimpleTable(String tableName) {
993 this(tableName, toOptionalQuotedExpr(
994 new StringBuilder(), tableName, true).toString());
995 }
996
997 private SimpleTable(String tableName, String tableExpr) {
998 _tableName = tableName;
999 _tableExpr = tableExpr;
1000 }
1001
1002 @Override
1003 protected void toString(StringBuilder sb, boolean isTopLevel) {
1004 sb.append(_tableExpr);
1005 }
1006
1007 @Override
1008 public boolean containsTable(String table) {
1009 return _tableName.equalsIgnoreCase(table);
1010 }
1011
1012 @Override
1013 public boolean addCondition(String table1, String table2, short type,
1014 String on) {
1015 return false;
1016 }
1017 }
1018
1019
1020
1021
1022 private final class Join extends TableSource
1023 {
1024 private final TableSource _from;
1025 private final TableSource _to;
1026 private final short _jType;
1027
1028 private final List<String> _on = new AppendableList<>(") AND (");
1029
1030 private Join(TableSource from, TableSource to, short type, String on) {
1031 _from = from;
1032 _to = to;
1033 _jType = type;
1034 _on.add(on);
1035 }
1036
1037 @Override
1038 protected void toString(StringBuilder sb, boolean isTopLevel) {
1039 String joinType = JOIN_TYPE_MAP.get(_jType);
1040 if(joinType == null) {
1041 throw new IllegalStateException(withErrorContext(
1042 "Unknown join type " + _jType));
1043 }
1044
1045 if(!isTopLevel) {
1046 sb.append("(");
1047 }
1048
1049 _from.toString(sb, false);
1050 sb.append(joinType);
1051 _to.toString(sb, false);
1052 sb.append(" ON ");
1053
1054 boolean multiOnExpr = (_on.size() > 1);
1055 if(multiOnExpr) {
1056 sb.append("(");
1057 }
1058 sb.append(_on);
1059 if(multiOnExpr) {
1060 sb.append(")");
1061 }
1062
1063 if(!isTopLevel) {
1064 sb.append(")");
1065 }
1066 }
1067
1068 @Override
1069 public boolean containsTable(String table) {
1070 return _from.containsTable(table) || _to.containsTable(table);
1071 }
1072
1073 @Override
1074 public boolean addCondition(String table1, String table2, short type,
1075 String on) {
1076 if(_from.containsTable(table1) && _from.containsTable(table2) &&
1077 _from.addCondition(table1, table2, type, on)) {
1078 return true;
1079 }
1080 if(_to.containsTable(table1) && _to.containsTable(table2) &&
1081 _to.addCondition(table1, table2, type, on)) {
1082 return true;
1083 }
1084 if(_jType == type) {
1085
1086 _on.add(0, on);
1087 return true;
1088 }
1089 return false;
1090 }
1091 }
1092 }