blob: 81c7f33683a5c1fef4d29c44ed999b5ef5fac371 [file] [log] [blame]
/*****************************************************************
* 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
*
* https://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.cayenne.dbsync.merge;
import java.sql.Types;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.apache.cayenne.dbsync.merge.token.ValueForNullProvider;
import org.apache.cayenne.dbsync.merge.factory.MergerTokenFactory;
import org.apache.cayenne.dbsync.merge.token.MergerToken;
import org.apache.cayenne.map.DbAttribute;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.dbsync.model.DetectedDbEntity;
class DbAttributeMerger extends AbstractMerger<DbEntity, DbAttribute> {
static int[] typesWithMaxLength = {
Types.NCHAR, Types.NVARCHAR,
Types.CHAR, Types.VARCHAR,
Types.BINARY, Types.VARBINARY,
Types.TIME, Types.TIMESTAMP
};
private final ValueForNullProvider valueForNull;
DbAttributeMerger(MergerTokenFactory tokenFactory, ValueForNullProvider valueForNull) {
super(tokenFactory);
this.valueForNull = valueForNull;
}
@Override
MergerDictionaryDiff<DbAttribute> createDiff(DbEntity original, DbEntity imported) {
return new MergerDictionaryDiff.Builder<DbAttribute>()
.originalDictionary(new DbAttributeDictionary(original))
.importedDictionary(new DbAttributeDictionary(imported))
.build();
}
/**
* Add column to db
* @param original attribute found in model but missing in db
*/
@Override
Collection<MergerToken> createTokensForMissingImported(DbAttribute original) {
DbEntity originalDbEntity = original.getEntity();
List<MergerToken> tokens = new LinkedList<>();
tokens.add(getTokenFactory().createAddColumnToDb(originalDbEntity, original));
// Create not null check
if (original.isMandatory()) {
if (valueForNull.hasValueFor(originalDbEntity, original)) {
tokens.add(getTokenFactory().createSetValueForNullToDb(originalDbEntity, original, valueForNull));
}
tokens.add(getTokenFactory().createSetNotNullToDb(originalDbEntity, original));
}
if(original.isPrimaryKey()
&& originalDbEntity instanceof DetectedDbEntity
&& "VIEW".equals(((DetectedDbEntity) originalDbEntity).getType())) {
// Views doesn't has PKs in a database, but if the user selects some PKs in a model, we put these keys.
return null;
}
return tokens;
}
/**
* Drop column in db
* @param imported attribute found in db but missing in model
*/
@Override
Collection<MergerToken> createTokensForMissingOriginal(DbAttribute imported) {
DbEntity originalDbEntity = getOriginalDictionary().getByName(imported.getEntity().getName().toUpperCase());
return Collections.singleton(getTokenFactory().createDropColumnToDb(originalDbEntity, imported));
}
/**
* Compare same attributes in model and db
*/
@Override
Collection<MergerToken> createTokensForSame(MergerDiffPair<DbAttribute> same) {
List<MergerToken> tokens = new LinkedList<>();
// isMandatory flag
checkMandatory(same.getOriginal(), same.getImported(), tokens);
// check type (including max length, scale and precision)
checkType(same.getOriginal(), same.getImported(), tokens);
// not implemented yet
// isGenerated flag
checkIsGenerated(same.getOriginal(), same.getImported(), tokens);
return tokens;
}
private void checkMandatory(DbAttribute original, DbAttribute imported, List<MergerToken> tokens) {
if(original.isMandatory() == imported.isMandatory()) {
return;
}
DbEntity originalDbEntity = original.getEntity();
if (original.isMandatory()) {
if (valueForNull.hasValueFor(originalDbEntity, original)) {
tokens.add(getTokenFactory().createSetValueForNullToDb(originalDbEntity, original, valueForNull));
}
tokens.add(getTokenFactory().createSetNotNullToDb(originalDbEntity, original));
} else {
tokens.add(getTokenFactory().createSetAllowNullToDb(originalDbEntity, original));
}
}
/**
* Check whether attributes have same type, max length, scale and precision
* @param original attribute in model
* @param imported attribute from db
* @return true if attributes not same
*/
private boolean needUpdateType(DbAttribute original, DbAttribute imported) {
if(original.getType() != imported.getType()) {
// Decimal and NUMERIC types are effectively equal so skip their interchange
if( (original.getType() == Types.DECIMAL || original.getType() == Types.NUMERIC) &&
(imported.getType() == Types.DECIMAL || imported.getType() == Types.NUMERIC)) {
return false;
}
return getTokenFactory().needUpdateSpecificType(original, imported);
}
if(original.getMaxLength() != imported.getMaxLength()) {
for(int type : typesWithMaxLength) {
if(original.getType() == type) {
return true;
}
}
}
if(needUpdateScale(original, imported)) {
return true;
}
if(original.getAttributePrecision() != imported.getAttributePrecision()) {
return true;
}
return false;
}
private boolean needUpdateScale(DbAttribute original, DbAttribute imported) {
if(original.getScale() == imported.getScale()) {
return false;
}
// -1 and 0 are actually equal values for scale
if((original.getScale() == -1 || original.getScale() == 0)
&& (imported.getScale() == -1 || imported.getScale() == 0)) {
return false;
}
return true;
}
private void checkType(DbAttribute original, DbAttribute imported, List<MergerToken> tokens) {
if(!needUpdateType(original, imported)) {
return;
}
DbEntity originalDbEntity = original.getEntity();
tokens.add(getTokenFactory().createSetColumnTypeToDb(originalDbEntity, imported, original));
}
private void checkIsGenerated(DbAttribute original, DbAttribute imported, List<MergerToken> tokens) {
if(original.isGenerated() == imported.isGenerated()) {
return;
}
tokens.add(getTokenFactory().createSetGeneratedFlagToDb(original.getEntity(), original, original.isGenerated()));
}
@Override
public List<MergerToken> createMergeTokens() {
throw new UnsupportedOperationException();
}
}