package com.tesora.dve.sql.transform;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.expression.ColumnKey;
import com.tesora.dve.sql.expression.ExpressionKey;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.node.LanguageNode;
import com.tesora.dve.sql.node.expression.ColumnInstance;
import com.tesora.dve.sql.node.expression.ExpressionNode;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.statement.dml.DMLStatement;
import com.tesora.dve.sql.util.ListSet;
public class SchemaMapper {
protected CopyContext copyContext;
protected ListSet<DMLStatement> orig = new ListSet<DMLStatement>();
protected DMLStatement copy;
// construct a mapper with the given originals, copy, and copy context
public SchemaMapper(Collection<DMLStatement> originals, DMLStatement copyStatement, CopyContext cc) {
orig.addAll(originals);
copy = copyStatement;
copyContext = cc;
copyContext.setFixed(true);
}
public CopyContext getCopyContext() {
return copyContext;
}
public ListSet<DMLStatement> getOriginals() {
return orig;
}
// make sure we share a source eventually
public boolean hasSameRoot(SchemaMapper other) {
HashSet<DMLStatement> ts = new HashSet<DMLStatement>();
HashSet<DMLStatement> os = new HashSet<DMLStatement>();
collectStatements(ts);
other.collectStatements(os);
ts.retainAll(os);
return !ts.isEmpty();
}
protected void collectStatements(Set<DMLStatement> into) {
into.addAll(orig);
for(DMLStatement dmls : orig) {
SchemaMapper sm = dmls.getMapper();
if (sm != null) sm.collectStatements(into);
}
}
public void remove(PETable pet) {
copyContext.removeTable(pet);
for(DMLStatement source : orig) {
SchemaMapper mapper = source.getMapper();
if (mapper == null) continue;
mapper.remove(pet);
}
}
public void remove(TableKey tt) {
copyContext.removeTable(tt);
// the goal is to make the table invisible - remove it from elsewhere too
for(DMLStatement source : orig) {
SchemaMapper mapper = source.getMapper();
if (mapper == null) continue;
mapper.remove(tt);
}
}
// we have only a few actual operations we can do - given a current statement and a source statement,
// and an expression within that source, we can produce an expression using the current statement table/column
// instances
public ColumnKey copyColumnKeyForward(ColumnKey in) {
ColumnKey out = copyContext.getColumnKey(in);
if (out != null) return out;
for(DMLStatement source : orig) {
SchemaMapper mapper = source.getMapper();
if (mapper == null) continue;
out = mapper.copyColumnKeyForward(in);
if (out == null) continue;
out = copyContext.getColumnKey(out);
if (out == null) continue;
return out;
}
return null;
}
public TableKey copyTableKeyForward(TableKey in) {
TableKey out = copyContext.getTableKey(in);
if (out != null) return out;
for(DMLStatement source : orig) {
SchemaMapper mapper = source.getMapper();
if (mapper == null) continue;
out = mapper.copyTableKeyForward(in);
if (out == null) continue;
out = copyContext.getTableKey(out);
if (out == null) continue;
return out;
}
return null;
}
public ColumnKey mapExpressionToColumn(ExpressionNode in) {
if (in instanceof ColumnInstance)
return copyColumnKeyForward(((ColumnInstance)in).getColumnKey());
// special mapper - we have a noncolumn input, which we are going to search backwards through contexts
// for a match and then map forward as a column key
ExpressionKey ek = new ExpressionKey(in);
return mapExpression(ek);
}
public boolean isOf(ExpressionNode in) {
ListSet<ColumnInstance> cis = ColumnInstanceCollector.getColumnInstances(in);
ListSet<TableKey> ofTables = new ListSet<TableKey>();
for(ColumnInstance ci : cis) {
ofTables.add(ci.getColumnKey().getTableKey());
}
// we're of this mapper if all of our table keys are amongst mapped to tables in the copy context
return copyContext.getMappedToTables().containsAll(ofTables);
}
private ColumnKey mapExpression(ExpressionKey ek) {
ColumnKey ck = copyContext.getColumnKey(ek);
if (ck != null) return ck;
for(DMLStatement source : orig) {
SchemaMapper mapper = source.getMapper();
if (mapper == null) continue;
ck = mapper.mapExpression(ek);
if (ck == null) continue;
ck = copyContext.getColumnKey((ColumnKey)ck);
if (ck == null) continue;
return ck;
}
return null;
}
public <T extends LanguageNode> T copyForward(T in) {
UpdatingContext uc = new UpdatingContext(this);
return CopyVisitor.copy(in, uc);
}
public <T extends LanguageNode> List<T> copyForward(List<T> in) throws PEException {
ArrayList<T> out = new ArrayList<T>();
for(T c : in)
out.add(copyForward(c));
return out;
}
// we have our own copy context, which is an updating context - it does the search backwards
private static class UpdatingContext extends CopyContext {
private final SchemaMapper last;
public UpdatingContext(SchemaMapper l) {
super("UpdatingContext");
last = l;
}
@Override
public TableKey getTableKey(TableKey tk) {
return last.copyTableKeyForward(tk);
}
@Override
public ColumnKey getColumnKey(ColumnKey ck) {
ColumnKey out = last.copyColumnKeyForward(ck);
if (out == null && last.getCopyContext().isTargetTable(ck.getTableKey()))
return ck;
return out;
}
@Override
public TableInstance getTableInstance(TableInstance in) {
TableKey out = last.copyTableKeyForward(in.getTableKey());
if (out == null)
throw new SchemaException(Pass.REWRITER, "Unable to map table " + in + " forward");
return out.toInstance();
}
@Override
public ColumnInstance getColumnInstance(ColumnInstance in) {
ColumnKey ick = in.getColumnKey();
ColumnKey out = getColumnKey(ick);
if (out == null) {
// System.out.println("Can't find: " + in.getColumnKey());
// System.out.println(last.dumpChain(""));
// getColumnKey(ick);
throw new SchemaException(Pass.REWRITER, "Unable to map column " + in + " forward");
}
return out.toInstance();
}
@Override
public boolean isFixed() {
// always true
return true;
}
}
public String dumpChain(String offset) {
StringBuffer buf = new StringBuffer();
buf.append(offset).append("c: ").append(copy.toString()).append(PEConstants.LINE_SEPARATOR);
int oc = 0;
for(DMLStatement dmls : orig) {
buf.append(offset).append("o[").append(oc).append("]: ").append(dmls.toString()).append(PEConstants.LINE_SEPARATOR);
oc++;
if (dmls.getMapper() != null)
buf.append(dmls.getMapper().dumpChain(offset + " "));
}
return buf.toString();
}
}