com.tesora.dve.tools.analyzer.Analyzer.java Source code

Java tutorial

Introduction

Here is the source code for com.tesora.dve.tools.analyzer.Analyzer.java

Source

package com.tesora.dve.tools.analyzer;

/*
 * #%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.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.tesora.dve.server.bootstrap.BootstrapWiring;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.sql.transexec.spi.TransientEngine;
import com.tesora.dve.sql.transexec.spi.TransientEngineFactory;
import org.apache.commons.lang.StringUtils;

import com.tesora.dve.common.PECharsetUtils;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.db.DBNative;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.sql.ParserException;
import com.tesora.dve.sql.ParserException.Pass;
import com.tesora.dve.sql.SchemaException;
import com.tesora.dve.sql.node.expression.TableInstance;
import com.tesora.dve.sql.parser.InvokeParser;
import com.tesora.dve.sql.parser.ParserInvoker;
import com.tesora.dve.sql.parser.ParserInvoker.LineInfo;
import com.tesora.dve.sql.parser.ParserOptions;
import com.tesora.dve.sql.schema.Name;
import com.tesora.dve.sql.schema.PEAbstractTable;
import com.tesora.dve.sql.schema.PEColumn;
import com.tesora.dve.sql.schema.PEDatabase;
import com.tesora.dve.sql.schema.PEForeignKey;
import com.tesora.dve.sql.schema.PEForeignKeyColumn;
import com.tesora.dve.sql.schema.PEKeyColumnBase;
import com.tesora.dve.sql.schema.PETable;
import com.tesora.dve.sql.schema.PETemplate;
import com.tesora.dve.sql.schema.QualifiedName;
import com.tesora.dve.sql.schema.SchemaContext;
import com.tesora.dve.sql.schema.UnqualifiedName;
import com.tesora.dve.sql.schema.cache.SchemaSourceFactory;
import com.tesora.dve.sql.schema.modifiers.EngineTableModifier.EngineTag;
import com.tesora.dve.sql.schema.validate.ValidateResult;
import com.tesora.dve.sql.statement.Statement;
import com.tesora.dve.sql.statement.session.UseDatabaseStatement;
import com.tesora.dve.sql.statement.session.UseStatement;
import com.tesora.dve.sql.statement.session.UseTenantStatement;
import com.tesora.dve.sql.template.jaxb.Template;
import com.tesora.dve.tools.aitemplatebuilder.CorpusStats;
import com.tesora.dve.tools.analyzer.jaxb.DatabasesType.Database;
import com.tesora.dve.tools.analyzer.jaxb.DbAnalyzerReport;
import com.tesora.dve.tools.analyzer.jaxb.TablesType.Table;

public abstract class Analyzer {
    static {
        BootstrapWiring.rewire();
    }

    private static final int TRANSIENT_SITE_NUM = 2;
    private static final String TRANSIENT_SITE_USER = "root";
    private static final String TRANSIENT_SITE_PASS = "password";

    protected final TransientEngine tee;
    private final AnalyzerOptions options;
    protected final AnalyzerInvoker invoker;
    protected AnalyzerSource currentSource;
    protected String primaryDB;

    protected DbAnalyzerReport parentReport;

    public Analyzer(AnalyzerOptions opts) throws Throwable {
        if (opts == null) {
            throw new IllegalArgumentException();
        }
        SchemaSourceFactory.reset();
        // Build at least 2-site storage group so we guarantee we require redistribution.
        this.tee = buildExecutionEngine(TRANSIENT_SITE_NUM);
        this.options = opts;
        this.invoker = new AnalyzerInvoker(this);
        this.currentSource = null;
    }

    public void loadSchema(Template template, Database db, DBNative dbNative) throws PEException {
        final List<String> tableCreates = AnalyzerUtils.buildCreateTableStatements(db, dbNative);
        // Build view statements separately as they can depend on each other.
        final List<String> viewCreates = AnalyzerUtils.buildCreateViewStatements(db, dbNative);
        final List<String> tableNames = AnalyzerUtils.getTableNames(db);

        primaryDB = db.getName();

        try {

            final List<String> dbload = new ArrayList<String>();
            dbload.add("create template " + template.getName() + " xml='" + PETemplate.build(template) + "'");
            dbload.add("create database " + primaryDB + " default persistent group g1 using template " + primaryDB
                    + " strict");
            dbload.add("use " + primaryDB);
            dbload.add("set foreign_key_checks=0");
            dbload.addAll(tableCreates);
            dbload.add("set foreign_key_checks=1");

            tee.parse(dbload.toArray(new String[dbload.size()]), true);
            final ParserOptions opts = ParserOptions.TEST.setResolve().setIgnoreMissingUser();
            while (!viewCreates.isEmpty()) {
                final int before = viewCreates.size();
                for (final Iterator<String> iter = viewCreates.iterator(); iter.hasNext();) {
                    final String sql = iter.next();
                    final SchemaContext pc = tee.getPersistenceContext();
                    pc.refresh(true);
                    try {
                        final List<Statement> stmts = InvokeParser
                                .parse(InvokeParser.buildInputState(sql, pc), opts, pc).getStatements();
                        for (final Statement s : stmts) {
                            tee.dispatch(s);
                        }
                        iter.remove();
                    } catch (final SchemaException e) {
                        if (e.getMessage().startsWith("No such table")) {
                            continue;
                        }

                        throw new PEException("TEE: unable to parse '" + sql + "': " + e.getMessage(), e);
                    } catch (final Exception e) {
                        throw new PEException("TEE: unable to parse '" + sql + "': " + e.getMessage(), e);
                    }
                }
                final int after = viewCreates.size();
                if (after == before) {
                    throw new PEException("TEE: unable to load views. Circular reference?");
                }
            }
            tee.getPersistenceContext().forceMutableSource();

            resolveDanglingFKs(db);
            if (options.isValidateFKsEnabled()) {
                validateFKs(primaryDB, tableNames);
            }

        } catch (final Throwable t) {
            throw new PEException("Unable to load schema '" + t.getMessage() + "'", t);
        }
    }

    private void resolveDanglingFKs(final Database db) throws PEException {
        final SchemaContext context = tee.getPersistenceContext();

        final UnqualifiedName dbName = new UnqualifiedName(db.getName());
        final PEDatabase peDb = context.findPEDatabase(dbName);

        if (peDb != null) {
            for (final Table table : db.getTables().getTable()) {
                if (table.isView() != Boolean.TRUE) {
                    final UnqualifiedName tableName = new UnqualifiedName(table.getName());
                    final TableInstance ti = peDb.getSchema().buildInstance(context, tableName, null);
                    if (ti != null) {
                        final PETable peTable = ti.getAbstractTable().asTable();
                        final List<PEForeignKey> fks = peTable.getForeignKeys(context);
                        for (final PEForeignKey fk : fks) {
                            if (fk.isForward() && (fk.getTargetTable(context) == null)) {
                                final Name targetTableName = fk.getTargetTableName(context);
                                final TableInstance tti = peDb.getSchema().buildInstance(context,
                                        targetTableName.getUnqualified(), null);
                                if (tti != null) {
                                    final PETable targetTable = tti.getAbstractTable().asTable();
                                    fk.setTargetTable(context, targetTable);
                                    for (final PEKeyColumnBase keyColumn : fk.getKeyColumns()) {
                                        final PEForeignKeyColumn fkColumn = (PEForeignKeyColumn) keyColumn;
                                        final Name targetColumnName = fkColumn.getTargetColumnName();
                                        final PEColumn targetColumn = targetTable.lookup(context, targetColumnName);
                                        if (targetColumn != null) {
                                            fkColumn.setTargetColumn(targetColumn);
                                        } else {
                                            final QualifiedName fullColumnName = new QualifiedName(
                                                    targetTableName.getUnqualified(),
                                                    targetColumnName.getUnqualified());
                                            throw new PEException("TEE: target column '" + fullColumnName.getSQL()
                                                    + "' not found in the schema");
                                        }
                                    }
                                } else {
                                    final QualifiedName fullTableName = new QualifiedName(dbName,
                                            targetTableName.getUnqualified());
                                    throw new PEException("TEE: target table '" + fullTableName.getSQL()
                                            + "' not found in the schema");
                                }
                            }
                        }
                    } else {
                        final QualifiedName fullTableName = new QualifiedName(dbName, tableName);
                        throw new PEException("TEE: table '" + fullTableName.getSQL() + "' does not exist");
                    }
                }
            }
        } else {
            throw new PEException("TEE: database '" + dbName.getSQL() + "' does not exist");
        }
    }

    private void validateFKs(final String dbName, List<String> tableNames) {
        final SchemaContext sc = tee.getPersistenceContext();
        final PEDatabase db = sc.findPEDatabase(new UnqualifiedName(dbName));
        if (db == null) {
            return;
        }
        final ArrayList<ValidateResult> results = new ArrayList<ValidateResult>();
        for (final String tn : tableNames) {
            final TableInstance ti = db.getSchema().buildInstance(sc, new UnqualifiedName(tn), null);
            if (ti == null) {
                continue;
            }
            final PEAbstractTable<?> pet = ti.getAbstractTable();
            if (pet.isView()) {
                continue;
            }
            final List<PEForeignKey> fks = pet.asTable().getForeignKeys(sc);
            for (final PEForeignKey pefk : fks) {
                results.addAll(pefk.validate(sc, false));
            }
        }
        if (results.isEmpty()) {
            return;
        }

        final StringBuilder errorMessage = new StringBuilder();
        for (final ValidateResult vr : results) {
            errorMessage.append(vr.getMessage(sc)).append(PEConstants.LINE_SEPARATOR);
        }
        throw new SchemaException(Pass.PLANNER, errorMessage.toString());
    }

    public void resolveSchemaObjects(final List<Database> databases, final CorpusStats corpusStats) {
        final SchemaContext context = tee.getPersistenceContext();

        /* Load all tables from given static report databases. */
        for (final Database staticReportDb : databases) {
            final String databaseName = staticReportDb.getName();
            for (final Table staticReportTable : staticReportDb.getTables().getTable()) {
                final String tableName = staticReportTable.getName();
                final int tableRowCount = staticReportTable.getRowCount();
                final Long tableDataLength = staticReportTable.getDataLength();
                final String engine = staticReportTable.getEngine();
                corpusStats.addTable(corpusStats.new TableStats(databaseName, tableName, tableRowCount,
                        tableDataLength, EngineTag.findEngine(engine)));
            }
        }

        /* Resolve additional information if available. */
        for (final CorpusStats.TableStats table : corpusStats.getStatistics()) {
            final UnqualifiedName databaseName = new UnqualifiedName(table.getSchemaName());
            final UnqualifiedName tableName = new UnqualifiedName(table.getTableName());
            final PEDatabase peDb = context.findPEDatabase(databaseName);
            if (peDb != null) {
                // TODO: handle views in the analyzer
                final TableInstance ti = peDb.getSchema().buildInstance(context, tableName, null);
                final PEAbstractTable<?> tabular = ti.getAbstractTable();
                if (tabular.isView() != Boolean.TRUE) {
                    final PETable peTable = tabular.asTable();
                    if (peTable != null) {
                        corpusStats.resolveTableColumns(peTable, context);
                        corpusStats.resolveTableForeignKeys(peTable, context);
                    }
                }
            }
        }
    }

    public abstract void onStatement(String sql, SourcePosition sp, Statement s) throws Throwable;

    public abstract void onException(String sql, SourcePosition sp, Throwable t);

    public abstract void onNotice(String sql, SourcePosition sp, String message);

    public SourcePosition convert(LineInfo li, AnalyzerSource src) {
        if (src != null) {
            return src.convert(li);
        }

        return new SourcePosition(li);
    }

    private TransientEngine buildExecutionEngine(final int numPersistentSites) throws Throwable {
        final TransientEngine engine = Singletons.require(TransientEngineFactory.class).create("atemp");

        final List<String> persistentSiteNames = new ArrayList<String>();
        final List<String> persistentDeclarations = new ArrayList<String>();
        for (int i = 1; i <= numPersistentSites; ++i) {
            final String siteName = "site" + i;
            final String siteHostUrl = "jdbc:mysql://s" + i + "/db" + i;
            persistentDeclarations.add("create persistent site " + siteName + " url='" + siteHostUrl + "' user='"
                    + TRANSIENT_SITE_USER + "' password='" + TRANSIENT_SITE_PASS + "'");
            persistentSiteNames.add(siteName);
        }
        persistentDeclarations.add("create persistent group g1 add " + StringUtils.join(persistentSiteNames, ','));

        engine.parse(persistentDeclarations.toArray(new String[] {}));

        return engine;
    }

    public void refresh() {
        tee.getPersistenceContext().refresh(true);
    }

    public AnalyzerOptions getOptions() {
        return options;
    }

    public ParserInvoker getInvoker() {
        return invoker;
    }

    public void setSource(AnalyzerSource as) {
        currentSource = as;
    }

    public AnalyzerSource getCurrentSource() {
        return currentSource;
    }

    public String getPrimaryDatabase() {
        return primaryDB;
    }

    @SuppressWarnings("unused")
    public void onFinished() throws PEException {
        // does nothing by default
    }

    protected static class AnalyzerInvoker extends ParserInvoker {

        protected Analyzer sink;
        private final Map<Integer, String> cdb;
        private Integer lastConn;

        public AnalyzerInvoker(Analyzer a) {
            super(ParserOptions.NONE);
            sink = a;
            cdb = new HashMap<Integer, String>();
            lastConn = null;
        }

        @SuppressWarnings("unused")
        public boolean omit(LineInfo info, String line) {
            return false;
        }

        @Override
        public String parseOneLine(LineInfo info, String line) throws Throwable {
            final String trimmed = line.trim();
            if (trimmed.equalsIgnoreCase("Connect")) {
                return line;
            }
            if (trimmed.equalsIgnoreCase("Quit")) {
                cdb.remove(info.getConnectionID());
                return line;
            }
            if ((lastConn != null) && (lastConn.intValue() != info.getConnectionID())) {
                // we have to execute a use first
                final String db = cdb.get(info.getConnectionID());
                if (db != null) {
                    sink.tee.parse(new String[] { "use " + db });
                }
            }
            lastConn = info.getConnectionID();

            if (omit(info, line)) {
                return line;
            }

            final byte[] bytes = PECharsetUtils.getBytes(line, PECharsetUtils.ISO_8859_1);

            sink.refresh();
            try {
                final List<Statement> stmts = InvokeParser
                        .parse(bytes, sink.tee.getPersistenceContext(), PECharsetUtils.ISO_8859_1).getStatements();
                for (final Statement s : stmts) {
                    if (s instanceof UseStatement) {
                        if (s instanceof UseDatabaseStatement) {
                            final UseDatabaseStatement us = (UseDatabaseStatement) s;
                            cdb.put(info.getConnectionID(),
                                    us.getDatabase(sink.tee.getPersistenceContext()).getName().get());
                            sink.tee.dispatch(s);
                        } else if (s instanceof UseTenantStatement) {
                            final UseTenantStatement us = (UseTenantStatement) s;
                            cdb.put(info.getConnectionID(), us.getTenant().getExternalID());
                            sink.tee.dispatch(s);
                        }
                    }
                    sink.onStatement(line, sink.convert(info, sink.getCurrentSource()), s);
                }
            } catch (final ParserException se) {
                sink.onException(line, sink.convert(info, sink.getCurrentSource()), se);
            }
            return line;
        }

    }
}