diff --git a/src/main/java/com/avaje/ebean/annotation/Index.java b/src/main/java/com/avaje/ebean/annotation/Index.java new file mode 100644 index 000000000..96e0491f6 --- /dev/null +++ b/src/main/java/com/avaje/ebean/annotation/Index.java @@ -0,0 +1,24 @@ +package com.avaje.ebean.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation for declaring an index on a single column. + * + * @author rvbiljouw + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Index { + + /** + * Name of the index + * + * @return index name + */ + String value() default ""; + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateIndexVisitor.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateIndexVisitor.java new file mode 100644 index 000000000..0d11865d8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/CreateIndexVisitor.java @@ -0,0 +1,87 @@ +package com.avaje.ebeaninternal.server.ddl; + +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne; +import com.avaje.ebeaninternal.server.deploy.BeanPropertyCompound; + +/** + * A visitor that creates indexes for columns annotated with ColumnIndex + * + * @author rvbiljouw + * @see com.avaje.ebean.annotation.Index + */ +public class CreateIndexVisitor extends AbstractBeanVisitor { + private final IndexPropertyVisitor iv; + + public CreateIndexVisitor(DdlGenContext ctx) { + this.iv = new IndexPropertyVisitor(ctx); + } + + @Override + public void visitBegin() { + + } + + @Override + public boolean visitBean(BeanDescriptor descriptor) { + return descriptor.isInheritanceRoot(); + } + + @Override + public PropertyVisitor visitProperty(BeanProperty p) { + return iv; + } + + @Override + public void visitBeanEnd(BeanDescriptor descriptor) { + visitInheritanceProperties(descriptor, iv); + } + + @Override + public void visitEnd() { + + } + + private static final class IndexPropertyVisitor extends BaseTablePropertyVisitor { + private final DdlGenContext ctx; + + public IndexPropertyVisitor(DdlGenContext ctx) { + this.ctx = ctx; + } + + @Override + public void visitEmbeddedScalar(BeanProperty p, BeanPropertyAssocOne embedded) { + + } + + @Override + public void visitOneImported(BeanPropertyAssocOne p) { + + } + + @Override + public void visitScalar(BeanProperty p) { + String baseTable = p.getBeanDescriptor().getBaseTable(); + if (p.isIndexed()) { + String indexName = p.getIndexName(); + if (indexName.length() == 0) { + indexName = ctx.getDdlSyntax().getIndexName(baseTable, p.getDbColumn(), ctx.incrementIxCount()); + } + ctx.write("create index ") + .write(indexName) + .write(" on ") + .write(baseTable) + .write("(") + .write(p.getDbColumn()) + .write(");") + .writeNewLine(); + } + } + + @Override + public void visitCompoundScalar(BeanPropertyCompound compound, BeanProperty p) { + + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java b/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java index c536a725b..320c63529 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java +++ b/src/main/java/com/avaje/ebeaninternal/server/ddl/DdlGenerator.java @@ -98,17 +98,17 @@ public class DdlGenerator implements SpiEbeanPlugin { } } - protected void writeDrop(String dropFile) { + protected void writeDrop(String dropFile) { - try { - String c = generateDropDdl(); - writeFile(dropFile, c); + try { + String c = generateDropDdl(); + writeFile(dropFile, c); - } catch (IOException e) { - String msg = "Error generating Drop DDL"; - throw new PersistenceException(msg, e); - } - } + } catch (IOException e) { + String msg = "Error generating Drop DDL"; + throw new PersistenceException(msg, e); + } + } protected void writeCreate(String createFile) { @@ -149,6 +149,9 @@ public class DdlGenerator implements SpiEbeanPlugin { AddForeignKeysVisitor fkeys = new AddForeignKeysVisitor(ctx); VisitorUtil.visit(server, fkeys); + CreateIndexVisitor indexes = new CreateIndexVisitor(ctx); + VisitorUtil.visit(server, indexes); + ctx.flush(); createContent = ctx.getContent(); return createContent; diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java index 7eff03cb3..58f4f5641 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -78,7 +78,7 @@ public class BeanProperty implements ElPropertyValue { * Flag set if this maps to the inheritance discriminator column */ final boolean discriminator; - + /** * Flag to mark the property as embedded. This could be on * BeanPropertyAssocOne rather than here. Put it here for checking Id type @@ -92,7 +92,7 @@ public class BeanProperty implements ElPropertyValue { final boolean version; final boolean naturalKey; - + /** * Set if this property is nullable. */ @@ -136,7 +136,7 @@ public class BeanProperty implements ElPropertyValue { * True if the property is a Clob, Blob LongVarchar or LongVarbinary. */ final boolean lob; - + final boolean fetchEager; final boolean isTransient; @@ -147,7 +147,7 @@ public class BeanProperty implements ElPropertyValue { final String name; final int propertyIndex; - + /** * The reflected field. */ @@ -262,6 +262,10 @@ public class BeanProperty implements ElPropertyValue { final boolean jsonDeserialize; + final boolean indexed; + + final String indexName; + public BeanProperty(DeployBeanProperty deploy) { this(null, null, deploy); } @@ -271,7 +275,10 @@ public class BeanProperty implements ElPropertyValue { this.descriptor = descriptor; this.name = InternString.intern(deploy.getName()); this.propertyIndex = deploy.getPropertyIndex(); - + + this.indexed = deploy.isIndexed(); + this.indexName = deploy.getIndexName(); + this.unidirectionalShadow = deploy.isUndirectionalShadow(); this.discriminator = deploy.isDiscriminator(); this.localEncrypted = deploy.isLocalEncrypted(); @@ -326,7 +333,7 @@ public class BeanProperty implements ElPropertyValue { this.lob = isLobType(dbType); this.propertyType = deploy.getPropertyType(); this.field = deploy.getField(); - + EntityType et = descriptor == null ? null : descriptor.getEntityType(); this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), false, null); this.elPlaceHolderEncrypted = tableAliasIntern(descriptor, deploy.getElPlaceHolder(et), dbEncrypted, dbColumn); @@ -362,6 +369,9 @@ public class BeanProperty implements ElPropertyValue { this.name = InternString.intern(source.getName()); this.propertyIndex = source.propertyIndex; + this.indexed = source.isIndexed(); + this.indexName = source.getIndexName(); + this.dbColumn = InternString.intern(override.getDbColumn()); this.sqlFormulaJoin = InternString.intern(override.getSqlFormulaJoin()); this.sqlFormulaSelect = InternString.intern(override.getSqlFormulaSelect()); @@ -410,7 +420,7 @@ public class BeanProperty implements ElPropertyValue { this.lob = isLobType(dbType); this.propertyType = source.getPropertyType(); this.field = source.getField(); - + this.elPlaceHolder = override.replace(source.elPlaceHolder, source.dbColumn); this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn); @@ -446,7 +456,7 @@ public class BeanProperty implements ElPropertyValue { } public ElPropertyValue buildElPropertyValue(String propName, String remainder, ElPropertyChainBuilder chain, - boolean propertyDeploy) { + boolean propertyDeploy) { throw new PersistenceException("Not valid on scalar bean property " + getFullBeanName()); } @@ -475,17 +485,17 @@ public class BeanProperty implements ElPropertyValue { * Return true if this property maps to the inheritance discriminator column. */ public boolean isDiscriminator() { - return discriminator; + return discriminator; } - + /** * Return true if the underlying type is mutable. */ public boolean isMutableScalarType() { - if (scalarType == null) { - return false; - } - return scalarType.isMutable(); + if (scalarType == null) { + return false; + } + return scalarType.isMutable(); } public void copyProperty(EntityBean sourceBean, EntityBean destBean) { @@ -725,14 +735,14 @@ public class BeanProperty implements ElPropertyValue { private static Object[] NO_ARGS = new Object[0]; - public Object getCacheDataValue(EntityBean bean){ - return getValue(bean); + public Object getCacheDataValue(EntityBean bean) { + return getValue(bean); } - - public void setCacheDataValue(EntityBean bean, Object cacheData){ - setValue(bean, cacheData); + + public void setCacheDataValue(EntityBean bean, Object cacheData) { + setValue(bean, cacheData); } - + /** * Return the value of the property method. */ @@ -745,12 +755,12 @@ public class BeanProperty implements ElPropertyValue { throw new RuntimeException(msg, ex); } } - + /** * Explicitly use reflection to get value. */ public Object getValueViaReflection(Object bean) { - try { + try { return readMethod.invoke(bean, NO_ARGS); } catch (Exception ex) { String beanType = bean == null ? "null" : bean.getClass().getName(); @@ -805,7 +815,7 @@ public class BeanProperty implements ElPropertyValue { * Return the position of this property in the enhanced bean. */ public int getPropertyIndex() { - return propertyIndex; + return propertyIndex; } public String getElName() { @@ -819,10 +829,10 @@ public class BeanProperty implements ElPropertyValue { return false; } - + @Override public boolean containsFormulaWithJoin() { - return formula && sqlFormulaJoin != null; + return formula && sqlFormulaJoin != null; } public boolean containsManySince(String sinceProperty) { @@ -880,12 +890,12 @@ public class BeanProperty implements ElPropertyValue { /** * Return true if the mutable value is considered dirty. - * This is only used for 'mutable' scalar types like hstore etc. + * This is only used for 'mutable' scalar types like hstore etc. */ public boolean isDirtyValue(Object value) { - return scalarType.isDirty(value); + return scalarType.isDirty(value); } - + /** * Return the scalarType. */ @@ -904,12 +914,12 @@ public class BeanProperty implements ElPropertyValue { public boolean isDateTimeCapable() { return scalarType != null && scalarType.isDateTimeCapable(); } - + public int getJdbcType() { - return scalarType == null ? 0 : scalarType.getJdbcType(); + return scalarType == null ? 0 : scalarType.getJdbcType(); } - public Object parseDateTime(long systemTimeMillis) { + public Object parseDateTime(long systemTimeMillis) { return scalarType.parseDateTime(systemTimeMillis); } @@ -972,10 +982,10 @@ public class BeanProperty implements ElPropertyValue { * Return true if this is the natural key property. */ public boolean isNaturalKey() { - return naturalKey; + return naturalKey; } - /** + /** * Return true if this property is mandatory. */ public boolean isNullable() { @@ -1008,9 +1018,9 @@ public class BeanProperty implements ElPropertyValue { * Return true if this property is loadable from a resultSet. */ public boolean isLoadProperty() { - return !isTransient || formula; + return !isTransient || formula; } - + /** * Return true if this is a version column used for concurrency checking. */ @@ -1051,10 +1061,10 @@ public class BeanProperty implements ElPropertyValue { * Lob's usually default to fetch lazy. */ public boolean isFetchEager() { - return fetchEager; + return fetchEager; } - /** + /** * Return true if this is mapped to a Clob Blob LongVarchar or * LongVarbinary. */ @@ -1064,17 +1074,17 @@ public class BeanProperty implements ElPropertyValue { private boolean isLobType(int type) { switch (type) { - case Types.CLOB: - return true; - case Types.BLOB: - return true; - case Types.LONGVARBINARY: - return true; - case Types.LONGVARCHAR: - return true; + case Types.CLOB: + return true; + case Types.BLOB: + return true; + case Types.LONGVARBINARY: + return true; + case Types.LONGVARCHAR: + return true; - default: - return false; + default: + return false; } } @@ -1175,7 +1185,7 @@ public class BeanProperty implements ElPropertyValue { @SuppressWarnings("unchecked") public void jsonWrite(WriteJsonContext ctx, EntityBean bean) { - if(!jsonSerialize){ + if (!jsonSerialize) { return; } Object value = getValueIntercept(bean); @@ -1187,15 +1197,15 @@ public class BeanProperty implements ElPropertyValue { } public void jsonRead(ReadJsonContext ctx, EntityBean bean) { - if(!jsonDeserialize){ + if (!jsonDeserialize) { return; } - String jsonValue; - try { - jsonValue = ctx.readScalarValue(); - } catch (TextException e){ - throw new TextException("Error reading property "+getFullBeanName(), e); - } + String jsonValue; + try { + jsonValue = ctx.readScalarValue(); + } catch (TextException e) { + throw new TextException("Error reading property " + getFullBeanName(), e); + } Object objValue; if (jsonValue == null) { objValue = null; @@ -1204,4 +1214,12 @@ public class BeanProperty implements ElPropertyValue { } setValue(bean, objValue); } + + public boolean isIndexed() { + return indexed; + } + + public String getIndexName() { + return indexName; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java index a7eb6ba77..277319337 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/meta/DeployBeanProperty.java @@ -217,6 +217,9 @@ public class DeployBeanProperty { private int sortOrder; + private boolean indexed; + private String indexName; + public DeployBeanProperty(DeployBeanDescriptor desc, Class propertyType, ScalarType scalarType, ScalarTypeConverter typeConverter) { this.desc = desc; this.propertyType = propertyType; @@ -930,4 +933,19 @@ public class DeployBeanProperty { return desc.getFullName() + "." + name; } + public boolean isIndexed() { + return indexed; + } + + public void setIndexed(boolean indexed) { + this.indexed = indexed; + } + + public String getIndexName() { + return indexName; + } + + public void setIndexName(String indexName) { + this.indexName = indexName; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java index 6aa33235b..be531fa8e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -4,36 +4,18 @@ import java.sql.Types; import java.util.Map; import java.util.UUID; -import javax.persistence.Basic; -import javax.persistence.Column; -import javax.persistence.EmbeddedId; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Lob; -import javax.persistence.PersistenceException; -import javax.persistence.SequenceGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; -import javax.persistence.Version; +import javax.persistence.*; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; -import com.avaje.ebean.annotation.CreatedTimestamp; -import com.avaje.ebean.annotation.EmbeddedColumns; -import com.avaje.ebean.annotation.Encrypted; -import com.avaje.ebean.annotation.Expose; -import com.avaje.ebean.annotation.Formula; -import com.avaje.ebean.annotation.UpdatedTimestamp; +import com.avaje.ebean.annotation.*; import com.avaje.ebean.config.EncryptDeploy; import com.avaje.ebean.config.EncryptDeploy.Mode; import com.avaje.ebean.config.GlobalProperties; import com.avaje.ebean.config.dbplatform.DbEncrypt; import com.avaje.ebean.config.dbplatform.DbEncryptFunction; import com.avaje.ebean.config.dbplatform.IdType; +import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.deploy.generatedproperty.GeneratedPropertyFactory; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty; import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssoc; @@ -65,7 +47,7 @@ public class AnnotationFields extends AnnotationParser { if (GlobalProperties.getBoolean("ebean.lobEagerFetch", false)) { defaultLobFetchType = FetchType.EAGER; - } + } } /** @@ -104,7 +86,7 @@ public class AnnotationFields extends AnnotationParser { if (prop.isId() && !prop.isEmbedded()) { prop.setEmbedded(true); } - readEmbeddedAttributeOverrides((DeployBeanPropertyAssocOne)prop); + readEmbeddedAttributeOverrides((DeployBeanPropertyAssocOne) prop); } } @@ -149,7 +131,7 @@ public class AnnotationFields extends AnnotationParser { if (id != null) { readId(id, prop); } - + // determine the JDBC type using Lob/Temporal // otherwise based on the property Class Lob lob = get(prop, Lob.class); @@ -199,9 +181,9 @@ public class AnnotationFields extends AnnotationParser { if (notNull != null && isNotNullOnAllValidationGroups(notNull.groups())) { // Not null on all validation groups so enable // DDL generation of Not Null Constraint - prop.setNullable(false); + prop.setNullable(false); } - + Size size = get(prop, Size.class); if (size != null) { if (size.max() < Integer.MAX_VALUE) { @@ -229,7 +211,7 @@ public class AnnotationFields extends AnnotationParser { } else { throw new RuntimeException("Can't use EmbeddedColumns on ScalarType " - + prop.getFullBeanName()); + + prop.getFullBeanName()); } } @@ -246,7 +228,7 @@ public class AnnotationFields extends AnnotationParser { if (!prop.isTransient()) { EncryptDeploy encryptDeploy = util.getEncryptDeploy(info.getDescriptor().getBaseTableFull(), - prop.getDbColumn()); + prop.getDbColumn()); if (encryptDeploy == null || encryptDeploy.getMode().equals(Mode.MODE_ANNOTATION)) { Encrypted encrypted = get(prop, Encrypted.class); if (encrypted != null) { @@ -257,6 +239,20 @@ public class AnnotationFields extends AnnotationParser { } } + Index index = get(prop, Index.class); + if (index != null) { + if(hasRelationshipItem(prop)) { + throw new RuntimeException("Can't use Index on foreign key relationships."); + } + prop.setIndexed(true); + prop.setIndexName(index.value()); + } + } + + private boolean hasRelationshipItem(DeployBeanProperty prop) { + return get(prop, OneToMany.class) != null || + get(prop, ManyToOne.class) != null || + get(prop, OneToOne.class) != null; } /** @@ -313,7 +309,7 @@ public class AnnotationFields extends AnnotationParser { } } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({"unchecked", "rawtypes"}) private ScalarTypeEncryptedWrapper createScalarType(DeployBeanProperty prop, ScalarType st) { // Use Java Encryptor wrapping the logical scalar type diff --git a/src/test/java/com/avaje/tests/ddl/MainDdlWithSchema.java b/src/test/java/com/avaje/tests/ddl/MainDdlWithSchema.java index f5980724f..f68711f8c 100644 --- a/src/test/java/com/avaje/tests/ddl/MainDdlWithSchema.java +++ b/src/test/java/com/avaje/tests/ddl/MainDdlWithSchema.java @@ -8,21 +8,21 @@ import com.avaje.tests.model.basic.EBasic; public class MainDdlWithSchema { - public static void main(String[] args) { + public static void main(String[] args) { - ServerConfig cfg = new ServerConfig(); - cfg.setName("pg"); - cfg.loadFromProperties(); - cfg.addClass(EBasic.class); - - // .... other settings ... - UnderscoreNamingConvention naming = new UnderscoreNamingConvention(); - naming.setSchema("foo"); - cfg.setNamingConvention(naming); - cfg.setDdlGenerate(true); - cfg.setDdlRun(true); - EbeanServer ebean = EbeanServerFactory.create(cfg); - //ebean.createSqlUpdate("create schema foo").execute(); - System.out.println(ebean); - } + ServerConfig cfg = new ServerConfig(); + cfg.setName("h2"); + cfg.loadFromProperties(); + cfg.addClass(EBasic.class); + + // .... other settings ... + //UnderscoreNamingConvention naming = new UnderscoreNamingConvention(); + //naming.setSchema("test"); + //cfg.setNamingConvention(naming); + cfg.setDdlGenerate(true); + cfg.setDdlRun(true); + EbeanServer ebean = EbeanServerFactory.create(cfg); + //ebean.createSqlUpdate("create schema foo").execute(); + System.out.println(ebean); + } } diff --git a/src/test/java/com/avaje/tests/model/basic/EBasic.java b/src/test/java/com/avaje/tests/model/basic/EBasic.java index a9f5d98c6..14ec83de7 100644 --- a/src/test/java/com/avaje/tests/model/basic/EBasic.java +++ b/src/test/java/com/avaje/tests/model/basic/EBasic.java @@ -7,6 +7,7 @@ import javax.persistence.Id; import javax.persistence.Table; import com.avaje.ebean.annotation.EnumValue; +import com.avaje.ebean.annotation.Index; @Entity @Table(name = "e_basic") @@ -28,6 +29,7 @@ public class EBasic { Status status; + @Index String name; String description;