You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

QueryImpl.java 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832
  1. /*
  2. Copyright (c) 2008 Health Market Science, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl.query;
  14. import java.util.ArrayList;
  15. import java.util.Collection;
  16. import java.util.Iterator;
  17. import java.util.List;
  18. import com.healthmarketscience.jackcess.RowId;
  19. import com.healthmarketscience.jackcess.impl.DatabaseImpl;
  20. import com.healthmarketscience.jackcess.impl.RowIdImpl;
  21. import com.healthmarketscience.jackcess.impl.RowImpl;
  22. import static com.healthmarketscience.jackcess.impl.query.QueryFormat.*;
  23. import com.healthmarketscience.jackcess.query.Query;
  24. import org.apache.commons.lang3.builder.ToStringBuilder;
  25. import org.apache.commons.logging.Log;
  26. import org.apache.commons.logging.LogFactory;
  27. /**
  28. * Base class for classes which encapsulate information about an Access query.
  29. * The {@link #toSQLString()} method can be used to convert this object into
  30. * the actual SQL string which this query data represents.
  31. *
  32. * @author James Ahlborn
  33. */
  34. public abstract class QueryImpl implements Query
  35. {
  36. protected static final Log LOG = LogFactory.getLog(QueryImpl.class);
  37. private static final Row EMPTY_ROW = new Row();
  38. private final String _name;
  39. private final List<Row> _rows;
  40. private final int _objectId;
  41. private final Type _type;
  42. private final int _objectFlag;
  43. protected QueryImpl(String name, List<Row> rows, int objectId, int objectFlag,
  44. Type type)
  45. {
  46. _name = name;
  47. _rows = rows;
  48. _objectId = objectId;
  49. _type = type;
  50. _objectFlag = objectFlag;
  51. if(type != Type.UNKNOWN) {
  52. short foundType = getShortValue(getQueryType(rows),
  53. _type.getValue());
  54. if(foundType != _type.getValue()) {
  55. throw new IllegalStateException(withErrorContext(
  56. "Unexpected query type " + foundType));
  57. }
  58. }
  59. }
  60. /**
  61. * Returns the name of the query.
  62. */
  63. @Override
  64. public String getName() {
  65. return _name;
  66. }
  67. /**
  68. * Returns the type of the query.
  69. */
  70. @Override
  71. public Type getType() {
  72. return _type;
  73. }
  74. @Override
  75. public boolean isHidden() {
  76. return((_objectFlag & DatabaseImpl.HIDDEN_OBJECT_FLAG) != 0);
  77. }
  78. /**
  79. * Returns the unique object id of the query.
  80. */
  81. @Override
  82. public int getObjectId() {
  83. return _objectId;
  84. }
  85. @Override
  86. public int getObjectFlag() {
  87. return _objectFlag;
  88. }
  89. /**
  90. * Returns the rows from the system query table from which the query
  91. * information was derived.
  92. */
  93. public List<Row> getRows() {
  94. return _rows;
  95. }
  96. protected List<Row> getRowsByAttribute(Byte attribute) {
  97. return getRowsByAttribute(getRows(), attribute);
  98. }
  99. protected Row getRowByAttribute(Byte attribute) {
  100. return getUniqueRow(getRowsByAttribute(getRows(), attribute));
  101. }
  102. public Row getTypeRow() {
  103. return getRowByAttribute(TYPE_ATTRIBUTE);
  104. }
  105. protected List<Row> getParameterRows() {
  106. return getRowsByAttribute(PARAMETER_ATTRIBUTE);
  107. }
  108. protected Row getFlagRow() {
  109. return getRowByAttribute(FLAG_ATTRIBUTE);
  110. }
  111. protected Row getRemoteDatabaseRow() {
  112. return getRowByAttribute(REMOTEDB_ATTRIBUTE);
  113. }
  114. protected List<Row> getTableRows() {
  115. return getRowsByAttribute(TABLE_ATTRIBUTE);
  116. }
  117. protected List<Row> getColumnRows() {
  118. return getRowsByAttribute(COLUMN_ATTRIBUTE);
  119. }
  120. protected List<Row> getJoinRows() {
  121. return getRowsByAttribute(JOIN_ATTRIBUTE);
  122. }
  123. protected Row getWhereRow() {
  124. return getRowByAttribute(WHERE_ATTRIBUTE);
  125. }
  126. protected List<Row> getGroupByRows() {
  127. return getRowsByAttribute(GROUPBY_ATTRIBUTE);
  128. }
  129. protected Row getHavingRow() {
  130. return getRowByAttribute(HAVING_ATTRIBUTE);
  131. }
  132. protected List<Row> getOrderByRows() {
  133. return getRowsByAttribute(ORDERBY_ATTRIBUTE);
  134. }
  135. protected abstract void toSQLString(StringBuilder builder);
  136. protected void toSQLParameterString(StringBuilder builder) {
  137. // handle any parameters
  138. List<String> params = getParameters();
  139. if(!params.isEmpty()) {
  140. builder.append("PARAMETERS ").append(params)
  141. .append(';').append(NEWLINE);
  142. }
  143. }
  144. @Override
  145. public List<String> getParameters()
  146. {
  147. return (new RowFormatter(getParameterRows()) {
  148. @Override protected void format(StringBuilder builder, Row row) {
  149. String typeName = PARAM_TYPE_MAP.get(row.flag);
  150. if(typeName == null) {
  151. throw new IllegalStateException(withErrorContext(
  152. "Unknown param type " + row.flag));
  153. }
  154. builder.append(row.name1).append(' ').append(typeName);
  155. if((TEXT_FLAG.equals(row.flag)) && (getIntValue(row.extra, 0) > 0)) {
  156. builder.append('(').append(row.extra).append(')');
  157. }
  158. }
  159. }).format();
  160. }
  161. protected List<String> getFromTables()
  162. {
  163. // grab the list of query tables
  164. List<TableSource> tableExprs = new ArrayList<TableSource>();
  165. for(Row table : getTableRows()) {
  166. StringBuilder builder = new StringBuilder();
  167. if(table.expression != null) {
  168. toQuotedExpr(builder, table.expression).append(IDENTIFIER_SEP_CHAR);
  169. }
  170. if(table.name1 != null) {
  171. toOptionalQuotedExpr(builder, table.name1, true);
  172. }
  173. toAlias(builder, table.name2);
  174. String key = ((table.name2 != null) ? table.name2 : table.name1);
  175. tableExprs.add(new SimpleTable(key, builder.toString()));
  176. }
  177. // combine the tables with any query joins
  178. List<Row> joins = getJoinRows();
  179. for(Row joinRow : joins) {
  180. String fromTable = joinRow.name1;
  181. String toTable = joinRow.name2;
  182. TableSource fromTs = null;
  183. TableSource toTs = null;
  184. // combine existing join expressions containing the target tables
  185. for(Iterator<TableSource> joinIter = tableExprs.iterator();
  186. (joinIter.hasNext() && ((fromTs == null) || (toTs == null))); ) {
  187. TableSource ts = joinIter.next();
  188. if((fromTs == null) && ts.containsTable(fromTable)) {
  189. fromTs = ts;
  190. // special case adding expr to existing join
  191. if((toTs == null) && ts.containsTable(toTable)) {
  192. toTs = ts;
  193. break;
  194. }
  195. joinIter.remove();
  196. } else if((toTs == null) && ts.containsTable(toTable)) {
  197. toTs = ts;
  198. joinIter.remove();
  199. }
  200. }
  201. if(fromTs == null) {
  202. fromTs = new SimpleTable(fromTable);
  203. }
  204. if(toTs == null) {
  205. toTs = new SimpleTable(toTable);
  206. }
  207. if(fromTs == toTs) {
  208. if(fromTs.sameJoin(joinRow.flag, joinRow.expression)) {
  209. // easy-peasy, we just added the join expression to existing join,
  210. // nothing more to do
  211. continue;
  212. }
  213. throw new IllegalStateException(withErrorContext(
  214. "Inconsistent join types for " + fromTable + " and " + toTable));
  215. }
  216. // new join expression
  217. tableExprs.add(new Join(fromTs, toTs, joinRow.flag, joinRow.expression));
  218. }
  219. // convert join objects to SQL strings
  220. List<String> result = new AppendableList<String>();
  221. for(TableSource ts : tableExprs) {
  222. result.add(ts.toString());
  223. }
  224. return result;
  225. }
  226. protected String getFromRemoteDbPath()
  227. {
  228. return getRemoteDatabaseRow().name1;
  229. }
  230. protected String getFromRemoteDbType()
  231. {
  232. return getRemoteDatabaseRow().expression;
  233. }
  234. protected String getWhereExpression()
  235. {
  236. return getWhereRow().expression;
  237. }
  238. protected List<String> getOrderings()
  239. {
  240. return (new RowFormatter(getOrderByRows()) {
  241. @Override protected void format(StringBuilder builder, Row row) {
  242. builder.append(row.expression);
  243. if(DESCENDING_FLAG.equalsIgnoreCase(row.name1)) {
  244. builder.append(" DESC");
  245. }
  246. }
  247. }).format();
  248. }
  249. @Override
  250. public String getOwnerAccessType() {
  251. return(hasFlag(OWNER_ACCESS_SELECT_TYPE) ?
  252. "WITH OWNERACCESS OPTION" : DEFAULT_TYPE);
  253. }
  254. protected boolean hasFlag(int flagMask)
  255. {
  256. return hasFlag(getFlagRow(), flagMask);
  257. }
  258. protected boolean supportsStandardClauses() {
  259. return true;
  260. }
  261. /**
  262. * Returns the actual SQL string which this query data represents.
  263. */
  264. @Override
  265. public String toSQLString()
  266. {
  267. StringBuilder builder = new StringBuilder();
  268. if(supportsStandardClauses()) {
  269. toSQLParameterString(builder);
  270. }
  271. toSQLString(builder);
  272. if(supportsStandardClauses()) {
  273. String accessType = getOwnerAccessType();
  274. if(!DEFAULT_TYPE.equals(accessType)) {
  275. builder.append(NEWLINE).append(accessType);
  276. }
  277. builder.append(';');
  278. }
  279. return builder.toString();
  280. }
  281. @Override
  282. public String toString() {
  283. return ToStringBuilder.reflectionToString(this);
  284. }
  285. /**
  286. * Creates a concrete Query instance from the given query data.
  287. *
  288. * @param objectFlag the flag indicating the type of the query
  289. * @param name the name of the query
  290. * @param rows the rows from the system query table containing the data
  291. * describing this query
  292. * @param objectId the unique object id of this query
  293. *
  294. * @return a Query instance for the given query data
  295. */
  296. public static QueryImpl create(int objectFlag, String name, List<Row> rows,
  297. int objectId)
  298. {
  299. // remove other object flags before testing for query type
  300. int objTypeFlag = objectFlag & OBJECT_FLAG_MASK;
  301. if(objTypeFlag == 0) {
  302. // sometimes the query rows tell a different story
  303. short rowTypeFlag = getShortValue(getQueryType(rows), objTypeFlag);
  304. Type rowType = TYPE_MAP.get(rowTypeFlag);
  305. if((rowType != null) && (rowType.getObjectFlag() != objTypeFlag)) {
  306. // use row type instead of object flag type
  307. objTypeFlag = rowType.getObjectFlag();
  308. }
  309. }
  310. try {
  311. switch(objTypeFlag) {
  312. case SELECT_QUERY_OBJECT_FLAG:
  313. return new SelectQueryImpl(name, rows, objectId, objectFlag);
  314. case MAKE_TABLE_QUERY_OBJECT_FLAG:
  315. return new MakeTableQueryImpl(name, rows, objectId, objectFlag);
  316. case APPEND_QUERY_OBJECT_FLAG:
  317. return new AppendQueryImpl(name, rows, objectId, objectFlag);
  318. case UPDATE_QUERY_OBJECT_FLAG:
  319. return new UpdateQueryImpl(name, rows, objectId, objectFlag);
  320. case DELETE_QUERY_OBJECT_FLAG:
  321. return new DeleteQueryImpl(name, rows, objectId, objectFlag);
  322. case CROSS_TAB_QUERY_OBJECT_FLAG:
  323. return new CrossTabQueryImpl(name, rows, objectId, objectFlag);
  324. case DATA_DEF_QUERY_OBJECT_FLAG:
  325. return new DataDefinitionQueryImpl(name, rows, objectId, objectFlag);
  326. case PASSTHROUGH_QUERY_OBJECT_FLAG:
  327. return new PassthroughQueryImpl(name, rows, objectId, objectFlag);
  328. case UNION_QUERY_OBJECT_FLAG:
  329. return new UnionQueryImpl(name, rows, objectId, objectFlag);
  330. default:
  331. // unknown querytype
  332. throw new IllegalStateException(withErrorContext(
  333. "unknown query object flag " + objTypeFlag, name));
  334. }
  335. } catch(IllegalStateException e) {
  336. LOG.warn(withErrorContext("Failed parsing query", name), e);
  337. }
  338. // return unknown query
  339. return new UnknownQueryImpl(name, rows, objectId, objectFlag);
  340. }
  341. private static Short getQueryType(List<Row> rows)
  342. {
  343. return getFirstRowByAttribute(rows, TYPE_ATTRIBUTE).flag;
  344. }
  345. private static List<Row> getRowsByAttribute(List<Row> rows, Byte attribute) {
  346. List<Row> result = new ArrayList<Row>();
  347. for(Row row : rows) {
  348. if(attribute.equals(row.attribute)) {
  349. result.add(row);
  350. }
  351. }
  352. return result;
  353. }
  354. private static Row getFirstRowByAttribute(List<Row> rows, Byte attribute) {
  355. for(Row row : rows) {
  356. if(attribute.equals(row.attribute)) {
  357. return row;
  358. }
  359. }
  360. return EMPTY_ROW;
  361. }
  362. protected Row getUniqueRow(List<Row> rows) {
  363. if(rows.size() == 1) {
  364. return rows.get(0);
  365. }
  366. if(rows.isEmpty()) {
  367. return EMPTY_ROW;
  368. }
  369. throw new IllegalStateException(withErrorContext(
  370. "Unexpected number of rows for" + rows));
  371. }
  372. protected static List<Row> filterRowsByFlag(
  373. List<Row> rows, final short flag)
  374. {
  375. return new RowFilter() {
  376. @Override protected boolean keep(Row row) {
  377. return hasFlag(row, flag);
  378. }
  379. }.filter(rows);
  380. }
  381. protected static List<Row> filterRowsByNotFlag(
  382. List<Row> rows, final short flag)
  383. {
  384. return new RowFilter() {
  385. @Override protected boolean keep(Row row) {
  386. return !hasFlag(row, flag);
  387. }
  388. }.filter(rows);
  389. }
  390. protected static boolean hasFlag(Row row, int flagMask)
  391. {
  392. return((getShortValue(row.flag, 0) & flagMask) != 0);
  393. }
  394. protected static short getShortValue(Short s, int def) {
  395. return ((s != null) ? (short)s : (short)def);
  396. }
  397. protected static int getIntValue(Integer i, int def) {
  398. return ((i != null) ? (int)i : def);
  399. }
  400. protected static StringBuilder toOptionalQuotedExpr(StringBuilder builder,
  401. String fullExpr,
  402. boolean isIdentifier)
  403. {
  404. String[] exprs = (isIdentifier ?
  405. IDENTIFIER_SEP_PAT.split(fullExpr) :
  406. new String[]{fullExpr});
  407. for(int i = 0; i < exprs.length; ++i) {
  408. String expr = exprs[i];
  409. if(QUOTABLE_CHAR_PAT.matcher(expr).find()) {
  410. toQuotedExpr(builder, expr);
  411. } else {
  412. builder.append(expr);
  413. }
  414. if(i < (exprs.length - 1)) {
  415. builder.append(IDENTIFIER_SEP_CHAR);
  416. }
  417. }
  418. return builder;
  419. }
  420. protected static StringBuilder toQuotedExpr(StringBuilder builder,
  421. String expr)
  422. {
  423. return (!isQuoted(expr) ?
  424. builder.append('[').append(expr).append(']') :
  425. builder.append(expr));
  426. }
  427. protected static boolean isQuoted(String expr) {
  428. return ((expr.length() >= 2) &&
  429. (expr.charAt(0) == '[') && (expr.charAt(expr.length() - 1) == ']'));
  430. }
  431. protected static StringBuilder toRemoteDb(StringBuilder builder,
  432. String remoteDbPath,
  433. String remoteDbType) {
  434. if((remoteDbPath != null) || (remoteDbType != null)) {
  435. // note, always include path string, even if empty
  436. builder.append(" IN '");
  437. if(remoteDbPath != null) {
  438. builder.append(remoteDbPath);
  439. }
  440. builder.append('\'');
  441. if(remoteDbType != null) {
  442. builder.append(" [").append(remoteDbType).append(']');
  443. }
  444. }
  445. return builder;
  446. }
  447. protected static StringBuilder toAlias(StringBuilder builder,
  448. String alias) {
  449. if(alias != null) {
  450. toOptionalQuotedExpr(builder.append(" AS "), alias, false);
  451. }
  452. return builder;
  453. }
  454. private String withErrorContext(String msg) {
  455. return withErrorContext(msg, getName());
  456. }
  457. private static String withErrorContext(String msg, String queryName) {
  458. return msg + " (Query: " + queryName + ")";
  459. }
  460. private static final class UnknownQueryImpl extends QueryImpl
  461. {
  462. private UnknownQueryImpl(String name, List<Row> rows, int objectId,
  463. int objectFlag)
  464. {
  465. super(name, rows, objectId, objectFlag, Type.UNKNOWN);
  466. }
  467. @Override
  468. protected void toSQLString(StringBuilder builder) {
  469. throw new UnsupportedOperationException();
  470. }
  471. }
  472. /**
  473. * Struct containing the information from a single row of the system query
  474. * table.
  475. */
  476. public static final class Row
  477. {
  478. private final RowId _id;
  479. public final Byte attribute;
  480. public final String expression;
  481. public final Short flag;
  482. public final Integer extra;
  483. public final String name1;
  484. public final String name2;
  485. public final Integer objectId;
  486. public final byte[] order;
  487. private Row() {
  488. this._id = null;
  489. this.attribute = null;
  490. this.expression = null;
  491. this.flag = null;
  492. this.extra = null;
  493. this.name1 = null;
  494. this.name2= null;
  495. this.objectId = null;
  496. this.order = null;
  497. }
  498. public Row(com.healthmarketscience.jackcess.Row tableRow) {
  499. this(tableRow.getId(),
  500. tableRow.getByte(COL_ATTRIBUTE),
  501. tableRow.getString(COL_EXPRESSION),
  502. tableRow.getShort(COL_FLAG),
  503. tableRow.getInt(COL_EXTRA),
  504. tableRow.getString(COL_NAME1),
  505. tableRow.getString(COL_NAME2),
  506. tableRow.getInt(COL_OBJECTID),
  507. tableRow.getBytes(COL_ORDER));
  508. }
  509. public Row(RowId id, Byte attribute, String expression, Short flag,
  510. Integer extra, String name1, String name2,
  511. Integer objectId, byte[] order)
  512. {
  513. this._id = id;
  514. this.attribute = attribute;
  515. this.expression = expression;
  516. this.flag = flag;
  517. this.extra = extra;
  518. this.name1 = name1;
  519. this.name2= name2;
  520. this.objectId = objectId;
  521. this.order = order;
  522. }
  523. public com.healthmarketscience.jackcess.Row toTableRow()
  524. {
  525. com.healthmarketscience.jackcess.Row tableRow = new RowImpl((RowIdImpl)_id);
  526. tableRow.put(COL_ATTRIBUTE, attribute);
  527. tableRow.put(COL_EXPRESSION, expression);
  528. tableRow.put(COL_FLAG, flag);
  529. tableRow.put(COL_EXTRA, extra);
  530. tableRow.put(COL_NAME1, name1);
  531. tableRow.put(COL_NAME2, name2);
  532. tableRow.put(COL_OBJECTID, objectId);
  533. tableRow.put(COL_ORDER, order);
  534. return tableRow;
  535. }
  536. @Override
  537. public String toString() {
  538. return ToStringBuilder.reflectionToString(this);
  539. }
  540. }
  541. protected static abstract class RowFormatter
  542. {
  543. private final List<Row> _list;
  544. protected RowFormatter(List<Row> list) {
  545. _list = list;
  546. }
  547. public List<String> format() {
  548. return format(new AppendableList<String>());
  549. }
  550. public List<String> format(List<String> strs) {
  551. for(Row row : _list) {
  552. StringBuilder builder = new StringBuilder();
  553. format(builder, row);
  554. strs.add(builder.toString());
  555. }
  556. return strs;
  557. }
  558. protected abstract void format(StringBuilder builder, Row row);
  559. }
  560. protected static abstract class RowFilter
  561. {
  562. protected RowFilter() {
  563. }
  564. public List<Row> filter(List<Row> list) {
  565. for(Iterator<Row> iter = list.iterator(); iter.hasNext(); ) {
  566. if(!keep(iter.next())) {
  567. iter.remove();
  568. }
  569. }
  570. return list;
  571. }
  572. protected abstract boolean keep(Row row);
  573. }
  574. protected static class AppendableList<E> extends ArrayList<E>
  575. {
  576. private static final long serialVersionUID = 0L;
  577. protected AppendableList() {
  578. }
  579. protected AppendableList(Collection<? extends E> c) {
  580. super(c);
  581. }
  582. protected String getSeparator() {
  583. return ", ";
  584. }
  585. @Override
  586. public String toString() {
  587. StringBuilder builder = new StringBuilder();
  588. for(Iterator<E> iter = iterator(); iter.hasNext(); ) {
  589. builder.append(iter.next().toString());
  590. if(iter.hasNext()) {
  591. builder.append(getSeparator());
  592. }
  593. }
  594. return builder.toString();
  595. }
  596. }
  597. /**
  598. * Base type of something which provides table data in a query
  599. */
  600. private static abstract class TableSource
  601. {
  602. @Override
  603. public String toString() {
  604. StringBuilder sb = new StringBuilder();
  605. toString(sb, true);
  606. return sb.toString();
  607. }
  608. protected abstract void toString(StringBuilder sb, boolean isTopLevel);
  609. public abstract boolean containsTable(String table);
  610. public abstract boolean sameJoin(short type, String on);
  611. }
  612. /**
  613. * Table data provided by a single table expression.
  614. */
  615. private static final class SimpleTable extends TableSource
  616. {
  617. private final String _tableName;
  618. private final String _tableExpr;
  619. private SimpleTable(String tableName) {
  620. this(tableName, toOptionalQuotedExpr(
  621. new StringBuilder(), tableName, true).toString());
  622. }
  623. private SimpleTable(String tableName, String tableExpr) {
  624. _tableName = tableName;
  625. _tableExpr = tableExpr;
  626. }
  627. @Override
  628. protected void toString(StringBuilder sb, boolean isTopLevel) {
  629. sb.append(_tableExpr);
  630. }
  631. @Override
  632. public boolean containsTable(String table) {
  633. return _tableName.equalsIgnoreCase(table);
  634. }
  635. @Override
  636. public boolean sameJoin(short type, String on) {
  637. return false;
  638. }
  639. }
  640. /**
  641. * Table data provided by a join expression.
  642. */
  643. private final class Join extends TableSource
  644. {
  645. private final TableSource _from;
  646. private final TableSource _to;
  647. private final short _jType;
  648. // combine all the join expressions with "AND"
  649. private final List<String> _on = new AppendableList<String>() {
  650. private static final long serialVersionUID = 0L;
  651. @Override
  652. protected String getSeparator() {
  653. return ") AND (";
  654. }
  655. };
  656. private Join(TableSource from, TableSource to, short type, String on) {
  657. _from = from;
  658. _to = to;
  659. _jType = type;
  660. _on.add(on);
  661. }
  662. @Override
  663. protected void toString(StringBuilder sb, boolean isTopLevel) {
  664. String joinType = JOIN_TYPE_MAP.get(_jType);
  665. if(joinType == null) {
  666. throw new IllegalStateException(withErrorContext(
  667. "Unknown join type " + _jType));
  668. }
  669. if(!isTopLevel) {
  670. sb.append("(");
  671. }
  672. _from.toString(sb, false);
  673. sb.append(joinType);
  674. _to.toString(sb, false);
  675. sb.append(" ON ");
  676. boolean multiOnExpr = (_on.size() > 1);
  677. if(multiOnExpr) {
  678. sb.append("(");
  679. }
  680. sb.append(_on);
  681. if(multiOnExpr) {
  682. sb.append(")");
  683. }
  684. if(!isTopLevel) {
  685. sb.append(")");
  686. }
  687. }
  688. @Override
  689. public boolean containsTable(String table) {
  690. return _from.containsTable(table) || _to.containsTable(table);
  691. }
  692. @Override
  693. public boolean sameJoin(short type, String on) {
  694. if(_jType == type) {
  695. // note, AND conditions are added in _reverse_ order
  696. _on.add(0, on);
  697. return true;
  698. }
  699. return false;
  700. }
  701. }
  702. }