diff --git a/.travis/print_surefire_reports b/.travis/print_surefire_reports old mode 100644 new mode 100755 diff --git a/.travis/setup_database b/.travis/setup_database old mode 100644 new mode 100755 diff --git a/pom.xml b/pom.xml index b49ee2142..485139656 100644 --- a/pom.xml +++ b/pom.xml @@ -3,13 +3,13 @@ org.avaje - oss-parent + java8-parent 1.1 org.avaje.ebean ebean - 8.7.2-SNAPSHOT + 9.1.1-SNAPSHOT jar ebean @@ -19,22 +19,6 @@ UTF-8 - - - The Apache Software License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - repo - - - - - - rbygrave - Rob Bygrave - robin.bygrave@gmail.com - - - scm:git:https://github.com/ebean-orm/ebean.git HEAD @@ -236,16 +220,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - 3.2 - - 1.8 - 1.8 - - - org.avaje.ebean ebean-maven-plugin @@ -303,7 +277,7 @@ maven-javadoc-plugin 2.9.1 - Ebean 8 + Ebean 9 src/main/java/com/avaje/ebean/overview.html 1.8 org.avaje.doclet.PygmentsDoclet diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java index 6289dd810..97671b6ed 100644 --- a/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java +++ b/src/main/java/com/avaje/ebean/config/dbplatform/DatabasePlatform.java @@ -197,6 +197,13 @@ public class DatabasePlatform { return platform == null || platform.name().equalsIgnoreCase(name); } + /** + * Return true if this matches the given platform. + */ + public boolean isPlatform(Platform platform) { + return platform.name().equalsIgnoreCase(name); + } + /** * Return the name of the DatabasePlatform. *

diff --git a/src/main/java/com/avaje/ebean/event/AbstractBeanPersistListener.java b/src/main/java/com/avaje/ebean/event/AbstractBeanPersistListener.java index 20af32c96..6f2c885c5 100644 --- a/src/main/java/com/avaje/ebean/event/AbstractBeanPersistListener.java +++ b/src/main/java/com/avaje/ebean/event/AbstractBeanPersistListener.java @@ -13,8 +13,7 @@ import java.util.Set; public abstract class AbstractBeanPersistListener implements BeanPersistListener { /** - * Notified that a bean has been inserted locally. Return true if you want the - * cluster to be notified of the event. + * Notified that a bean has been inserted. * * @param bean The bean that was inserted. */ @@ -23,8 +22,7 @@ public abstract class AbstractBeanPersistListener implements BeanPersistListener } /** - * Notified that a bean has been updated locally. Return true if you want the - * cluster to be notified of the event. + * Notified that a bean has been updated. * * @param bean The bean that was updated. * @param updatedProperties The properties that were modified by this update. @@ -34,8 +32,7 @@ public abstract class AbstractBeanPersistListener implements BeanPersistListener } /** - * Notified that a bean has been deleted locally. Return true if you want the - * cluster to be notified of the event. + * Notified that a bean has been deleted. * * @param bean The bean that was deleted. */ @@ -43,4 +40,12 @@ public abstract class AbstractBeanPersistListener implements BeanPersistListener public void deleted(Object bean) { } + /** + * Notified that a bean has been soft deleted. + * + * @param bean The bean that was deleted. + */ + @Override + public void softDeleted(Object bean) { + } } diff --git a/src/main/java/com/avaje/ebean/event/BeanPersistAdapter.java b/src/main/java/com/avaje/ebean/event/BeanPersistAdapter.java index 7cdab42ba..5d0412a49 100644 --- a/src/main/java/com/avaje/ebean/event/BeanPersistAdapter.java +++ b/src/main/java/com/avaje/ebean/event/BeanPersistAdapter.java @@ -46,6 +46,13 @@ public abstract class BeanPersistAdapter implements BeanPersistController { return true; } + /** + * Returns true indicating normal processing should continue. + */ + public boolean preSoftDelete(BeanPersistRequest request) { + return true; + } + /** * Does nothing by default. */ @@ -64,4 +71,10 @@ public abstract class BeanPersistAdapter implements BeanPersistController { public void postUpdate(BeanPersistRequest request) { } + /** + * Does nothing by default. + */ + public void postSoftDelete(BeanPersistRequest request) { + } + } diff --git a/src/main/java/com/avaje/ebean/event/BeanPersistController.java b/src/main/java/com/avaje/ebean/event/BeanPersistController.java index 84a53a79a..6896350d0 100644 --- a/src/main/java/com/avaje/ebean/event/BeanPersistController.java +++ b/src/main/java/com/avaje/ebean/event/BeanPersistController.java @@ -89,6 +89,12 @@ public interface BeanPersistController { */ boolean preDelete(BeanPersistRequest request); + /** + * Prior to a soft delete perform some action. Return true if you want the + * default functionality to continue. + */ + boolean preSoftDelete(BeanPersistRequest request); + /** * Called after the insert was performed. */ @@ -104,4 +110,8 @@ public interface BeanPersistController { */ void postDelete(BeanPersistRequest request); + /** + * Called after the soft delete was performed. + */ + void postSoftDelete(BeanPersistRequest request); } diff --git a/src/main/java/com/avaje/ebean/event/BeanPersistListener.java b/src/main/java/com/avaje/ebean/event/BeanPersistListener.java index 45389c83f..ad4c0eb66 100644 --- a/src/main/java/com/avaje/ebean/event/BeanPersistListener.java +++ b/src/main/java/com/avaje/ebean/event/BeanPersistListener.java @@ -67,4 +67,12 @@ public interface BeanPersistListener { */ void deleted(Object bean); + /** + * Notified that a bean has been soft deleted. + * + * @param bean + * The bean that was soft deleted. + */ + void softDeleted(Object bean); + } diff --git a/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java b/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java index c98837a6d..1a1519b4c 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java +++ b/src/main/java/com/avaje/ebeaninternal/api/ManyWhereJoins.java @@ -155,4 +155,11 @@ public class ManyWhereJoins implements Serializable { aggregation = true; } + /** + * Ensure we have the join required to support the aggregation properties. + */ + public void addAggregationJoin(String property) { + this.aggregation = true; + joins.put(property, new PropertyJoin(property, SqlJoinType.INNER)); + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOriginQuery.java b/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOriginQuery.java index 4c1c2044e..b0a938393 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOriginQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/autotune/service/ProfileOriginQuery.java @@ -1,9 +1,9 @@ package com.avaje.ebeaninternal.server.autotune.service; import com.avaje.ebeaninternal.server.autotune.AutoTuneCollection; -import com.avaje.ebeaninternal.server.util.LongAdder; import java.io.Serializable; +import java.util.concurrent.atomic.LongAdder; /** * Used to accumulate query execution statistics for paths relative to the origin query. diff --git a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java index f6444b840..8a5dc8a32 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java +++ b/src/main/java/com/avaje/ebeaninternal/server/cache/DefaultServerCache.java @@ -4,7 +4,7 @@ import com.avaje.ebean.BackgroundExecutor; import com.avaje.ebean.cache.ServerCache; import com.avaje.ebean.cache.ServerCacheOptions; import com.avaje.ebean.cache.ServerCacheStatistics; -import com.avaje.ebeaninternal.server.util.LongAdder; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,6 +16,7 @@ import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.LongAdder; /** * The default cache implementation. @@ -38,7 +39,6 @@ public class DefaultServerCache implements ServerCache { */ protected final Map map; - // LongAdder is a highly concurrent low latency counter (back ported from Java8) protected final LongAdder missCount = new LongAdder(); protected final LongAdder hitCount = new LongAdder(); protected final LongAdder insertCount = new LongAdder(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/CObjectGraphNodeStatistics.java b/src/main/java/com/avaje/ebeaninternal/server/core/CObjectGraphNodeStatistics.java index 9b223b2d0..96fe96d6a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/CObjectGraphNodeStatistics.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/CObjectGraphNodeStatistics.java @@ -1,10 +1,10 @@ package com.avaje.ebeaninternal.server.core; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.LongAdder; import com.avaje.ebean.meta.*; import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebeaninternal.server.util.LongAdder; /** * Helper to collect the query execution statistics for a given node. diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index 7fe283f0d..c38f9b257 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -373,6 +373,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP beanDescriptor.docStoreInsert(idValue, this, txn); break; case UPDATE: + case SOFT_DELETE: beanDescriptor.docStoreUpdate(idValue, this, txn); break; case DELETE: @@ -392,6 +393,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue); break; case UPDATE: + case SOFT_DELETE: docStoreUpdates.queueIndex(beanDescriptor.getDocStoreQueueId(), idValue); break; case DELETE: @@ -422,6 +424,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP beanPersistListener.deleted(bean); break; + case SOFT_DELETE: + beanPersistListener.softDeleted(bean); + break; + default: } } @@ -641,7 +647,7 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } switch (type) { case INSERT: - persistExecute.executeInsertBean(this); + executeInsert(); return -1; case UPDATE: @@ -649,16 +655,16 @@ public final class PersistRequestBean extends PersistRequest implements BeanP // store the updated properties for sending later updatedProperties = getUpdatedProperties(); } - persistExecute.executeUpdateBean(this); + executeUpdate(); return -1; case SOFT_DELETE: prepareForSoftDelete(); - persistExecute.executeUpdateBean(this); + executeSoftDelete(); return -1; case DELETE: - return persistExecute.executeDeleteBean(this); + return executeDelete(); default: throw new RuntimeException("Invalid type " + type); @@ -787,13 +793,11 @@ public final class PersistRequestBean extends PersistRequest implements BeanP controller.postInsert(this); break; case UPDATE: - case SOFT_DELETE: - /* - * In the previous version, in case SOFT_DELETE the method controller.postDelete was called. However, - * this lead to an inconsistent situation, because first controller.preUpdate is called. - */ controller.postUpdate(this); break; + case SOFT_DELETE: + controller.postSoftDelete(this); + break; case DELETE: controller.postDelete(this); break; @@ -1047,4 +1051,32 @@ public final class PersistRequestBean extends PersistRequest implements BeanP public long getVersion() { return version; } + + public void executeInsert() { + if (controller == null || controller.preInsert(this)) { + beanManager.getBeanPersister().insert(this); + } + } + + public void executeUpdate() { + if (controller == null || controller.preUpdate(this)) { + postControllerPrepareUpdate(); + beanManager.getBeanPersister().update(this); + } + } + + public void executeSoftDelete() { + if (controller == null || controller.preSoftDelete(this)) { + postControllerPrepareUpdate(); + beanManager.getBeanPersister().update(this); + } + } + + public int executeDelete() { + if (controller == null || controller.preDelete(this)) { + return beanManager.getBeanPersister().delete(this); + } + // delete handled by the BeanController so return 0 + return 0; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java index e2b806d04..06f3218fa 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistController.java @@ -122,6 +122,13 @@ public class ChainedBeanPersistController implements BeanPersistController { } } + @Override + public void postSoftDelete(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + chain[i].postSoftDelete(request); + } + } + @Override public boolean preDelete(BeanPersistRequest request) { for (int i = 0; i < chain.length; i++) { @@ -132,6 +139,16 @@ public class ChainedBeanPersistController implements BeanPersistController { return true; } + @Override + public boolean preSoftDelete(BeanPersistRequest request) { + for (int i = 0; i < chain.length; i++) { + if (!chain[i].preSoftDelete(request)) { + return false; + } + } + return true; + } + @Override public boolean preInsert(BeanPersistRequest request) { for (int i = 0; i < chain.length; i++) { diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistListener.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistListener.java index a5a211209..813caf459 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistListener.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/ChainedBeanPersistListener.java @@ -18,7 +18,7 @@ public class ChainedBeanPersistListener implements BeanPersistListener { /** * Construct adding 2 BeanPersistListener's. */ - public ChainedBeanPersistListener(BeanPersistListener c1, BeanPersistListener c2) { + ChainedBeanPersistListener(BeanPersistListener c1, BeanPersistListener c2) { this(addList(c1, c2)); } @@ -89,6 +89,12 @@ public class ChainedBeanPersistListener implements BeanPersistListener { } } + public void softDeleted(Object bean) { + for (int i = 0; i < chain.length; i++) { + chain[i].softDeleted(bean); + } + } + public void inserted(Object bean) { for (int i = 0; i < chain.length; i++) { chain[i].inserted(bean); diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java index a9f59bae7..467cbb193 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/DefaultPersistExecute.java @@ -1,9 +1,9 @@ package com.avaje.ebeaninternal.server.persist; -import com.avaje.ebean.event.BeanPersistController; import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.*; -import com.avaje.ebeaninternal.server.deploy.BeanManager; +import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; +import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; +import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; /** * Default PersistExecute implementation using DML statements. @@ -11,7 +11,7 @@ import com.avaje.ebeaninternal.server.deploy.BeanManager; * Supports the use of PreparedStatement batching. *

*/ -public final class DefaultPersistExecute implements PersistExecute { +final class DefaultPersistExecute implements PersistExecute { private final ExeCallableSql exeCallableSql; @@ -27,7 +27,7 @@ public final class DefaultPersistExecute implements PersistExecute { /** * Construct this DmlPersistExecute. */ - public DefaultPersistExecute(Binder binder, int defaultBatchSize) { + DefaultPersistExecute(Binder binder, int defaultBatchSize) { this.exeOrmUpdate = new ExeOrmUpdate(binder); this.exeUpdateSql = new ExeUpdateSql(binder); @@ -41,51 +41,6 @@ public final class DefaultPersistExecute implements PersistExecute { return new BatchControl(t, defaultBatchSize, true); } - /** - * execute the bean insert request. - */ - public void executeInsertBean(PersistRequestBean request) { - - BeanManager mgr = request.getBeanManager(); - BeanPersister persister = mgr.getBeanPersister(); - - BeanPersistController controller = request.getBeanController(); - if (controller == null || controller.preInsert(request)) { - persister.insert(request); - } - } - - /** - * execute the bean update request. - */ - public void executeUpdateBean(PersistRequestBean request) { - - BeanManager mgr = request.getBeanManager(); - BeanPersister persister = mgr.getBeanPersister(); - - BeanPersistController controller = request.getBeanController(); - if (controller == null || controller.preUpdate(request)) { - request.postControllerPrepareUpdate(); - persister.update(request); - } - } - - /** - * execute the bean delete request. - */ - public int executeDeleteBean(PersistRequestBean request) { - - BeanManager mgr = request.getBeanManager(); - BeanPersister persister = mgr.getBeanPersister(); - - BeanPersistController controller = request.getBeanController(); - if (controller == null || controller.preDelete(request)) { - return persister.delete(request); - } - // delete handled by the BeanController so return 0 - return 0; - } - /** * Execute the updateSqlRequest */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java b/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java index c0183aa4b..ddfd2d41e 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/PersistExecute.java @@ -1,7 +1,6 @@ package com.avaje.ebeaninternal.server.persist; import com.avaje.ebeaninternal.api.SpiTransaction; -import com.avaje.ebeaninternal.server.core.PersistRequestBean; import com.avaje.ebeaninternal.server.core.PersistRequestCallableSql; import com.avaje.ebeaninternal.server.core.PersistRequestOrmUpdate; import com.avaje.ebeaninternal.server.core.PersistRequestUpdateSql; @@ -20,21 +19,6 @@ public interface PersistExecute { */ BatchControl createBatchControl(SpiTransaction t); - /** - * Execute a Bean (or MapBean) insert. - */ - void executeInsertBean(PersistRequestBean request); - - /** - * Execute a Bean (or MapBean) update. - */ - void executeUpdateBean(PersistRequestBean request); - - /** - * Execute a Bean (or MapBean) delete. - */ - int executeDeleteBean(PersistRequestBean request); - /** * Execute a Update. */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java index c1cd7ca85..88389ed8a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanStats.java @@ -1,17 +1,17 @@ package com.avaje.ebeaninternal.server.query; +import com.avaje.ebean.bean.ObjectGraphNode; +import com.avaje.ebean.meta.MetaQueryPlanOriginCount; +import com.avaje.ebean.meta.MetaQueryPlanStatistic; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; - -import com.avaje.ebean.bean.ObjectGraphNode; -import com.avaje.ebean.meta.MetaQueryPlanOriginCount; -import com.avaje.ebean.meta.MetaQueryPlanStatistic; -import com.avaje.ebeaninternal.server.util.LongAdder; -import com.avaje.ebeaninternal.server.util.LongMaxUpdater; +import java.util.concurrent.atomic.LongAccumulator; +import java.util.concurrent.atomic.LongAdder; /** * Statistics for a specific query plan that can accumulate. @@ -26,7 +26,7 @@ public final class CQueryPlanStats { private final LongAdder totalBeans = new LongAdder(); - private final LongMaxUpdater maxTime = new LongMaxUpdater(); + private final LongAccumulator maxTime = new LongAccumulator(Math::max, Long.MIN_VALUE); private final AtomicLong startTime = new AtomicLong(System.currentTimeMillis()); @@ -51,7 +51,7 @@ public final class CQueryPlanStats { count.increment(); totalBeans.add(loadedBeanCount); totalTime.add(timeMicros); - maxTime.update(timeMicros); + maxTime.accumulate(timeMicros); // not safe but should be atomic lastQueryTime = System.currentTimeMillis(); @@ -106,9 +106,9 @@ public final class CQueryPlanStats { // not guaranteed to be consistent due to time gaps between getting each value out of LongAdders but can live with that // relative to the cost of making sure count and totalTime etc are all guaranteed to be consistent if (reset) { - return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.maxThenReset(), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime, origins); + return new Snapshot(queryPlan, count.sumThenReset(), totalTime.sumThenReset(), totalBeans.sumThenReset(), maxTime.getThenReset(), startTime.getAndSet(System.currentTimeMillis()), lastQueryTime, origins); } - return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.max(), startTime.get(), lastQueryTime, origins); + return new Snapshot(queryPlan, count.sum(), totalTime.sum(), totalBeans.sum(), maxTime.get(), startTime.get(), lastQueryTime, origins); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java index 3029a2690..ff3597337 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeBuilder.java @@ -236,11 +236,17 @@ public class SqlTreeBuilder { } } + OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false); + SqlTreeProperties props = getBaseSelect(desc, queryProps); + if (prefix == null && !rawSql) { + if (props.requireSqlDistinct(manyWhereJoins)) { + query.setSqlDistinct(true); + } addManyWhereJoins(myJoinList); } - SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList); + SqlTreeNode selectNode = buildNode(prefix, prop, desc, myJoinList, props); if (joinList != null) { joinList.add(selectNode); } @@ -264,11 +270,7 @@ public class SqlTreeBuilder { } } - private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, List myList) { - - OrmQueryProperties queryProps = queryDetail.getChunk(prefix, false); - - SqlTreeProperties props = getBaseSelect(desc, queryProps); + private SqlTreeNode buildNode(String prefix, BeanPropertyAssoc prop, BeanDescriptor desc, List myList, SqlTreeProperties props) { if (prefix == null) { buildExtraJoins(desc, myList); diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java index d18936676..35b2b37bb 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNode.java @@ -10,7 +10,7 @@ import com.avaje.ebeaninternal.server.deploy.DbSqlContext; import java.sql.SQLException; import java.util.List; -public interface SqlTreeNode { +interface SqlTreeNode { String COMMA = ", "; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java index c244f13ab..aa7031b1b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -27,13 +27,13 @@ import java.util.Map; /** * Normal bean included in the query. */ -public class SqlTreeNodeBean implements SqlTreeNode { +class SqlTreeNodeBean implements SqlTreeNode { private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0]; protected final BeanDescriptor desc; - protected final IdBinder idBinder; + private final IdBinder idBinder; /** * The children which will be other SelectBean or SelectProxyBean. @@ -43,39 +43,39 @@ public class SqlTreeNodeBean implements SqlTreeNode { /** * Set to true if this is a partial object fetch. */ - protected final boolean partialObject; + private final boolean partialObject; protected final BeanProperty[] properties; /** * Extra where clause added by Where annotation on associated many. */ - protected final String extraWhere; + private final String extraWhere; - protected final BeanPropertyAssoc nodeBeanProp; + private final BeanPropertyAssoc nodeBeanProp; /** * False if report bean and has no id property. */ - protected final boolean readId; + private final boolean readId; - protected final boolean disableLazyLoad; + private final boolean disableLazyLoad; protected final InheritInfo inheritInfo; protected final String prefix; - protected final Map pathMap; + private final Map pathMap; - protected final BeanPropertyAssocMany lazyLoadParent; + final BeanPropertyAssocMany lazyLoadParent; - protected final SpiQuery.TemporalMode temporalMode; + final SpiQuery.TemporalMode temporalMode; - protected final boolean temporalVersions; + private final boolean temporalVersions; private final IdBinder lazyLoadParentIdBinder; - protected String baseTableAlias; + String baseTableAlias; /** * Table alias set if this bean node includes a join to a intersection @@ -83,12 +83,12 @@ public class SqlTreeNodeBean implements SqlTreeNode { */ private boolean intersectionAsOfTableAlias; - private boolean aggregation; + private final boolean aggregation; /** * Construct for leaf node. */ - public SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, SqlTreeProperties props, + SqlTreeNodeBean(String prefix, BeanPropertyAssoc beanProp, SqlTreeProperties props, List myChildren, boolean disableLazyLoad) { this(prefix, beanProp, beanProp.getTargetDescriptor(), props, myChildren, true, null, SpiQuery.TemporalMode.CURRENT, disableLazyLoad); @@ -97,7 +97,7 @@ public class SqlTreeNodeBean implements SqlTreeNode { /** * Construct for root node. */ - public SqlTreeNodeBean(BeanDescriptor desc, SqlTreeProperties props, List myList, boolean withId, + SqlTreeNodeBean(BeanDescriptor desc, SqlTreeProperties props, List myList, boolean withId, BeanPropertyAssocMany many, SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad) { this(null, null, desc, props, myList, withId, many, temporalMode, disableLazyLoad); } @@ -127,6 +127,7 @@ public class SqlTreeNodeBean implements SqlTreeNode { this.partialObject = props.isPartialObject(); this.properties = props.getProps(); + this.aggregation = props.isAggregation(); this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[myChildren.size()]); pathMap = createPathMap(prefix, desc); @@ -447,9 +448,6 @@ public class SqlTreeNodeBean implements SqlTreeNode { for (int i = 0; i < props.length; i++) { props[i].appendSelect(ctx, subQuery); - if (props[i].isAggregation()) { - aggregation = true; - } } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java index 45f30b8bc..cd31bc405 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeExtraJoin.java @@ -22,7 +22,7 @@ import java.util.List; * etc in this case we must add an extra join. *

*/ -public class SqlTreeNodeExtraJoin implements SqlTreeNode { +class SqlTreeNodeExtraJoin implements SqlTreeNode { private final BeanPropertyAssoc assocBeanProperty; @@ -34,7 +34,7 @@ public class SqlTreeNodeExtraJoin implements SqlTreeNode { private List children; - public SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty, boolean pathContainsMany) { + SqlTreeNodeExtraJoin(String prefix, BeanPropertyAssoc assocBeanProperty, boolean pathContainsMany) { this.prefix = prefix; this.assocBeanProperty = assocBeanProperty; this.pathContainsMany = pathContainsMany; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java index defe4263d..b2691c3d9 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyRoot.java @@ -8,11 +8,11 @@ import com.avaje.ebeaninternal.server.deploy.DbSqlContext; import java.sql.SQLException; import java.util.List; -public final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { +final class SqlTreeNodeManyRoot extends SqlTreeNodeBean { private final BeanPropertyAssocMany manyProp; - public SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany prop, SqlTreeProperties props, List myList, boolean disableLazyLoad) { + SqlTreeNodeManyRoot(String prefix, BeanPropertyAssocMany prop, SqlTreeProperties props, List myList, boolean disableLazyLoad) { super(prefix, prop, props, myList, disableLazyLoad); this.manyProp = prop; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java index b0d7b2944..a753d5a25 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeManyWhereJoin.java @@ -17,7 +17,7 @@ import java.util.List; /** * Join to Many (or child of a many) to support where clause predicates on many properties. */ -public class SqlTreeNodeManyWhereJoin implements SqlTreeNode { +class SqlTreeNodeManyWhereJoin implements SqlTreeNode { private final String parentPrefix; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java index f3f027bce..a0b386b81 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeNodeRoot.java @@ -11,7 +11,7 @@ import java.util.List; /** * Represents the root node of the Sql Tree. */ -public final class SqlTreeNodeRoot extends SqlTreeNodeBean { +final class SqlTreeNodeRoot extends SqlTreeNodeBean { private final TableJoin includeJoin; diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java index 2fb593a46..8b519f851 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/SqlTreeProperties.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import com.avaje.ebeaninternal.api.ManyWhereJoins; import com.avaje.ebeaninternal.server.deploy.BeanProperty; import com.avaje.ebeaninternal.server.deploy.TableJoin; @@ -33,6 +34,8 @@ public class SqlTreeProperties { private boolean allProperties; + private boolean aggregation; + public SqlTreeProperties() { } @@ -80,4 +83,41 @@ public class SqlTreeProperties { this.allProperties = true; } + /** + * Check for an aggregation property and set manyWhereJoin as needed. + *

+ * Return true if a Sql distinct is required. + *

+ */ + public boolean requireSqlDistinct(ManyWhereJoins manyWhereJoins) { + String joinProperty = aggregationJoin(); + if (joinProperty != null) { + aggregation = true; + manyWhereJoins.addAggregationJoin(joinProperty); + return false; + } else{ + return manyWhereJoins.requireSqlDistinct(); + } + } + + /** + * Return true if this contains an aggregation property. + */ + public boolean isAggregation() { + return aggregation; + } + + /** + * Return the property to join for aggregation. + */ + private String aggregationJoin() { + if (!allProperties) { + for (BeanProperty beanProperty : propsList) { + if (beanProperty.isAggregation()) { + return beanProperty.getElPrefix(); + } + } + } + return null; + } } \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java index d1237712d..8ffb97800 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java +++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java @@ -373,9 +373,6 @@ public class DefaultOrmQuery implements SpiQuery { if (havingExpressions != null) { havingExpressions.containsMany(beanDescriptor, manyWhereJoins); } - if (manyWhereJoins.requireSqlDistinct()) { - setSqlDistinct(true); - } } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java b/src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java index 0899fa40a..9bd2d23b7 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java @@ -5,7 +5,7 @@ import java.util.UUID; /** * Type conversion for use with ScalarTypeArrayList. */ -public interface ArrayElementConverter { +interface ArrayElementConverter { /** * Convert the array element to the logical type. @@ -32,6 +32,11 @@ public interface ArrayElementConverter { */ ArrayElementConverter INTEGER = new IntegerConverter(); + /** + * The Double converter implementation. + */ + ArrayElementConverter DOUBLE = new DoubleConverter(); + class LongConverter implements ArrayElementConverter { @Override @@ -56,6 +61,18 @@ public interface ArrayElementConverter { } } + class DoubleConverter implements ArrayElementConverter { + + @Override + public Double toElement(Object rawValue) { + if (rawValue instanceof Double) { + return (Double) rawValue; + } else { + return ((Number) rawValue).doubleValue(); + } + } + } + /** * String converter (noop based). */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java index c299d45e1..6ef22fba2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java @@ -7,6 +7,7 @@ import com.avaje.ebean.annotation.EnumValue; import com.avaje.ebean.config.CompoundType; import com.avaje.ebean.config.CompoundTypeProperty; import com.avaje.ebean.config.JsonConfig; +import com.avaje.ebean.config.Platform; import com.avaje.ebean.config.ScalarTypeConverter; import com.avaje.ebean.config.ServerConfig; import com.avaje.ebean.config.dbplatform.DatabasePlatform; @@ -185,6 +186,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { */ private ScalarType jsonNodeJsonb; + private final PlatformArrayTypeFactory arrayTypeFactory; + /** * Create the DefaultTypeManager. */ @@ -204,6 +207,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { this.extraTypeFactory = new DefaultTypeFactory(config); this.postgres = isPostgres(config.getDatabasePlatform()); + this.arrayTypeFactory = arrayTypeFactory(postgres, config.getDatabasePlatform()); + this.offlineMigrationGeneration = DbOffline.isGenerateMigration(); initialiseStandard(jsonDateTime, config); @@ -220,6 +225,19 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { } } + /** + * Return the factory to use to support DB ARRAY types. + */ + private PlatformArrayTypeFactory arrayTypeFactory(boolean postgres, DatabasePlatform databasePlatform) { + if (postgres) { + return ScalarTypeArrayList.factory(); + } else if (databasePlatform.isPlatform(Platform.H2)) { + return ScalarTypeArrayListH2.factory(); + } + // not supported for this DB platform + return null; + } + /** * Load custom scalar types registered via ExtraTypeFactory and ServiceLoader. */ @@ -397,9 +415,9 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable { public ScalarType getArrayScalarType(Class type, DbArray dbArray, Type genericType) { if (type.equals(List.class)) { - if (postgres) { + if (arrayTypeFactory != null) { Type valueType = getValueType(genericType); - return ScalarTypeArrayList.typeFor(valueType); + return arrayTypeFactory.typeFor(valueType); } // fallback to JSON storage in VARCHAR column return new ScalarTypeJsonList.Varchar(getDocType(getValueType(genericType))); diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/PlatformArrayTypeFactory.java b/src/main/java/com/avaje/ebeaninternal/server/type/PlatformArrayTypeFactory.java new file mode 100644 index 000000000..ba6a9fe17 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/PlatformArrayTypeFactory.java @@ -0,0 +1,15 @@ +package com.avaje.ebeaninternal.server.type; + +import java.lang.reflect.Type; + +/** + * Factory for platform specific handling/ScalarTypes for DB ARRAY. + */ +public interface PlatformArrayTypeFactory { + + /** + * Return the ScalarType to handle DB ARRAY for the given element type. + */ + ScalarType typeFor(Type valueType); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java index 0e7b693db..4aa61b15a 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java @@ -22,30 +22,38 @@ import java.util.UUID; public class ScalarTypeArrayList extends ScalarTypeJsonCollection { private static ScalarTypeArrayList UUID = new ScalarTypeArrayList("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID); - private static ScalarTypeArrayList LONG = new ScalarTypeArrayList("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG); - private static ScalarTypeArrayList INTEGER = new ScalarTypeArrayList("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER); - + private static ScalarTypeArrayList DOUBLE = new ScalarTypeArrayList("float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE); private static ScalarTypeArrayList STRING = new ScalarTypeArrayList("varchar", DocPropertyType.STRING, ArrayElementConverter.STRING); - /** - * Return the ScalarType to use based on the List's generic parameter type. - */ - public static ScalarTypeArrayList typeFor(Type valueType) { - if (valueType.equals(UUID.class)) { - return UUID; + static PlatformArrayTypeFactory factory() { + return new Factory(); + } + + static class Factory implements PlatformArrayTypeFactory { + + /** + * Return the ScalarType to use based on the List's generic parameter type. + */ + public ScalarTypeArrayList typeFor(Type valueType) { + if (valueType.equals(UUID.class)) { + return UUID; + } + if (valueType.equals(Long.class)) { + return LONG; + } + if (valueType.equals(Integer.class)) { + return INTEGER; + } + if (valueType.equals(Double.class)) { + return DOUBLE; + } + if (valueType.equals(String.class)) { + return STRING; + } + throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } - if (valueType.equals(Long.class)) { - return LONG; - } - if (valueType.equals(Integer.class)) { - return INTEGER; - } - if (valueType.equals(String.class)) { - return STRING; - } - throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } private final String arrayType; @@ -79,7 +87,7 @@ public class ScalarTypeArrayList extends ScalarTypeJsonCollection { return new ModifyAwareList(list); } - private Object[] toArray(List value) { + protected Object[] toArray(List value) { return value.toArray(); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayListH2.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayListH2.java new file mode 100644 index 000000000..b9b4175d8 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayListH2.java @@ -0,0 +1,67 @@ +package com.avaje.ebeaninternal.server.type; + +import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType; + +import java.lang.reflect.Type; +import java.sql.SQLException; +import java.sql.Types; +import java.util.List; + +/** + * H2 database support for DB ARRAY. + */ +class ScalarTypeArrayListH2 extends ScalarTypeArrayList { + + private static ScalarTypeArrayListH2 UUID = new ScalarTypeArrayListH2("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID); + private static ScalarTypeArrayListH2 LONG = new ScalarTypeArrayListH2("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG); + private static ScalarTypeArrayListH2 INTEGER = new ScalarTypeArrayListH2("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER); + private static ScalarTypeArrayListH2 DOUBLE = new ScalarTypeArrayListH2("double", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE); + private static ScalarTypeArrayListH2 STRING = new ScalarTypeArrayListH2("varchar", DocPropertyType.STRING, ArrayElementConverter.STRING); + + static PlatformArrayTypeFactory factory() { + return new ScalarTypeArrayListH2.Factory(); + } + + static class Factory implements PlatformArrayTypeFactory { + + /** + * Return the ScalarType to use based on the List's generic parameter type. + */ + public ScalarTypeArrayListH2 typeFor(Type valueType) { + if (valueType.equals(java.util.UUID.class)) { + return UUID; + } + if (valueType.equals(Long.class)) { + return LONG; + } + if (valueType.equals(Integer.class)) { + return INTEGER; + } + if (valueType.equals(Double.class)) { + return DOUBLE; + } + if (valueType.equals(String.class)) { + return STRING; + } + throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); + } + } + + private ScalarTypeArrayListH2(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { + super(arrayType, docPropertyType, converter); + } + + @Override + public String getDbColumnDefn() { + return "array"; + } + + @Override + public void bind(DataBind bind, List value) throws SQLException { + if (value == null) { + bind.setNull(Types.ARRAY); + } else { + bind.setObject(toArray(value)); + } + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/LongAdder.java b/src/main/java/com/avaje/ebeaninternal/server/util/LongAdder.java deleted file mode 100644 index 6ffd97d23..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/util/LongAdder.java +++ /dev/null @@ -1,201 +0,0 @@ -package com.avaje.ebeaninternal.server.util; - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - - -import java.util.concurrent.atomic.AtomicLong; -import java.io.Serializable; - -/** - * One or more variables that together maintain an initially zero - * {@code long} sum. When updates (method {@link #add}) are contended - * across threads, the set of variables may grow dynamically to reduce - * contention. Method {@link #sum} (or, equivalently, {@link - * #longValue}) returns the current total combined across the - * variables maintaining the sum. - * - *

This class is usually preferable to {@link AtomicLong} when - * multiple threads update a common sum that is used for purposes such - * as collecting statistics, not for fine-grained synchronization - * control. Under low update contention, the two classes have similar - * characteristics. But under high contention, expected throughput of - * this class is significantly higher, at the expense of higher space - * consumption. - * - *

This class extends {@link Number}, but does not define - * methods such as {@code equals}, {@code hashCode} and {@code - * compareTo} because instances are expected to be mutated, and so are - * not useful as collection keys. - * - *

jsr166e note: This class is targeted to be placed in - * java.util.concurrent.atomic. - * - * @since 1.8 - * @author Doug Lea - */ -public class LongAdder extends Striped64 implements Serializable { - private static final long serialVersionUID = 7249069246863182397L; - - /** - * Version of plus for use in retryUpdate - */ - final long fn(long v, long x) { return v + x; } - - /** - * Creates a new adder with initial sum of zero. - */ - public LongAdder() { - } - - /** - * Adds the given value. - * - * @param x the value to add - */ - public void add(long x) { - Cell[] as; long b, v; HashCode hc; Cell a; int n; - if ((as = cells) != null || !casBase(b = base, b + x)) { - boolean uncontended = true; - int h = (hc = threadHashCode.get()).code; - if (as == null || (n = as.length) < 1 || - (a = as[(n - 1) & h]) == null || - !(uncontended = a.cas(v = a.value, v + x))) - retryUpdate(x, hc, uncontended); - } - } - - /** - * Equivalent to {@code add(1)}. - */ - public void increment() { - add(1L); - } - - /** - * Equivalent to {@code add(-1)}. - */ - public void decrement() { - add(-1L); - } - - /** - * Returns the current sum. The returned value is NOT an - * atomic snapshot; invocation in the absence of concurrent - * updates returns an accurate result, but concurrent updates that - * occur while the sum is being calculated might not be - * incorporated. - * - * @return the sum - */ - public long sum() { - long sum = base; - Cell[] as = cells; - if (as != null) { - int n = as.length; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null) - sum += a.value; - } - } - return sum; - } - - /** - * Resets variables maintaining the sum to zero. This method may - * be a useful alternative to creating a new adder, but is only - * effective if there are no concurrent updates. Because this - * method is intrinsically racy, it should only be used when it is - * known that no threads are concurrently updating. - */ - public void reset() { - internalReset(0L); - } - - /** - * Equivalent in effect to {@link #sum} followed by {@link - * #reset}. This method may apply for example during quiescent - * points between multithreaded computations. If there are - * updates concurrent with this method, the returned value is - * not guaranteed to be the final value occurring before - * the reset. - * - * @return the sum - */ - public long sumThenReset() { - long sum = base; - Cell[] as = cells; - base = 0L; - if (as != null) { - int n = as.length; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null) { - sum += a.value; - a.value = 0L; - } - } - } - return sum; - } - - /** - * Returns the String representation of the {@link #sum}. - * @return the String representation of the {@link #sum} - */ - public String toString() { - return Long.toString(sum()); - } - - /** - * Equivalent to {@link #sum}. - * - * @return the sum - */ - public long longValue() { - return sum(); - } - - /** - * Returns the {@link #sum} as an {@code int} after a narrowing - * primitive conversion. - */ - public int intValue() { - return (int)sum(); - } - - /** - * Returns the {@link #sum} as a {@code float} - * after a widening primitive conversion. - */ - public float floatValue() { - return (float)sum(); - } - - /** - * Returns the {@link #sum} as a {@code double} after a widening - * primitive conversion. - */ - public double doubleValue() { - return (double)sum(); - } - - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - s.defaultWriteObject(); - s.writeLong(sum()); - } - - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); - busy = 0; - cells = null; - base = s.readLong(); - } - -} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/LongMaxUpdater.java b/src/main/java/com/avaje/ebeaninternal/server/util/LongMaxUpdater.java deleted file mode 100644 index b702ff262..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/util/LongMaxUpdater.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.avaje.ebeaninternal.server.util; - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -import java.io.Serializable; - -/** - * One or more variables that together maintain a running {@code long} - * maximum with initial value {@code Long.MIN_VALUE}. When updates - * (method {@link #update}) are contended across threads, the set of - * variables may grow dynamically to reduce contention. Method {@link - * #max} (or, equivalently, {@link #longValue}) returns the current - * maximum across the variables maintaining updates. - * - *

This class extends {@link Number}, but does not define - * methods such as {@code equals}, {@code hashCode} and {@code - * compareTo} because instances are expected to be mutated, and so are - * not useful as collection keys. - * - *

jsr166e note: This class is targeted to be placed in - * java.util.concurrent.atomic. - * - * @since 1.8 - * @author Doug Lea - */ -public class LongMaxUpdater extends Striped64 implements Serializable { - private static final long serialVersionUID = 7249069246863182397L; - - /** - * Version of max for use in retryUpdate - */ - final long fn(long v, long x) { return v > x ? v : x; } - - /** - * Creates a new instance with initial maximum of {@code - * Long.MIN_VALUE}. - */ - public LongMaxUpdater() { - base = Long.MIN_VALUE; - } - - /** - * Updates the maximum to be at least the given value. - * - * @param x the value to update - */ - public void update(long x) { - Cell[] as; long b, v; HashCode hc; Cell a; int n; - if ((as = cells) != null || - (b = base) < x && !casBase(b, x)) { - boolean uncontended = true; - int h = (hc = threadHashCode.get()).code; - if (as == null || (n = as.length) < 1 || - (a = as[(n - 1) & h]) == null || - ((v = a.value) < x && !(uncontended = a.cas(v, x)))) - retryUpdate(x, hc, uncontended); - } - } - - /** - * Returns the current maximum. The returned value is - * NOT an atomic snapshot; invocation in the absence of - * concurrent updates returns an accurate result, but concurrent - * updates that occur while the value is being calculated might - * not be incorporated. - * - * @return the maximum - */ - public long max() { - Cell[] as = cells; - long max = base; - if (as != null) { - int n = as.length; - long v; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null && (v = a.value) > max) - max = v; - } - } - return max; - } - - /** - * Resets variables maintaining updates to {@code Long.MIN_VALUE}. - * This method may be a useful alternative to creating a new - * updater, but is only effective if there are no concurrent - * updates. Because this method is intrinsically racy, it should - * only be used when it is known that no threads are concurrently - * updating. - */ - public void reset() { - internalReset(Long.MIN_VALUE); - } - - /** - * Equivalent in effect to {@link #max} followed by {@link - * #reset}. This method may apply for example during quiescent - * points between multithreaded computations. If there are - * updates concurrent with this method, the returned value is - * not guaranteed to be the final value occurring before - * the reset. - * - * @return the maximum - */ - public long maxThenReset() { - Cell[] as = cells; - long max = base; - base = Long.MIN_VALUE; - if (as != null) { - int n = as.length; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null) { - long v = a.value; - a.value = Long.MIN_VALUE; - if (v > max) - max = v; - } - } - } - return max; - } - - /** - * Returns the String representation of the {@link #max}. - * @return the String representation of the {@link #max} - */ - public String toString() { - return Long.toString(max()); - } - - /** - * Equivalent to {@link #max}. - * - * @return the maximum - */ - public long longValue() { - return max(); - } - - /** - * Returns the {@link #max} as an {@code int} after a narrowing - * primitive conversion. - */ - public int intValue() { - return (int)max(); - } - - /** - * Returns the {@link #max} as a {@code float} - * after a widening primitive conversion. - */ - public float floatValue() { - return (float)max(); - } - - /** - * Returns the {@link #max} as a {@code double} after a widening - * primitive conversion. - */ - public double doubleValue() { - return (double)max(); - } - - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - s.defaultWriteObject(); - s.writeLong(max()); - } - - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - s.defaultReadObject(); - busy = 0; - cells = null; - base = s.readLong(); - } - -} diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/Stripped64.java b/src/main/java/com/avaje/ebeaninternal/server/util/Stripped64.java deleted file mode 100644 index eed973fb6..000000000 --- a/src/main/java/com/avaje/ebeaninternal/server/util/Stripped64.java +++ /dev/null @@ -1,342 +0,0 @@ -package com.avaje.ebeaninternal.server.util; - -/* - * Written by Doug Lea with assistance from members of JCP JSR-166 - * Expert Group and released to the public domain, as explained at - * http://creativecommons.org/publicdomain/zero/1.0/ - */ - -import java.util.Random; - -/** - * A package-local class holding common representation and mechanics - * for classes supporting dynamic striping on 64bit values. The class - * extends Number so that concrete subclasses must publicly do so. - */ -abstract class Striped64 extends Number { - /* - * This class maintains a lazily-initialized table of atomically - * updated variables, plus an extra "base" field. The table size - * is a power of two. Indexing uses masked per-thread hash codes. - * Nearly all declarations in this class are package-private, - * accessed directly by subclasses. - * - * Table entries are of class Cell; a variant of AtomicLong padded - * to reduce cache contention on most processors. Padding is - * overkill for most Atomics because they are usually irregularly - * scattered in memory and thus don't interfere much with each - * other. But Atomic objects residing in arrays will tend to be - * placed adjacent to each other, and so will most often share - * cache lines (with a huge negative performance impact) without - * this precaution. - * - * In part because Cells are relatively large, we avoid creating - * them until they are needed. When there is no contention, all - * updates are made to the base field. Upon first contention (a - * failed CAS on base update), the table is initialized to size 2. - * The table size is doubled upon further contention until - * reaching the nearest power of two greater than or equal to the - * number of CPUS. Table slots remain empty (null) until they are - * needed. - * - * A single spinlock ("busy") is used for initializing and - * resizing the table, as well as populating slots with new Cells. - * There is no need for a blocking lock; when the lock is not - * available, threads try other slots (or the base). During these - * retries, there is increased contention and reduced locality, - * which is still better than alternatives. - * - * Per-thread hash codes are initialized to random values. - * Contention and/or table collisions are indicated by failed - * CASes when performing an update operation (see method - * retryUpdate). Upon a collision, if the table size is less than - * the capacity, it is doubled in size unless some other thread - * holds the lock. If a hashed slot is empty, and lock is - * available, a new Cell is created. Otherwise, if the slot - * exists, a CAS is tried. Retries proceed by "double hashing", - * using a secondary hash (Marsaglia XorShift) to try to find a - * free slot. - * - * The table size is capped because, when there are more threads - * than CPUs, supposing that each thread were bound to a CPU, - * there would exist a perfect hash function mapping threads to - * slots that eliminates collisions. When we reach capacity, we - * search for this mapping by randomly varying the hash codes of - * colliding threads. Because search is random, and collisions - * only become known via CAS failures, convergence can be slow, - * and because threads are typically not bound to CPUS forever, - * may not occur at all. However, despite these limitations, - * observed contention rates are typically low in these cases. - * - * It is possible for a Cell to become unused when threads that - * once hashed to it terminate, as well as in the case where - * doubling the table causes no thread to hash to it under - * expanded mask. We do not try to detect or remove such cells, - * under the assumption that for long-running instances, observed - * contention levels will recur, so the cells will eventually be - * needed again; and for short-lived ones, it does not matter. - */ - - /** - * Padded variant of AtomicLong supporting only raw accesses plus CAS. - * The value field is placed between pads, hoping that the JVM doesn't - * reorder them. - * - * JVM intrinsics note: It would be possible to use a release-only - * form of CAS here, if it were provided. - */ - static final class Cell { - volatile long p0, p1, p2, p3, p4, p5, p6; - volatile long value; - volatile long q0, q1, q2, q3, q4, q5, q6; - Cell(long x) { value = x; } - - final boolean cas(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val); - } - - // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long valueOffset; - static { - try { - UNSAFE = getUnsafe(); - Class ak = Cell.class; - valueOffset = UNSAFE.objectFieldOffset - (ak.getDeclaredField("value")); - } catch (Exception e) { - throw new Error(e); - } - } - - } - - /** - * Holder for the thread-local hash code. The code is initially - * random, but may be set to a different value upon collisions. - */ - static final class HashCode { - static final Random rng = new Random(); - int code; - HashCode() { - int h = rng.nextInt(); // Avoid zero to allow xorShift rehash - code = (h == 0) ? 1 : h; - } - } - - /** - * The corresponding ThreadLocal class - */ - static final class ThreadHashCode extends ThreadLocal { - public HashCode initialValue() { return new HashCode(); } - } - - /** - * Static per-thread hash codes. Shared across all instances to - * reduce ThreadLocal pollution and because adjustments due to - * collisions in one table are likely to be appropriate for - * others. - */ - static final ThreadHashCode threadHashCode = new ThreadHashCode(); - - /** Number of CPUS, to place bound on table size */ - static final int NCPU = Runtime.getRuntime().availableProcessors(); - - /** - * Table of cells. When non-null, size is a power of 2. - */ - transient volatile Cell[] cells; - - /** - * Base value, used mainly when there is no contention, but also as - * a fallback during table initialization races. Updated via CAS. - */ - transient volatile long base; - - /** - * Spinlock (locked via CAS) used when resizing and/or creating Cells. - */ - transient volatile int busy; - - /** - * Package-private default constructor - */ - Striped64() { - } - - /** - * CASes the base field. - */ - final boolean casBase(long cmp, long val) { - return UNSAFE.compareAndSwapLong(this, baseOffset, cmp, val); - } - - /** - * CASes the busy field from 0 to 1 to acquire lock. - */ - final boolean casBusy() { - return UNSAFE.compareAndSwapInt(this, busyOffset, 0, 1); - } - - /** - * Computes the function of current and new value. Subclasses - * should open-code this update function for most uses, but the - * virtualized form is needed within retryUpdate. - * - * @param currentValue the current value (of either base or a cell) - * @param newValue the argument from a user update call - * @return result of the update function - */ - abstract long fn(long currentValue, long newValue); - - /** - * Handles cases of updates involving initialization, resizing, - * creating new Cells, and/or contention. See above for - * explanation. This method suffers the usual non-modularity - * problems of optimistic retry code, relying on rechecked sets of - * reads. - * - * @param x the value - * @param hc the hash code holder - * @param wasUncontended false if CAS failed before call - */ - final void retryUpdate(long x, HashCode hc, boolean wasUncontended) { - int h = hc.code; - boolean collide = false; // True if last slot nonempty - for (;;) { - Cell[] as; Cell a; int n; long v; - if ((as = cells) != null && (n = as.length) > 0) { - if ((a = as[(n - 1) & h]) == null) { - if (busy == 0) { // Try to attach new Cell - Cell r = new Cell(x); // Optimistically create - if (busy == 0 && casBusy()) { - boolean created = false; - try { // Recheck under lock - Cell[] rs; int m, j; - if ((rs = cells) != null && - (m = rs.length) > 0 && - rs[j = (m - 1) & h] == null) { - rs[j] = r; - created = true; - } - } finally { - busy = 0; - } - if (created) - break; - continue; // Slot is now non-empty - } - } - collide = false; - } - else if (!wasUncontended) // CAS already known to fail - wasUncontended = true; // Continue after rehash - else if (a.cas(v = a.value, fn(v, x))) - break; - else if (n >= NCPU || cells != as) - collide = false; // At max size or stale - else if (!collide) - collide = true; - else if (busy == 0 && casBusy()) { - try { - if (cells == as) { // Expand table unless stale - Cell[] rs = new Cell[n << 1]; - for (int i = 0; i < n; ++i) - rs[i] = as[i]; - cells = rs; - } - } finally { - busy = 0; - } - collide = false; - continue; // Retry with expanded table - } - h ^= h << 13; // Rehash - h ^= h >>> 17; - h ^= h << 5; - } - else if (busy == 0 && cells == as && casBusy()) { - boolean init = false; - try { // Initialize table - if (cells == as) { - Cell[] rs = new Cell[2]; - rs[h & 1] = new Cell(x); - cells = rs; - init = true; - } - } finally { - busy = 0; - } - if (init) - break; - } - else if (casBase(v = base, fn(v, x))) - break; // Fall back on using base - } - hc.code = h; // Record index for next time - } - - - /** - * Sets base and all cells to the given value. - */ - final void internalReset(long initialValue) { - Cell[] as = cells; - base = initialValue; - if (as != null) { - int n = as.length; - for (int i = 0; i < n; ++i) { - Cell a = as[i]; - if (a != null) - a.value = initialValue; - } - } - } - - // Unsafe mechanics - private static final sun.misc.Unsafe UNSAFE; - private static final long baseOffset; - private static final long busyOffset; - static { - try { - UNSAFE = getUnsafe(); - Class sk = Striped64.class; - baseOffset = UNSAFE.objectFieldOffset - (sk.getDeclaredField("base")); - busyOffset = UNSAFE.objectFieldOffset - (sk.getDeclaredField("busy")); - } catch (Exception e) { - throw new Error(e); - } - } - - /** - * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. - * Replace with a simple call to Unsafe.getUnsafe when integrating - * into a jdk. - * - * @return a sun.misc.Unsafe - */ - private static sun.misc.Unsafe getUnsafe() { - try { - return sun.misc.Unsafe.getUnsafe(); - } catch (SecurityException tryReflectionInstead) {} - try { - return java.security.AccessController.doPrivileged - (new java.security.PrivilegedExceptionAction() { - public sun.misc.Unsafe run() throws Exception { - Class k = sun.misc.Unsafe.class; - for (java.lang.reflect.Field f : k.getDeclaredFields()) { - f.setAccessible(true); - Object x = f.get(null); - if (k.isInstance(x)) - return k.cast(x); - } - throw new NoSuchFieldError("the Unsafe"); - }}); - } catch (java.security.PrivilegedActionException e) { - throw new RuntimeException("Could not initialize intrinsics", - e.getCause()); - } - } -} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayListTest.java b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayListTest.java index 9a60004e5..22398359e 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayListTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayListTest.java @@ -14,8 +14,7 @@ public class ScalarTypeArrayListTest { DataReader mock = Mockito.mock(DataReader.class); Mockito.when(mock.getArray()).thenReturn(null); - ScalarTypeArrayList scalarType = ScalarTypeArrayList.typeFor(Long.class); + ScalarType scalarType = ScalarTypeArrayList.factory().typeFor(Long.class); scalarType.read(mock); - } } \ No newline at end of file diff --git a/src/test/java/com/avaje/tests/model/array/EArrayBean.java b/src/test/java/com/avaje/tests/model/array/EArrayBean.java index a16670a8c..c16960592 100644 --- a/src/test/java/com/avaje/tests/model/array/EArrayBean.java +++ b/src/test/java/com/avaje/tests/model/array/EArrayBean.java @@ -27,6 +27,9 @@ public class EArrayBean { @DbArray List otherIds = new ArrayList(); + @DbArray + List doubs; + @Version Long version; @@ -70,6 +73,14 @@ public class EArrayBean { this.otherIds = otherIds; } + public List getDoubs() { + return doubs; + } + + public void setDoubs(List doubs) { + this.doubs = doubs; + } + public Long getVersion() { return version; } diff --git a/src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java b/src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java index 21b945bc2..405cbdfa7 100644 --- a/src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java +++ b/src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java @@ -28,11 +28,17 @@ public class TestDbArray_basic extends BaseTestCase { phNumbers.add("4321"); phNumbers.add("9823"); + + List doubles = new ArrayList(); + doubles.add(1.3); + doubles.add(2.4); + bean.getUids().add(UUID.randomUUID()); bean.getUids().add(UUID.randomUUID()); bean.getOtherIds().add(95L); bean.getOtherIds().add(96L); bean.getOtherIds().add(97L); + bean.setDoubs(doubles); Ebean.save(bean); diff --git a/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java b/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java index f381bd20e..525dcb169 100644 --- a/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java +++ b/src/test/java/com/avaje/tests/query/aggregation/TestAggregationCount.java @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class TestAggregationCount extends BaseTestCase { @Test - public void test() { + public void testFull() { TEventOne one = new TEventOne("first"); one.getLogs().add(new TEventMany("all", 1, 10)); @@ -28,8 +28,7 @@ public class TestAggregationCount extends BaseTestCase { two.getLogs().add(new TEventMany("alf", 30, 13)); Ebean.save(two); - - Query query = Ebean.find(TEventOne.class) + Query query2 = Ebean.find(TEventOne.class) .select("name, count, totalUnits, totalAmount") .where() .startsWith("logs.description", "a") @@ -37,14 +36,14 @@ public class TestAggregationCount extends BaseTestCase { .ge("count", 1) .orderBy().asc("name"); - List list = query.findList(); + List list = query2.findList(); for (TEventOne eventOne : list) { System.out.println(eventOne.getId() + " " + eventOne.getName() + " count:" + eventOne.getCount() + " units:" + eventOne.getTotalUnits() + " amount:" + eventOne.getTotalAmount()); } assertThat(list).isNotEmpty(); - String sql = query.getGeneratedSql(); + String sql = query2.getGeneratedSql(); assertThat(sql).contains("select t0.id c0, t0.name c1, count(u1.*) c2, sum(u1.units) c3, sum(u1.units * u1.amount) c4 from tevent_one t0"); assertThat(sql).contains("from tevent_one t0 join tevent_many u1 on u1.event_id = t0.id "); assertThat(sql).contains("where u1.description like ? "); @@ -52,4 +51,39 @@ public class TestAggregationCount extends BaseTestCase { } + @Test + public void testSelectOnly() { + + Query query0 = Ebean.find(TEventOne.class) + .select("name, count, totalUnits, totalAmount"); + + query0.findList(); + assertThat(query0.getGeneratedSql()).contains("select t0.id c0, t0.name c1, count(u1.*) c2, sum(u1.units) c3, sum(u1.units * u1.amount) c4 from tevent_one t0"); + assertThat(query0.getGeneratedSql()).contains("group by t0.id, t0.name"); + } + + @Test + public void testSelectWhere() { + + Query query0 = Ebean.find(TEventOne.class) + .select("name, count, totalUnits, totalAmount") + .where().gt("logs.description", "a").query(); + + query0.findList(); + assertThat(query0.getGeneratedSql()).contains("select t0.id c0, t0.name c1, count(u1.*) c2, sum(u1.units) c3, sum(u1.units * u1.amount) c4 from tevent_one t0"); + assertThat(query0.getGeneratedSql()).contains("group by t0.id, t0.name"); + } + + @Test + public void testSelectHavingOrderBy() { + + Query query1 = Ebean.find(TEventOne.class) + .select("name, count, totalUnits, totalAmount") + .having().ge("count", 1) + .orderBy().asc("name"); + + query1.findList(); + assertThat(query1.getGeneratedSql()).contains("having count(u1.*) >= ? order by t0.name"); + } + }