/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * 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 org.jkiss.dbeaver.model.impl.sql.edit.struct; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.edit.DBECommandContext; import org.jkiss.dbeaver.model.edit.DBEPersistAction; import org.jkiss.dbeaver.model.edit.prop.DBECommandComposite; import org.jkiss.dbeaver.model.impl.DBObjectNameCaseTransformer; import org.jkiss.dbeaver.model.impl.edit.DBECommandAbstract; import org.jkiss.dbeaver.model.impl.edit.SQLDatabasePersistAction; import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCTable; import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCTableColumn; import org.jkiss.dbeaver.model.impl.sql.edit.SQLObjectEditor; import org.jkiss.dbeaver.model.messages.ModelMessages; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.SQLUtils; import org.jkiss.dbeaver.model.struct.DBSDataType; import org.jkiss.utils.CommonUtils; import java.util.List; /** * JDBC table column manager */ public abstract class SQLTableColumnManager<OBJECT_TYPE extends JDBCTableColumn<TABLE_TYPE>, TABLE_TYPE extends JDBCTable> extends SQLObjectEditor<OBJECT_TYPE, TABLE_TYPE> { public static final long DDL_FEATURE_OMIT_COLUMN_CLAUSE_IN_DROP = 1; public static final String QUOTE = "'"; protected interface ColumnModifier<OBJECT_TYPE extends DBPObject> { void appendModifier(OBJECT_TYPE column, StringBuilder sql, DBECommandAbstract<OBJECT_TYPE> command); } protected final ColumnModifier<OBJECT_TYPE> DataTypeModifier = new ColumnModifier<OBJECT_TYPE>() { @Override public void appendModifier(OBJECT_TYPE column, StringBuilder sql, DBECommandAbstract<OBJECT_TYPE> command) { final String typeName = column.getTypeName(); DBPDataKind dataKind = column.getDataKind(); final DBSDataType dataType = findDataType(column.getDataSource(), typeName); sql.append(' ').append(typeName); if (dataType == null) { log.debug("Type name '" + typeName + "' is not supported by driver"); //$NON-NLS-1$ //$NON-NLS-2$ } else { dataKind = dataType.getDataKind(); } String modifiers = SQLUtils.getColumnTypeModifiers(column, typeName, dataKind); if (modifiers != null) { sql.append(modifiers); } } }; protected final ColumnModifier<OBJECT_TYPE> NotNullModifier = new ColumnModifier<OBJECT_TYPE>() { @Override public void appendModifier(OBJECT_TYPE column, StringBuilder sql, DBECommandAbstract<OBJECT_TYPE> command) { if (column.isRequired()) { sql.append(" NOT NULL"); //$NON-NLS-1$ } } }; protected final ColumnModifier<OBJECT_TYPE> NullNotNullModifier = new ColumnModifier<OBJECT_TYPE>() { @Override public void appendModifier(OBJECT_TYPE column, StringBuilder sql, DBECommandAbstract<OBJECT_TYPE> command) { sql.append(column.isRequired() ? " NOT NULL" : " NULL"); } }; protected final ColumnModifier<OBJECT_TYPE> NullNotNullModifierConditional = new ColumnModifier<OBJECT_TYPE>() { @Override public void appendModifier(OBJECT_TYPE column, StringBuilder sql, DBECommandAbstract<OBJECT_TYPE> command) { if (command instanceof DBECommandComposite) { if (((DBECommandComposite) command).getProperty("required") == null) { // Do not set NULL/NOT NULL if it wasn't chaged return; } } NullNotNullModifier.appendModifier(column, sql, command); } }; protected final ColumnModifier<OBJECT_TYPE> DefaultModifier = new ColumnModifier<OBJECT_TYPE>() { @Override public void appendModifier(OBJECT_TYPE column, StringBuilder sql, DBECommandAbstract<OBJECT_TYPE> command) { String defaultValue = CommonUtils.toString(column.getDefaultValue()); if (!CommonUtils.isEmpty(defaultValue)) { DBPDataKind dataKind = column.getDataKind(); boolean useQuotes = false;//dataKind == DBPDataKind.STRING; if (!defaultValue.startsWith(QUOTE) && !defaultValue.endsWith(QUOTE)) { if (useQuotes && defaultValue.trim().startsWith(QUOTE)) { useQuotes = false; } if (dataKind == DBPDataKind.DATETIME) { final char firstChar = defaultValue.trim().charAt(0); if (!Character.isLetter(firstChar) && firstChar != '(' && firstChar != '[') { useQuotes = true; } } } sql.append(" DEFAULT "); //$NON-NLS-1$ if (useQuotes) sql.append(QUOTE); sql.append(defaultValue); if (useQuotes) sql.append(QUOTE); } } }; protected ColumnModifier[] getSupportedModifiers(OBJECT_TYPE column) { return new ColumnModifier[] {DataTypeModifier, NotNullModifier, DefaultModifier}; } @Override public boolean canEditObject(OBJECT_TYPE object) { TABLE_TYPE table = object.getParentObject(); return table != null && !table.isView(); } @Override public boolean canCreateObject(TABLE_TYPE parent) { return parent != null && !parent.isView(); } @Override public boolean canDeleteObject(OBJECT_TYPE object) { return canEditObject(object); } @Override public long getMakerOptions() { return FEATURE_EDITOR_ON_CREATE; } protected long getDDLFeatures(OBJECT_TYPE object) { return 0; } private boolean hasDDLFeature(OBJECT_TYPE object, long feature) { return (getDDLFeatures(object) & feature) != 0; } @Override protected void addObjectCreateActions(List<DBEPersistAction> actions, ObjectCreateCommand command) { final TABLE_TYPE table = command.getObject().getTable(); actions.add( new SQLDatabasePersistAction( ModelMessages.model_jdbc_create_new_table_column, "ALTER TABLE " + table.getFullyQualifiedName(DBPEvaluationContext.DDL) + " ADD " + getNestedDeclaration(table, command)) ); } @Override protected void addObjectDeleteActions(List<DBEPersistAction> actions, ObjectDeleteCommand command) { actions.add( new SQLDatabasePersistAction( ModelMessages.model_jdbc_drop_table_column, "ALTER TABLE " + command.getObject().getTable().getFullyQualifiedName(DBPEvaluationContext.DDL) + //$NON-NLS-2$ " DROP " + (hasDDLFeature(command.getObject(), DDL_FEATURE_OMIT_COLUMN_CLAUSE_IN_DROP) ? "" : "COLUMN ") + DBUtils.getQuotedIdentifier(command.getObject())) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ ); } protected String getNewColumnName(DBRProgressMonitor monitor, DBECommandContext context, TABLE_TYPE table) { for (int i = 1; ; i++) { final String name = DBObjectNameCaseTransformer.transformName(table.getDataSource(), "Column" + i); try { // check for existing columns boolean exists = table.getAttribute(monitor, name) != null; if (!exists) { // Check for new columns (they are present only within command context) for (DBPObject contextObject : context.getEditedObjects()) { if (contextObject instanceof JDBCTableColumn && ((JDBCTableColumn) contextObject).getTable() == table && name.equalsIgnoreCase(((JDBCTableColumn) contextObject).getName())) { exists = true; break; } } } if (!exists) { return name; } } catch (DBException e) { log.warn(e); return name; } } } @Override protected StringBuilder getNestedDeclaration(TABLE_TYPE owner, DBECommandAbstract<OBJECT_TYPE> command) { OBJECT_TYPE column = command.getObject(); // Create column String columnName = DBUtils.getQuotedIdentifier(column.getDataSource(), column.getName()); if (command instanceof SQLObjectEditor.ObjectRenameCommand) { columnName = DBUtils.getQuotedIdentifier(column.getDataSource(), ((ObjectRenameCommand) command).getNewName()); } StringBuilder decl = new StringBuilder(40); decl.append(columnName); for (ColumnModifier<OBJECT_TYPE> modifier : getSupportedModifiers(column)) { modifier.appendModifier(column, decl, command); } return decl; } @Override protected void validateObjectProperties(ObjectChangeCommand command) throws DBException { if (CommonUtils.isEmpty(command.getObject().getName())) { throw new DBException("Column name cannot be empty"); } if (CommonUtils.isEmpty(command.getObject().getTypeName())) { throw new DBException("Column type name cannot be empty"); } } private static DBSDataType findDataType(DBPDataSource dataSource, String typeName) { if (dataSource instanceof DBPDataTypeProvider) { return ((DBPDataTypeProvider) dataSource).getLocalDataType(typeName); } return null; } protected static DBSDataType findBestDataType(DBPDataSource dataSource, String ... typeNames) { if (dataSource instanceof DBPDataTypeProvider) { return DBUtils.findBestDataType(((DBPDataTypeProvider) dataSource).getLocalDataTypes(), typeNames); } return null; } }