/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.ambari.server.orm.helpers.dbms; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.List; import org.apache.ambari.server.orm.DBAccessor; import org.apache.commons.lang.StringUtils; import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; import org.eclipse.persistence.internal.databaseaccess.Platform; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.platform.database.DatabasePlatform; import org.eclipse.persistence.tools.schemaframework.FieldDefinition; import org.eclipse.persistence.tools.schemaframework.ForeignKeyConstraint; import org.eclipse.persistence.tools.schemaframework.TableDefinition; import org.eclipse.persistence.tools.schemaframework.UniqueKeyConstraint; public class GenericDbmsHelper implements DbmsHelper { protected final DatabasePlatform databasePlatform; public GenericDbmsHelper(DatabasePlatform databasePlatform) { this.databasePlatform = databasePlatform; } @Override public boolean supportsColumnTypeChange() { return false; } @Override public String quoteObjectName(String name) { return "\"" + name + "\""; } @Override public String getRenameColumnStatement(String tableName, String oldName, DBAccessor.DBColumnInfo columnInfo) { StringBuilder stringBuilder = new StringBuilder(); writeAlterTableClause(stringBuilder, tableName); writeColumnRenameString(stringBuilder, oldName, columnInfo); return stringBuilder.toString(); } @Override public String getAlterColumnStatement(String tableName, DBAccessor.DBColumnInfo columnInfo) { StringBuilder stringBuilder = new StringBuilder(); writeAlterTableClause(stringBuilder, tableName); writeColumnModifyString(stringBuilder, columnInfo); return stringBuilder.toString(); } @Override public String getSetNullableStatement(String tableName, DBAccessor.DBColumnInfo columnInfo, boolean nullable) { StringBuilder stringBuilder = new StringBuilder(); writeAlterTableClause(stringBuilder, tableName); writeSetNullableString(stringBuilder, tableName, columnInfo, nullable); return stringBuilder.toString(); } /** {@inheritDoc} */ @Override public String getCopyColumnToAnotherTableStatement(String sourceTable, String sourceColumnName, String sourceIDColumnName, String targetTable, String targetColumnName, String targetIDColumnName) { throw new UnsupportedOperationException("Column copy is not supported for generic DB"); } public StringBuilder writeAlterTableClause(StringBuilder builder, String tableName) { builder.append("ALTER TABLE ").append(tableName).append(" "); return builder; } public StringBuilder writeColumnModifyString(StringBuilder builder, DBAccessor.DBColumnInfo columnInfo) { throw new UnsupportedOperationException("Column type modification not supported for generic DB"); } public StringBuilder writeColumnRenameString(StringBuilder builder, String oldName, DBAccessor.DBColumnInfo newColumnInfo) { throw new UnsupportedOperationException("Column rename not supported for generic DB"); } public StringBuilder writeColumnType(StringBuilder builder, DBAccessor.DBColumnInfo columnInfo) { FieldTypeDefinition fieldType = columnInfo.getDbType(); if (fieldType == null) { fieldType = databasePlatform.getFieldTypeDefinition(columnInfo.getType()); } if (fieldType == null) { throw new IllegalArgumentException("Unable to convert data type"); } FieldDefinition definition = convertToFieldDefinition(columnInfo); StringWriter writer = new StringWriter(); try { databasePlatform.printFieldTypeSize(writer, definition, fieldType, false); //Ambari doesn't use identity fields } catch (IOException ignored) { // no writing to file } builder.append(writer.toString()); return builder; } public String writeGetTableConstraints(String databaseName, String tableName){ throw new UnsupportedOperationException("List of table constraints is not supported for generic DB"); } public StringBuilder writeAddPrimaryKeyString(StringBuilder builder, String constraintName, String... columnName){ builder.append("ADD CONSTRAINT ").append(constraintName).append(" PRIMARY KEY (").append(StringUtils.join(columnName, ",")).append(")"); return builder; } public StringBuilder writeSetNullableString(StringBuilder builder, String tableName, DBAccessor.DBColumnInfo columnInfo, boolean nullable) { throw new UnsupportedOperationException( "Column nullable modification not supported for generic DB"); } public StringBuilder writeDropTableColumnStatement(StringBuilder builder, String columnName){ builder.append("DROP COLUMN ").append(columnName); return builder; } /** * @param builder String Builder passed by reference * @param constraintName Constraint Name used by Postgres * @param cascade In postgres, can perform a CASCADE delete. In the other DB flavors, this is ignored. * @return Return the String Builder */ public StringBuilder writeDropPrimaryKeyStatement(StringBuilder builder, String constraintName, boolean cascade){ // constraintName required only for postgres return builder.append("DROP PRIMARY KEY"); } @Override public String getDropPrimaryKeyStatement(String tableName, String constraintName, boolean cascade){ StringBuilder builder = writeAlterTableClause(new StringBuilder(), tableName); return writeDropPrimaryKeyStatement(builder, constraintName, cascade).toString(); } /** * get create table statement * @param tableName * @param columns * @param primaryKeyColumns * @return */ @Override public String getCreateTableStatement(String tableName, List<DBAccessor.DBColumnInfo> columns, List<String> primaryKeyColumns) { Writer stringWriter = new StringWriter(); writeCreateTableStatement(stringWriter, tableName, columns, primaryKeyColumns); return stringWriter.toString(); } /** * Write create table statement to writer * TODO default Value of column not supported */ public Writer writeCreateTableStatement(Writer writer, String tableName, List<DBAccessor.DBColumnInfo> columns, List<String> primaryKeyColumns) { //TODO validateNames(String tableName, List<DBAccessor.DBColumnInfo> columns) //TODO validatePKNames(List<DBAccessor.DBColumnInfo> columns, String... primaryKeyColumns) TableDefinition tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); for (DBAccessor.DBColumnInfo columnInfo : columns) { //TODO case-sensitive for now int length = columnInfo.getLength() != null ? columnInfo.getLength() : 0; if (primaryKeyColumns.contains(columnInfo.getName())) { tableDefinition.addIdentityField(columnInfo.getName(), columnInfo.getType(), length); } else { FieldDefinition fieldDefinition = convertToFieldDefinition(columnInfo); tableDefinition.addField(fieldDefinition); } } //TODO possibly move code to avoid unnecessary dependencies and allow extension tableDefinition.buildCreationWriter(createStubAbstractSessionFromPlatform(databasePlatform), writer); return writer; } public FieldDefinition convertToFieldDefinition(DBAccessor.DBColumnInfo columnInfo) { int length = columnInfo.getLength() != null ? columnInfo.getLength() : 0; FieldDefinition fieldDefinition = new FieldDefinition(columnInfo.getName(), columnInfo.getType(), length); fieldDefinition.setShouldAllowNull(columnInfo.isNullable()); if (null != columnInfo.getDefaultValue() && isConstraintSupportedAfterNullability()) { fieldDefinition.setConstraint("DEFAULT " + escapeParameter(columnInfo.getDefaultValue())); } return fieldDefinition; } @Override public String getDropUniqueConstraintStatement(String tableName, String constraintName){ UniqueKeyConstraint uniqueKeyConstraint = new UniqueKeyConstraint(); uniqueKeyConstraint.setName(constraintName); Writer writer = new StringWriter(); TableDefinition tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); tableDefinition.buildUniqueConstraintDeletionWriter(createStubAbstractSessionFromPlatform(databasePlatform), uniqueKeyConstraint, writer); return writer.toString(); } @Override public String getTableConstraintsStatement(String databaseName, String tablename){ return writeGetTableConstraints(databaseName, tablename); } /** * get create index statement * @param indexName * @param tableName * @param columnNames * @return */ @Override public String getCreateIndexStatement(String indexName, String tableName, String... columnNames) { return getCreateIndexStatement(indexName, tableName, false, columnNames); } /** * get create index statement * @param indexName The name of the index to be created * @param tableName The database table the index to be created on * @param columnNames The columns included into the index * @param isUnique Specifies whether unique index is to be created. * @return The sql statement for creating the index */ @Override public String getCreateIndexStatement(String indexName, String tableName, boolean isUnique, String... columnNames) { //TODO validateColumnNames() String createIndex = databasePlatform.buildCreateIndex(tableName, indexName, "", isUnique, columnNames); return createIndex; } /** * {@inheritDoc} */ @Override public String getDropIndexStatement(String indexName, String tableName) { String dropIndex = databasePlatform.buildDropIndex(tableName, indexName); return dropIndex; } @Override public String getAddUniqueConstraintStatement(String tableName, String constraintName, String... columnNames){ UniqueKeyConstraint uniqueKeyConstraint = new UniqueKeyConstraint(); uniqueKeyConstraint.setName(constraintName); for (String columnName: columnNames){ uniqueKeyConstraint.addSourceField(columnName); } TableDefinition tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); Writer writer = new StringWriter(); tableDefinition.buildUniqueConstraintCreationWriter(createStubAbstractSessionFromPlatform(databasePlatform), uniqueKeyConstraint, writer); return writer.toString(); } @Override public String getAddPrimaryKeyConstraintStatement(String tableName, String constraintName, String... columnName){ StringBuilder builder = writeAlterTableClause(new StringBuilder(), tableName); builder = writeAddPrimaryKeyString(builder, constraintName, columnName); return builder.toString(); } @Override public String getAddForeignKeyStatement(String tableName, String constraintName, List<String> keyColumns, String referenceTableName, List<String> referenceColumns, boolean shouldCascadeOnDelete) { ForeignKeyConstraint foreignKeyConstraint = new ForeignKeyConstraint(); foreignKeyConstraint.setName(constraintName); foreignKeyConstraint.setTargetTable(referenceTableName); foreignKeyConstraint.setSourceFields(keyColumns); foreignKeyConstraint.setTargetFields(referenceColumns); foreignKeyConstraint.setShouldCascadeOnDelete(shouldCascadeOnDelete); TableDefinition tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); Writer writer = new StringWriter(); tableDefinition.buildConstraintCreationWriter(createStubAbstractSessionFromPlatform(databasePlatform), foreignKeyConstraint, writer); return writer.toString(); } @Override public String getAddColumnStatement(String tableName, DBAccessor.DBColumnInfo columnInfo) { Writer writer = new StringWriter(); TableDefinition tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); tableDefinition.buildAddFieldWriter(createStubAbstractSessionFromPlatform(databasePlatform), convertToFieldDefinition(columnInfo), writer); return writer.toString(); } @Override public String getDropTableColumnStatement(String tableName, String columnName){ StringBuilder builder = writeAlterTableClause(new StringBuilder(), tableName); return writeDropTableColumnStatement(builder, columnName).toString(); } @Override public String getRenameColumnStatement(String tableName, String oldColumnName, String newColumnName) { throw new UnsupportedOperationException("Rename operation not supported."); } @Override public String getDropTableStatement(String tableName) { Writer writer = new StringWriter(); TableDefinition tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); tableDefinition.buildDeletionWriter (createStubAbstractSessionFromPlatform(databasePlatform), writer); return writer.toString(); } @Override public String getDropFKConstraintStatement(String tableName, String constraintName) { Writer writer = new StringWriter(); ForeignKeyConstraint foreignKeyConstraint = new ForeignKeyConstraint(); foreignKeyConstraint.setName(constraintName); foreignKeyConstraint.setTargetTable(tableName); TableDefinition tableDefinition = new TableDefinition(); tableDefinition.setName(tableName); tableDefinition.buildConstraintDeletionWriter( createStubAbstractSessionFromPlatform(databasePlatform), foreignKeyConstraint, writer); return writer.toString(); } @Override public String getDropSequenceStatement(String sequenceName) { StringWriter writer = new StringWriter(); String defaultStmt = String.format("DROP sequence %s", sequenceName); try { Writer w = databasePlatform.buildSequenceObjectDeletionWriter(writer, sequenceName); return w != null ? w.toString() : defaultStmt; } catch (IOException e) { e.printStackTrace(); } return defaultStmt; } public AbstractSession createStubAbstractSessionFromPlatform (final DatabasePlatform databasePlatform) { return new AbstractSession() { @Override public Platform getDatasourcePlatform() { return databasePlatform; } @Override public DatabasePlatform getPlatform() { return databasePlatform; } }; } /** * {@inheritDoc} */ @Override public boolean isConstraintSupportedAfterNullability() { return true; } /** * Gets an escaped version of the specified value suitable for including as a * parameter when building statements. * * @param value * the value to escape * @return the escaped value */ protected String escapeParameter(Object value) { // Only String and number supported. // Taken from: // org.eclipse.persistence.internal.databaseaccess.appendParameterInternal Object dbValue = databasePlatform.convertToDatabaseType(value); String valueString = value.toString(); if (dbValue instanceof String || dbValue instanceof Enum) { valueString = "'" + value.toString() + "'"; } return valueString; } }