Advertisement
Guest User

Untitled

a guest
Oct 29th, 2018
1,361
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 22.92 KB | None | 0 0
  1. package edu.berkeley.cs186.database.query;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.HashMap;
  5. import java.util.HashSet;
  6. import java.util.Iterator;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Set;
  10.  
  11. import edu.berkeley.cs186.database.Database;
  12. import edu.berkeley.cs186.database.DatabaseException;
  13. import edu.berkeley.cs186.database.databox.DataBox;
  14. import edu.berkeley.cs186.database.table.Record;
  15. import edu.berkeley.cs186.database.table.Schema;
  16. import sun.reflect.generics.reflectiveObjects.NotImplementedException;
  17.  
  18. /**
  19. * QueryPlan provides a set of functions to generate simple queries. Calling the methods corresponding
  20. * to SQL syntax stores the information in the QueryPlan, and calling execute generates and executes
  21. * a QueryPlan DAG.
  22. */
  23. public class QueryPlan {
  24. public enum PredicateOperator {
  25. EQUALS,
  26. NOT_EQUALS,
  27. LESS_THAN,
  28. LESS_THAN_EQUALS,
  29. GREATER_THAN,
  30. GREATER_THAN_EQUALS
  31. }
  32.  
  33. private Database.Transaction transaction;
  34. private QueryOperator finalOperator;
  35. private String startTableName;
  36.  
  37. private List<String> joinTableNames;
  38. private List<String> joinLeftColumnNames;
  39. private List<String> joinRightColumnNames;
  40. private List<String> selectColumnNames;
  41. private List<PredicateOperator> selectOperators;
  42. private List<DataBox> selectDataBoxes;
  43. private List<String> projectColumns;
  44. private String groupByColumn;
  45. private boolean hasCount;
  46. private String averageColumnName;
  47. private String sumColumnName;
  48.  
  49. /**
  50. * Creates a new QueryPlan within transaction. The base table is startTableName.
  51. *
  52. * @param transaction the transaction containing this query
  53. * @param startTableName the source table for this query
  54. */
  55. public QueryPlan(Database.Transaction transaction, String startTableName) {
  56. this.transaction = transaction;
  57. this.startTableName = startTableName;
  58.  
  59. this.projectColumns = new ArrayList<String>();
  60. this.joinTableNames = new ArrayList<String>();
  61. this.joinLeftColumnNames = new ArrayList<String>();
  62. this.joinRightColumnNames = new ArrayList<String>();
  63.  
  64. this.selectColumnNames = new ArrayList<String>();
  65. this.selectOperators = new ArrayList<PredicateOperator>();
  66. this.selectDataBoxes = new ArrayList<DataBox>();
  67.  
  68. this.hasCount = false;
  69. this.averageColumnName = null;
  70. this.sumColumnName = null;
  71.  
  72. this.groupByColumn = null;
  73.  
  74. this.finalOperator = null;
  75. }
  76.  
  77. public QueryOperator getFinalOperator() {
  78. return this.finalOperator;
  79. }
  80.  
  81. /**
  82. * Add a project operator to the QueryPlan with a list of column names. Can only specify one set
  83. * of projections.
  84. *
  85. * @param columnNames the columns to project
  86. * @throws QueryPlanException
  87. */
  88. public void project(List<String> columnNames) throws QueryPlanException {
  89. if (!this.projectColumns.isEmpty()) {
  90. throw new QueryPlanException("Cannot add more than one project operator to this query.");
  91. }
  92.  
  93. if (columnNames.isEmpty()) {
  94. throw new QueryPlanException("Cannot project no columns.");
  95. }
  96.  
  97. this.projectColumns = columnNames;
  98. }
  99.  
  100. /**
  101. * Add a select operator. Only returns columns in which the column fulfills the predicate relative
  102. * to value.
  103. *
  104. * @param column the column to specify the predicate on
  105. * @param comparison the comparator
  106. * @param value the value to compare against
  107. * @throws QueryPlanException
  108. */
  109. public void select(String column, PredicateOperator comparison, DataBox value) throws QueryPlanException {
  110. this.selectColumnNames.add(column);
  111. this.selectOperators.add(comparison);
  112. this.selectDataBoxes.add(value);
  113. }
  114.  
  115. /**
  116. * Set the group by column for this query.
  117. *
  118. * @param column the column to group by
  119. * @throws QueryPlanException
  120. */
  121. public void groupBy(String column) throws QueryPlanException {
  122. this.groupByColumn = column;
  123. }
  124.  
  125. /**
  126. * Add a count aggregate to this query. Only can specify count(*).
  127. *
  128. * @throws QueryPlanException
  129. */
  130. public void count() throws QueryPlanException {
  131. this.hasCount = true;
  132. }
  133.  
  134. /**
  135. * Add an average on column. Can only average over integer or float columns.
  136. *
  137. * @param column the column to average
  138. * @throws QueryPlanException
  139. */
  140. public void average(String column) throws QueryPlanException {
  141. this.averageColumnName = column;
  142. }
  143.  
  144. /**
  145. * Add a sum on column. Can only sum integer or float columns
  146. *
  147. * @param column the column to sum
  148. * @throws QueryPlanException
  149. */
  150. public void sum(String column) throws QueryPlanException {
  151. this.sumColumnName = column;
  152. }
  153.  
  154. /**
  155. * Join the leftColumnName column of the existing queryplan against the rightColumnName column
  156. * of tableName.
  157. *
  158. * @param tableName the table to join against
  159. * @param leftColumnName the join column in the existing QueryPlan
  160. * @param rightColumnName the join column in tableName
  161. */
  162. public void join(String tableName, String leftColumnName, String rightColumnName) {
  163. this.joinTableNames.add(tableName);
  164. this.joinLeftColumnNames.add(leftColumnName);
  165. this.joinRightColumnNames.add(rightColumnName);
  166. }
  167.  
  168. //Returns a 2-array of table name, column name
  169. public String[] getJoinLeftColumnNameByIndex(int i) {
  170. return this.joinLeftColumnNames.get(i).split("\\.");
  171. }
  172.  
  173. //Returns a 2-array of table name, column name
  174. public String[] getJoinRightColumnNameByIndex(int i) {
  175. return this.joinRightColumnNames.get(i).split("\\.");
  176. }
  177.  
  178.  
  179. /**
  180. * Generates a naïve QueryPlan in which all joins are at the bottom of the DAG followed by all select
  181. * predicates, an optional group by operator, and a set of projects (in that order).
  182. *
  183. * @return an iterator of records that is the result of this query
  184. * @throws DatabaseException
  185. * @throws QueryPlanException
  186. */
  187. public Iterator<Record> execute() throws DatabaseException, QueryPlanException {
  188. String indexColumn = this.checkIndexEligible();
  189.  
  190. if (indexColumn != null) {
  191. this.generateIndexPlan(indexColumn);
  192. } else {
  193. // start off with the start table scan as the source
  194. this.finalOperator = new SequentialScanOperator(this.transaction, this.startTableName);
  195.  
  196. this.addJoins();
  197. this.addSelects();
  198. this.addGroupBy();
  199. this.addProjects();
  200. }
  201.  
  202. return this.finalOperator.execute();
  203. }
  204.  
  205. /**
  206. * Generates an optimal QueryPlan based on the System R cost-based query optimizer.
  207. *
  208. * @return an iterator of records that is the result of this query
  209. * @throws DatabaseException
  210. * @throws QueryPlanException
  211. */
  212. public Iterator<Record> executeOptimal() throws DatabaseException, QueryPlanException {
  213.  
  214.  
  215. // Pass 1: Iterate through all single tables. For each single table, find
  216. // the lowest cost QueryOperator to access that table. Construct a mapping
  217. // of each table name to its lowest cost operator.
  218. Map<Set, QueryOperator> map = new HashMap<>();
  219. List<String> singleTables=new ArrayList<>();
  220. singleTables.add(startTableName);
  221. singleTables.addAll(joinTableNames);
  222. for (String table : singleTables) {
  223. Set s=new HashSet();
  224. s.add(table);
  225. QueryOperator qop = minCostSingleAccess(table);
  226. map.put(s, qop);
  227. }
  228.  
  229. // Pass i: On each pass, use the results from the previous pass to find the
  230. // lowest cost joins with each single table. Repeat until all tables have
  231. // been joined.
  232. Map prevMap=new HashMap<Set,QueryOperator>(map);
  233. for(int i=0; i<joinTableNames.size();i++){
  234. prevMap=minCostJoins(prevMap,map);
  235. }
  236. // Get the lowest cost operator from the last pass, add GROUP BY and SELECT
  237. // operators, and return an iterator on the final operator
  238. finalOperator=minCostOperator(prevMap);
  239. addGroupBy();
  240. addProjects();
  241. return finalOperator.iterator();
  242. }
  243.  
  244. /**
  245. * Gets all SELECT predicates for which there exists an index on the column
  246. * referenced in that predicate for the given table.
  247. *
  248. * @return an ArrayList of SELECT predicates
  249. */
  250. private List<Integer> getEligibleIndexColumns(String table) {
  251. List<Integer> selectIndices = new ArrayList<Integer>();
  252.  
  253. for (int i = 0; i < this.selectColumnNames.size(); i++) {
  254. String column = this.selectColumnNames.get(i);
  255.  
  256. if (this.transaction.indexExists(table, column) &&
  257. this.selectOperators.get(i) != PredicateOperator.NOT_EQUALS) {
  258. selectIndices.add(i);
  259. }
  260. }
  261.  
  262. return selectIndices;
  263. }
  264.  
  265. /**
  266. * Gets all columns for which there exists an index for that table
  267. *
  268. * @return an ArrayList of column names
  269. */
  270. private List<String> getAllIndexColumns(String table) throws DatabaseException {
  271. List<String> indexColumns = new ArrayList<String>();
  272.  
  273. Schema schema = this.transaction.getSchema(table);
  274. List<String> columnNames = schema.getFieldNames();
  275.  
  276. for (int i = 0; i < columnNames.size(); i++) {
  277. String column = columnNames.get(i);
  278.  
  279. if (this.transaction.indexExists(table, column)) {
  280. indexColumns.add(table + "." + column);
  281. }
  282. }
  283.  
  284. return indexColumns;
  285. }
  286.  
  287. /**
  288. * Applies all eligible SELECT predicates to a given source, except for the
  289. * predicate at index except. The purpose of except is because there might
  290. * be one SELECT predicate that was already used for an index scan, so no
  291. * point applying it again. A SELECT predicate is represented as elements of
  292. * this.selectColumnNames, this.selectOperators, and this.selectDataBoxes that
  293. * correspond to the same index of these lists.
  294. *
  295. * @return a new QueryOperator after SELECT has been applied
  296. * @throws DatabaseException
  297. * @throws QueryPlanException
  298. */
  299. private QueryOperator addEligibleSelections(QueryOperator source, int except) throws QueryPlanException, DatabaseException {
  300.  
  301. for (int i = 0; i < this.selectColumnNames.size(); i++) {
  302. if (i == except) {
  303. continue;
  304. }
  305.  
  306. PredicateOperator curPred = this.selectOperators.get(i);
  307. DataBox curValue = this.selectDataBoxes.get(i);
  308. try {
  309. String colName = source.checkSchemaForColumn(source.getOutputSchema(), selectColumnNames.get(i));
  310. source = new SelectOperator(source, colName, curPred, curValue);
  311. } catch (QueryPlanException err) {
  312. continue;
  313. }
  314. }
  315.  
  316. return source;
  317. }
  318.  
  319. /**
  320. * Finds the lowest cost QueryOperator that scans the given table. First
  321. * determine the cost of a sequential scan for the given table. Then for every index that can be
  322. * used on that table, determine the cost of an index scan. Keep track of
  323. * the minimum cost operation. Then push down eligible projects (SELECT
  324. * predicates). If an index scan was chosen, exclude that SELECT predicate when
  325. * pushing down selects. This method will be called during the first pass of the search
  326. * algorithm to determine the most efficient way to access each single table.
  327. *
  328. * @return a QueryOperator that has the lowest cost of scanning the given table which is
  329. * either a SequentialScanOperator or an IndexScanOperator nested within any possible
  330. * pushed down select operators
  331. * @throws DatabaseException
  332. * @throws QueryPlanException
  333. */
  334. public QueryOperator minCostSingleAccess(String table) throws DatabaseException, QueryPlanException {
  335.  
  336. QueryOperator minOp = new SequentialScanOperator(this.transaction, table);
  337.  
  338.  
  339. // 1. Find the cost of a sequential scan of the table
  340. int cost = minOp.estimateIOCost();
  341. // 2. For each eligible index column, find the cost of an index scan of the
  342. // table and retain the lowest cost operator
  343. List<Integer> indCols = getEligibleIndexColumns(table);
  344. int minOpindex = -1;
  345. for (int i = 0; i < indCols.size(); i++) {
  346. int index = indCols.get(i);
  347. IndexScanOperator temp = new IndexScanOperator(transaction, table, selectColumnNames.get(index),
  348. selectOperators.get(index), selectDataBoxes.get(index));
  349. if (temp.estimateIOCost() < cost) {
  350. minOp = temp;
  351. cost = temp.estimateIOCost();
  352. minOpindex = index;
  353. }
  354. }
  355. // 3. Push down SELECT predicates that apply to this table and that were not
  356. // used for an index scan
  357. minOp = addEligibleSelections(minOp, minOpindex);
  358. return minOp;
  359. }
  360.  
  361. /**
  362. * Given a join condition between an outer relation represented by leftOp
  363. * and an inner relation represented by rightOp, find the lowest cost join
  364. * operator out of all the possible join types in JoinOperator.JoinType.
  365. *
  366. * @return lowest cost join QueryOperator between the input operators
  367. * @throws QueryPlanException
  368. */
  369. private QueryOperator minCostJoinType(QueryOperator leftOp,
  370. QueryOperator rightOp,
  371. String leftColumn,
  372. String rightColumn) throws QueryPlanException,
  373. DatabaseException {
  374. QueryOperator minOp = null;
  375.  
  376. int minCost = Integer.MAX_VALUE;
  377. List<QueryOperator> allJoins = new ArrayList<QueryOperator>();
  378. allJoins.add(new SNLJOperator(leftOp, rightOp, leftColumn, rightColumn, this.transaction));
  379. allJoins.add(new BNLJOperator(leftOp, rightOp, leftColumn, rightColumn, this.transaction));
  380.  
  381. for (QueryOperator join : allJoins) {
  382. int joinCost = join.estimateIOCost();
  383. if (joinCost < minCost) {
  384. minOp = join;
  385. minCost = joinCost;
  386. }
  387. }
  388. return minOp;
  389. }
  390.  
  391. /**
  392. * Iterate through all table sets in the previous pass of the search. For each
  393. * table set, check each join predicate to see if there is a valid join
  394. * condition with a new table. If so, check the cost of each type of join and
  395. * keep the minimum cost join. Construct and return a mapping of each set of
  396. * table names being joined to its lowest cost join operator. A join predicate
  397. * is represented as elements of this.joinTableNames, this.joinLeftColumnNames,
  398. * and this.joinRightColumnNames that correspond to the same index of these lists.
  399. *
  400. * @return a mapping of table names to a join QueryOperator
  401. * @throws QueryPlanException
  402. */
  403. private Map<Set, QueryOperator> minCostJoins(Map<Set, QueryOperator> prevMap,
  404. Map<Set, QueryOperator> pass1Map) throws QueryPlanException,
  405. DatabaseException {
  406.  
  407.  
  408. Map<Set, QueryOperator> map = new HashMap<Set, QueryOperator>();
  409.  
  410.  
  411. //We provide a basic description of the logic you have to implement
  412.  
  413. //Input: prevMap (maps a set of tables to a query operator--the operator that joins the set)
  414. //Input: pass1Map (each set is a singleton with one table and single table access query operator)
  415.  
  416. //FOR EACH set of tables in prevMap:
  417. for (Set s : prevMap.keySet()) {
  418. //FOR EACH join condition listed in the query
  419.  
  420. for (int i = 0; i < joinTableNames.size(); i++) {
  421. //get the left side and the right side (table name and column)
  422. String[] left = getJoinLeftColumnNameByIndex(i);
  423. String[] right = getJoinRightColumnNameByIndex(i);
  424. String leftTable = left[0];
  425. String leftCol = left[1];
  426. String rightTable = right[0];
  427. String rightCol = right[1];
  428. /**
  429. * Case 1. Set contains left table but not right, use pass1Map to
  430. * fetch the right operator to access the rightTable
  431. *
  432. * Case 2. Set contains right table but not left, use pass1Map to
  433. * fetch the right operator to access the leftTable.
  434. *
  435. * Case 3. Set contains neither or both the left table or right table (contiue loop)
  436. *
  437. * --- Then given the operator, use minCostJoinType to calculate the cheapest join with that
  438. * and the previously joined tables.
  439. */
  440. QueryOperator leftQOP = prevMap.get(s);
  441. QueryOperator rightQOP = prevMap.get(s);
  442. Set union = new HashSet();
  443. union.addAll(s);
  444. if (s.contains(leftTable) && !s.contains(rightTable)) {
  445. Set rTable=new HashSet();
  446. rTable.add(rightTable);
  447. rightQOP = pass1Map.get(rTable);
  448. union.add(rightTable);
  449. } else if (s.contains(rightTable) && !s.contains(leftTable)) {
  450. Set lTable=new HashSet();
  451. lTable.add(leftTable);
  452. leftQOP = pass1Map.get(lTable);
  453. union.add(leftTable);
  454. } else {
  455. continue;
  456. }
  457. QueryOperator newQOP = minCostJoinType(leftQOP, rightQOP, leftCol, rightCol);
  458. /**
  459. * Create a new set that is the union of the new table and previously
  460. * joined tables. Add to result map this value mapping to the result from
  461. * minCostJoinType if it doesn't exist or it exists and cost is lower.
  462. */
  463. if (!map.containsKey(union) || newQOP.estimateIOCost() < map.get(union).estimateIOCost()) {
  464. map.put(union, newQOP);
  465. }
  466. }
  467. }
  468.  
  469.  
  470. return map;
  471. }
  472.  
  473. /**
  474. * Finds the lowest cost QueryOperator in the given mapping. A mapping is
  475. * generated on each pass of the search algorithm, and relates a set of tables
  476. * to the lowest cost QueryOperator accessing those tables. This method is
  477. * called at the end of the search algorithm after all passes have been
  478. * processed.
  479. *
  480. * @return a QueryOperator in the given mapping
  481. * @throws QueryPlanException
  482. */
  483. private QueryOperator minCostOperator(Map<Set, QueryOperator> map) throws QueryPlanException, DatabaseException {
  484. QueryOperator minOp = null;
  485. QueryOperator newOp;
  486. int minCost = Integer.MAX_VALUE;
  487. int newCost;
  488. for (Set tables : map.keySet()) {
  489. newOp = map.get(tables);
  490. newCost = newOp.getIOCost();
  491. if (newCost < minCost) {
  492. minOp = newOp;
  493. minCost = newCost;
  494. }
  495. }
  496. return minOp;
  497. }
  498.  
  499. private String checkIndexEligible() {
  500. if (this.selectColumnNames.size() > 0
  501. && this.groupByColumn == null
  502. && this.joinTableNames.size() == 0) {
  503.  
  504. int index = 0;
  505. for (String column : selectColumnNames) {
  506. if (this.transaction.indexExists(this.startTableName, column)) {
  507. if (this.selectOperators.get(index) != PredicateOperator.NOT_EQUALS) {
  508. return column;
  509. }
  510. }
  511.  
  512. index++;
  513. }
  514. }
  515.  
  516. return null;
  517. }
  518.  
  519. private void generateIndexPlan(String indexColumn) throws QueryPlanException, DatabaseException {
  520. int selectIndex = this.selectColumnNames.indexOf(indexColumn);
  521. PredicateOperator operator = this.selectOperators.get(selectIndex);
  522. DataBox value = this.selectDataBoxes.get(selectIndex);
  523.  
  524. this.finalOperator = new IndexScanOperator(this.transaction, this.startTableName, indexColumn, operator,
  525. value);
  526.  
  527. this.selectColumnNames.remove(selectIndex);
  528. this.selectOperators.remove(selectIndex);
  529. this.selectDataBoxes.remove(selectIndex);
  530.  
  531. this.addSelects();
  532. this.addProjects();
  533. }
  534.  
  535. private void addJoins() throws QueryPlanException, DatabaseException {
  536. int index = 0;
  537.  
  538. for (String joinTable : this.joinTableNames) {
  539. SequentialScanOperator scanOperator = new SequentialScanOperator(this.transaction, joinTable);
  540.  
  541. SNLJOperator joinOperator = new SNLJOperator(finalOperator, scanOperator,
  542. this.joinLeftColumnNames.get(index), this.joinRightColumnNames.get(index), this.transaction); //changed from new JoinOperator
  543.  
  544. this.finalOperator = joinOperator;
  545. index++;
  546. }
  547. }
  548.  
  549. private void addSelects() throws QueryPlanException, DatabaseException {
  550. int index = 0;
  551.  
  552. for (String selectColumn : this.selectColumnNames) {
  553. PredicateOperator operator = this.selectOperators.get(index);
  554. DataBox value = this.selectDataBoxes.get(index);
  555.  
  556. SelectOperator selectOperator = new SelectOperator(this.finalOperator, selectColumn,
  557. operator, value);
  558.  
  559. this.finalOperator = selectOperator;
  560. index++;
  561. }
  562. }
  563.  
  564. private void addGroupBy() throws QueryPlanException, DatabaseException {
  565. if (this.groupByColumn != null) {
  566. if (this.projectColumns.size() > 2 || (this.projectColumns.size() == 1 &&
  567. !this.projectColumns.get(0).equals(this.groupByColumn))) {
  568. throw new QueryPlanException("Can only project columns specified in the GROUP BY clause.");
  569. }
  570.  
  571. GroupByOperator groupByOperator = new GroupByOperator(this.finalOperator, this.transaction,
  572. this.groupByColumn);
  573.  
  574. this.finalOperator = groupByOperator;
  575. }
  576. }
  577.  
  578. private void addProjects() throws QueryPlanException, DatabaseException {
  579. if (!this.projectColumns.isEmpty() || this.hasCount || this.sumColumnName != null
  580. || this.averageColumnName != null) {
  581. ProjectOperator projectOperator = new ProjectOperator(this.finalOperator, this.projectColumns,
  582. this.hasCount, this.averageColumnName, this.sumColumnName);
  583.  
  584. this.finalOperator = projectOperator;
  585. }
  586. }
  587.  
  588. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement