/* * Copyright 2007 - 2017 the original author or authors. * * 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.sf.jailer.entitygraph; import java.io.IOException; import java.io.OutputStreamWriter; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import net.sf.jailer.ExecutionContext; import net.sf.jailer.configuration.DBMS; import net.sf.jailer.database.SQLDialect; import net.sf.jailer.database.Session; import net.sf.jailer.database.Session.ResultSetReader; import net.sf.jailer.datamodel.Association; import net.sf.jailer.datamodel.Column; import net.sf.jailer.datamodel.DataModel; import net.sf.jailer.datamodel.PrimaryKey; import net.sf.jailer.datamodel.Table; import net.sf.jailer.importfilter.ImportFilterManager; import net.sf.jailer.subsetting.TransformerFactory; import net.sf.jailer.util.JobManager; /** * Persistent graph of entities. * * @author Ralf Wisser */ public abstract class EntityGraph { /** * Name of the graph-table. */ public static final String ENTITY_GRAPH = "JAILER_GRAPH"; /** * Name of the (helper) set-table. */ public static final String ENTITY_SET_ELEMENT = "JAILER_SET"; /** * Name of the entity-table. */ public static final String ENTITY = "JAILER_ENTITY"; /** * Name of the dependency-table. */ public static final String DEPENDENCY = "JAILER_DEPENDENCY"; /** * Sets birthday of subject rows. * * @param birthdayOfSubject birthday of subject rows */ public abstract void setBirthdayOfSubject(int birthdayOfSubject); public final DataModel dataModel; /** * The execution context. */ protected final ExecutionContext executionContext; protected boolean inDeleteMode = false; /** * The unique ID of the graph. */ public final int graphID; protected EntityGraph(int graphID, DataModel dataModel, ExecutionContext executionContext) { this.executionContext = executionContext; this.graphID = graphID; this.dataModel = dataModel; } /** * Unique IDs for each association to be used for explanation. */ public Map<Association, Integer> explainIdOfAssociation = new HashMap<Association, Integer>(); /** * Copies an entity-graph. * * @param graph the graph to copy * @param graphID the unique ID of the graph * @param session for executing SQL-Statements * @return the newly created entity-graph * @throws Exception */ public abstract EntityGraph copy(int graphID, Session session) throws SQLException; /** * Finds an entity-graph. * * @param graphID the unique ID of the graph * @param universalPrimaryKey the universal primary key * @param session for executing SQL-Statements * @return the entity-graph * @throws Exception */ public abstract EntityGraph find(int graphID, Session session, PrimaryKey universalPrimaryKey) throws SQLException, Exception; /** * Gets the age of the graph. * * @return the age of the graph */ public abstract int getAge() throws SQLException; /** * Sets the age of the graph. * * @param age the age of the graph */ public abstract void setAge(int age) throws SQLException; /** * Gets the number of entities in the graph. * * @return the number of entities in the graph */ public abstract long getSize() throws SQLException; /** * Gets the number of entities form given tables in the graph. * * @return the number of entities in the graph * @throws SQLException */ public long getSize(final Set<Table> tables) throws SQLException { final long[] total = new long[1]; total[0] = 0; getSession().executeQuery("Select type, count(*) From " + dmlTableReference(ENTITY, getSession()) + " Where r_entitygraph=" + graphID + " and birthday>=0 group by type", new Session.AbstractResultSetReader() { public void readCurrentRow(ResultSet resultSet) throws SQLException { Table table = dataModel.getTableByOrdinal(resultSet.getInt(1)); if (tables.contains(table)) { long count = resultSet.getLong(2); total[0] += count; } } }); return total[0]; } /** * Deletes the graph. */ public abstract void delete() throws SQLException; /** * Adds entities to the graph. * * @param table the table * @param condition the condition in SQL that the entities must fulfill * @param today the birthday of the new entities * * @return row-count */ public abstract long addEntities(Table table, String condition, int today) throws SQLException; /** * Resolves an association. Retrieves and adds all entities * associated with an entity born yesterday in the graph * and adds the dependencies. * * @param table the table * @param association the association to resolve * @param today the birthday of the new entities * * @return row-count or -1, if association is ignored */ public abstract long resolveAssociation(Table table, Association association, int today) throws SQLException; /** * Adds dependencies. * * @param from source of dependency * @param fromAlias alias for from-table * @param to destination of dependency * @param toAlias alias for to-table * @param condition condition of dependency * @param aggregationId id of aggregation association (for XML export), 0 if not applicable * @param dependencyId id of dependency */ public abstract void addDependencies(Table from, String fromAlias, Table to, String toAlias, String condition, int aggregationId, int dependencyId, boolean isAssociationReversed) throws SQLException; /** * Gets distinct association-ids of all edged. */ public abstract Set<Integer> getDistinctDependencyIDs() throws SQLException; /** * Marks all entities of a given table which don't dependent on other entities, * s.t. they can be read and deleted. */ public abstract void markIndependentEntities(Table table) throws SQLException; /** * Marks all rows which are not target of a dependency. */ public abstract void markRoots(Table table) throws SQLException; /** * Reads all entities of a given table which are marked as independent or as roots. * * @param table the table * @param orderByPK if <code>true</code>, result will be ordered by primary keys */ public abstract void readMarkedEntities(Table table, boolean orderByPK) throws SQLException; /** * Reads all entities of a given table which are marked as independent or as roots. * * @param reader for reading the result-set * @param table the table * @param orderByPK if <code>true</code>, result will be ordered by primary keys */ public abstract void readMarkedEntities(Table table, Session.ResultSetReader reader, String selectionSchema, String originalPKAliasPrefix, boolean orderByPK) throws SQLException; /** * Unites the graph with another one and deletes the other graph. * * @param graph the graph to be united with this graph */ public abstract void uniteWith(EntityGraph graph) throws SQLException; /** * Reads all entities of a given table. * * @param table the table * @param orderByPK if <code>true</code>, result will be ordered by primary keys */ public abstract void readEntities(Table table, boolean orderByPK) throws SQLException; /** * Updates columns of a table. * * @param table the table * @param columns the columns; */ public abstract void updateEntities(Table table, Set<Column> columns, OutputStreamWriter scriptFileWriter, DBMS targetConfiguration) throws SQLException; /** * Reads some columns of all entities of a given table without using filters. * * @param table the table * @param columns the columns * @param reader to read */ public abstract long readUnfilteredEntityColumns(final Table table, final List<Column> columns, final Session.ResultSetReader reader) throws SQLException; /** * Deletes all entities which are marked as independent. */ public abstract void deleteIndependentEntities(Table table) throws SQLException; /** * Deletes all entities from a given table. */ public abstract long deleteEntities(Table table) throws SQLException; /** * Counts the entities of a given table in this graph. * * @param table the table * @return the number of entities from table in this graph */ public abstract long countEntities(Table table) throws SQLException; /** * Removes all entities from this graph which are associated with an entity * outside the graph. * * @param deletedEntitiesAreMarked if true, consider entity as deleted if its birthday is negative * @param association the association * @return number of removed entities */ public abstract long removeAssociatedDestinations(Association association, boolean deletedEntitiesAreMarked) throws SQLException; /** * Reads all entities which depends on given entity. * * @param table the table from which to read entities * @param association the dependency * @param resultSet current row is given entity * @param reader reads the entities * @param selectionSchema the selection schema */ public abstract void readDependentEntities(Table table, Association association, ResultSet resultSet, ResultSetMetaData resultSetMetaData, ResultSetReader reader, Map<String, Integer> typeCache, String selectionSchema, String originalPKAliasPrefix) throws SQLException; /** * Marks all entities which depends on given entity as traversed. * * @param table the table from which to read entities * @param association the dependency * @param resultSet current row is given entity */ public abstract void markDependentEntitiesAsTraversed(Association association, ResultSet resultSet, ResultSetMetaData resultSetMetaData, Map<String, Integer> typeCache) throws SQLException; /** * Reads all non-traversed dependencies. * * @param table the source of dependencies to look for * @param reader reads the entities */ public abstract void readNonTraversedDependencies(Table table, ResultSetReader reader) throws SQLException; /** * Removes all reflexive dependencies of given table. * * @param table the table */ public abstract void removeReflexiveDependencies(Table table) throws SQLException; /** * Gets some statistical information. */ public abstract List<String> getStatistics(final DataModel dataModel, Set<Table> tables) throws SQLException; /** * Gets total row-count. * * @return total row-count */ public abstract long getTotalRowcount(); /** * Whether or not to store additional information in order to create a 'explain.log'. * * @param explain <code>true</code> iff predecessors of each entity must be stored */ public abstract void setExplain(boolean explain); /** * Gets the universal primary key. * * @return the universal primary key */ public abstract PrimaryKey getUniversalPrimaryKey(); /** * Shuts down statement-executor. */ public abstract void shutDown() throws SQLException; /** * Gets the session. * * @return the session */ public abstract Session getSession(); /** * Creates a unique ID for a new graph. * * @return a unique ID */ public static int createUniqueGraphID() { return Math.abs((int) System.currentTimeMillis()) % 65536; } private int lobCount = 0; /** * Increments lob-counter and returns new value. */ public synchronized int incLobCount() { return ++lobCount; } public abstract DataModel getDatamodel(); public static long maxTotalRowcount; /** * Closes the graph. Deletes the local database. */ abstract public void close() throws SQLException; /** * Gets some statistical information. */ protected final List<String> getStatistics(Session session, final DataModel dataModel, Set<Table> tables) throws SQLException { final List<String> statistic = new ArrayList<String>(); final long[] total = new long[1]; total[0] = 0; final Set<Table> remaining = new HashSet<Table>(tables); session.executeQuery("Select type, count(*) From " + dmlTableReference(ENTITY, session) + " Where r_entitygraph=" + graphID + " and birthday>=0 group by type", new Session.AbstractResultSetReader() { public void readCurrentRow(ResultSet resultSet) throws SQLException { Table table = dataModel.getTableByOrdinal(resultSet.getInt(1)); String type = dataModel.getDisplayName(table); remaining.remove(table); long count = resultSet.getLong(2); total[0] += count; while (type.length() < 30) { type = type + " "; } statistic.add(type + " " + count); } }); for (Table table: remaining) { String type = dataModel.getDisplayName(table); while (type.length() < 30) { type = type + " "; } statistic.add(type + " 0"); } Collections.sort(statistic); statistic.add(0, "" + total[0]); return statistic; } /** * Removes all dependencies for a given association. * * @param association the asociation */ public void removeDependencies(Association association) throws SQLException { String delete; delete = "Delete from " + dmlTableReference(DEPENDENCY, getSession()) + " Where depend_id=" + association.getId() + " and r_entitygraph=" + graphID; getSession().executeUpdate(delete); } public abstract Session getTargetSession(); public void setDeleteMode(boolean deleteMode) { inDeleteMode = deleteMode; } protected int typeName(Table table) { return table.getOrdinal(); } /** * The {@link TransformerFactory}. */ private TransformerFactory transformerFactory; /** * The {@link ImportFilterManager}. */ protected ImportFilterManager importFilterManager; /** * Sets the {@link TransformerFactory}. * * @param transformerFactory the factory */ public void setTransformerFactory(TransformerFactory transformerFactory) { this.transformerFactory = transformerFactory; } /** * Gets the {@link TransformerFactory}. * * @return the factory */ public TransformerFactory getTransformerFactory() { return transformerFactory; } /** * Sets the {@link ImportFilterManager} * * @param importFilterManager the {@link ImportFilterManager} */ public void setImportFilterManager(ImportFilterManager importFilterManager) { this.importFilterManager = importFilterManager; } /** * Gets the {@link ImportFilterManager} * * @return the {@link ImportFilterManager} */ public ImportFilterManager getImportFilterManager() { return importFilterManager; } /** * Insert the values of columns with non-derived-import-filters into the local database. */ public void fillAndWriteMappingTables(JobManager jobManager, final OutputStreamWriter dmlResultWriter, int numberOfEntities, final Session targetSession, final DBMS targetDBMSConfiguration, DBMS dbmsConfiguration) throws SQLException, IOException { if (importFilterManager != null) { importFilterManager.createMappingTables(dbmsConfiguration, dmlResultWriter); importFilterManager.fillAndWriteMappingTables(this, jobManager, dmlResultWriter, numberOfEntities, targetSession, targetDBMSConfiguration); } } /** * Creates the DROP-statements for the mapping tables. */ public void dropMappingTables(OutputStreamWriter result, DBMS targetDBMSConfiguration) throws IOException, SQLException { if (importFilterManager != null) { importFilterManager.dropMappingTables(result); } } /** * Gets table reference for DML statements for a given working table. * * @param tableName the working table * @param session holds connection to DBMS * @return table reference for the working table * @throws SQLException */ protected String dmlTableReference(String tableName, Session session) throws SQLException { return SQLDialect.dmlTableReference(tableName, session, executionContext); } }