/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.persistence.query.sql; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * INTERNAL: SQL optimizer that removes table- and column references * that are redundant. */ public class RedundantTablesSQLOptimizer extends FilterSQLOptimizer { // RULE: 'select B.id from ... where A.id = B.id (A and B same table // and no other expressions referencing B' => replace B.id with A.id // 1. build data structures (analyze) // TC = {t : (c1, cN)} // TV = (t, tN); tables that are referenced by verbatim values/expressions // CE = {c : (e1, eN)} // 2. figure out which tables are elegible and how to replace them // 3. loop over query and replace table references (filter) protected Map tcmap = new HashMap(10); protected Map cemap = new HashMap(10); protected Set tvset = new HashSet(3); protected Map rcmap = new HashMap(); protected Map rtmap = new HashMap(); public SQLQuery optimize(SQLQuery query) { // analyze query Analyzer analyzer = new Analyzer(); analyzer.analyze(query); //! System.out.println("TC: " + tcmap); //! System.out.println("TV: " + tvset); //! System.out.println("CE: " + cemap); // figure out which tables to replace Iterator iter = tcmap.keySet().iterator(); while (iter.hasNext()) { SQLTable tbl = (SQLTable)iter.next(); Set cols = (Set)tcmap.get(tbl); // table is eligible if single column reference and not part of // verbatim expression if (cols.size() == 1 && !tvset.contains(tbl)) { //! System.out.println("Table " + tbl + " eligible for removal."); // find equals expression SQLColumns col1 = (SQLColumns)cols.iterator().next(); Set ce = (Set)cemap.get(col1); if (ce != null) { Iterator iter2 = ce.iterator(); while (iter2.hasNext()) { SQLColumns col2 = (SQLColumns)iter2.next(); if (rcmap.containsKey(col2)) { // TODO: may need more steps here col2 = (SQLColumns)rcmap.get(col2); } if (!rcmap.containsKey(col1)) { //! System.out.println("Found " + col1 + " -> " + col2); rcmap.put(col1, col2); rtmap.put(col1.getTable(), col2.getTable()); } } } } } //! System.out.println("RTMAP: " + rtmap); //! System.out.println("RCMAP: " + rcmap); // optimize query super.optimize(query); return query; } protected void addTableVerbatim(SQLTable tbl) { tvset.add(tbl); } protected void addTableColumns(SQLExpressionIF expr, SQLColumns cols) { // update tcmap addEntry(tcmap, cols.getTable(), cols); // update cemap if (expr != null && expr instanceof SQLEquals) { SQLEquals eq = (SQLEquals)expr; Object left = eq.getLeft(); Object right = eq.getRight(); if (left.equals(cols)) { if (!right.equals(cols) && right instanceof SQLColumns) addEntry(cemap, cols, right); } else if (left instanceof SQLColumns) { addEntry(cemap, cols, left); } } } protected void addEntry(Map map, Object key, Object value) { Set vals = (Set)map.get(key); if (vals == null) { vals = new HashSet(3); map.put(key, vals); } vals.add(value); } /* --- analysis -- */ class Analyzer extends AbstractSQLAnalyzer { protected void analyzeVerbatim(SQLExpressionIF expr, SQLVerbatim value) { SQLTable[] tables = value.getTables(); for (int i=0; i < tables.length; i++) { addTableVerbatim(tables[i]); } } protected void analyzeVerbatimExpression(SQLVerbatimExpression expr) { SQLTable[] tables = expr.getTables(); for (int i=0; i < tables.length; i++) { addTableVerbatim(tables[i]); } } protected void analyzeColumns(SQLExpressionIF expr, SQLColumns value) { addTableColumns(expr, value); } } /* --- optimization -- */ protected SQLValueIF filterValue(SQLValueIF value) { // ISSUE: should possibly prefer values in select clause, so that // the same column aliases can be kept // delegate to super class super.filterValue(value); // process column if (value.getType() == SQLValueIF.COLUMNS) { SQLColumns col1 = (SQLColumns)value; Object o = rcmap.get(col1); if (o != null) { SQLColumns col2 = (SQLColumns)o; // value types could be different if (!col1.getValueType().equals(col2.getValueType())) { SQLColumns coln = new SQLColumns(col2.getTable(), col2.getColumns()); // keep alias coln.setAlias((col2.getAlias() == null ? col1.getAlias() : col2.getAlias())); // keep value type and field handler coln.setValueType(col1.getValueType()); coln.setFieldHandler(col1.getFieldHandler()); return coln; } else { // keep alias if (col2.getAlias() == null) col2.setAlias(col1.getAlias()); return col2; } } } return value; } }