All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dk.eobjects.metamodel.QueryPostprocessDataContextStrategy Maven / Gradle / Ivy

The newest version!
/**
 *  This file is part of MetaModel.
 *
 *  MetaModel is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  MetaModel 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with MetaModel.  If not, see .
 */
package dk.eobjects.metamodel;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import dk.eobjects.metamodel.data.DataSet;
import dk.eobjects.metamodel.data.MaxRowsDataSetStrategyWrapper;
import dk.eobjects.metamodel.data.Row;
import dk.eobjects.metamodel.detect.ColumnTypeTransformer;
import dk.eobjects.metamodel.detect.ColumnTypeTransformerDataSetStrategy;
import dk.eobjects.metamodel.query.FilterItem;
import dk.eobjects.metamodel.query.FromItem;
import dk.eobjects.metamodel.query.GroupByItem;
import dk.eobjects.metamodel.query.JoinType;
import dk.eobjects.metamodel.query.OperatorType;
import dk.eobjects.metamodel.query.OrderByItem;
import dk.eobjects.metamodel.query.Query;
import dk.eobjects.metamodel.query.SelectItem;
import dk.eobjects.metamodel.schema.Column;
import dk.eobjects.metamodel.schema.ColumnType;
import dk.eobjects.metamodel.schema.Relationship;
import dk.eobjects.metamodel.schema.Schema;
import dk.eobjects.metamodel.schema.Table;
import dk.eobjects.metamodel.schema.TableType;

/**
 * Abstract DataContextStrategy for data sources that do not support SQL
 * queries. Instead this superclass only requires that a subclass can
 * materialize a single table at a time. Then the query will be executed by post
 * processing data client-side.
 */
public abstract class QueryPostprocessDataContextStrategy implements
		IDataContextStrategy {

	public static final String INFORMATION_SCHEMA_NAME = "information_schema";
	protected final Log _log = LogFactory.getLog(getClass());
	private Map _columnTypeTransformers = new HashMap();
	private String[] _schemaNames;
	private Schema _mainSchema;

	public void setColumnTypeTransformer(String qualifiedColumnName,
			ColumnTypeTransformer transformer) {
		_columnTypeTransformers.put(qualifiedColumnName, transformer);
	}

	public void setColumnTypeTransformer(Column column,
			ColumnTypeTransformer transformer) {
		_columnTypeTransformers.put(column.getQualifiedLabel(), transformer);
	}

	public DataSet executeQuery(Query query) {
		List selectItems = query.getSelectClause().getItems();
		List fromItems = query.getFromClause().getItems();
		List whereItems = query.getWhereClause().getItems();
		List whereSelectItems = query.getWhereClause()
				.getEvaluatedSelectItems();
		List groupByItems = query.getGroupByClause().getItems();
		List havingSelectItems = query.getHavingClause()
				.getEvaluatedSelectItems();
		List havingItems = query.getHavingClause().getItems();
		List orderByItems = query.getOrderByClause().getItems();

		// Check for very simple queries with max rows property set (typically
		// preview), see Ticket #187
		previewTable: if (query.getMaxRows() != null && whereItems.isEmpty()
				&& groupByItems.isEmpty() && havingItems.isEmpty()
				&& orderByItems.isEmpty() && fromItems.size() == 1) {
			Table table = fromItems.get(0).getTable();
			if (table != null) {
				Column[] columns = new Column[selectItems.size()];
				int i = 0;
				for (Iterator it = selectItems.iterator(); it
						.hasNext();) {
					SelectItem item = it.next();
					if (item.getFunction() != null
							|| item.getExpression() != null) {
						break previewTable;
					}
					columns[i] = item.getColumn();
					i++;
				}

				DataSet dataSet = materializeTable(table, columns, query
						.getMaxRows());
				dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
				return dataSet;
			}
		}

		// Materialize the tables in the from clause
		DataSet[] fromDataSets = new DataSet[fromItems.size()];
		for (int i = 0; i < fromDataSets.length; i++) {
			FromItem fromItem = fromItems.get(i);
			fromDataSets[i] = materializeFromItem(fromItem);
		}

		// Creates a list for all select items that are needed to execute query
		// (some may only be used as part of a filter, but not shown in result)
		List workSelectItems = collectSelectItems(selectItems,
				whereSelectItems, havingSelectItems);

		// Execute the query using the raw data
		DataSet dataSet = MetaModelHelper.getCarthesianProduct(fromDataSets,
				whereItems);
		if (groupByItems.size() > 0) {
			dataSet = MetaModelHelper.getGrouped(workSelectItems, dataSet,
					groupByItems);
		} else {
			dataSet = MetaModelHelper.getAggregated(workSelectItems, dataSet);
		}
		dataSet = MetaModelHelper.getFiltered(dataSet, havingItems);

		if (query.getSelectClause().isDistinct()) {
			dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
			dataSet = MetaModelHelper.getDistinct(dataSet);
			dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
		} else {
			dataSet = MetaModelHelper.getOrdered(dataSet, orderByItems);
			dataSet = MetaModelHelper.getSelection(selectItems, dataSet);
		}

		if (query.getMaxRows() != null) {
			dataSet = new DataSet(new MaxRowsDataSetStrategyWrapper(dataSet,
					query.getMaxRows()));
		}
		return dataSet;
	}

	public QueryPostprocessDataContextStrategy transformColumnTypes(Table table) {
		return transformColumnTypes(table.getColumnsOfType(ColumnType.VARCHAR));
	}

	public QueryPostprocessDataContextStrategy transformColumnTypes(
			Column... columns) {
		Table[] tables = MetaModelHelper.getTables(columns);
		for (Table table : tables) {
			Column[] columnsToQuery = MetaModelHelper.getTableColumns(table,
					columns);
			columnsToQuery = MetaModelHelper.getColumnsByType(columnsToQuery,
					ColumnType.VARCHAR);
			DataSet dataSet = executeQuery(new Query().select(columnsToQuery)
					.from(table));

			for (Column column : columnsToQuery) {
				ColumnTypeTransformer transformer = new ColumnTypeTransformer(
						column);
				_columnTypeTransformers.put(column.getQualifiedLabel(),
						transformer);
			}
			while (dataSet.next()) {
				Row row = dataSet.getRow();
				for (Column column : columnsToQuery) {
					ColumnTypeTransformer transformer = _columnTypeTransformers
							.get(column.getQualifiedLabel());
					Object value = row.getValue(column);
					if (value instanceof String || value == null) {
						transformer.registerValue((String) value);
					}
				}
			}
			for (Column column : columnsToQuery) {
				ColumnTypeTransformer transformer = _columnTypeTransformers
						.get(column.getQualifiedLabel());
				transformer.narrowColumnType();
				if (column.getType() == ColumnType.VARCHAR) {
					_columnTypeTransformers.remove(column.getQualifiedLabel());
				}
			}
		}
		return this;
	}

	/**
	 * Performs column type transformation for the whole schema
	 */
	public QueryPostprocessDataContextStrategy autoTransformColumnTypes() {
		Schema schema = getMainSchemaInternal();
		Table[] tables = schema.getTables();
		for (Table table : tables) {
			transformColumnTypes(table);
		}
		return this;
	}

	public DataSet materializeFromItem(FromItem fromItem) {
		DataSet dataSet;
		JoinType joinType = fromItem.getJoin();
		if (fromItem.getTable() != null) {
			// We need to materialize a single table
			Table table = fromItem.getTable();
			Column[] columns = table.getColumns();
			Query query = fromItem.getQuery();
			if (query != null) {
				List selectItems = collectSelectItems(query
						.getSelectClause().getItems(), query.getWhereClause()
						.getEvaluatedSelectItems(), query.getHavingClause()
						.getEvaluatedSelectItems());
				List colsToMaterialize = new ArrayList();
				for (Iterator it = selectItems.iterator(); it
						.hasNext();) {
					SelectItem selectItem = it.next();
					Column column = selectItem.getColumn();
					if (column != null && column.getTable() != null
							&& column.getTable().equals(table)) {
						colsToMaterialize.add(column);
					}
				}
				selectItems = null;
				columns = colsToMaterialize
						.toArray(new Column[colsToMaterialize.size()]);
			}
			// Dispatching to the concrete subclass of
			// QueryPostprocessDataContextStrategy
			if (_log.isDebugEnabled()) {
				_log.debug("calling materializeTable(" + table.getName() + ","
						+ ArrayUtils.toString(columns) + ",-1");
			}
			dataSet = materializeTable(table, columns, -1);
		} else if (joinType != null) {
			// We need to (recursively) materialize a joined FromItem
			if (fromItem.getLeftSide() == null
					|| fromItem.getRightSide() == null) {
				throw new IllegalArgumentException(
						"Joined FromItem requires both left and right side: "
								+ fromItem);
			}
			DataSet[] fromItemDataSets = new DataSet[2];
			fromItemDataSets[0] = materializeFromItem(fromItem.getLeftSide());
			fromItemDataSets[1] = materializeFromItem(fromItem.getRightSide());
			SelectItem[] leftOn = fromItem.getLeftOn();
			SelectItem[] rightOn = fromItem.getRightOn();
			FilterItem[] onConditions = new FilterItem[leftOn.length];
			for (int i = 0; i < onConditions.length; i++) {
				FilterItem whereItem = new FilterItem(leftOn[i],
						OperatorType.EQUALS_TO, rightOn[i]);
				onConditions[i] = whereItem;
			}
			if (joinType == JoinType.INNER) {
				dataSet = MetaModelHelper.getCarthesianProduct(
						fromItemDataSets, onConditions);
			} else if (joinType == JoinType.LEFT) {
				dataSet = MetaModelHelper.getLeftJoin(fromItemDataSets[0],
						fromItemDataSets[1], onConditions);
			} else if (joinType == JoinType.RIGHT) {
				dataSet = MetaModelHelper.getRightJoin(fromItemDataSets[0],
						fromItemDataSets[1], onConditions);
			} else {
				throw new IllegalArgumentException(
						"FromItem type not supported: " + fromItem);
			}
		} else if (fromItem.getSubQuery() != null) {
			// We need to (recursively) materialize a subquery
			dataSet = executeQuery(fromItem.getSubQuery());
		} else {
			throw new IllegalArgumentException("FromItem type not supported: "
					+ fromItem);
		}
		if (dataSet == null) {
			throw new IllegalStateException(
					"FromItem was not succesfully materialized: " + fromItem);
		}
		return dataSet;
	}

	protected DataSet materializeTable(Table table, Column[] columns,
			int maxRows) {
		if (table != null) {
			Schema schema = table.getSchema();
			if (INFORMATION_SCHEMA_NAME.equals(schema.getName())) {
				return materializeInformationSchemaTable(table, columns,
						maxRows);
			} else {
				DataSet tableDataSet = materializeMainSchemaTable(table,
						columns, maxRows);
				if (_columnTypeTransformers.isEmpty()) {
					return tableDataSet;
				} else {
					DataSet transformedDataSet = new DataSet(
							new ColumnTypeTransformerDataSetStrategy(
									tableDataSet, _columnTypeTransformers));
					return transformedDataSet;
				}
			}
		}
		return null;
	}

	private List collectSelectItems(
			List selectClauseItems,
			List whereClauseItems,
			List havingClauseItems) {
		ArrayList result = new ArrayList(
				selectClauseItems);
		for (SelectItem selectItem : whereClauseItems) {
			if (!result.contains(selectItem)) {
				result.add(selectItem);
			}
		}
		for (SelectItem selectItem : havingClauseItems) {
			if (!result.contains(selectItem)) {
				result.add(selectItem);
			}
		}
		return result;
	}

	public String[] getSchemaNames() throws MetaModelException {
		if (_schemaNames == null) {
			_schemaNames = new String[2];
			_schemaNames[0] = INFORMATION_SCHEMA_NAME;
			_schemaNames[1] = getMainSchemaName();
		}
		return _schemaNames;
	}

	public String getDefaultSchemaName() throws MetaModelException {
		return getSchemaNames()[1];
	}

	public Schema getSchemaByName(String name) throws MetaModelException {
		if (name != null) {
			if (name.equals(getSchemaNames()[1])) {
				return getMainSchemaInternal();
			} else if (name.equals(INFORMATION_SCHEMA_NAME)) {
				return getInformationSchema();
			}
		}
		return null;
	}

	private Schema getInformationSchema() {
		// Create schema
		Schema informationSchema = new Schema(INFORMATION_SCHEMA_NAME);
		Table tablesTable = new Table("tables", TableType.TABLE,
				informationSchema);
		Table columnsTable = new Table("columns", TableType.TABLE,
				informationSchema);
		Table relationshipsTable = new Table("relationships", TableType.TABLE,
				informationSchema);
		informationSchema.addTable(tablesTable).addTable(columnsTable)
				.addTable(relationshipsTable);

		// Create "tables" table: name, type, num_columns, remarks
		tablesTable.addColumn(new Column("name", ColumnType.VARCHAR,
				tablesTable, 0, false));
		tablesTable.addColumn(new Column("type", ColumnType.VARCHAR,
				tablesTable, 1, true));
		tablesTable.addColumn(new Column("num_columns", ColumnType.INTEGER,
				tablesTable, 2, true));
		tablesTable.addColumn(new Column("remarks", ColumnType.VARCHAR,
				tablesTable, 3, true));

		// Create "columns" table: name, type, native_type, size, nullable,
		// indexed, table, remarks
		columnsTable.addColumn(new Column("name", ColumnType.VARCHAR,
				columnsTable, 0, false));
		columnsTable.addColumn(new Column("type", ColumnType.VARCHAR,
				columnsTable, 1, true));
		columnsTable.addColumn(new Column("native_type", ColumnType.VARCHAR,
				columnsTable, 2, true));
		columnsTable.addColumn(new Column("size", ColumnType.INTEGER,
				columnsTable, 3, true));
		columnsTable.addColumn(new Column("nullable", ColumnType.BOOLEAN,
				columnsTable, 4, true));
		columnsTable.addColumn(new Column("indexed", ColumnType.BOOLEAN,
				columnsTable, 5, true));
		columnsTable.addColumn(new Column("table", ColumnType.VARCHAR,
				columnsTable, 6, false));
		columnsTable.addColumn(new Column("remarks", ColumnType.VARCHAR,
				columnsTable, 7, true));

		// Create "relationships" table: primary_table, primary_column,
		// foreign_table, foreign_column
		relationshipsTable.addColumn(new Column("primary_table",
				ColumnType.VARCHAR, relationshipsTable, 0, false));
		relationshipsTable.addColumn(new Column("primary_column",
				ColumnType.VARCHAR, relationshipsTable, 1, false));
		relationshipsTable.addColumn(new Column("foreign_table",
				ColumnType.VARCHAR, relationshipsTable, 2, false));
		relationshipsTable.addColumn(new Column("foreign_column",
				ColumnType.VARCHAR, relationshipsTable, 3, false));

		Relationship.createRelationship(tablesTable.getColumnByName("name"),
				columnsTable.getColumnByName("table"));
		Relationship.createRelationship(tablesTable.getColumnByName("name"),
				relationshipsTable.getColumnByName("primary_table"));
		Relationship.createRelationship(tablesTable.getColumnByName("name"),
				relationshipsTable.getColumnByName("foreign_table"));
		Relationship.createRelationship(columnsTable.getColumnByName("name"),
				relationshipsTable.getColumnByName("primary_column"));
		Relationship.createRelationship(columnsTable.getColumnByName("name"),
				relationshipsTable.getColumnByName("foreign_column"));

		return informationSchema;
	}

	private DataSet materializeInformationSchemaTable(Table table,
			Column[] columns, int maxRows) {
		DataSet dataSet = null;
		String tableName = table.getName();
		if ("tables".equals(tableName)) {
			// "tables" columns: name, type, num_columns, remarks
			List data = new ArrayList();
			for (Table t : getMainSchemaInternal().getTables()) {
				String typeString = null;
				if (t.getType() != null) {
					typeString = t.getType().toString();
				}
				data.add(new Object[] { t.getName(), typeString,
						t.getColumnCount(), t.getRemarks() });
			}

			dataSet = new DataSet(MetaModelHelper.createSelectItems(table
					.getColumns()), data);
		} else if ("columns".equals(tableName)) {
			// "columns" columns: name, type, native_type, size, nullable,
			// indexed, table, remarks
			List data = new ArrayList();
			for (Table t : getMainSchemaInternal().getTables()) {
				for (Column c : t.getColumns()) {
					String typeString = null;
					if (t.getType() != null) {
						typeString = c.getType().toString();
					}
					data.add(new Object[] { c.getName(), typeString,
							c.getNativeType(), c.getColumnSize(),
							c.isNullable(), c.isIndexed(), t.getName(),
							c.getRemarks() });
				}
			}
			dataSet = new DataSet(MetaModelHelper.createSelectItems(table
					.getColumns()), data);
		} else if ("relationships".equals(tableName)) {
			// "relationships" columns: primary_table, primary_column,
			// foreign_table, foreign_column
			List data = new ArrayList();
			for (Relationship r : getMainSchemaInternal().getRelationships()) {
				Column[] primaryColumns = r.getPrimaryColumns();
				Column[] foreignColumns = r.getForeignColumns();
				Table pTable = r.getPrimaryTable();
				Table fTable = r.getForeignTable();
				for (int i = 0; i < primaryColumns.length; i++) {
					Column pColumn = primaryColumns[i];
					Column fColumn = foreignColumns[i];
					data.add(new Object[] { pTable.getName(),
							pColumn.getName(), fTable.getName(),
							fColumn.getName() });
				}
			}
			dataSet = new DataSet(MetaModelHelper.createSelectItems(table
					.getColumns()), data);
		} else {
			throw new IllegalArgumentException(
					"Cannot materialize non information_schema table: " + table);
		}

		// Handle column subset
		dataSet = MetaModelHelper.getSelection(MetaModelHelper
				.createSelectItems(columns), dataSet);

		// Handle maxRows
		if (maxRows != -1) {
			dataSet = new DataSet(new MaxRowsDataSetStrategyWrapper(dataSet,
					maxRows));
		}
		return dataSet;
	}

	protected Schema getMainSchemaInternal() {
		if (_mainSchema == null) {
			_mainSchema = getMainSchema();
		}
		return _mainSchema;
	}

	/**
	 * @return the main schema that subclasses of this class produce
	 */
	protected abstract Schema getMainSchema() throws MetaModelException;

	/**
	 * @return the name of the main schema that subclasses of this class produce
	 */
	protected abstract String getMainSchemaName() throws MetaModelException;

	/**
	 * Executes a simple one-table query against a table in the main schema of
	 * the subclasses of this class.
	 * 
	 * @param table
	 *            the table to query
	 * @param columns
	 *            the columns to include
	 * @param maxRows
	 *            the maximum amount of rows needed or -1 if all rows are
	 *            wanted.
	 * @return a dataset with the raw table content of the colums
	 */
	public abstract DataSet materializeMainSchemaTable(Table table,
			Column[] columns, int maxRows);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy