/* Copyright (C) 2006 EBI This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the itmplied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.biomart.builder.controller.dialects; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.biomart.builder.exceptions.ConstructorException; import org.biomart.builder.exceptions.PartitionException; import org.biomart.builder.model.Column; import org.biomart.builder.model.DataLink; import org.biomart.builder.model.DataSet; import org.biomart.builder.model.MartConstructorAction; import org.biomart.builder.model.PartitionTable; import org.biomart.builder.model.Relation; import org.biomart.builder.model.Schema; import org.biomart.builder.model.Table; import org.biomart.builder.model.DataSet.DataSetTable; import org.biomart.builder.model.Schema.JDBCSchema; import org.biomart.builder.model.TransformationUnit.UnrollTable; import org.biomart.common.resources.Log; import org.biomart.common.resources.Resources; /** * This class provides methods which generate atomic DDL or SQL statements. It * could be an interface, except for the static initializers which the * implementing classes use to register themselves. Once registered, the * {@link DatabaseDialect#getDialect(DataLink)} method will be able to identify * which dialect to use for a given {@link DataLink}. * * @author Richard Holland <holland@ebi.ac.uk> * @version $Revision: 1.25 $, $Date: 2008-02-20 11:47:30 $, modified by * $Author: rh4 $ * @since 0.5 */ public abstract class DatabaseDialect { private static final Set dialects = new HashSet(); private int maxTableNameLength = Integer.MAX_VALUE; private int maxColumnNameLength = Integer.MAX_VALUE; /** * Registers all known dialects for use with this system. Each implementing * class should be added to this list. */ static { DatabaseDialect.dialects.add(new MySQLDialect()); DatabaseDialect.dialects.add(new OracleDialect()); DatabaseDialect.dialects.add(new PostgreSQLDialect()); } /** * Common constructor for subclasses does nothing except log that the * subclass has been created and registered. */ protected DatabaseDialect() { Log.info("Registering dialect: " + this.getClass().getName()); } /** * Work out what kind of dialect to use for the given data link. It does * this by checking each registered dialect to see if the * {@link DatabaseDialect#understandsDataLink(DataLink)} method returns * <tt>true</tt> for that data link. It returns the first one that returns * <tt>true</tt>, ignoring any subsequent ones. * <p> * Note that it must be able to open the connection provided by * <tt>dataLink</tt> in order to get some basic attributes from it that * determine compatibility, such as maximum table and column name lengths. * * @param dataLink * the data link to work out the dialect for. * @return the appropriate DatabaseDialect, or <tt>null</tt> if none * found. * @throws SQLException * if it was unable to determine basic attributes from the * connection provided. */ public static DatabaseDialect getDialect(final DataLink dataLink) throws SQLException { for (final Iterator i = DatabaseDialect.dialects.iterator(); i .hasNext();) { final DatabaseDialect d = (DatabaseDialect) i.next(); if (d.understandsDataLink(dataLink)) { // Get maximum table/col name lengths. if (dataLink instanceof JDBCSchema) { final DatabaseMetaData dmd = ((JDBCSchema) dataLink) .getConnection(null).getMetaData(); d.setMaxColumnNameLength(dmd.getMaxColumnNameLength()); d.setMaxTableNameLength(dmd.getMaxTableNameLength()); } return d; } } return null; } /** * Given a particular action, return a SQL or DDL statement that will * perform it. If multiple statements are required to perform the action, * they are returned as an array. Each line of the array is considered to be * a complete single statement which could, for example, be executed * directly with a JDBC database function such as * {@link PreparedStatement#execute()}. * <p> * Note that the statements returned should not be parameterised. They * should contain all values hard-coded into them, as the user may choose to * save them to file for later use, preventing parameterisation from * working. * * @param action * the action to translate into SQL or DDL. * @return the statement(s) that represent the action. * @throws ConstructorException * if the action was not able to be converted into one or more * SQL or DDL statements. */ public abstract String[] getStatementsForAction(MartConstructorAction action) throws ConstructorException; /** * Call this method before using the dialect for anything. This is necessary * in order to clear out any state it may be keeping track of. */ public abstract void reset(); /** * Test to see whether this particular dialect implementation can understand * the data link given, ie. it knows how to interact with it and speak the * appropriate version of SQL or DDL. * * @param dataLink * the data link to test compatibility with. * @return <tt>true</tt> if it understands it, <tt>false</tt> if not. */ public abstract boolean understandsDataLink(DataLink dataLink); private void setMaxTableNameLength(final int value) { this.maxTableNameLength = value; } private void setMaxColumnNameLength(final int value) { this.maxColumnNameLength = value; } /** * Use this method to check if the given table name is acceptable to this * database dialect. Throws an exception if it is not, otherwise does * nothing. * * @param tableName * the table name to check. * @throws ConstructorException * if the name is not acceptable. */ protected void checkTableName(final String tableName) throws ConstructorException { if (tableName.length() > this.maxTableNameLength) throw new ConstructorException(Resources.get("nameTooLong", tableName)); } /** * Use this method to check if the given column name is acceptable to this * database dialect. Throws an exception if it is not, otherwise does * nothing. * * @param columnName * the table name to check. * @throws ConstructorException * if the name is not acceptable. */ protected void checkColumnName(final String columnName) throws ConstructorException { if (columnName.length() > this.maxColumnNameLength) throw new ConstructorException(Resources.get("nameTooLong", columnName)); } /** * Get the SQL for unrolling a table's rolled-up relations. * * @param schemaPrefix * the value to substitute for ':schemaPrefix'. * @param dataset * the dataset. * @param dsTable * the dataset table. * @param parentRel * the parent relation. * @param childRel * the child relation. * @param schemaPartition * the schema partition, or <tt>null</tt> for none. * @param templateSchema * the template schema. * @param utu * the unroll unit leading to this. * @return the SQL. */ public abstract String getUnrollTableSQL(final String schemaPrefix, final DataSet dataset, final DataSetTable dsTable, final Relation parentRel, final Relation childRel, final String schemaPartition, final Schema templateSchema, final UnrollTable utu); /** * Gets the SQL to return rows of a partition table. * * @param schemaPrefix * the value to substitute for ':schemaPrefix'. * @param positionMap * the map to populate with column names to column positions. * @param pt * the partition table to get rows for. * @param ds * the dataset. * @param schema * the schema to connect to. * @param usablePartition * the partition to connect to in the schema. * @return the SQL for the rows. positionMap will also have been populated * at this stage. * @throws PartitionException * if anything goes wrong. */ public abstract String getPartitionTableRowsSQL(final String schemaPrefix, final Map positionMap, final PartitionTable pt, final DataSet ds, final Schema schema, final String usablePartition) throws PartitionException; /** * Get SQL to return rows from a table. * * @param schemaName * the schema to use. * @param table * the table to get rows from. * @return the SQL. */ public abstract String getSimpleRowsSQL(final String schemaName, final Table table); /** * Get SQL to return unique values from a column. * * @param schemaName * the schema to use. * @param column * the column to get values from. * @return the SQL. */ public abstract String getUniqueValuesSQL(final String schemaName, final Column column); }