package com.tesora.dve.sql.schema.cache;
/*
* #%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.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.lockmanager.LockType;
import com.tesora.dve.sql.expression.MTTableKey;
import com.tesora.dve.sql.expression.TableKey;
import com.tesora.dve.sql.parser.ExtractedLiteral;
import com.tesora.dve.sql.parser.ExtractedLiteral.Type;
import com.tesora.dve.sql.schema.LockInfo;
import com.tesora.dve.sql.schema.Name;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.mt.IPETenant;
import com.tesora.dve.sql.schema.mt.PETenant;
import com.tesora.dve.sql.schema.mt.TableScope;
import com.tesora.dve.sql.schema.mt.TableScope.ScopeCacheKey;
import com.tesora.dve.sql.statement.CacheableStatement;
import com.tesora.dve.sql.transform.execution.ConnectionValuesMap;
import com.tesora.dve.sql.transform.execution.RootExecutionPlan;
import com.tesora.dve.sql.transform.execution.RebuiltPlan;
import com.tesora.dve.sql.util.Functional;
import com.tesora.dve.sql.util.UnaryFunction;
public class MTCachedPlan implements RegularCachedPlan {
private final PlanCacheKey pck;
private final List<ExtractedLiteral.Type> extractedTypes;
private final List<CachedTSPlan> plans;
private final Cache<SchemaCacheKey<PETenant>,MatchResult> tenants;
private final LockType lockType;
public MTCachedPlan(PlanCacheKey pck, List<ExtractedLiteral> extractedLiterals, int cacheDepth, LockType lt) {
this.pck = pck;
extractedTypes = new ArrayList<ExtractedLiteral.Type>();
for(ExtractedLiteral el : extractedLiterals)
extractedTypes.add(el.getType());
plans = new ArrayList<CachedTSPlan>();
lockType = lt;
tenants = CacheBuilder.newBuilder().maximumSize(cacheDepth)
.removalListener(new OnRemoval(this))
.build();
}
@Override
public boolean invalidate(SchemaCacheKey<?> unloaded) {
if (unloaded.getCacheSegment() == CacheSegment.TENANT) {
tenants.invalidate(unloaded);
return tenants.size() == 0;
} else if (unloaded.getCacheSegment() == CacheSegment.TABLE) {
HashSet<CachedTSPlan> lost = new HashSet<CachedTSPlan>();
for(CachedTSPlan ctsp : plans) {
if (ctsp.invalidate(unloaded))
lost.add(ctsp);
}
if (lost.isEmpty()) return false;
HashSet<SchemaCacheKey<PETenant>> keys = new HashSet<SchemaCacheKey<PETenant>>(tenants.asMap().keySet());
for(Iterator<SchemaCacheKey<PETenant>> iter = keys.iterator(); iter.hasNext();) {
MatchResult thePlan = tenants.getIfPresent(iter.next());
if (thePlan == null || !lost.contains(thePlan.getPlan()))
iter.remove();
}
tenants.invalidateAll(keys);
synchronized(plans) {
plans.removeAll(lost);
}
return tenants.size() == 0 || plans.size() == 0;
} else if (unloaded.getCacheSegment() == CacheSegment.SCOPE) {
ScopeCacheKey sck = (ScopeCacheKey) unloaded;
SchemaCacheKey<PETenant> tenantKey = sck.getTenantCacheKey();
MatchResult results = tenants.getIfPresent(tenantKey);
if (results != null) {
if (results.matches(sck)) {
tenants.invalidate(tenantKey);
}
}
}
return false;
}
@SuppressWarnings("unchecked")
public void take(SchemaContext sc, CacheableStatement originalStatement, RootExecutionPlan thePlan) {
// first build the CachedTSPlan and then try to merge it in.
// we seek to minimize the time within the lock.
List<VisibilityRecord> visible = new ArrayList<VisibilityRecord>();
ArrayList<SchemaCacheKey<TableScope>> scopes = new ArrayList<SchemaCacheKey<TableScope>>();
for(TableKey tk : originalStatement.getAllTableKeys()) {
if (tk instanceof MTTableKey) {
MTTableKey mtk = (MTTableKey) tk;
VisibilityRecord vr = new VisibilityRecord(sc,mtk.getScope().getName(),mtk.getScope().getTable(sc));
visible.add(vr);
scopes.add((SchemaCacheKey<TableScope>) mtk.getScope().getCacheKey());
}
}
scopes.trimToSize();
SchemaEdge<IPETenant> ick = sc.getCurrentTenant();
PETenant ten = (PETenant)ick.get(sc);
SchemaCacheKey<PETenant> ck = (SchemaCacheKey<PETenant>) ten.getCacheKey();
CachedTSPlan np = new CachedTSPlan(this,thePlan,visible);
int max = plans.size();
MatchResult mr = null;
for(int i = 0; i < max; i++) {
CachedTSPlan existing = plans.get(i);
if (existing.matches(sc,np)) {
existing.increment();
mr = new MatchResult(existing,scopes);
tenants.put(ck, mr);
return;
}
}
synchronized(plans) {
plans.add(np);
}
if (mr == null)
mr = new MatchResult(np,scopes);
tenants.put(ck,mr);
}
protected void onRemoval(MatchResult ctsp) {
if (ctsp.getPlan().decrement()) {
synchronized(plans) {
plans.remove(ctsp);
}
}
}
@Override
public PlanCacheKey getKey() {
return pck;
}
@Override
public List<ExtractedLiteral.Type> getLiteralTypes() {
return null;
}
@Override
public RebuiltPlan showPlan(SchemaContext sc,
List<ExtractedLiteral> literals) throws PEException {
return null;
}
@Override
public RebuiltPlan rebuildPlan(SchemaContext sc,
List<ExtractedLiteral> literals) throws PEException {
if (!isValidLiterals(literals)) return new RebuiltPlan(null, null, true,null,lockType);
IPETenant currentTenant = sc.getCurrentTenant().get(sc);
if (currentTenant == null)
return new RebuiltPlan(null, null, true,null,lockType);
MatchResult candidate = findMatching(sc, (PETenant)currentTenant);
if (candidate == null)
return new RebuiltPlan(null, null, true,null,lockType);
return candidate.getPlan().rebuildPlan(sc, literals,
Functional.apply(candidate.getScopes(), new UnaryFunction<SchemaCacheKey<?>,SchemaCacheKey<TableScope>>() {
@Override
public SchemaCacheKey<?> evaluate(
SchemaCacheKey<TableScope> object) {
return object;
}
}));
}
@Override
public boolean isValid(SchemaContext sc, List<ExtractedLiteral> literals)
throws PEException {
if (!isValidLiterals(literals))
return false;
return findMatching(sc, (PETenant)sc.getCurrentTenant().get(sc)) != null;
}
@SuppressWarnings("unchecked")
private MatchResult findMatching(SchemaContext sc, PETenant tenant) throws PEException {
SchemaCacheKey<PETenant> ck = (SchemaCacheKey<PETenant>) tenant.getCacheKey();
MatchResult candidate = tenants.getIfPresent(ck);
if (candidate != null) return candidate;
int max = plans.size();
for(int i = 0; i < max; i++) {
CachedTSPlan ctsp = plans.get(i);
List<SchemaCacheKey<TableScope>> matching = ctsp.slowMatch(sc, tenant, lockType);
if (matching != null) {
ctsp.increment();
MatchResult mr = new MatchResult(ctsp,matching);
tenants.put(ck, mr);
return mr;
}
}
return null;
}
private boolean isValidLiterals(List<ExtractedLiteral> literals) {
if (extractedTypes.size() != literals.size())
return false;
for(int i = 0; i < extractedTypes.size(); i++) {
if (extractedTypes.get(i) != literals.get(i).getType())
return false;
}
return true;
}
// in mt mode, a cached plan consists of cached table specific (TS) plans
// each TS plan contains a list of mappings from visible names to backing PETables
// as well as a set of SchemaCacheKeys that point to tenants
// each TS is for a different set of backing PETables
public static class CachedTSPlan implements RegularCachedPlan {
private final RootExecutionPlan thePlan;
private final List<VisibilityRecord> mapping;
private final MTCachedPlan parent;
private final AtomicInteger refCount;
public CachedTSPlan(MTCachedPlan mtcp, RootExecutionPlan thePlan, List<VisibilityRecord> theMapping) {
parent = mtcp;
this.thePlan = thePlan;
this.thePlan.setOwningCache(this);
this.mapping = theMapping;
this.refCount = new AtomicInteger(1);
}
public void increment() {
refCount.incrementAndGet();
}
public boolean decrement() {
int was = refCount.decrementAndGet();
return (was <= 0);
}
@Override
public PlanCacheKey getKey() {
return parent.getKey();
}
@Override
public RebuiltPlan rebuildPlan(SchemaContext sc,
List<ExtractedLiteral> literals) throws PEException {
ConnectionValuesMap cv = thePlan.resetForNewPlan(sc, literals);
return new RebuiltPlan(thePlan, cv, false,null,parent.lockType);
}
public RebuiltPlan rebuildPlan(SchemaContext sc,
List<ExtractedLiteral> literals, List<SchemaCacheKey<?>> scopes) throws PEException {
ConnectionValuesMap cv = thePlan.resetForNewPlan(sc, literals);
return new RebuiltPlan(thePlan, cv, false,scopes.toArray(new SchemaCacheKey<?>[0]),parent.lockType);
}
@Override
public boolean isValid(SchemaContext sc, List<ExtractedLiteral> literals)
throws PEException {
return parent.isValid(sc, literals);
}
public List<SchemaCacheKey<TableScope>> slowMatch(SchemaContext sc, PETenant pet, LockType lt) {
ArrayList<SchemaCacheKey<TableScope>> matching = new ArrayList<SchemaCacheKey<TableScope>>();
for(VisibilityRecord vr : mapping) {
SchemaCacheKey<TableScope> m = vr.matches(sc, pet, lt);
if (m == null)
return null;
matching.add(m);
}
matching.trimToSize();
return matching;
}
public boolean matches(SchemaContext sc, CachedTSPlan other) {
Set<String> mine = visibilityShortHand(sc);
Set<String> yours = other.visibilityShortHand(sc);
return mine.equals(yours);
}
protected Set<String> visibilityShortHand(SchemaContext sc) {
HashSet<String> out = new HashSet<String>();
for(VisibilityRecord vr : mapping)
out.add(vr.buildKey(sc));
return out;
}
@Override
public boolean invalidate(SchemaCacheKey<?> unloaded) {
if (unloaded.getCacheSegment() != CacheSegment.TABLE) return false;
for(VisibilityRecord vr : mapping) {
if (vr.getCacheKey().equals(unloaded))
return true;
}
return false;
}
@Override
public RebuiltPlan showPlan(SchemaContext sc,
List<ExtractedLiteral> literals) throws PEException {
return null;
}
@Override
public List<Type> getLiteralTypes() {
return null;
}
}
private static class VisibilityRecord {
private final Name localName;
private final SchemaCacheKey<PETable> backingTable;
@SuppressWarnings("unchecked")
public VisibilityRecord(SchemaContext sc, Name visibleName, PETable backing) {
this.localName = visibleName;
this.backingTable = (SchemaCacheKey<PETable>) backing.getCacheKey();
}
@SuppressWarnings("unchecked")
public SchemaCacheKey<TableScope> matches(SchemaContext sc, PETenant pet, LockType lt) {
TableScope ts = pet.lookupScope(sc, localName, new LockInfo(lt,"lookup scope for mt cached plan"));
if (ts == null) return null;
if (!backingTable.equals(ts.getTableKey())) return null;
return (SchemaCacheKey<TableScope>) ts.getCacheKey();
}
public String buildKey(SchemaContext sc) {
return localName.get() + "/" + backingTable;
}
public SchemaCacheKey<PETable> getCacheKey() {
return backingTable;
}
}
private static class OnRemoval implements RemovalListener<SchemaCacheKey<PETenant>,MatchResult> {
private final MTCachedPlan parent;
public OnRemoval(MTCachedPlan ctcp) {
parent = ctcp;
}
@Override
public void onRemoval(
RemovalNotification<SchemaCacheKey<PETenant>, MatchResult> arg0) {
parent.onRemoval(arg0.getValue());
}
}
private static class MatchResult {
private List<SchemaCacheKey<TableScope>> scopes;
private CachedTSPlan plan;
public MatchResult(CachedTSPlan thePlan, List<SchemaCacheKey<TableScope>> matching) {
scopes = matching;
plan = thePlan;
}
public CachedTSPlan getPlan() {
return plan;
}
public boolean matches(SchemaCacheKey<TableScope> in) {
return scopes.contains(in);
}
public List<SchemaCacheKey<TableScope>> getScopes() {
return scopes;
}
}
}