/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florent Guillaume
*/
package org.eclipse.ecr.core.storage.sql.jdbc.db;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.ecr.core.storage.sql.ColumnType;
import org.eclipse.ecr.core.storage.sql.Model;
import org.eclipse.ecr.core.storage.sql.jdbc.dialect.Dialect;
/**
* The basic implementation of a SQL table.
*/
public class TableImpl implements Table {
private static final long serialVersionUID = 1L;
protected final Dialect dialect;
protected final String key;
protected final String name;
/** Map of logical names to columns. */
private final LinkedHashMap<String, Column> columns;
private Column primaryColumn;
/** Logical names of indexed columns. */
private final List<String[]> indexedColumns;
/** Those of the indexed columns that concern fulltext. */
private final Map<String[], String> fulltextIndexedColumns;
/**
* Creates a new empty table.
*/
public TableImpl(Dialect dialect, String name, String key) {
this.dialect = dialect;
this.key = key; // Model table name
this.name = name;
// we use a LinkedHashMap to have deterministic ordering
columns = new LinkedHashMap<String, Column>();
indexedColumns = new LinkedList<String[]>();
fulltextIndexedColumns = new HashMap<String[], String>();
}
@Override
public boolean isAlias() {
return false;
}
@Override
public Table getRealTable() {
return this;
}
@Override
public Dialect getDialect() {
return dialect;
}
@Override
public String getKey() {
return key;
}
@Override
public String getPhysicalName() {
return name;
}
@Override
public String getQuotedName() {
return dialect.openQuote() + name + dialect.closeQuote();
}
@Override
public String getQuotedSuffixedName(String suffix) {
return dialect.openQuote() + name + suffix + dialect.closeQuote();
}
@Override
public Column getColumn(String name) {
return columns.get(name);
}
@Override
public Column getPrimaryColumn() {
if (primaryColumn == null) {
for (Column column : columns.values()) {
if (column.isPrimary()) {
primaryColumn = column;
break;
}
}
}
return primaryColumn;
}
@Override
public Collection<Column> getColumns() {
return columns.values();
}
/**
* Adds a column without dialect physical name canonicalization (for
* directories).
*/
public Column addColumn(String name, Column column) {
if (columns.containsKey(name)) {
throw new IllegalArgumentException("duplicate column " + name);
}
columns.put(name, column);
return column;
}
@Override
public Column addColumn(String name, ColumnType type, String key,
Model model) {
String physicalName = dialect.getTableName(name);
Column column = new Column(this, physicalName, type, key);
return addColumn(name, column);
}
/**
* Adds an index on one or several columns.
*
* @param columnNames the column names
*/
@Override
public void addIndex(String... columnNames) {
indexedColumns.add(columnNames);
}
@Override
public void addFulltextIndex(String indexName, String... columnNames) {
addIndex(columnNames);
fulltextIndexedColumns.put(columnNames, indexName);
}
@Override
public boolean hasFulltextIndex() {
return !fulltextIndexedColumns.isEmpty();
}
/**
* Computes the SQL statement to create the table.
*
* @return the SQL create string.
*/
@Override
public String getCreateSql() {
StringBuilder buf = new StringBuilder();
buf.append("CREATE TABLE ");
buf.append(getQuotedName());
buf.append(" (");
for (Iterator<Column> it = columns.values().iterator(); it.hasNext();) {
addOneColumn(buf, it.next());
if (it.hasNext()) {
buf.append(", ");
}
}
// unique
// check
buf.append(')');
buf.append(dialect.getTableTypeString(this));
return buf.toString();
}
/**
* Computes the SQL statement to alter a table and add a column to it.
*
* @param column the column to add
* @return the SQL alter table string
*/
@Override
public String getAddColumnSql(Column column) {
StringBuilder buf = new StringBuilder();
buf.append("ALTER TABLE ");
buf.append(getQuotedName());
buf.append(' ');
buf.append(dialect.getAddColumnString());
buf.append(' ');
addOneColumn(buf, column);
return buf.toString();
}
/**
* Adds to buf the column name and its type and constraints for create /
* alter.
*/
protected void addOneColumn(StringBuilder buf, Column column) {
buf.append(column.getQuotedName());
buf.append(' ');
if (column.isIdentity()) {
throw new UnsupportedOperationException();
} else {
buf.append(column.getSqlTypeString());
String defaultValue = column.getDefaultValue();
if (defaultValue != null) {
buf.append(" DEFAULT ");
buf.append(defaultValue);
}
if (column.isNullable()) {
buf.append(dialect.getNullColumnString());
} else {
buf.append(" NOT NULL");
}
}
// unique
// check
}
@Override
public List<String> getPostCreateSqls(Model model) {
List<String> sqls = new LinkedList<String>();
for (Column column : columns.values()) {
postAddColumn(column, sqls, model);
}
return sqls;
}
@Override
public List<String> getPostAddSqls(Column column, Model model) {
List<String> sqls = new LinkedList<String>();
postAddColumn(column, sqls, model);
return sqls;
}
protected void postAddColumn(Column column, List<String> sqls, Model model) {
if (column.isPrimary()) {
StringBuilder buf = new StringBuilder();
String constraintName = dialect.openQuote()
+ dialect.getPrimaryKeyConstraintName(key)
+ dialect.closeQuote();
buf.append("ALTER TABLE ");
buf.append(getQuotedName());
buf.append(dialect.getAddPrimaryKeyConstraintString(constraintName));
buf.append('(');
buf.append(column.getQuotedName());
buf.append(')');
sqls.add(buf.toString());
}
Table ft = column.getForeignTable();
if (ft != null) {
Column fc = ft.getColumn(column.getForeignKey());
String constraintName = dialect.openQuote()
+ dialect.getForeignKeyConstraintName(key,
column.getPhysicalName(), ft.getPhysicalName())
+ dialect.closeQuote();
StringBuilder buf = new StringBuilder();
buf.append("ALTER TABLE ");
buf.append(getQuotedName());
buf.append(dialect.getAddForeignKeyConstraintString(constraintName,
new String[] { column.getQuotedName() },
ft.getQuotedName(), new String[] { fc.getQuotedName() },
true));
if (dialect.supportsCircularCascadeDeleteConstraints()
|| (Model.MAIN_KEY.equals(fc.getPhysicalName()) && Model.MAIN_KEY.equals(column.getPhysicalName()))) {
// MS SQL Server can't have circular ON DELETE CASCADE.
// Use a trigger INSTEAD OF DELETE to cascade deletes
// recursively for:
// - hierarchy.parentid
// - versions.versionableid
// - proxies.versionableid
buf.append(" ON DELETE CASCADE");
}
sqls.add(buf.toString());
}
// add indexes for this column
String columnName = column.getKey();
INDEXES: //
for (String[] columnNames : indexedColumns) {
List<String> names = new ArrayList<String>(
Arrays.asList(columnNames));
// check that column is part of this index
if (!names.contains(columnName)) {
continue;
}
// check that column is the last one mentioned
for (Column c : getColumns()) {
String key = c.getKey();
names.remove(key);
if (names.isEmpty()) {
// last one?
if (!columnName.equals(key)) {
continue INDEXES;
}
break;
}
}
// add this index now, as all columns have been created
List<Column> cols = new ArrayList<Column>(columnNames.length);
List<String> qcols = new ArrayList<String>(columnNames.length);
List<String> pcols = new ArrayList<String>(columnNames.length);
for (String name : columnNames) {
Column col = getColumn(name);
cols.add(col);
qcols.add(col.getQuotedName());
pcols.add(col.getPhysicalName());
}
String quotedIndexName = dialect.openQuote()
+ dialect.getIndexName(key, pcols) + dialect.closeQuote();
String createIndexSql;
String indexName = fulltextIndexedColumns.get(columnNames);
if (indexName != null) {
createIndexSql = dialect.getCreateFulltextIndexSql(indexName,
quotedIndexName, this, cols, model);
} else {
createIndexSql = dialect.getCreateIndexSql(quotedIndexName,
getQuotedName(), qcols);
}
sqls.add(createIndexSql);
}
}
/**
* Computes the SQL statement to drop the table.
* <p>
* TODO drop constraints and indexes
*
* @return the SQL drop string.
*/
@Override
public String getDropSql() {
StringBuilder buf = new StringBuilder();
buf.append("DROP TABLE ");
if (dialect.supportsIfExistsBeforeTableName()) {
buf.append("IF EXISTS ");
}
buf.append(getQuotedName());
buf.append(dialect.getCascadeDropConstraintsString());
if (dialect.supportsIfExistsAfterTableName()) {
buf.append(" IF EXISTS");
}
return buf.toString();
}
@Override
public String toString() {
return "Table(" + name + ')';
}
}