#374 - MsSqlServer - adjust tests (due to jdbc batch support etc) add in support for unique index handling

This commit is contained in:
Robin Bygrave
2015-08-06 21:49:04 +12:00
parent 3a9668fe6c
commit 1dd0841f00
31 changed files with 586 additions and 360 deletions
@@ -23,6 +23,7 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
// due to lack of support for getGeneratedKeys in batch mode
this.disallowBatchOnCascade = true;
this.idInExpandedForm = true;
this.selectCountWithAlias = true;
this.sqlLimiter = new MsSqlServer2005SqlLimiter();
this.platformDdl = new MsSqlServerDdl(dbTypeMap, dbIdentity);
this.dbIdentity.setIdType(IdType.IDENTITY);
@@ -46,9 +47,9 @@ public class MsSqlServer2005Platform extends DatabasePlatform {
dbTypeMap.put(Types.LONGVARBINARY, new DbType("image"));
dbTypeMap.put(Types.LONGVARCHAR, new DbType("text"));
dbTypeMap.put(Types.DATE, new DbType("datetime"));
dbTypeMap.put(Types.TIME, new DbType("datetime"));
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime"));
dbTypeMap.put(Types.DATE, new DbType("date"));
dbTypeMap.put(Types.TIME, new DbType("time"));
dbTypeMap.put(Types.TIMESTAMP, new DbType("datetime2"));
}
@@ -29,6 +29,11 @@ public class BaseTableDdl implements TableDdl {
*/
protected IndexSet indexSet = new IndexSet();
/**
* Used when unique constraints specifically for OneToOne can't be created normally (MsSqlServer).
*/
protected IndexSet externalUnique = new IndexSet();
// counters used when constraint names are truncated due to maximum length
// and these counters are used to keep the constraint name unique
protected int countCheck;
@@ -45,10 +50,11 @@ public class BaseTableDdl implements TableDdl {
}
/**
* Reset counters and index set for each table.
* Reset counters and index set for each table processed.
*/
protected void reset() {
indexSet.clear();
externalUnique.clear();
countCheck = 0;
countUnique = 0;
countForeignKey = 0;
@@ -57,7 +63,7 @@ public class BaseTableDdl implements TableDdl {
/**
* Generate the appropriate 'create table' and matching 'drop table' statements
* and add them to the 'apply' and 'rollback' buffers.
* and add them to the appropriate 'apply' and 'rollback' buffers.
*/
@Override
public void generate(DdlWrite writer, CreateTable createTable) throws IOException {
@@ -98,6 +104,8 @@ public class BaseTableDdl implements TableDdl {
apply.newLine().append(")").endOfStatement();
writeUniqueOneToOneConstraints(writer, createTable);
// add drop table to the rollback buffer - do this before
// we drop the related sequence (if sequences are used)
dropTable(writer.rollback(), tableName);
@@ -118,6 +126,29 @@ public class BaseTableDdl implements TableDdl {
}
/**
* Specific handling of OneToOne unique constraints for MsSqlServer.
* For all other DB platforms these unique constraints are done inline as per normal.
*/
private void writeUniqueOneToOneConstraints(DdlWrite write, CreateTable createTable) throws IOException {
String tableName = createTable.getName();
for (IndexColumns index : externalUnique.indexes) {
String uqName = determineUniqueConstraintName(tableName, index.joinedNames());
write.apply()
.append(platformDdl.createExternalUniqueForOneToOne(uqName, tableName, index.columnsArray()))
.endOfStatement();
// register it so we check against effective duplication
// when creating the foreign key indexes
indexSet.add(index);
write.rollbackForeignKeys()
.append(platformDdl.dropIndex(uqName, tableName))
.endOfStatement();
}
}
private void writeSequence(DdlWrite writer, CreateTable createTable) throws IOException {
// explicit sequence use or platform decides
@@ -231,7 +262,7 @@ public class BaseTableDdl implements TableDdl {
private void appendColumns(String[] columns, DdlBuffer buffer) throws IOException {
buffer.append(" (");
for (int i = 0; i <columns.length ; i++) {
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
buffer.append(",");
}
@@ -284,11 +315,17 @@ public class BaseTableDdl implements TableDdl {
*/
protected void writeUniqueConstraints(DdlBuffer apply, CreateTable createTable) throws IOException {
boolean inlineUniqueOneToOne = platformDdl.isInlineUniqueOneToOne();
List<Column> columns = createTable.getColumn();
for (Column column : columns) {
if (isTrue(column.isUnique())) {
if (isTrue(column.isUnique()) || (inlineUniqueOneToOne && isTrue(column.isUniqueOneToOne()))) {
// normal mechanism for adding unique constraint
inlineUniqueConstraintSingle(apply, createTable.getName(), column);
indexSet.add(column);
} else if (!inlineUniqueOneToOne && isTrue(column.isUniqueOneToOne())) {
// MsSqlServer specific mechanism for adding unique constraints (that allow nulls)
externalUnique.add(column);
}
}
}
@@ -459,7 +496,10 @@ public class BaseTableDdl implements TableDdl {
return Boolean.TRUE.equals(value);
}
private int toInt(BigInteger value) {
/**
* Return as an int value with 0 when it is null.
*/
protected int toInt(BigInteger value) {
return (value == null) ? 0 : value.intValue();
}
@@ -497,7 +537,7 @@ public class BaseTableDdl implements TableDdl {
*/
public boolean add(String[] columns) {
IndexColumns newIndex = new IndexColumns(columns);
for (int i = 0; i <indexes.size() ; i++) {
for (int i = 0; i < indexes.size(); i++) {
if (indexes.get(i).isMatch(newIndex)) {
return false;
}
@@ -505,6 +545,14 @@ public class BaseTableDdl implements TableDdl {
indexes.add(newIndex);
return true;
}
/**
* Add the externally created unique constraint here so that we check later if foreign key indexes
* don't need to be created (as the columns match this unique constraint).
*/
public void add(IndexColumns index) {
indexes.add(index);
}
}
/**
@@ -525,7 +573,7 @@ public class BaseTableDdl implements TableDdl {
* Construct representing index.
*/
public IndexColumns(String[] columnNames) {
for (int i = 0; i <columnNames.length; i++) {
for (int i = 0; i < columnNames.length; i++) {
columns.add(columnNames[i]);
}
}
@@ -537,8 +585,36 @@ public class BaseTableDdl implements TableDdl {
return columns.equals(other.columns);
}
/**
* Add a unique index based on the single column.
*/
protected void add(String column) {
columns.add(column);
}
/**
* Return the columns as a string array.
*/
public String[] columnsArray() {
return columns.toArray(new String[columns.size()]);
}
/**
* Return the column names all joined with underscore.
*/
public String joinedNames() {
if (columns.size() == 1) {
return columns.get(0);
} else {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
sb.append("_");
}
sb.append(columns.get(i));
}
return sb.toString();
}
}
}
}
@@ -10,32 +10,42 @@ public class MsSqlServerDdl extends PlatformDdl {
public MsSqlServerDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
super(platformTypes, dbIdentity);
this.identitySuffix = " generated by default as identity";
this.identitySuffix = " identity(1,1)";
this.foreignKeyRestrict = "";
this.inlineUniqueOneToOne = false;
}
@Override
public String dropTable(String tableName) {
return "IF OBJECT_ID('" + tableName + "', 'U') IS NOT NULL drop table " + tableName;
}
@Override
public String alterTableDropForeignKey(String tableName, String fkName) {
return "IF OBJECT_ID('" + fkName + "', 'F') IS NOT NULL " + super.alterTableDropForeignKey(tableName, fkName);
}
/**
* MsSqlServer specific null handling on unique constraints.
*/
@Override
public String createExternalUniqueForOneToOne(String uqName, String tableName, String[] columns) {
// issues#233
String start = "create unique nonclustered index " + uqName + " on " + tableName+ "(";
StringBuilder sb = new StringBuilder(start);
for (int i = 0; i < columns.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(columns[i]);
}
sb.append(") where");
for (int i = 0; i < columns.length; i++) {
sb.append(" ").append(columns[i]).append(" is not null");
}
return sb.toString();
}
// /**
// * MS SQL Server specific DDL Syntax.
// */
// public class MsDdlSyntax extends DbDdlSyntax {
//
// MsDdlSyntax() {
// this.identity = "identity(1,1)";
// this.dropKeyConstraints = true;
// }
//
// /**
// * Return some DDL to disable constraints on the given table.
// */
// public String dropKeyConstraintPrefix(String tableName, String fkName) {
// return "IF OBJECT_ID('"+fkName+"', 'F') IS NOT NULL";
// }
//
// /**
// * Return prefix text that goes before drop table.
// */
// public String dropTablePrefix(String tableName) {
// return "IF OBJECT_ID('"+tableName+"', 'U') IS NOT NULL ";
// }
//
// }
}
@@ -47,6 +47,10 @@ public class PlatformDdl {
protected String identitySuffix = " auto_increment";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
*/
protected boolean inlineUniqueOneToOne = true;
public PlatformDdl(DbTypeMap platformTypes, DbIdentity dbIdentity) {
this.dbIdentity = dbIdentity;
@@ -157,4 +161,19 @@ public class PlatformDdl {
}
/**
* Return true if unique constraints for OneToOne can be inlined as normal.
* Returns false for MsSqlServer due to it's null handling for unique constraints.
*/
public boolean isInlineUniqueOneToOne() {
return inlineUniqueOneToOne;
}
/**
* Overridden by MsSqlServer for specific null handling on unique constraints.
*/
public String createExternalUniqueForOneToOne(String uqName, String tableName, String[] columns) {
// does nothing by default, really this is a MsSqlServer specific requirement
return "";
}
}
@@ -42,6 +42,8 @@ public class Column {
protected String checkConstraint;
@XmlAttribute(name = "unique")
protected Boolean unique;
@XmlAttribute(name = "uniqueOneToOne")
protected Boolean uniqueOneToOne;
@XmlAttribute(name = "primaryKey")
protected Boolean primaryKey;
@XmlAttribute(name = "identity")
@@ -153,6 +155,30 @@ public class Column {
this.unique = value;
}
/**
* Gets the value of the uniqueOneToOne property.
*
* @return
* possible object is
* {@link Boolean }
*
*/
public Boolean isUniqueOneToOne() {
return uniqueOneToOne;
}
/**
* Sets the value of the uniqueOneToOne property.
*
* @param value
* allowed object is
* {@link Boolean }
*
*/
public void setUniqueOneToOne(Boolean value) {
this.uniqueOneToOne = value;
}
/**
* Gets the value of the primaryKey property.
*
@@ -15,8 +15,15 @@ public class MColumn {
private boolean notnull;
private boolean primaryKey;
private boolean identity;
private boolean unique;
/**
* Special unique for OneToOne as we need to handle that different
* specifically for MsSqlServer.
*/
private boolean uniqueOneToOne;
public MColumn(Column column) {
this.name = column.getName();
this.type = column.getType();
@@ -104,15 +111,31 @@ public class MColumn {
return unique;
}
/**
* Set unique specifically for OneToOne mapping.
* We need special DDL for this case for MsSqlServer.
*/
public void setUniqueOneToOne(boolean uniqueOneToOne) {
this.uniqueOneToOne = uniqueOneToOne;
}
/**
* Return true if this is unique for a OneToOne.
*/
public boolean isUniqueOneToOne() {
return uniqueOneToOne;
}
public Column createColumn() {
Column c = new Column();
c.setName(name);
c.setType(type);
if (notnull) c.setNotnull(notnull);
if (unique) c.setUnique(unique);
if (primaryKey) c.setPrimaryKey(primaryKey);
if (identity) c.setIdentity(identity);
if (notnull) c.setNotnull(true);
if (unique) c.setUnique(true);
if (uniqueOneToOne) c.setUniqueOneToOne(true);
if (primaryKey) c.setPrimaryKey(true);
if (identity) c.setIdentity(true);
c.setCheckConstraint(checkConstraint);
c.setReferences(references);
@@ -9,16 +9,33 @@ package com.avaje.ebean.dbmigration.model;
*/
public class MCompoundUniqueConstraint {
/**
* Flag if true indicates this was specifically created for a OneToOne mapping.
*/
private final boolean oneToOne;
/**
* The columns combined to be unique.
*/
private final String[] columns;
public MCompoundUniqueConstraint(String[] columns) {
public MCompoundUniqueConstraint(String[] columns, boolean oneToOne) {
this.columns = columns;
this.oneToOne = oneToOne;
}
/**
* Return the columns for this unique constraint.
*/
public String[] getColumns() {
return columns;
}
/**
* Return true if this unqiue constraint is specifically for OneToOne mapping.
*/
public boolean isOneToOne() {
return oneToOne;
}
}
@@ -248,19 +248,19 @@ public class MTable {
/**
* Add a compound unique constraint.
*/
public void addCompoundUniqueConstraint(String[] columns) {
compoundUniqueConstraints.add(new MCompoundUniqueConstraint(columns));
public void addCompoundUniqueConstraint(String[] columns, boolean oneToOne) {
compoundUniqueConstraints.add(new MCompoundUniqueConstraint(columns, oneToOne));
}
/**
* Add a compound unique constraint.
*/
public void addCompoundUniqueConstraint(List<MColumn> columns) {
public void addCompoundUniqueConstraint(List<MColumn> columns, boolean oneToOne) {
String[] cols = new String[columns.size()];
for (int i = 0; i < columns.size(); i++) {
cols[i] = columns.get(i).getName();
}
addCompoundUniqueConstraint(cols);
addCompoundUniqueConstraint(cols, oneToOne);
}
public void addForeignKey(MCompoundForeignKey compoundKey) {
@@ -3,34 +3,30 @@ package com.avaje.ebean.dbmigration.model.build;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebean.config.dbplatform.IdType;
import com.avaje.ebean.dbmigration.migration.IdentityType;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
import com.avaje.ebean.dbmigration.model.MColumn;
import com.avaje.ebean.dbmigration.model.MTable;
import com.avaje.ebean.dbmigration.model.visitor.BeanPropertyVisitor;
import com.avaje.ebean.dbmigration.model.visitor.BeanVisitor;
import com.avaje.ebeaninternal.server.type.ScalarType;
import java.sql.Types;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.CompoundUniqueContraint;
import com.avaje.ebeaninternal.server.deploy.InheritInfo;
/**
* Used to build the Model objects MTable etc.
*/
public class ModelBuildBeanVisitor implements BeanVisitor {
private final ModelBuildContext ctx;
private final ModelBuildContext ctx;
public ModelBuildBeanVisitor(ModelBuildContext ctx) {
this.ctx = ctx;
}
public ModelBuildBeanVisitor(ModelBuildContext ctx) {
this.ctx = ctx;
}
/**
* Return the PropertyVisitor used to read all the property meta data
* and in this case add MColumn objects to the model.
* <p>
* This creates an MTable and adds it to the model.
* This creates an MTable and adds it to the model.
* </p>
*/
public BeanPropertyVisitor visitBean(BeanDescriptor<?> descriptor) {
@@ -59,7 +55,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
CompoundUniqueContraint[] compoundUniqueConstraints = descriptor.getCompoundUniqueConstraints();
if (compoundUniqueConstraints != null) {
for (int i = 0; i < compoundUniqueConstraints.length; i++) {
table.addCompoundUniqueConstraint(compoundUniqueConstraints[i].getColumns());
table.addCompoundUniqueConstraint(compoundUniqueConstraints[i].getColumns(), false);
}
}
@@ -68,7 +64,6 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
private void setIdentity(BeanDescriptor<?> descriptor, MTable table) {
if (IdType.GENERATOR == descriptor.getIdType()) {
// explicit generator like UUID
table.setIdentityType(IdentityType.GENERATOR);
@@ -94,20 +89,7 @@ public class ModelBuildBeanVisitor implements BeanVisitor {
table.setSequenceInitial(initialValue);
table.setSequenceAllocate(allocationSize);
}
return;
}
BeanProperty idProperty = descriptor.getIdProperty();
if (idProperty != null) {
ScalarType<Object> scalarType = idProperty.getScalarType();
if (scalarType != null) {
int jdbcType = scalarType.getJdbcType();
if (jdbcType == Types.VARCHAR) {
System.out.println("asd");
}
}
}
}
}
@@ -115,14 +115,13 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
modelColumns.add(col);
}
if (p.isOneToOne()) {
// Adding the unique constraint restricts the cardinality from OneToMany down to OneToOne
// adding the unique constraint restricts the cardinality from OneToMany down to OneToOne
// for MsSqlServer we need different DDL to handle NULL values on this constraint
if (modelColumns.size() == 1) {
modelColumns.get(0).setUnique(true);
modelColumns.get(0).setUniqueOneToOne(true);
} else {
table.addCompoundUniqueConstraint(modelColumns);
table.addCompoundUniqueConstraint(modelColumns, true);
}
}
}
@@ -169,6 +169,7 @@ public class DefaultCsvCallback<T> implements CsvCallback<T> {
logger.info("Creating transaction, batchSize[" + persistBatchSize + "]");
transaction.setBatchMode(true);
transaction.setBatchSize(persistBatchSize);
transaction.setBatchGetGeneratedKeys(false);
} else {
// explicitly turn off JDBC batching in case