Compare commits

...
Author SHA1 Message Date
Rob Bygrave 7d343455ac [maven-release-plugin] prepare release ebean-11.4.1 2017-11-06 14:48:55 +13:00
Rob Bygrave 21e0b748a6 #1199 - Remove deprecated findUnique() method - please migrate to findOne() 2017-11-03 03:35:19 +13:00
Rob Bygrave 2fab733e4e Update agent and maven plugin for 11.4.1 enhancement (@Transactional and PropertyChangeListener changes) 2017-11-03 03:30:53 +13:00
Rob Bygrave 7260a1b6eb #1198 - Remove support for PropertyChangeListener from entity beans 2017-11-03 03:08:43 +13:00
Rob Bygrave 434a005366 #1198 - Remove support for PropertyChangeListener from entity beans 2017-11-03 03:05:59 +13:00
Rob Bygrave ff67ef60b0 #1197 - Refactor how @Transactional methods internally managed 2017-11-03 02:13:56 +13:00
Rob Bygrave 0a1747d8e3 #1194 - Change from using Jetbrains @Nullable to ... class retention based javax annotation @Nonnull 2017-11-02 15:56:15 +13:00
Rob Bygrave f36499f71f No effective change - remove use of jetbrains @NotNull in tests 2017-11-02 15:26:35 +13:00
rob bygrave da818f17c2 #1190 - MySql throwing DataIntegrityException when it should throw the more specific DuplicateKeyException
Ah, remove the 23000 SQLState to just use the error codes for MySql.
2017-11-02 01:16:52 +13:00
rob bygrave 10eb2bc21f #1193 - For RawSql ignore the Postgres cast double colon like ::text 2017-11-02 00:03:38 +13:00
rob bygrave 66fe278e3f #1192 - findNative() with @Embedded beans ... the embedded bean isn't loaded properly 2017-11-01 23:03:57 +13:00
rob bygrave 10607385e0 #1191 - Trim logged SQL removing new line chars (for sql from SqlUpdate) 2017-11-01 22:00:55 +13:00
rob bygrave db6562df76 #1190 - MySql throwing DataIntegrityException when it should throw the more specific DuplicateKeyException 2017-11-01 21:54:48 +13:00
rob bygrave c0ec87d4cd #1189 - ExpressionList.isNull and SoftDelete 2017-11-01 21:48:23 +13:00
rob bygrave 0dbba7a211 #1189 - ExpressionList.isNull and SoftDelete 2017-11-01 21:26:15 +13:00
rob bygrave e75670f207 #1188 - When using inheritance getReference() on a leaf type ... should not hit the DB 2017-11-01 20:42:18 +13:00
rob bygrave fb8db14f3a #1186 - Tidy, remove H2MultiValueBind for now 2017-10-31 22:14:01 +13:00
rob bygrave c00b1849ef Merge branch 'feature/1186-MultiValueBind' 2017-10-31 22:11:30 +13:00
rob bygrave 1e1c518641 #1186 - ENH: Support using TableValues / Array for select prop in (?) queries
Use Postgres Array ANY and ALL without select unnest
2017-10-31 22:09:35 +13:00
Rob Bygrave d5b42f9ba1 [maven-release-plugin] prepare for next development iteration 2017-10-31 10:52:44 +13:00
rob bygrave 5bccdccbd6 #1186 - ENH: Support using TableValues / Array for select prop in (?) queries 2017-10-31 02:53:34 +13:00
rob bygrave 6e62b72fb9 #1186 - ENH: Support using TableValues / Array for select prop in (?) queries 2017-10-31 02:53:20 +13:00
121 changed files with 1796 additions and 1289 deletions
+13 -8
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.3.1</version>
<version>11.4.1</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.3.1</tag>
<tag>ebean-11.4.1</tag>
</scm>
<profiles>
@@ -72,10 +72,15 @@
</profiles>
<dependencies>
<!--
Class retention Nonnull and Nullable annotations
to assist with IDE auto-completion with Ebean API
-->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
<groupId>io.ebean</groupId>
<artifactId>ebean-jsr305</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
@@ -210,7 +215,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.1.1</version>
<version>11.4.1</version>
<scope>test</scope>
</dependency>
@@ -219,7 +224,7 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.193</version>
<version>1.4.196</version>
<scope>provided</scope>
</dependency>
@@ -283,7 +288,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.1.1</version>
<version>11.4.1</version>
<executions>
<execution>
<id>test</id>
-11
View File
@@ -1,6 +1,5 @@
package io.ebean;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
@@ -99,16 +98,6 @@ public interface BeanState {
*/
void setReadOnly(boolean readOnly);
/**
* Add a propertyChangeListener.
*/
void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Remove a propertyChangeListener.
*/
void removePropertyChangeListener(PropertyChangeListener listener);
/**
* Advanced - Used to programmatically build a partially or fully loaded
* entity bean. First create an entity bean via
+1 -1
View File
@@ -2,7 +2,7 @@ package io.ebean;
import io.ebeanservice.docstore.api.DocQueryRequest;
import io.ebeanservice.docstore.api.RawDoc;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
+1 -1
View File
@@ -5,7 +5,7 @@ import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ServerConfig;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+18 -27
View File
@@ -7,7 +7,9 @@ import io.ebean.meta.MetaInfoManager;
import io.ebean.plugin.SpiServer;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
@@ -732,6 +734,7 @@ public interface EbeanServer {
* @param beanType the type of entity bean to fetch
* @param id the id value
*/
@Nullable
<T> T find(Class<T> beanType, Object id);
/**
@@ -775,6 +778,7 @@ public interface EbeanServer {
* @param beanType the type of entity bean
* @param id the id value
*/
@Nonnull
<T> T getReference(Class<T> beanType, Object id);
/**
@@ -790,6 +794,7 @@ public interface EbeanServer {
*
* @see Query#findIds()
*/
@Nonnull
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
@@ -809,6 +814,7 @@ public interface EbeanServer {
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
@Nonnull
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
@@ -885,6 +891,7 @@ public interface EbeanServer {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
@@ -910,6 +917,7 @@ public interface EbeanServer {
* @return the list of fetched beans.
* @see Query#findList()
*/
@Nonnull
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
@@ -925,6 +933,7 @@ public interface EbeanServer {
* @return a Future object for the row count query
* @see Query#findFutureCount()
*/
@Nonnull
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
@@ -940,6 +949,7 @@ public interface EbeanServer {
* @return a Future object for the list of Id's
* @see Query#findFutureIds()
*/
@Nonnull
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
@@ -956,6 +966,7 @@ public interface EbeanServer {
* @return a Future object for the list result of the query
* @see Query#findFutureList()
*/
@Nonnull
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
@@ -987,6 +998,7 @@ public interface EbeanServer {
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
@@ -1012,6 +1024,7 @@ public interface EbeanServer {
* @return the set of fetched beans.
* @see Query#findSet()
*/
@Nonnull
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
@@ -1028,6 +1041,7 @@ public interface EbeanServer {
* @return the map of fetched beans.
* @see Query#findMap()
*/
@Nonnull
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
@@ -1060,6 +1074,7 @@ public interface EbeanServer {
* @return the list of values for the selected property
* @see Query#findSingleAttributeList()
*/
@Nonnull
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
@@ -1087,21 +1102,9 @@ public interface EbeanServer {
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default <T> T findUnique(Query<T> query, Transaction transaction) {
return findOne(query, transaction);
}
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
@@ -1143,6 +1146,7 @@ public interface EbeanServer {
* @return the list of fetched MapBean.
* @see SqlQuery#findList()
*/
@Nonnull
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
@@ -1184,19 +1188,6 @@ public interface EbeanServer {
@Nullable
SqlRow findOne(SqlQuery query, Transaction transaction);
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default SqlRow findUnique(SqlQuery query, Transaction transaction) {
return findOne(query, transaction);
}
/**
* Either Insert or Update the bean depending on its state.
* <p>
+15 -14
View File
@@ -5,8 +5,9 @@ import io.ebean.search.MultiMatch;
import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import javax.persistence.NonUniqueResultException;
import java.sql.Timestamp;
import java.util.Collection;
@@ -192,6 +193,7 @@ public interface ExpressionList<T> {
*
* @see Query#findList()
*/
@Nonnull
List<T> findList();
/**
@@ -199,6 +201,7 @@ public interface ExpressionList<T> {
*
* @see Query#findIds()
*/
@Nonnull
<A> List<A> findIds();
/**
@@ -214,6 +217,7 @@ public interface ExpressionList<T> {
*
* @see Query#findSet()
*/
@Nonnull
Set<T> findSet();
/**
@@ -221,6 +225,7 @@ public interface ExpressionList<T> {
*
* @see Query#findMap()
*/
@Nonnull
<K> Map<K, T> findMap();
/**
@@ -253,6 +258,7 @@ public interface ExpressionList<T> {
*
* @return the list of values for the selected property
*/
@Nonnull
<A> List<A> findSingleAttributeList();
/**
@@ -272,21 +278,9 @@ public interface ExpressionList<T> {
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default T findUnique() {
return findOne();
}
/**
* Execute find row count query in a background thread.
* <p>
@@ -297,6 +291,7 @@ public interface ExpressionList<T> {
*
* @return a Future object for the row count query
*/
@Nonnull
FutureRowCount<T> findFutureCount();
/**
@@ -309,6 +304,7 @@ public interface ExpressionList<T> {
*
* @return a Future object for the list of Id's
*/
@Nonnull
FutureIds<T> findFutureIds();
/**
@@ -321,6 +317,7 @@ public interface ExpressionList<T> {
*
* @return a Future object for the list result of the query
*/
@Nonnull
FutureList<T> findFutureList();
/**
@@ -352,6 +349,7 @@ public interface ExpressionList<T> {
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
PagedList<T> findPagedList();
/**
@@ -361,6 +359,7 @@ public interface ExpressionList<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersions();
/**
@@ -370,11 +369,13 @@ public interface ExpressionList<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
/**
* Add some filter predicate expressions to the many property.
*/
@Nonnull
ExpressionList<T> filterMany(String prop);
/**
+4 -1
View File
@@ -1,6 +1,7 @@
package io.ebean;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
@@ -132,6 +133,7 @@ public class Finder<I, T> {
* <p>
* Equivalent to {@link EbeanServer#getReference(Class, Object)}
*/
@Nonnull
public T ref(I id) {
return db().getReference(type, id);
}
@@ -158,6 +160,7 @@ public class Finder<I, T> {
/**
* Retrieves all entities of the given type.
*/
@Nonnull
public List<T> all() {
return query().findList();
}
+3
View File
@@ -1,5 +1,6 @@
package io.ebean;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.concurrent.Future;
@@ -109,11 +110,13 @@ public interface PagedList<T> {
*
* }</pre>
*/
@Nonnull
Future<Integer> getFutureCount();
/**
* Return the list of entities for this page.
*/
@Nonnull
List<T> getList();
/**
+15 -15
View File
@@ -1,7 +1,7 @@
package io.ebean;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import java.sql.Timestamp;
import java.util.List;
@@ -597,6 +597,7 @@ public interface Query<T> {
*
* @see EbeanServer#findIds(Query, Transaction)
*/
@Nonnull
<A> List<A> findIds();
/**
@@ -637,6 +638,7 @@ public interface Query<T> {
*
* }</pre>
*/
@Nonnull
QueryIterator<T> findIterate();
/**
@@ -735,6 +737,7 @@ public interface Query<T> {
*
* @see EbeanServer#findList(Query, Transaction)
*/
@Nonnull
List<T> findList();
/**
@@ -753,6 +756,7 @@ public interface Query<T> {
*
* @see EbeanServer#findSet(Query, Transaction)
*/
@Nonnull
Set<T> findSet();
/**
@@ -775,6 +779,7 @@ public interface Query<T> {
*
* @see EbeanServer#findMap(Query, Transaction)
*/
@Nonnull
<K> Map<K, T> findMap();
/**
@@ -807,6 +812,7 @@ public interface Query<T> {
*
* @return the list of values for the selected property
*/
@Nonnull
<A> List<A> findSingleAttributeList();
/**
@@ -855,21 +861,9 @@ public interface Query<T> {
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* Deprecated - please migrate to findOne().
* <p>
* This proceeded findOne which was introduced to better match spring data.
* This will be deprecated at some future point.
* </p>
*/
@Deprecated
@Nullable
default T findUnique() {
return findOne();
}
/**
* Return versions of a @History entity bean.
* <p>
@@ -882,6 +876,7 @@ public interface Query<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersions();
/**
@@ -891,6 +886,7 @@ public interface Query<T> {
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end);
/**
@@ -928,6 +924,7 @@ public interface Query<T> {
*
* @return a Future object for the row count query
*/
@Nonnull
FutureRowCount<T> findFutureCount();
/**
@@ -940,6 +937,7 @@ public interface Query<T> {
*
* @return a Future object for the list of Id's
*/
@Nonnull
FutureIds<T> findFutureIds();
/**
@@ -951,6 +949,7 @@ public interface Query<T> {
*
* @return a Future object for the list result of the query
*/
@Nonnull
FutureList<T> findFutureList();
/**
@@ -980,6 +979,7 @@ public interface Query<T> {
*
* @return The PagedList
*/
@Nonnull
PagedList<T> findPagedList();
/**
+5 -8
View File
@@ -1,5 +1,7 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
@@ -43,6 +45,7 @@ public interface SqlQuery extends Serializable {
/**
* Execute the query returning a list.
*/
@Nonnull
List<SqlRow> findList();
/**
@@ -71,21 +74,15 @@ public interface SqlQuery extends Serializable {
* PersistenceException.
* </p>
*/
@Nullable
SqlRow findOne();
/**
* Execute the query returning an optional row.
*/
@Nonnull
Optional<SqlRow> findOneOrEmpty();
/**
* Deprecated - please migrate to findOne().
*/
@Deprecated
default SqlRow findUnique() {
return findOne();
}
/**
* The same as bind for named parameters.
*/
+19 -12
View File
@@ -25,32 +25,32 @@ import java.util.concurrent.Callable;
*/
public final class TxScope {
int profileId;
private int profileId;
TxType type;
private TxType type;
String serverName;
private String serverName;
TxIsolation isolation;
private TxIsolation isolation;
PersistBatch batch;
private PersistBatch batch;
PersistBatch batchOnCascade;
private PersistBatch batchOnCascade;
int batchSize;
private int batchSize;
boolean skipGeneratedKeys;
private boolean skipGeneratedKeys;
boolean readOnly;
private boolean readOnly;
/**
* Set this to false if the JDBC batch should not be automatically be flushed when a query is executed.
*/
boolean flushOnQuery = true;
private boolean flushOnQuery = true;
ArrayList<Class<? extends Throwable>> rollbackFor;
private ArrayList<Class<? extends Throwable>> rollbackFor;
ArrayList<Class<? extends Throwable>> noRollbackFor;
private ArrayList<Class<? extends Throwable>> noRollbackFor;
/**
* Helper method to create a TxScope with REQUIRES.
@@ -275,6 +275,13 @@ public final class TxScope {
return this;
}
/**
* Return the isolation level.
*/
public int getIsolationLevel() {
return isolation != null ? isolation.getLevel() : -1;
}
/**
* Return the Isolation level this transaction should run with.
*/
@@ -1,6 +1,5 @@
package io.ebean.bean;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
/**
@@ -32,16 +31,6 @@ public interface EntityBean extends Serializable {
*/
Object _ebean_newInstance();
/**
* Add a PropertyChangeListener to this bean.
*/
void addPropertyChangeListener(PropertyChangeListener listener);
/**
* Remove a PropertyChangeListener from this bean.
*/
void removePropertyChangeListener(PropertyChangeListener listener);
/**
* Generated method that sets the loaded state on all the embedded beans on
* this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o);
@@ -3,15 +3,12 @@ package io.ebean.bean;
import io.ebean.Ebean;
import io.ebean.ValuePair;
import java.util.Arrays;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.math.BigDecimal;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -34,8 +31,6 @@ public final class EntityBeanIntercept implements Serializable {
private transient NodeUsageCollector nodeUsageCollector;
private transient PropertyChangeSupport pcs;
private transient PersistenceContext persistenceContext;
private transient BeanLoader beanLoader;
@@ -128,46 +123,6 @@ public final class EntityBeanIntercept implements Serializable {
this.persistenceContext = persistenceContext;
}
/**
* Add a property change listener for this entity bean.
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
if (pcs == null) {
pcs = new PropertyChangeSupport(owner);
}
pcs.addPropertyChangeListener(listener);
}
/**
* Add a property change listener for this entity bean for a specific
* property.
*/
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (pcs == null) {
pcs = new PropertyChangeSupport(owner);
}
pcs.addPropertyChangeListener(propertyName, listener);
}
/**
* Remove a property change listener for this entity bean.
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
if (pcs != null) {
pcs.removePropertyChangeListener(listener);
}
}
/**
* Remove a property change listener for this entity bean for a specific
* property.
*/
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (pcs != null) {
pcs.removePropertyChangeListener(propertyName, listener);
}
}
/**
* Turn on profile collection.
*/
@@ -920,49 +875,17 @@ public final class EntityBeanIntercept implements Serializable {
}
}
/**
* Called for "enhancement" postSetter processing. This is around a PUTFIELD
* so no need to check the newValue afterwards.
*/
public void postSetter(PropertyChangeEvent event) {
if (pcs != null && event != null) {
pcs.firePropertyChange(event);
}
}
/**
* Called for "subclassed" postSetter processing. Here the newValue has to be
* re-fetched (and passed into this method) in case there is code inside the
* setter that further mutates the value.
*/
public void postSetter(PropertyChangeEvent event, Object newValue) {
if (pcs != null && event != null) {
if (newValue != null && newValue.equals(event.getNewValue())) {
pcs.firePropertyChange(event);
} else {
pcs.firePropertyChange(event.getPropertyName(), event.getOldValue(), newValue);
}
}
}
/**
* OneToMany and ManyToMany don't have any interception so just check for
* PropertyChangeSupport.
*/
public PropertyChangeEvent preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setLoadedProperty(propertyIndex);
// Bean itself not considered dirty when many changed
if (pcs != null) {
return new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
} else {
return null;
}
}
private void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
@@ -991,175 +914,140 @@ public final class EntityBeanIntercept implements Serializable {
* Check to see if the values are not equal. If they are not equal then create
* the old values for use with ConcurrencyMode.ALL.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!areEqual(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* Check for primitive boolean.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* Check for primitive int.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* long.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* double.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (Double.compare(oldValue, newValue) != 0) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* float.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (Float.compare(oldValue, newValue) != 0) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* short.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* char.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* byte.
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (oldValue != newValue) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* char[].
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!Arrays.equals(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* byte[].
*/
public PropertyChangeEvent preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else if (!Arrays.equals(oldValue, newValue)) {
setChangedPropertyValue(propertyIndex, intercept, oldValue);
} else {
return null;
}
return (pcs == null) ? null : new PropertyChangeEvent(owner, getProperty(propertyIndex), oldValue, newValue);
}
/**
* Explicitly set an old value.
*/
public void setOldValue(int propertyIndex,Object oldValue) {
public void setOldValue(int propertyIndex, Object oldValue) {
setChangedPropertyValue(propertyIndex, true, oldValue);
}
}
@@ -34,6 +34,10 @@ public class SqlCodeTranslator implements SqlExceptionTranslator {
public PersistenceException translate(String message, SQLException e) {
DataErrorType errorType = map.get(e.getSQLState());
if (errorType == null) {
// fall back to error code
errorType = map.get(String.valueOf(e.getErrorCode()));
}
if (errorType != null) {
switch (errorType) {
case AcquireLock:
@@ -42,8 +42,8 @@ public class MySqlPlatform extends DatabasePlatform {
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("1205")
.addDuplicateKey("1062")
.addDataIntegrity("630","839","840","893","1169","1215","1216","1217","1364","1451","1452","1557","23000")
.addDuplicateKey("1062", "1169")
.addDataIntegrity("630", "839", "840", "893", "1215", "1216", "1217", "1364", "1451", "1452", "1557")
.build();
this.openQuote = "`";
@@ -79,7 +79,7 @@ public class MySqlPlatform extends DatabasePlatform {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
@@ -24,4 +24,13 @@ public interface BeanQueryRequest<T> {
*/
Query<T> getQuery();
/**
* Return true if multi-value binding using Array or Table Values is supported.
*/
boolean isMultiValueIdSupported();
/**
* Return true if multi-value binding is supported for this value type.
*/
boolean isMultiValueSupported(Class<?> valueType);
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.api;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.TxScope;
/**
@@ -10,28 +9,20 @@ import io.ebean.TxScope;
public class HelpScopeTrans {
/**
* Create a ScopeTrans for a given methods TxScope.
* Entering an enhanced transactional method.
*/
public static ScopeTrans createScopeTrans(TxScope txScope) {
EbeanServer server = Ebean.getServer(txScope.getServerName());
SpiEbeanServer iserver = (SpiEbeanServer) server;
return iserver.createScopeTrans(txScope);
public static void enter(TxScope txScope) {
server().scopedTransactionEnter(txScope);
}
/**
* Exiting the method in an expected fashion.
* <p>
* That is returning successfully or via a caught exception.
* Unexpected exceptions are caught via the Thread uncaughtExceptionHandler.
* </p>
*
* @param returnOrThrowable the return or throwable object
* @param opCode the opcode for ATHROW or ARETURN etc
* @param scopeTrans the scoped transaction the method was run with.
* Exiting an enhanced transactional method.
*/
public static void onExitScopeTrans(Object returnOrThrowable, int opCode, ScopeTrans scopeTrans) {
public static void exit(Object returnOrThrowable, int opCode) {
server().scopedTransactionExit(returnOrThrowable, opCode);
}
scopeTrans.onExit(returnOrThrowable, opCode);
private static SpiEbeanServer server() {
return (SpiEbeanServer) Ebean.getDefaultServer();
}
}
@@ -103,7 +103,8 @@ public class LoadBeanRequest extends LoadRequest {
idList.add(desc.getId(bean));
}
if (!idList.isEmpty()) {
if (!desc.isMultiValueIdSupported() && !idList.isEmpty()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
// for performance make up the Id's to the batch size
@@ -107,11 +107,13 @@ public class LoadManyRequest extends LoadRequest {
for (BeanCollection<?> bc : batch) {
idList.add(many.getParentId(bc.getOwnerBean()));
}
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
if (!many.getTargetDescriptor().isMultiValueIdSupported()) {
int extraIds = batchSize - batch.size();
if (extraIds > 0) {
Object firstId = idList.get(0);
for (int i = 0; i < extraIds; i++) {
idList.add(firstId);
}
}
}
@@ -8,17 +8,10 @@ import java.util.ArrayList;
/**
* Used internally to handle the scoping of transactions for methods.
*/
public class ScopeTrans implements Thread.UncaughtExceptionHandler {
public class ScopeTrans {
private static final int OPCODE_ATHROW = 191;
private final SpiTransactionScopeManager scopeMgr;
/**
* The suspended transaction (can be null).
*/
private final SpiTransaction suspendedTransaction;
/**
* The transaction in scope (can be null).
*/
@@ -61,15 +54,11 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
private boolean rolledBack;
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope,
SpiTransaction suspendedTransaction, SpiTransactionScopeManager scopeMgr) {
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
this.rollbackOnChecked = rollbackOnChecked;
this.created = created;
this.transaction = transaction;
this.suspendedTransaction = suspendedTransaction;
this.scopeMgr = scopeMgr;
this.noRollbackFor = txScope.getNoRollbackFor();
this.rollbackFor = txScope.getRollbackFor();
@@ -108,55 +97,30 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
}
/**
* Called when the Thread catches any uncaught exception.
* For example, an unexpected NullPointerException or Error.
* Complete the transaction from enhanced transactional. Try to commit.
*/
@Override
public void uncaughtException(Thread thread, Throwable e) {
// rollback transaction if required
caughtThrowable(e);
// reinstate suspended transaction
onFinally();
}
/**
* Returned via RETURN or expected Exception from the method.
*
* @param returnOrThrowable the return value or Throwable
* @param opCode indicates
*/
public void onExit(Object returnOrThrowable, int opCode) {
void complete(Object returnOrThrowable, int opCode) {
if (opCode == OPCODE_ATHROW) {
// exited with a Throwable
caughtThrowable((Throwable) returnOrThrowable);
}
onFinally();
complete();
}
/**
* Commit if the transaction exists and has not already been rolled back.
* Also reinstate the suspended transaction if there was one.
* Complete the transaction programmatically. Try to commit.
*/
public void onFinally() {
try {
if (!rolledBack) {
commitTransaction();
}
} finally {
restoreSuspended();
public void complete() {
if (!rolledBack) {
commitTransaction();
}
}
protected void restoreSuspended() {
if (created || suspendedTransaction != null) {
// put the previously suspended transaction
// back onto the ThreadLocal or equivalent
scopeMgr.replace(suspendedTransaction);
public void end() {
if (created) {
transaction.end();
}
}
@@ -247,17 +211,10 @@ public class ScopeTrans implements Thread.UncaughtExceptionHandler {
}
}
if (e instanceof RuntimeException) {
return true;
} else {
// checked exceptions...
// EJB defaults this to false which is not intuitive IMO
// Ebean makes this configurable (default to true)
return rollbackOnChecked;
}
// checked exceptions...
// EJB defaults this to false which is not intuitive IMO
// Ebean makes this configurable (default to true)
return e instanceof RuntimeException || rollbackOnChecked;
}
}
@@ -1,432 +1,116 @@
package io.ebeaninternal.api;
import io.ebean.annotation.PersistBatch;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import io.ebeaninternal.server.transaction.TransactionScopeManager;
import io.ebeaninternal.server.util.ArrayStack;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Wrapper of a ScopeTrans request and it's underlying transaction.
* Manage scoped (typically thread local) transactions.
*
* These can be nested and internally they are pushed and popped from a stack.
*/
public class ScopedTransaction implements SpiTransaction {
public class ScopedTransaction extends SpiTransactionProxy {
private final ScopeTrans scopeTrans;
private final TransactionScopeManager manager;
private final SpiTransaction transaction;
/**
* Stack of 'nested' transactions.
*/
private ArrayStack<ScopeTrans> stack = new ArrayStack<>();
private boolean committed;
private ScopeTrans current;
public ScopedTransaction(ScopeTrans scopeTrans) {
this.scopeTrans = scopeTrans;
this.transaction = scopeTrans.getTransaction();
public ScopedTransaction(TransactionScopeManager manager) {
this.manager = manager;
}
@Override
public PersistenceException translate(String message, SQLException cause) {
return transaction.translate(message, cause);
/**
* Push the scope transaction.
*/
public void push(ScopeTrans scopeTrans) {
if (current != null) {
stack.push(current);
}
current = scopeTrans;
transaction = scopeTrans.getTransaction();
}
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
/**
* Exiting an enhanced transactional method.
*/
public void complete(Object returnOrThrowable, int opCode) {
current.complete(returnOrThrowable, opCode);
pop();
}
@Override
public void commit() {
scopeTrans.commitTransaction();
committed = true;
/**
* Programmatic complete - finally block, try to commit.
*/
public void complete() {
current.complete();
pop();
}
@Override
public void rollback() throws PersistenceException {
scopeTrans.rollback(null);
}
@Override
public void rollback(Throwable e) throws PersistenceException {
scopeTrans.rollback(e);
}
@Override
public void setRollbackOnly() {
scopeTrans.setRollbackOnly();
}
@Override
public boolean isRollbackOnly() {
return transaction.isRollbackOnly();
}
@Override
public void end() throws PersistenceException {
try {
if (!committed) {
scopeTrans.rollback(null);
}
} finally {
scopeTrans.restoreSuspended();
private void pop() {
if (!stack.isEmpty()) {
current = stack.pop();
} else {
manager.set(null);
}
}
@Override
public long profileOffset() {
return transaction.profileOffset();
}
@Override
public void profileEvent(SpiProfileTransactionEvent event) {
transaction.profileEvent(event);
}
@Override
public ProfileStream profileStream() {
return transaction.profileStream();
}
@Override
public void setTenantId(Object tenantId) {
transaction.setTenantId(tenantId);
}
@Override
public Object getTenantId() {
return transaction.getTenantId();
}
@Override
public DocStoreTransaction getDocStoreTransaction() {
return transaction.getDocStoreTransaction();
}
@Override
public DocStoreMode getDocStoreMode() {
return transaction.getDocStoreMode();
}
@Override
public void setDocStoreMode(DocStoreMode mode) {
transaction.setDocStoreMode(mode);
}
@Override
public int getDocStoreBatchSize() {
return transaction.getDocStoreBatchSize();
}
@Override
public void setDocStoreBatchSize(int batchSize) {
transaction.setDocStoreBatchSize(batchSize);
}
@Override
public String getLogPrefix() {
return transaction.getLogPrefix();
}
@Override
public boolean isLogSql() {
return transaction.isLogSql();
}
@Override
public boolean isLogSummary() {
return transaction.isLogSummary();
}
@Override
public void logSql(String msg) {
transaction.logSql(msg);
}
@Override
public void logSummary(String msg) {
transaction.logSummary(msg);
}
@Override
public void setSkipCache(boolean skipCache) {
transaction.setSkipCache(skipCache);
}
@Override
public boolean isSkipCache() {
return transaction.isSkipCache();
}
@Override
public void addBeanChange(BeanChange beanChange) {
transaction.addBeanChange(beanChange);
}
@Override
public void sendChangeLog(ChangeSet changes) {
transaction.sendChangeLog(changes);
}
@Override
public void registerDeferred(PersistDeferredRelationship derived) {
transaction.registerDeferred(derived);
}
@Override
public void registerDeleteBean(Integer hash) {
transaction.registerDeleteBean(hash);
}
@Override
public void unregisterDeleteBean(Integer hash) {
transaction.unregisterDeleteBean(hash);
}
@Override
public boolean isRegisteredDeleteBean(Integer hash) {
return transaction.isRegisteredDeleteBean(hash);
}
@Override
public void unregisterBean(Object bean) {
transaction.unregisterBean(bean);
}
@Override
public boolean isRegisteredBean(Object bean) {
return transaction.isRegisteredBean(bean);
}
@Override
public String getId() {
return transaction.getId();
}
@Override
public void register(TransactionCallback callback) {
transaction.register(callback);
}
@Override
public boolean isReadOnly() {
return transaction.isReadOnly();
}
@Override
public void setReadOnly(boolean readOnly) {
transaction.setReadOnly(readOnly);
}
@Override
public boolean isActive() {
return transaction.isActive();
}
@Override
public void setPersistCascade(boolean persistCascade) {
transaction.setPersistCascade(persistCascade);
}
@Override
public void setUpdateAllLoadedProperties(boolean updateAllLoaded) {
transaction.setUpdateAllLoadedProperties(updateAllLoaded);
}
@Override
public Boolean isUpdateAllLoadedProperties() {
return transaction.isUpdateAllLoadedProperties();
}
@Override
public void setBatchMode(boolean useBatch) {
transaction.setBatchMode(useBatch);
}
@Override
public void setBatch(PersistBatch persistBatchMode) {
transaction.setBatch(persistBatchMode);
}
@Override
public PersistBatch getBatch() {
return transaction.getBatch();
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
transaction.setBatchOnCascade(batchOnCascadeMode);
}
@Override
public PersistBatch getBatchOnCascade() {
return transaction.getBatchOnCascade();
}
@Override
public void setBatchSize(int batchSize) {
transaction.setBatchSize(batchSize);
}
@Override
public int getBatchSize() {
return transaction.getBatchSize();
}
@Override
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
transaction.setBatchGetGeneratedKeys(getGeneratedKeys);
}
@Override
public Boolean getBatchGetGeneratedKeys() {
return transaction.getBatchGetGeneratedKeys();
}
@Override
public void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
transaction.setBatchFlushOnMixed(batchFlushOnMixed);
}
@Override
public void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
transaction.setBatchFlushOnQuery(batchFlushOnQuery);
}
@Override
public boolean isBatchFlushOnQuery() {
return transaction.isBatchFlushOnQuery();
}
@Override
public void flush() throws PersistenceException {
transaction.flush();
}
@Override
public void flushBatch() throws PersistenceException {
flush();
}
@Override
public Connection getConnection() {
return transaction.getConnection();
}
@Override
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
transaction.addModification(tableName, inserts, updates, deletes);
}
@Override
public void putUserObject(String name, Object value) {
transaction.putUserObject(name, value);
}
@Override
public Object getUserObject(String name) {
return transaction.getUserObject(name);
}
@Override
public void depth(int diff) {
transaction.depth();
}
@Override
public int depth() {
return transaction.depth();
}
@Override
public boolean isExplicit() {
return transaction.isExplicit();
}
@Override
public TransactionEvent getEvent() {
return transaction.getEvent();
}
@Override
public boolean isPersistCascade() {
return transaction.isPersistCascade();
}
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
return transaction.isBatchThisRequest(type);
}
@Override
public BatchControl getBatchControl() {
return transaction.getBatchControl();
}
@Override
public void setBatchControl(BatchControl control) {
transaction.setBatchControl(control);
}
@Override
public PersistenceContext getPersistenceContext() {
return transaction.getPersistenceContext();
}
@Override
public void setPersistenceContext(PersistenceContext context) {
transaction.setPersistenceContext(context);
}
@Override
public Connection getInternalConnection() {
return transaction.getInternalConnection();
}
@Override
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) {
return transaction.isSaveAssocManyIntersection(intersectionTable, beanName);
}
@Override
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
return transaction.checkBatchEscalationOnCascade(request);
}
@Override
public void flushBatchOnCascade() {
transaction.flushBatchOnCascade();
}
@Override
public void flushBatchOnRollback() {
transaction.flushBatchOnRollback();
}
@Override
public void markNotQueryOnly() {
transaction.markNotQueryOnly();
}
@Override
public void checkBatchEscalationOnCollection() {
transaction.checkBatchEscalationOnCollection();
}
@Override
public void flushBatchOnCollection() {
transaction.flushBatchOnCollection();
public void end() throws PersistenceException {
current.end();
pop();
}
@Override
public void close() {
transaction.close();
end();
}
@Override
public void commit() {
current.commitTransaction();
}
@Override
public void rollback() throws PersistenceException {
current.rollback(null);
}
@Override
public void rollback(Throwable e) throws PersistenceException {
current.rollback(e);
}
@Override
public void setRollbackOnly() {
current.setRollbackOnly();
}
/**
* Return the current transaction.
*/
public SpiTransaction current() {
return transaction;
}
/**
* Rollback for Error.
*/
public Error caughtError(Error e) {
return current.caughtError(e);
}
/**
* Maybe rollback based on TxScope rollback on settings.
*/
public Exception caughtThrowable(Exception e) {
return current.caughtThrowable(e);
}
}
@@ -125,11 +125,6 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
SpiTransaction currentServerTransaction();
/**
* Create a ScopeTrans for a method for the given scope definition.
*/
ScopeTrans createScopeTrans(TxScope txScope);
/**
* Create a ServerTransaction for query purposes.
*
@@ -213,4 +208,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Create DDL handler given the platform and configuration of the server.
*/
DdlHandler createDdlHandler();
/**
* Start an enhanced transactional method.
*/
void scopedTransactionEnter(TxScope txScope);
/**
* Handle the end of an enhanced Transactional method.
*/
void scopedTransactionExit(Object returnOrThrowable, int opCode);
}
@@ -70,4 +70,9 @@ public interface SpiExpressionRequest {
* Escapes a string to use it as exact match in Like clause.
*/
String escapeLikeString(String value);
/**
* Append IN expression taking into account platform and type support for Multi-value.
*/
void appendInExpression(boolean not, Object[] bindValues);
}
@@ -0,0 +1,387 @@
package io.ebeaninternal.api;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.bean.PersistenceContext;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.persist.BatchControl;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeanservice.docstore.api.DocStoreTransaction;
import javax.persistence.PersistenceException;
import java.sql.Connection;
import java.sql.SQLException;
/**
* Proxy for an underlying SpiTransaction (most of the API).
*/
abstract class SpiTransactionProxy implements SpiTransaction {
protected SpiTransaction transaction;
@Override
public PersistenceException translate(String message, SQLException cause) {
return transaction.translate(message, cause);
}
@Override
public void commitAndContinue() {
transaction.commitAndContinue();
}
@Override
public boolean isRollbackOnly() {
return transaction.isRollbackOnly();
}
@Override
public long profileOffset() {
return transaction.profileOffset();
}
@Override
public void profileEvent(SpiProfileTransactionEvent event) {
transaction.profileEvent(event);
}
@Override
public ProfileStream profileStream() {
return transaction.profileStream();
}
@Override
public void setTenantId(Object tenantId) {
transaction.setTenantId(tenantId);
}
@Override
public Object getTenantId() {
return transaction.getTenantId();
}
@Override
public DocStoreTransaction getDocStoreTransaction() {
return transaction.getDocStoreTransaction();
}
@Override
public DocStoreMode getDocStoreMode() {
return transaction.getDocStoreMode();
}
@Override
public void setDocStoreMode(DocStoreMode mode) {
transaction.setDocStoreMode(mode);
}
@Override
public int getDocStoreBatchSize() {
return transaction.getDocStoreBatchSize();
}
@Override
public void setDocStoreBatchSize(int batchSize) {
transaction.setDocStoreBatchSize(batchSize);
}
@Override
public String getLogPrefix() {
return transaction.getLogPrefix();
}
@Override
public boolean isLogSql() {
return transaction.isLogSql();
}
@Override
public boolean isLogSummary() {
return transaction.isLogSummary();
}
@Override
public void logSql(String msg) {
transaction.logSql(msg);
}
@Override
public void logSummary(String msg) {
transaction.logSummary(msg);
}
@Override
public void setSkipCache(boolean skipCache) {
transaction.setSkipCache(skipCache);
}
@Override
public boolean isSkipCache() {
return transaction.isSkipCache();
}
@Override
public void addBeanChange(BeanChange beanChange) {
transaction.addBeanChange(beanChange);
}
@Override
public void sendChangeLog(ChangeSet changes) {
transaction.sendChangeLog(changes);
}
@Override
public void registerDeferred(PersistDeferredRelationship derived) {
transaction.registerDeferred(derived);
}
@Override
public void registerDeleteBean(Integer hash) {
transaction.registerDeleteBean(hash);
}
@Override
public void unregisterDeleteBean(Integer hash) {
transaction.unregisterDeleteBean(hash);
}
@Override
public boolean isRegisteredDeleteBean(Integer hash) {
return transaction.isRegisteredDeleteBean(hash);
}
@Override
public void unregisterBean(Object bean) {
transaction.unregisterBean(bean);
}
@Override
public boolean isRegisteredBean(Object bean) {
return transaction.isRegisteredBean(bean);
}
@Override
public String getId() {
return transaction.getId();
}
@Override
public void register(TransactionCallback callback) {
transaction.register(callback);
}
@Override
public boolean isReadOnly() {
return transaction.isReadOnly();
}
@Override
public void setReadOnly(boolean readOnly) {
transaction.setReadOnly(readOnly);
}
@Override
public boolean isActive() {
return transaction.isActive();
}
@Override
public void setPersistCascade(boolean persistCascade) {
transaction.setPersistCascade(persistCascade);
}
@Override
public void setUpdateAllLoadedProperties(boolean updateAllLoaded) {
transaction.setUpdateAllLoadedProperties(updateAllLoaded);
}
@Override
public Boolean isUpdateAllLoadedProperties() {
return transaction.isUpdateAllLoadedProperties();
}
@Override
public void setBatchMode(boolean useBatch) {
transaction.setBatchMode(useBatch);
}
@Override
public void setBatch(PersistBatch persistBatchMode) {
transaction.setBatch(persistBatchMode);
}
@Override
public PersistBatch getBatch() {
return transaction.getBatch();
}
@Override
public void setBatchOnCascade(PersistBatch batchOnCascadeMode) {
transaction.setBatchOnCascade(batchOnCascadeMode);
}
@Override
public PersistBatch getBatchOnCascade() {
return transaction.getBatchOnCascade();
}
@Override
public void setBatchSize(int batchSize) {
transaction.setBatchSize(batchSize);
}
@Override
public int getBatchSize() {
return transaction.getBatchSize();
}
@Override
public void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
transaction.setBatchGetGeneratedKeys(getGeneratedKeys);
}
@Override
public Boolean getBatchGetGeneratedKeys() {
return transaction.getBatchGetGeneratedKeys();
}
@Override
public void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
transaction.setBatchFlushOnMixed(batchFlushOnMixed);
}
@Override
public void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
transaction.setBatchFlushOnQuery(batchFlushOnQuery);
}
@Override
public boolean isBatchFlushOnQuery() {
return transaction.isBatchFlushOnQuery();
}
@Override
public void flush() throws PersistenceException {
transaction.flush();
}
@Override
public void flushBatch() throws PersistenceException {
flush();
}
@Override
public Connection getConnection() {
return transaction.getConnection();
}
@Override
public void addModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
transaction.addModification(tableName, inserts, updates, deletes);
}
@Override
public void putUserObject(String name, Object value) {
transaction.putUserObject(name, value);
}
@Override
public Object getUserObject(String name) {
return transaction.getUserObject(name);
}
@Override
public void depth(int diff) {
transaction.depth();
}
@Override
public int depth() {
return transaction.depth();
}
@Override
public boolean isExplicit() {
return transaction.isExplicit();
}
@Override
public TransactionEvent getEvent() {
return transaction.getEvent();
}
@Override
public boolean isPersistCascade() {
return transaction.isPersistCascade();
}
@Override
public boolean isBatchThisRequest(PersistRequest.Type type) {
return transaction.isBatchThisRequest(type);
}
@Override
public BatchControl getBatchControl() {
return transaction.getBatchControl();
}
@Override
public void setBatchControl(BatchControl control) {
transaction.setBatchControl(control);
}
@Override
public PersistenceContext getPersistenceContext() {
return transaction.getPersistenceContext();
}
@Override
public void setPersistenceContext(PersistenceContext context) {
transaction.setPersistenceContext(context);
}
@Override
public Connection getInternalConnection() {
return transaction.getInternalConnection();
}
@Override
public boolean isSaveAssocManyIntersection(String intersectionTable, String beanName) {
return transaction.isSaveAssocManyIntersection(intersectionTable, beanName);
}
@Override
public boolean checkBatchEscalationOnCascade(PersistRequestBean<?> request) {
return transaction.checkBatchEscalationOnCascade(request);
}
@Override
public void flushBatchOnCascade() {
transaction.flushBatchOnCascade();
}
@Override
public void flushBatchOnRollback() {
transaction.flushBatchOnRollback();
}
@Override
public void markNotQueryOnly() {
transaction.markNotQueryOnly();
}
@Override
public void checkBatchEscalationOnCollection() {
transaction.checkBatchEscalationOnCollection();
}
@Override
public void flushBatchOnCollection() {
transaction.flushBatchOnCollection();
}
}
@@ -12,7 +12,7 @@ import org.slf4j.LoggerFactory;
*/
public abstract class BeanRequest {
private static final Logger log = LoggerFactory.getLogger(BeanRequest.class);
static final Logger log = LoggerFactory.getLogger(BeanRequest.class);
/**
* The server processing the request.
@@ -182,7 +182,6 @@ public class DefaultBeanLoader {
}
}
/**
* Load a batch of beans for +query or +lazy loading.
*/
@@ -5,7 +5,6 @@ import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
@@ -14,12 +13,9 @@ import java.util.Set;
*/
public class DefaultBeanState implements BeanState {
private final EntityBean entityBean;
private final EntityBeanIntercept intercept;
public DefaultBeanState(EntityBean entityBean) {
this.entityBean = entityBean;
this.intercept = entityBean._ebean_getIntercept();
}
@@ -73,16 +69,6 @@ public class DefaultBeanState implements BeanState {
intercept.setReadOnly(readOnly);
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
entityBean.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
entityBean.removePropertyChangeListener(listener);
}
@Override
public void setLoaded() {
intercept.setLoaded();
@@ -629,7 +629,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
InheritInfo inheritInfo = desc.getInheritInfo();
if (inheritInfo == null || inheritInfo.getChildren().isEmpty()) {
if (inheritInfo == null || inheritInfo.isConcrete()) {
return (T) desc.contextRef(pc, null, false, id);
}
@@ -659,7 +659,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction createTransaction() {
return transactionManager.createTransaction(0,true, -1);
return transactionManager.createTransaction(0, true, -1);
}
/**
@@ -671,7 +671,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction createTransaction(TxIsolation isolation) {
return transactionManager.createTransaction(0,true, isolation.getLevel());
return transactionManager.createTransaction(0, true, isolation.getLevel());
}
@Override
@@ -681,7 +681,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> T executeCall(TxScope scope, Callable<T> c) {
ScopeTrans scopeTrans = createScopeTrans(scope);
ScopedTransaction scopeTrans = scopedTransaction(scope);
try {
return c.call();
@@ -692,7 +692,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
throw new PersistenceException(scopeTrans.caughtThrowable(e));
} finally {
scopeTrans.onFinally();
scopeTrans.complete();
}
}
@@ -703,18 +703,18 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void execute(TxScope scope, Runnable r) {
ScopeTrans scopeTrans = createScopeTrans(scope);
ScopedTransaction t = scopedTransaction(scope);
try {
r.run();
} catch (Error e) {
throw scopeTrans.caughtError(e);
throw t.caughtError(e);
} catch (Exception e) {
throw new PersistenceException(scopeTrans.caughtThrowable(e));
throw new PersistenceException(t.caughtThrowable(e));
} finally {
scopeTrans.onFinally();
t.complete();
}
}
@@ -758,51 +758,14 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public ScopeTrans createScopeTrans(TxScope txScope) {
public void scopedTransactionEnter(TxScope txScope) {
beginTransaction(txScope);
}
if (txScope == null) {
// create a TxScope with default settings
txScope = new TxScope();
} else {
// check for implied batch mode via setting batchSize
txScope.checkBatchMode();
}
SpiTransaction suspended = null;
// get current transaction from ThreadLocal or equivalent
SpiTransaction t = transactionScopeManager.get();
boolean newTransaction;
if (txScope.getType().equals(TxType.NOT_SUPPORTED)) {
// Suspend existing transaction and
// run without a transaction in scope
newTransaction = false;
suspended = t;
t = null;
transactionScopeManager.replace(null);
} else {
// create a new Transaction based on TxType and t
newTransaction = createNewTransaction(t, txScope);
if (newTransaction) {
// suspend existing transaction (if there is one)
suspended = t;
// create a new transaction
int isoLevel = -1;
TxIsolation isolation = txScope.getIsolation();
if (isolation != null) {
isoLevel = isolation.getLevel();
}
t = transactionManager.createTransaction(txScope.getProfileId(), true, isoLevel);
// note ScopeTrans.onFinally() restores the suspended transaction
transactionScopeManager.replace(t);
}
}
return new ScopeTrans(rollbackOnChecked, newTransaction, t, txScope, suspended, transactionScopeManager);
@Override
public void scopedTransactionExit(Object returnOrThrowable, int opCode) {
ScopedTransaction st = (ScopedTransaction) transactionScopeManager.getScoped();
st.complete(returnOrThrowable, opCode);
}
/**
@@ -828,9 +791,52 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public Transaction beginTransaction(TxScope scope) {
ScopeTrans scopeTrans = createScopeTrans(scope);
return new ScopedTransaction(scopeTrans);
public Transaction beginTransaction(TxScope txScope) {
return scopedTransaction(txScope);
}
/**
* Create a Scoped transaction which internally can 'nest' transactions on it's own stack.
*/
ScopedTransaction scopedTransaction(TxScope txScope) {
txScope = initTxScope(txScope);
boolean setToScope = false;
ScopedTransaction txnContainer = (ScopedTransaction) transactionScopeManager.get();
if (txnContainer == null) {
setToScope = true;
txnContainer = new ScopedTransaction(transactionScopeManager);
}
SpiTransaction transaction = txnContainer.current();
boolean createTransaction;
if (txScope.getType() == TxType.NOT_SUPPORTED) {
createTransaction = false;
transaction = null;
} else {
createTransaction = createNewTransaction(transaction, txScope);
if (createTransaction) {
transaction = transactionManager.createTransaction(txScope.getProfileId(), true, txScope.getIsolationLevel());
}
}
txnContainer.push(new ScopeTrans(rollbackOnChecked, createTransaction, transaction, txScope));
if (setToScope) {
transactionScopeManager.set(txnContainer);
}
return txnContainer;
}
private TxScope initTxScope(TxScope txScope) {
if (txScope == null) {
return new TxScope();
} else {
// check for implied batch mode via setting batchSize
txScope.checkBatchMode();
return txScope;
}
}
/**
@@ -842,7 +848,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public Transaction beginTransaction(TxIsolation isolation) {
// start an explicit transaction
SpiTransaction t = transactionManager.createTransaction(0,true, isolation.getLevel());
SpiTransaction t = transactionManager.createTransaction(0, true, isolation.getLevel());
try {
transactionScopeManager.set(t);
} catch (PersistenceException existingTransactionError) {
@@ -1717,7 +1723,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> List<T> draftRestore(Query<T> query, Transaction transaction) {
return executeInTrans((txn)-> persister.draftRestore(query, txn), transaction);
return executeInTrans((txn) -> persister.draftRestore(query, txn), transaction);
}
@Override
@@ -2063,7 +2069,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
transactionManager.remoteTransactionEvent(event);
}
private <P> P executeInTrans(Function<SpiTransaction,P> fun, Transaction t) {
private <P> P executeInTrans(Function<SpiTransaction, P> fun, Transaction t) {
ObtainedTransaction wrap = initTransIfRequired(t);
try {
P result = fun.apply(wrap.transaction());
@@ -2094,7 +2100,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public SpiTransaction beginServerTransaction() {
SpiTransaction t = transactionManager.createTransaction(0,false, -1);
SpiTransaction t = transactionManager.createTransaction(0, false, -1);
transactionScopeManager.set(t);
return t;
}
@@ -7,9 +7,9 @@ import io.ebean.cache.ServerCacheManager;
import io.ebean.config.ExternalTransactionManager;
import io.ebean.config.ProfilingConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.SlowQueryListener;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.config.SlowQueryListener;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
@@ -43,6 +43,8 @@ import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.persist.platform.PostgresMultiValueBind;
import io.ebeaninternal.server.query.CQueryEngine;
import io.ebeaninternal.server.query.DefaultOrmQueryEngine;
import io.ebeaninternal.server.query.DefaultRelationalQueryEngine;
@@ -122,6 +124,8 @@ public class InternalConfiguration {
*/
private final List<Plugin> plugins = new ArrayList<>();
private final MultiValueBind multiValueBind;
public InternalConfiguration(ClusterManager clusterManager,
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
@@ -138,6 +142,7 @@ public class InternalConfiguration {
this.expressionFactory = initExpressionFactory(serverConfig, databasePlatform);
this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses);
this.multiValueBind = createMultiValueBind(databasePlatform.getPlatform());
this.deployInherit = new DeployInherit(bootupClasses);
this.deployCreateProperties = new DeployCreateProperties(typeManager);
@@ -245,9 +250,9 @@ public class InternalConfiguration {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone);
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone);
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
}
/**
@@ -267,6 +272,16 @@ public class InternalConfiguration {
}
}
private MultiValueBind createMultiValueBind(Platform platform) {
// only Postgres at this stage
switch (platform) {
case POSTGRES:
return new PostgresMultiValueBind();
default:
return new MultiValueBind();
}
}
public SpiJsonContext createJsonContext(SpiEbeanServer server) {
return new DJsonContext(server, jsonFactory, typeManager);
}
@@ -280,7 +295,7 @@ public class InternalConfiguration {
}
public OrmQueryEngine createOrmQueryEngine() {
return new DefaultOrmQueryEngine(cQueryEngine);
return new DefaultOrmQueryEngine(cQueryEngine, binder);
}
public Persister createPersister(SpiEbeanServer server) {
@@ -478,4 +493,11 @@ public class InternalConfiguration {
}
return listener;
}
/**
* Return the platform specific MultiValue bind support.
*/
public MultiValueBind getMultiValueBind() {
return multiValueBind;
}
}
@@ -62,4 +62,9 @@ public interface OrmQueryEngine {
* Translate the SQLException to a specific persistence exception type if possible.
*/
<T> PersistenceException translate(OrmQueryRequest<T> request, String bindLog, String sql, SQLException e);
/**
* Return true if multi-value bind is supported for this type (and current platform).
*/
boolean isMultiValueSupported(Class<?> valueType);
}
@@ -89,6 +89,16 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
return queryEngine.translate(this, bindLog, sql, e);
}
@Override
public boolean isMultiValueIdSupported() {
return beanDescriptor.isMultiValueIdSupported();
}
@Override
public boolean isMultiValueSupported(Class<?> valueType) {
return queryEngine.isMultiValueSupported(valueType);
}
/**
* Mark the transaction as not being query only.
*/
@@ -49,6 +49,7 @@ import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.id.IdBinder;
import io.ebeaninternal.server.deploy.id.IdBinderSimple;
import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists;
@@ -116,6 +117,8 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
private final short profileBeanId;
private final boolean multiValueSupported;
public enum EntityType {
ORM, EMBEDDED, VIEW, SQL, DOC
}
@@ -402,6 +405,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
public BeanDescriptor(BeanDescriptorMap owner, DeployBeanDescriptor<T> deploy) {
this.owner = owner;
this.multiValueSupported = owner.isMultiValueSupported();
this.serverName = owner.getServerName();
this.entityType = deploy.getEntityType();
this.properties = deploy.getProperties();
@@ -935,9 +939,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
for (Object anIdList : idList) {
idBinder.bindId(delete, anIdList);
}
idBinder.addIdInBindValues(delete, idList);
return delete;
}
@@ -1661,7 +1663,7 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
* Return a raw expression for 'where parent id in ...' clause.
*/
public String getParentIdInExpr(int parentIdSize, String rawWhere) {
String inClause = idBinder.getIdInValueExpr(parentIdSize);
String inClause = idBinder.getIdInValueExpr(false, parentIdSize);
return idBinder.isIdInExpandedForm() ? inClause : rawWhere + inClause;
}
@@ -1672,6 +1674,20 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
return idBinder;
}
/**
* Return true if this bean type has a simple single Id property.
*/
public boolean isSimpleId() {
return idBinder instanceof IdBinderSimple;
}
/**
* Return true if this type has a simple Id and the platform supports mutli-value binding.
*/
public boolean isMultiValueIdSupported() {
return multiValueSupported && isSimpleId();
}
/**
* Return the sql for binding an id. This is the columns with table alias that
* make up the id.
@@ -45,6 +45,7 @@ import io.ebeaninternal.server.deploy.parse.DeployInherit;
import io.ebeaninternal.server.deploy.parse.DeployUtil;
import io.ebeaninternal.server.deploy.parse.ReadAnnotations;
import io.ebeaninternal.server.deploy.parse.TransientProperties;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.properties.BeanPropertiesReader;
import io.ebeaninternal.server.properties.BeanPropertyAccess;
import io.ebeaninternal.server.properties.EnhanceBeanPropertyAccess;
@@ -131,6 +132,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private final DocStoreFactory docStoreFactory;
private final MultiValueBind multiValueBind;
private int entityBeanCount;
private final boolean updateChangesOnly;
@@ -201,7 +204,8 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
this.dataSource = serverConfig.getDataSource();
this.encryptKeyManager = serverConfig.getEncryptKeyManager();
this.databasePlatform = serverConfig.getDatabasePlatform();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm());
this.multiValueBind = config.getMultiValueBind();
this.idBinderFactory = new IdBinderFactory(databasePlatform.isIdInExpandedForm(), multiValueBind);
this.eagerFetchLobs = serverConfig.isEagerFetchLobs();
this.asOfViewSuffix = getAsOfViewSuffix(databasePlatform, serverConfig);
@@ -252,6 +256,11 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return (historySupport == null) ? serverConfig.getAsOfViewSuffix() : historySupport.getVersionsBetweenSuffix(serverConfig.getAsOfViewSuffix());
}
@Override
public boolean isMultiValueSupported() {
return multiValueBind.isSupported();
}
@Override
public ServerConfig getServerConfig() {
return serverConfig;
@@ -36,6 +36,11 @@ public interface BeanDescriptorMap {
*/
NamingConvention getNamingConvention();
/**
* Return true if multiple values can be bound as Array or Table Value and hence share the same query plan.
*/
boolean isMultiValueSupported();
/**
* Return the BeanDescriptor for a given class.
*/
@@ -137,7 +137,7 @@ public final class BeanFkeyProperty implements ElPropertyValue {
* Returns null as not an AssocOne.
*/
@Override
public String getAssocIdInValueExpr(int size) {
public String getAssocIdInValueExpr(boolean not, int size) {
return null;
}
@@ -909,7 +909,7 @@ public class BeanProperty implements ElPropertyValue, Property {
}
@Override
public String getAssocIdInValueExpr(int size) {
public String getAssocIdInValueExpr(boolean not, int size) {
// Returns null as not an AssocOne.
return null;
}
@@ -18,6 +18,7 @@ import io.ebeaninternal.server.deploy.id.ImportedId;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
import io.ebeaninternal.server.el.ElPropertyChainBuilder;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.text.json.ReadJson;
import io.ebeaninternal.server.text.json.SpiJsonWriter;
@@ -366,8 +367,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
// Flatten the bind values if needed (embeddedId)
List<Object> bindValues = getBindParentIds(parentIds);
query.where().raw(expr, bindValues.toArray());
if (descriptor.isSimpleId()) {
query.where().raw(expr, new MultiValueWrapper(bindValues));
} else {
query.where().raw(expr, bindValues.toArray());
}
}
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t, ArrayList<Object> excludeDetailIds) {
@@ -383,10 +387,12 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
}
EbeanServer server = getBeanDescriptor().getEbeanServer();
Query<?> q = server.find(getPropertyType())
.where()
.raw(expr, bindValues.toArray())
.query();
Query<?> q = server.find(getPropertyType());
if (descriptor.isSimpleId()) {
q.where().raw(expr, new MultiValueWrapper(bindValues));
} else {
q.where().raw(expr, bindValues.toArray());
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
Expression idIn = q.getExpressionFactory().idIn(excludeDetailIds);
@@ -405,10 +411,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
for (Object aParentIdist : parentIdist) {
bindWhereParendId(delete, aParentIdist);
if (exportedProperties.length == 1) {
bindWhereParendId(delete, new MultiValueWrapper(parentIdist));
} else {
for (Object aParentIdist : parentIdist) {
bindWhereParendId(delete, aParentIdist);
}
}
return delete;
}
@@ -430,9 +439,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
private String buildInClauseBinding(int size, String bindProto) {
if (descriptor.isSimpleId()) {
return descriptor.getIdBinder().getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder(10 + (size * (bindProto.length() + 1)));
sb.append(" in");
sb.append(" (");
for (int i = 0; i < size; i++) {
if (i > 0) {
@@ -496,21 +507,22 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
StringBuilder sb = new StringBuilder();
SpiQuery<?> query = request.getQueryRequest().getQuery();
if (manyToMany) {
sb.append(query.isAsDraft() ? intersectionDraftTable : intersectionPublishTable);
} else {
sb.append(targetDescriptor.getBaseTable(query.getTemporalMode()));
}
sb.append(" where ");
sb.append(" x where ");
for (int i = 0; i < exportedProperties.length; i++) {
if (i > 0) {
sb.append(" and ");
}
exportedProperties[i].appendWhere(sb, path);
exportedProperties[i].appendWhere(sb, "x.", path);
}
if (targetDescriptor.isSoftDelete()) {
sb.append(" and ").append(targetDescriptor.getSoftDeletePredicate("x"));
}
return sb.toString();
}
@@ -534,8 +546,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
* Return the logical id value expression taking into account embedded id's.
*/
@Override
public String getAssocIdInValueExpr(int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(size);
public String getAssocIdInValueExpr(boolean not, int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(not, size);
}
/**
@@ -207,7 +207,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
String inClause = targetIdBinder.getIdInValueExpr(parentIdist.size());
String inClause = targetIdBinder.getIdInValueExpr(false, parentIdist.size());
sb.append(inClause);
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
@@ -256,7 +256,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
private List<Object> findIdsByParentIdList(List<Object> parentIdList, Transaction t) {
String rawWhere = deriveWhereParentIdSql(true);
String inClause = targetIdBinder.getIdInValueExpr(parentIdList.size());
String inClause = targetIdBinder.getIdInValueExpr(false, parentIdList.size());
String expr = rawWhere + inClause;
@@ -445,8 +445,8 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
* Return the logical id value expression taking into account embedded id's.
*/
@Override
public String getAssocIdInValueExpr(int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(size);
public String getAssocIdInValueExpr(boolean not, int size) {
return targetDescriptor.getIdBinder().getIdInValueExpr(not, size);
}
/**
@@ -53,8 +53,8 @@ public class ExportedProperty {
/**
* Append a logical where for the foreign db column to logical property name,
*/
public void appendWhere(StringBuilder sb, String path) {
sb.append(foreignDbColumn).append(" = ");
public void appendWhere(StringBuilder sb, String alias, String path) {
sb.append(alias).append(foreignDbColumn).append(" = ");
if (path != null) {
sb.append(path).append(".");
}
@@ -287,6 +287,13 @@ public class InheritInfo {
return parent == null;
}
/**
* Return true if this is considered a concrete type in the inheritance hierarchy.
*/
public boolean isConcrete() {
return discriminatorValue != null;
}
/**
* For a discriminator get the inheritance information for this tree.
*/
@@ -12,6 +12,7 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
/**
@@ -134,7 +135,15 @@ public interface IdBinder {
*/
void bindId(DefaultSqlUpdate sqlUpdate, Object value);
void addIdInBindValue(SpiExpressionRequest request, Object value);
/**
* Binds multiple id value to an update.
*/
void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids);
/**
* Binds multiple id value to a request.
*/
void addIdInBindValues(SpiExpressionRequest request, Collection<?> ids);
/**
* Return the sql for binding the id using an IN clause.
@@ -144,7 +153,7 @@ public interface IdBinder {
/**
* Return the binding expression (like "?" or "(?,?)")for the Id.
*/
String getIdInValueExpr(int size);
String getIdInValueExpr(boolean not, int size);
/**
* Same as getIdInValueExpr but for delete by id.
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.deploy.id;
import io.ebean.bean.EntityBean;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -8,13 +9,13 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebean.util.SplitName;
import io.ebeaninternal.server.type.DataBind;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -163,20 +164,13 @@ public final class IdBinderEmbedded implements IdBinder {
return props;
}
@Override
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
for (BeanProperty prop : props) {
request.addBindValue(prop.getValue((EntityBean) value));
}
}
@Override
public String getIdInValueExprDelete(int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
if (!idInExpandedForm) {
return getIdInValueExpr(size);
return getIdInValueExpr(false, size);
}
StringBuilder sb = new StringBuilder();
@@ -201,12 +195,14 @@ public final class IdBinderEmbedded implements IdBinder {
}
@Override
public String getIdInValueExpr(int size) {
public String getIdInValueExpr(boolean not, int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
StringBuilder sb = new StringBuilder();
if (not) {
sb.append(" not");
}
if (!idInExpandedForm) {
sb.append(" in");
}
@@ -294,6 +290,22 @@ public final class IdBinderEmbedded implements IdBinder {
}
}
@Override
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> values) {
for (Object value : values) {
bindId(sqlUpdate, value);
}
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> values) {
for (Object value : values) {
for (BeanProperty prop : props) {
request.addBindValue(prop.getValue((EntityBean) value));
}
}
}
@Override
public Object readData(DataInput dataInput) throws IOException {
@@ -12,6 +12,7 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
/**
@@ -85,18 +86,13 @@ public final class IdBinderEmpty implements IdBinder {
return null;
}
@Override
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
}
@Override
public String getIdInValueExprDelete(int size) {
return getIdInValueExpr(size);
return getIdInValueExpr(false, size);
}
@Override
public String getIdInValueExpr(int size) {
public String getIdInValueExpr(boolean not, int size) {
return "";
}
@@ -135,6 +131,16 @@ public final class IdBinderEmpty implements IdBinder {
}
@Override
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> ids) {
}
@Override
public void loadIgnore(DbReadContext ctx) {
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.id;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
/**
* Creates the appropriate IdConvertSet depending on the type of Id property(s).
@@ -12,8 +13,11 @@ public class IdBinderFactory {
private final boolean idInExpandedForm;
public IdBinderFactory(boolean idInExpandedForm) {
private final MultiValueBind multiValueBind;
public IdBinderFactory(boolean idInExpandedForm, MultiValueBind multiValueBind) {
this.idInExpandedForm = idInExpandedForm;
this.multiValueBind = multiValueBind;
}
/**
@@ -29,7 +33,7 @@ public class IdBinderFactory {
if (id.isEmbedded()) {
return new IdBinderEmbedded(idInExpandedForm, (BeanPropertyAssocOne<?>) id);
} else {
return new IdBinderSimple(id);
return new IdBinderSimple(id, multiValueBind);
}
}
@@ -7,6 +7,8 @@ import io.ebeaninternal.server.core.InternString;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
@@ -14,6 +16,8 @@ import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -27,14 +31,18 @@ public final class IdBinderSimple implements IdBinder {
private final Class<?> expectedType;
private final MultiValueBind multiValueBind;
@SuppressWarnings("rawtypes")
private final ScalarType scalarType;
public IdBinderSimple(BeanProperty idProperty) {
public IdBinderSimple(BeanProperty idProperty, MultiValueBind multiValueBind) {
this.idProperty = idProperty;
this.scalarType = idProperty.getScalarType();
this.expectedType = idProperty.getPropertyType();
bindIdSql = InternString.intern(idProperty.getDbColumn() + " = ? ");
this.multiValueBind = multiValueBind;
}
@Override
@@ -125,28 +133,29 @@ public final class IdBinderSimple implements IdBinder {
@Override
public String getIdInValueExprDelete(int size) {
return getIdInValueExpr(size);
return getIdInValueExpr(false, size);
}
@Override
public String getIdInValueExpr(int size) {
public String getIdInValueExpr(boolean not, int size) {
if (size <= 0) {
throw new IndexOutOfBoundsException("The size must be at least 1");
}
StringBuilder sb = new StringBuilder(2 * size + 10);
sb.append(" in");
sb.append(" (?");
for (int i = 1; i < size; i++) {
sb.append(",?");
}
sb.append(") ");
return sb.toString();
return multiValueBind.getInExpression(not, scalarType, size);
}
@Override
public void addIdInBindValue(SpiExpressionRequest request, Object value) {
value = convertSetId(value, null);
request.addBindValue(value);
public void addIdInBindValues(DefaultSqlUpdate sqlUpdate, Collection<?> ids) {
sqlUpdate.addParameter(new MultiValueWrapper(ids));
}
@Override
public void addIdInBindValues(SpiExpressionRequest request, Collection<?> values) {
List<Object> copy = new ArrayList<>(values);
for (int i = 0; i < copy.size(); i++) {
copy.set(i, convertSetId(copy.get(i), null));
}
request.addBindValue(new MultiValueWrapper(copy));
}
@Override
@@ -192,8 +192,8 @@ public class ElPropertyChain implements ElPropertyValue {
}
@Override
public String getAssocIdInValueExpr(int size) {
return lastElPropertyValue.getAssocIdInValueExpr(size);
public String getAssocIdInValueExpr(boolean not, int size) {
return lastElPropertyValue.getAssocIdInValueExpr(not, size);
}
@Override
@@ -14,7 +14,7 @@ public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
/**
* Return the logical id value expression taking into account embedded id's.
*/
String getAssocIdInValueExpr(int size);
String getAssocIdInValueExpr(boolean not, int size);
/**
* Return the logical id in expression taking into account embedded id's.
@@ -160,4 +160,8 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
return bindValues;
}
@Override
public void appendInExpression(boolean not, Object[] bindValues) {
append(binder.getInExpression(not, bindValues));
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.expression;
import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
@@ -18,10 +19,17 @@ public class IdInExpression extends NonPrepareExpression {
private final Collection<?> idCollection;
private boolean multiValueIdSupported;
public IdInExpression(Collection<?> idCollection) {
this.idCollection = idCollection;
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
multiValueIdSupported = request.isMultiValueIdSupported();
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
return null;
@@ -49,10 +57,7 @@ public class IdInExpression extends NonPrepareExpression {
DefaultExpressionRequest r = (DefaultExpressionRequest) request;
BeanDescriptor<?> descriptor = r.getBeanDescriptor();
IdBinder idBinder = descriptor.getIdBinder();
for (Object id : idCollection) {
idBinder.addIdInBindValue(request, id);
}
idBinder.addIdInBindValues(request, idCollection);
}
/**
@@ -67,7 +72,7 @@ public class IdInExpression extends NonPrepareExpression {
request.append("1=0"); // append false for this stage
} else {
request.append(descriptor.getIdBinder().getBindIdInSql(null));
String inClause = idBinder.getIdInValueExpr(idCollection.size());
String inClause = idBinder.getIdInValueExpr(false, idCollection.size());
request.append(inClause);
}
}
@@ -82,7 +87,7 @@ public class IdInExpression extends NonPrepareExpression {
request.append("1=0"); // append false for this stage
} else {
request.append(descriptor.getIdBinderInLHSSql());
String inClause = idBinder.getIdInValueExpr(idCollection.size());
String inClause = idBinder.getIdInValueExpr(false, idCollection.size());
request.append(inClause);
}
}
@@ -92,7 +97,12 @@ public class IdInExpression extends NonPrepareExpression {
*/
@Override
public void queryPlanHash(StringBuilder builder) {
builder.append("IdIn[").append("?").append(idCollection.size()).append("]");
builder.append("IdIn[?");
if (!multiValueIdSupported) {
// query plan specific to the number of parameters in the IN clause
builder.append(idCollection.size());
}
builder.append("]");
}
@Override
@@ -5,11 +5,13 @@ import io.ebean.event.BeanQueryRequest;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.persist.MultiValueWrapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
class InExpression extends AbstractExpression {
@@ -20,6 +22,8 @@ class InExpression extends AbstractExpression {
private Object[] bindValues;
private boolean multiValueSupported;
InExpression(String propertyName, Collection<?> sourceValues, boolean not) {
super(propertyName);
this.sourceValues = sourceValues;
@@ -43,6 +47,9 @@ class InExpression extends AbstractExpression {
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
bindValues = values();
if (bindValues.length > 0) {
multiValueSupported = request.isMultiValueSupported((bindValues[0]).getClass());
}
}
@Override
@@ -58,19 +65,24 @@ class InExpression extends AbstractExpression {
prop = null;
}
for (Object bindValue : bindValues) {
if (prop == null) {
request.addBindValue(bindValue);
} else {
if (prop == null) {
if (bindValues.length > 0) {
// if we have no property, we wrap them in a multi value wrapper.
// later the binder will decide, which bind strategy to use.
request.addBindValue(new MultiValueWrapper(Arrays.asList(bindValues)));
}
} else {
List<Object> idList = new ArrayList<>();
for (Object bindValue : bindValues) {
// extract the id values from the bean
Object[] ids = prop.getAssocIdValues((EntityBean) bindValue);
if (ids != null) {
for (Object id : ids) {
request.addBindValue(id);
}
Collections.addAll(idList, ids);
}
}
if (!idList.isEmpty()) {
request.addBindValue(new MultiValueWrapper(idList));
}
}
}
@@ -90,23 +102,12 @@ class InExpression extends AbstractExpression {
if (prop != null) {
request.append(prop.getAssocIdInExpr(propName));
String inClause = prop.getAssocIdInValueExpr(bindValues.length);
if (not) {
request.append(" not");
}
String inClause = prop.getAssocIdInValueExpr(not, bindValues.length);
request.append(inClause);
} else {
request.append(propName);
if (not) {
request.append(" not");
}
request.append(" in (?");
for (int i = 1; i < bindValues.length; i++) {
request.append(", ").append("?");
}
request.append(" ) ");
request.appendInExpression(not, bindValues);
}
}
@@ -121,7 +122,12 @@ class InExpression extends AbstractExpression {
builder.append("In[");
}
builder.append(propName);
builder.append(" ?").append(bindValues.length).append("]");
builder.append(" ?");
if (!multiValueSupported) {
// query plan specific to the number of parameters in the IN clause
builder.append(bindValues.length);
}
builder.append("]");
}
@Override
@@ -5,6 +5,7 @@ import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.server.core.DbExpressionHandler;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
@@ -19,6 +20,7 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
@@ -38,17 +40,20 @@ public class Binder {
private final DataTimeZone dataTimeZone;
private final MultiValueBind multiValueBind;
/**
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean asOfStandardsBased,
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone) {
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone, MultiValueBind multiValueBind) {
this.typeManager = typeManager;
this.asOfBindCount = asOfBindCount;
this.asOfStandardsBased = asOfStandardsBased;
this.dbExpressionHandler = dbExpressionHandler;
this.dataTimeZone = dataTimeZone;
this.multiValueBind = multiValueBind;
}
/**
@@ -168,6 +173,26 @@ public class Binder {
}
}
/**
* Return true if MultiValue binding is supported for the given type.
*/
public boolean isMultiValueSupported(Class<?> cls) {
try {
ScalarType<?> scalarType = getScalarType(cls);
return multiValueBind.isTypeSupported(scalarType.getJdbcType());
} catch (PersistenceException e) {
return false;
}
}
private ScalarType<?> getScalarType(Class<?> clazz) {
ScalarType<?> type = typeManager.getScalarType(clazz);
if (type == null) {
throw new PersistenceException("No ScalarType registered for " + clazz);
}
return type;
}
/**
* Bind an Object with unknown data type.
*/
@@ -178,15 +203,19 @@ public class Binder {
bindObject(dataBind, null, Types.OTHER);
return null;
} else if (value instanceof MultiValueWrapper) {
MultiValueWrapper wrapper = (MultiValueWrapper) value;
Collection<?> values = wrapper.getValues();
ScalarType<?> type = getScalarType(wrapper.getType());
int dbType = type.getJdbcType();
// let the multiValueBind decide what to do with the value
multiValueBind.bindMultiValues(dataBind, values, type, one -> bindObject(dataBind, one, dbType));
return values;
} else {
ScalarType<?> type = typeManager.getScalarType(value.getClass());
if (type == null) {
// the type is not registered with the TypeManager.
String msg = "No ScalarType registered for " + value.getClass();
throw new PersistenceException(msg);
} else if (!type.isJdbcNative()) {
ScalarType<?> type = getScalarType(value.getClass());
if (!type.isJdbcNative()) {
// convert to a JDBC native type
value = type.toJdbcType(value);
}
@@ -197,6 +226,14 @@ public class Binder {
}
}
/**
* Return the SQL in clause taking into account Multi-value support.
*/
public String getInExpression(boolean not, Object[] bindValues) {
ScalarType<?> type = getScalarType(bindValues[0].getClass());
return multiValueBind.getInExpression(not, type, bindValues.length);
}
/**
* bind a single value.
* <p>
@@ -0,0 +1,40 @@
package io.ebeaninternal.server.persist;
import java.util.Collection;
/**
* Wraps the multi values that are used for "property in (...)" queries
* @author Roland Praml, FOCONIS AG
*/
public class MultiValueWrapper {
private final Collection<?> values;
private Class<?> type;
public MultiValueWrapper(Collection<?> values) {
this.values = values;
this.type = values.iterator().next().getClass();
}
public Collection<?> getValues() {
return values;
}
public Class<?> getType() {
return type;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("Array[" + values.size() + "]={");
for (Object value : values) {
sb.append(value).append(',');
if (sb.length() > 50) {
sb.append("...}");
return sb.toString();
}
}
sb.setLength(sb.length() - 1);
sb.append('}');
return sb.toString();
}
}
@@ -48,7 +48,7 @@ public class PstmtFactory {
}
if (logSql) {
t.logSql(sql);
t.logSql(TrimLogSql.trim(sql));
}
Connection conn = t.getInternalConnection();
@@ -0,0 +1,93 @@
package io.ebeaninternal.server.persist.platform;
import io.ebean.config.dbplatform.ExtraDbTypes;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.SQLException;
import java.util.Collection;
import static java.sql.Types.BIGINT;
import static java.sql.Types.BIT;
import static java.sql.Types.BOOLEAN;
import static java.sql.Types.CHAR;
import static java.sql.Types.DATE;
import static java.sql.Types.DECIMAL;
import static java.sql.Types.DOUBLE;
import static java.sql.Types.FLOAT;
import static java.sql.Types.INTEGER;
import static java.sql.Types.NCHAR;
import static java.sql.Types.NUMERIC;
import static java.sql.Types.NVARCHAR;
import static java.sql.Types.REAL;
import static java.sql.Types.SMALLINT;
import static java.sql.Types.TIMESTAMP;
import static java.sql.Types.TIMESTAMP_WITH_TIMEZONE;
import static java.sql.Types.TIME_WITH_TIMEZONE;
import static java.sql.Types.TINYINT;
import static java.sql.Types.VARCHAR;
/**
* Base MultiValueBind for platform specific support.
*/
abstract class AbstractMultiValueBind extends MultiValueBind {
@Override
public boolean isSupported() {
return true;
}
@Override
public boolean isTypeSupported(int jdbcType) {
return getArrayType(jdbcType) != null;
}
@Override
public void bindMultiValues(DataBind dataBind, Collection<?> values, ScalarType<?> type, BindOne bindOne) throws SQLException {
String arrayType = getArrayType(type.getJdbcType());
if (arrayType == null) {
super.bindMultiValues(dataBind, values, type, bindOne);
} else {
dataBind.setArray(arrayType, toArray(values, type));
}
}
protected String getArrayType(int dbType) {
switch(dbType) {
case TINYINT:
case SMALLINT:
case INTEGER:
case BIGINT:
case DECIMAL: // TODO: we have no info about precision here
case NUMERIC:
return "bigint";
case REAL:
case FLOAT:
case DOUBLE:
return "float";
case BIT:
case BOOLEAN:
return "bit";
case DATE:
return "date";
case TIMESTAMP:
case TIME_WITH_TIMEZONE:
case TIMESTAMP_WITH_TIMEZONE:
return "timestamp";
//case LONGVARCHAR:
//case CLOB:
case CHAR:
case VARCHAR:
//case LONGNVARCHAR:
//case NCLOB:
case NCHAR:
case NVARCHAR:
return "varchar";
case ExtraDbTypes.UUID: // Db Native UUID
return "varchar";
default:
return null;
}
}
}
@@ -0,0 +1,70 @@
package io.ebeaninternal.server.persist.platform;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.ScalarType;
import java.sql.SQLException;
import java.util.Collection;
/**
* Default implementation for multi value help.
*/
public class MultiValueBind {
@FunctionalInterface
public interface BindOne {
void bind(Object value) throws SQLException;
}
protected Object[] toArray(Collection<?> values, ScalarType<?> type) {
Object[] array = new Object[values.size()];
int i = 0;
for (Object value : values) {
array[i++] = type.toJdbcType(value);
}
return array;
}
/**
* Defaults to not supported and using a bind value per element.
*/
public boolean isSupported() {
return false;
}
/**
* Defaults to not supported and using a bind value per element.
*/
public boolean isTypeSupported(int jdbcType) {
return false;
}
/**
* Default for multi values. They are appended one by one.
*/
public void bindMultiValues(DataBind dataBind, Collection<?> values, ScalarType<?> type, BindOne bindOne) throws SQLException {
for (Object value : values) {
if (!type.isJdbcNative()) {
value = type.toJdbcType(value);
}
bindOne.bind(value);
}
}
/**
* Appends the 'in' expression to the request. Must add leading & trailing space!
*/
public String getInExpression(boolean not, ScalarType<?> type, int size) {
StringBuilder sb = new StringBuilder();
if (not) {
sb.append(" not");
}
sb.append(" in (?");
for (int i = 1; i < size; i++) {
sb.append(", ").append("?");
}
sb.append(" ) ");
return sb.toString();
}
}
@@ -0,0 +1,25 @@
package io.ebeaninternal.server.persist.platform;
import io.ebean.config.dbplatform.ExtraDbTypes;
import io.ebeaninternal.server.type.ScalarType;
/**
* Multi value binder that uses Postgres Array.
*/
public class PostgresMultiValueBind extends AbstractMultiValueBind {
@Override
public String getInExpression(boolean not, ScalarType<?> type, int size) {
int dbType = type.getJdbcType();
if (dbType == ExtraDbTypes.UUID) {
return (not) ? " != all(?::uuid[])" : " = any(?::uuid[])";
}
String arrayType = getArrayType(dbType);
if (arrayType == null) {
return super.getInExpression(not, type, size);
} else {
return (not) ? " != all(?)" : " = any(?)";
}
}
}
@@ -449,8 +449,13 @@ class CQueryBuilder {
throw new PersistenceException(msg);
}
if (propertyName != null) {
String[] pathProp = SplitName.split(propertyName);
pathProps.addToPath(pathProp[0], pathProp[1]);
boolean assocProperty = el.isAssocProperty();
if (!assocProperty) {
pathProps.addToPath(null, propertyName);
} else {
String[] pathProp = SplitName.split(propertyName);
pathProps.addToPath(pathProp[0], pathProp[1]);
}
}
}
}
@@ -331,7 +331,7 @@ public class CQueryEngine {
* deemed to be a be a paging query - check that the order by contains the id
* property to ensure unique row ordering for predicable paging but only in
* case, this is not a distinct query
*
*
* @param request
*/
private <T> void prepareForPaging(OrmQueryRequest<T> request) {
@@ -10,6 +10,7 @@ import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.OrmQueryEngine;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.persist.Binder;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
@@ -28,11 +29,14 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
*/
private final CQueryEngine queryEngine;
private final Binder binder;
/**
* Create the Finder.
*/
public DefaultOrmQueryEngine(CQueryEngine queryEngine) {
public DefaultOrmQueryEngine(CQueryEngine queryEngine, Binder binder) {
this.queryEngine = queryEngine;
this.binder = binder;
}
@Override
@@ -40,6 +44,11 @@ public class DefaultOrmQueryEngine implements OrmQueryEngine {
return queryEngine.translate(request, bindLog, sql, e);
}
@Override
public boolean isMultiValueSupported(Class<?> cls) {
return binder.isMultiValueSupported(cls);
}
/**
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
*/
@@ -2,12 +2,12 @@ package io.ebeaninternal.server.querydefn;
import io.ebean.OrderBy;
import io.ebean.Query;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.CQueryPlanKey;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.rawsql.SpiRawSql;
/**
* Query plan key for ORM queries.
@@ -109,6 +109,10 @@ class OrmQueryPlanKey implements CQueryPlanKey {
return planHash;
}
public String toString() {
return description + " maxRows:" + maxRows + " firstRow:" + firstRow + " rawSqlKey:" + rawSqlKey + " planHash:" + planHash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -22,6 +22,11 @@ public class DefaultTransactionScopeManager extends TransactionScopeManager {
DefaultTransactionThreadLocal.end(serverName);
}
@Override
public SpiTransaction getScoped() {
return DefaultTransactionThreadLocal.get(serverName);
}
@Override
public SpiTransaction get() {
SpiTransaction t = DefaultTransactionThreadLocal.get(serverName);
@@ -32,8 +32,12 @@ public class ExternalTransactionScopeManager extends TransactionScopeManager {
}
@Override
public SpiTransaction get() {
public SpiTransaction getScoped() {
return get();
}
@Override
public SpiTransaction get() {
return (SpiTransaction) externalManager.getCurrentTransaction();
}
@@ -17,6 +17,11 @@ public abstract class TransactionScopeManager implements SpiTransactionScopeMana
this.serverName = transactionManager.getServerName();
}
/**
* Return the current Transaction allowing it to be inactive.
*/
public abstract SpiTransaction getScoped();
/**
* Return the current Transaction for this serverName and Thread.
*/
@@ -105,7 +105,7 @@ public class BindParamsParser {
// search for quotes and named params... in order...
int beginQuotePos = sql.indexOf(quote, startPos);
int nameParamStart = sql.indexOf(colon, startPos);
int nameParamStart = findNameStart(sql, startPos);
if (beginQuotePos > 0 && beginQuotePos < nameParamStart) {
// the quote precedes the named parameter...
// find and add up to the end quote
@@ -180,6 +180,23 @@ public class BindParamsParser {
}
}
/**
* Find the next named parameter start position (based on colon).
*/
static int findNameStart(String sql, int startPos) {
int colonPos = sql.indexOf(colon, startPos);
if (colonPos > -1) {
// validate the next character after the colon (ignore postgres cast)
char c = sql.charAt(colonPos + 1);
if (c == '_' || Character.isLetterOrDigit(c)) {
return colonPos;
} else {
return findNameStart(sql, colonPos + 2);
}
}
return -1;
}
/**
* Add an encryption key bind parameter.
*/
+1 -1
View File
@@ -1 +1 @@
api-version: 11.3
api-version: 11.4
+34 -2
View File
@@ -3,15 +3,20 @@ package io.ebean;
import io.ebean.annotation.Platform;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.HelpCreateQueryRequest;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import org.tests.model.basic.Country;
import org.avaje.agentloader.AgentLoader;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tests.model.basic.Country;
import java.sql.Types;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(ConditionalTestRunner.class)
public abstract class BaseTestCase {
@@ -81,7 +86,7 @@ public abstract class BaseTestCase {
public boolean isDb2() {
return Platform.DB2 == platform();
}
public boolean isPostgres() {
return Platform.POSTGRES == platform();
}
@@ -127,4 +132,31 @@ public abstract class BaseTestCase {
.setLoadBeanCache(true)
.findList();
}
/**
* Platform specific IN clause assert.
*/
protected void platformAssertIn(String sql, String containsIn) {
if (isPostgres()) {
assertThat(sql).contains(containsIn+" = any(");
} else {
assertThat(sql).contains(containsIn+" in ");
}
// H2 contains("where t0.name in (select * from table(x varchar = ?)");
}
/**
* Platform specific NOT IN clause assert.
*/
protected void platformAssertNotIn(String sql, String containsIn) {
if (isPostgres()) {
assertThat(sql).contains(containsIn+" != all(");
} else {
assertThat(sql).contains(containsIn+" not in ");
}
}
protected <T> OrmQueryRequest<T> createQueryRequest(SpiQuery.Type type, Query<T> query, Transaction t) {
return HelpCreateQueryRequest.create(server(), type, query, t);
}
}
@@ -1,8 +1,8 @@
package io.ebean;
import org.tests.model.basic.EBasicVer;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import org.tests.model.basic.EBasicVer;
import java.util.ArrayList;
import java.util.List;
@@ -29,7 +29,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
}
@Test
@@ -55,7 +55,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
}
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
}
@Test
@@ -75,7 +75,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
}
@@ -102,7 +102,7 @@ public class EbeanServer_deleteAllByIdTest extends BaseTestCase {
}
List<String> loggedSql = LoggedSqlCollector.stop();
assertThat(loggedSql).hasSize(1);
assertThat(loggedSql.get(0)).contains("delete from e_basicver where id in (?,?,?)");
platformAssertIn(loggedSql.get(0), "delete from e_basicver where id ");
}
private List<EBasicVer> beans(int count) {
@@ -1,49 +0,0 @@
package io.ebean;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Assert;
import org.junit.Test;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;
public class TestPropertyChangeListener extends BaseTestCase {
@Test
public void test() {
ResetBasicData.reset();
List<Customer> list = Ebean.find(Customer.class).findList();
Listener listener = new Listener();
Customer customer = list.get(0);
Ebean.getBeanState(customer).addPropertyChangeListener(listener);
customer.setName("modName");
customer.setSmallnote("modSmallNote");
Assert.assertEquals(2, listener.events.size());
Assert.assertEquals("modName", listener.events.get(0).getNewValue());
Assert.assertEquals("name", listener.events.get(0).getPropertyName());
Assert.assertEquals("modSmallNote", listener.events.get(1).getNewValue());
Assert.assertEquals("smallnote", listener.events.get(1).getPropertyName());
}
class Listener implements PropertyChangeListener {
List<PropertyChangeEvent> events = new ArrayList<>();
@Override
public void propertyChange(PropertyChangeEvent evt) {
events.add(evt);
}
}
}
+1 -1
View File
@@ -91,7 +91,7 @@ public class UpdateQueryTest extends BaseTestCase {
updateQuery.update();
assertThat(updateQuery.getGeneratedSql()).contains("update o_customer set status=? where not exists (select 1 from contact where customer_id = id) and id > ?");
assertThat(updateQuery.getGeneratedSql()).contains("update o_customer set status=? where not exists (select 1 from contact x where x.customer_id = id) and id > ?");
}
@Test
@@ -1,7 +1,6 @@
package io.ebean.common;
import io.ebean.bean.BeanCollection;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.ArrayList;
@@ -18,7 +17,6 @@ public class BeanListTest {
private Object object2 = new Object();
private Object object3 = new Object();
@NotNull
private List<Object> all() {
List<Object> all = new ArrayList<>();
all.add(object1);
@@ -27,7 +25,6 @@ public class BeanListTest {
return all;
}
@NotNull
private List<Object> some() {
List<Object> some = new ArrayList<>();
some.add(object2);
@@ -1,7 +1,6 @@
package io.ebean.common;
import io.ebean.bean.BeanCollection;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.LinkedHashMap;
@@ -16,7 +15,6 @@ public class BeanMapTest {
Object object2 = new Object();
Object object3 = new Object();
@NotNull
private Map<String, Object> all() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("1", object1);
@@ -25,7 +23,6 @@ public class BeanMapTest {
return all;
}
@NotNull
private Map<String, Object> some() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("2", object2);
@@ -1,7 +1,6 @@
package io.ebean.common;
import io.ebean.bean.BeanCollection;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.LinkedHashSet;
@@ -16,7 +15,6 @@ public class BeanSetTest {
Object object2 = new Object();
Object object3 = new Object();
@NotNull
private Set<Object> all() {
Set<Object> all = new LinkedHashSet<>();
all.add(object1);
@@ -25,7 +23,6 @@ public class BeanSetTest {
return all;
}
@NotNull
private Set<Object> some() {
Set<Object> some = new LinkedHashSet<>();
some.add(object2);
@@ -0,0 +1,36 @@
package io.ebeaninternal.api;
import io.ebean.Ebean;
import io.ebean.TxScope;
import org.junit.Test;
import org.tests.model.basic.Contact;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.basic.UUOne;
public class HelpScopeTransTest {
@Test
public void begin() throws Exception {
ResetBasicData.reset();
HelpScopeTrans.enter(TxScope.required());
HelpScopeTrans.enter(TxScope.required());
Ebean.find(Customer.class).findList();
HelpScopeTrans.enter(TxScope.required());
Ebean.find(Contact.class).findList();
HelpScopeTrans.exit(null, 1);
UUOne one = new UUOne();
one.setName("junk");
Ebean.save(one);
HelpScopeTrans.exit(null, 1);
HelpScopeTrans.exit(null, 1);
}
}
@@ -53,6 +53,16 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public void scopedTransactionEnter(TxScope txScope) {
}
@Override
public void scopedTransactionExit(Object returnOrThrowable, int opCode) {
}
@Override
public Object currentTenantId() {
return null;
@@ -163,11 +173,6 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
@Override
public ScopeTrans createScopeTrans(TxScope txScope) {
return null;
}
@Override
public SpiTransaction createQueryTransaction(Object tenantId) {
return null;
@@ -2,7 +2,7 @@ package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
@@ -139,7 +139,6 @@ public class EntryTest {
return drop;
}
@NotNull
static PendingDrops.Entry createEntry(ChangeSet... pending) {
PendingDrops.Entry entry = new PendingDrops.Entry(MigrationVersion.parse("1.1"));
@@ -1,7 +1,6 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -309,7 +308,6 @@ public class MColumnTest {
assertThat(getAlterColumn(diff).isHistoryExclude()).isEqualTo(false);
}
@NotNull
private AlterColumn getAlterColumn(ModelDiff diff) {
return (AlterColumn) diff.getApplyChanges().get(0);
}
@@ -3,7 +3,6 @@ package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.Migration;
import io.ebeaninternal.dbmigration.migrationreader.MigrationXmlReader;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.List;
@@ -67,7 +66,6 @@ public class ModelContainerTest {
assertThat(normalColumns).doesNotContain("zing", "zong", "boom", "baz", "bar");
}
@NotNull
private ModelContainer container_2_1() {
ModelContainer container = new ModelContainer();
container.apply(mig("2.0.model.xml"), ver("2.0"));
@@ -75,7 +73,6 @@ public class ModelContainerTest {
return container;
}
@NotNull
private ModelContainer container_1_1() {
ModelContainer container = new ModelContainer();
container.apply(mig("1.0.model.xml"), ver("1.0"));
@@ -4,7 +4,6 @@ import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebean.event.changelog.ChangeType;
import io.ebean.event.changelog.TxnState;
import org.jetbrains.annotations.NotNull;
import java.sql.Timestamp;
import java.util.LinkedHashMap;
@@ -33,7 +32,6 @@ public class Helper {
return cs;
}
@NotNull
private BeanChange createInsert(long startId) {
Map<String, Object> values = new LinkedHashMap<>();
@@ -47,7 +45,6 @@ public class Helper {
return new BeanChange("mytable", null, startId + 1, ChangeType.INSERT, "");//values, oldValues);
}
@NotNull
private BeanChange createUpdate(long startId) {
Map<String, Object> values = new LinkedHashMap<>();
values.put("name", "jim");
@@ -66,7 +63,6 @@ public class Helper {
return new BeanChange("mytable", null, startId + 2, ChangeType.UPDATE, "");//values, oldValues);
}
@NotNull
private BeanChange createDelete(long startId) {
return new BeanChange("mytable", null, startId + 3, ChangeType.DELETE, null);
}
@@ -0,0 +1,15 @@
package io.ebeaninternal.server.core;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebeaninternal.api.SpiQuery;
public class HelpCreateQueryRequest {
public static <T> OrmQueryRequest<T> create(EbeanServer server, SpiQuery.Type type, Query<T> query, Transaction t) {
DefaultServer defaultServer = (DefaultServer) server;
return (OrmQueryRequest<T>)defaultServer.createQueryRequest(type, query, t);
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.LinkedHashMap;
@@ -88,8 +87,6 @@ public class AllEqualsExpressionTest extends BaseExpressionTest {
assertThat(exp("a", null).isSameByBind(exp("a", 10))).isFalse();
}
@NotNull
private AllEqualsExpression exp(Map<String, Object> propMap) {
return new AllEqualsExpression(propMap);
}
@@ -1,13 +1,11 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class BetweenPropertyExpressionTest extends BaseExpressionTest {
@NotNull
private BetweenPropertyExpression exp(String lowProperty, String highProperty, Object value) {
return new BetweenPropertyExpression(lowProperty, highProperty, value);
}
@@ -1,7 +1,5 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.Arrays;
@@ -11,7 +9,6 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
public class ExistsQueryExpressionTest extends BaseExpressionTest {
@NotNull
private ExistsQueryExpression exp(boolean not, String sql, Object... bindValues) {
return new ExistsQueryExpression(not, sql, Arrays.asList(bindValues));
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
@@ -9,7 +8,6 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
public class IdExpressionTest extends BaseExpressionTest {
@NotNull
private IdExpression exp(Object value) {
return new IdExpression(value);
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.Arrays;
@@ -10,7 +9,6 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
public class IdInExpressionTest extends BaseExpressionTest {
@NotNull
private IdInExpression exp(Object... values) {
return new IdInExpression(Arrays.asList(values));
}
@@ -28,7 +26,7 @@ public class IdInExpressionTest extends BaseExpressionTest {
}
@Test
public void isSameByPlan_when_diffBindCount() {
public void isSameByPlan_when_diffBindCount_notPrepared() {
different(exp(10), exp(10, 20));
}
@@ -1,6 +1,11 @@
package io.ebeaninternal.server.expression;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.event.BeanQueryRequest;
import org.junit.Test;
import org.tests.model.basic.Customer;
import java.util.ArrayList;
import java.util.Arrays;
@@ -10,6 +15,19 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
public class InExpressionTest extends BaseExpressionTest {
/**
* Request with Multi-Value support.
*/
private TDQueryRequest<Customer> multi() {
return MULTI_VALUE;
}
/**
* Request with NO Multi-Value support.
*/
private TDQueryRequest<Customer> noMulti() {
return NO_MULTI_VALUE;
}
@Test
public void queryPlanHash_given_diffPropertyName_should_differentPlanHash() throws Exception {
@@ -19,8 +37,8 @@ public class InExpressionTest extends BaseExpressionTest {
InExpression ex1 = new InExpression("foo", values, false);
InExpression ex2 = new InExpression("bar", values, false);
ex1.prepareExpression(null);
ex2.prepareExpression(null);
ex1.prepareExpression(multi());
ex2.prepareExpression(multi());
different(ex1, ex2);
}
@@ -34,12 +52,25 @@ public class InExpressionTest extends BaseExpressionTest {
InExpression ex1 = new InExpression("foo", values1, false);
InExpression ex2 = new InExpression("foo", values2, false);
ex1.prepareExpression(null);
ex2.prepareExpression(null);
ex1.prepareExpression(noMulti());
ex2.prepareExpression(noMulti());
different(ex1, ex2);
}
@Test
public void queryPlanHash_given_diffBindCount_withMultiSupport_samePlanHash() throws Exception {
List<Integer> values1 = values(42, 92);
List<Integer> values2 = values(42, 92, 82);
InExpression ex1 = new InExpression("foo", values1, false);
InExpression ex2 = new InExpression("foo", values2, false);
ex1.prepareExpression(multi());
ex2.prepareExpression(multi());
same(ex1, ex2);
}
@Test
public void queryPlanHash_given_diffNotFlag_should_differentPlanHash() throws Exception {
@@ -48,8 +79,8 @@ public class InExpressionTest extends BaseExpressionTest {
InExpression ex1 = new InExpression("foo", values, true);
InExpression ex2 = new InExpression("foo", values, false);
ex1.prepareExpression(null);
ex2.prepareExpression(null);
ex1.prepareExpression(multi());
ex2.prepareExpression(multi());
different(ex1, ex2);
}
@@ -62,8 +93,8 @@ public class InExpressionTest extends BaseExpressionTest {
InExpression ex1 = new InExpression("foo", values, true);
InExpression ex2 = new InExpression("foo", values, true);
ex1.prepareExpression(null);
ex2.prepareExpression(null);
ex1.prepareExpression(multi());
ex2.prepareExpression(multi());
same(ex1, ex2);
}
@@ -78,7 +109,13 @@ public class InExpressionTest extends BaseExpressionTest {
private InExpression exp(String propName, boolean not, Object... values) {
InExpression ex = new InExpression(propName, Arrays.asList(values), not);
ex.prepareExpression(null);
ex.prepareExpression(multi());
return ex;
}
private InExpression expNoMulti(String propName, boolean not, Object... values) {
InExpression ex = new InExpression(propName, Arrays.asList(values), not);
ex.prepareExpression(noMulti());
return ex;
}
@@ -103,13 +140,15 @@ public class InExpressionTest extends BaseExpressionTest {
@Test
public void isSameByPlan_when_diffBind_same() {
different(exp("a", false, 10), exp("a", false, 10, 20));
same(exp("a", false, 10), exp("a", false, 10, 20));
different(expNoMulti("a", false, 10), expNoMulti("a", false, 10, 20));
}
@Test
public void isSameByPlan_when_diffBindCount() {
different(exp("a", false, 10), exp("a", false, 10, 20));
same(exp("a", false, 10), exp("a", false, 10, 20));
different(expNoMulti("a", false, 10), expNoMulti("a", false, 10, 20));
}
@Test
@@ -142,4 +181,42 @@ public class InExpressionTest extends BaseExpressionTest {
assertThat(exp("a", false, 10, "ABC").isSameByBind(exp("a", false, 10, "ABC", 30))).isFalse();
}
private static final TDQueryRequest<Customer> MULTI_VALUE= new TDQueryRequest<>(true);
private static final TDQueryRequest<Customer> NO_MULTI_VALUE = new TDQueryRequest<>(false);
static class TDQueryRequest<T> implements BeanQueryRequest<T> {
final boolean supported;
TDQueryRequest(boolean supported) {
this.supported = supported;
}
@Override
public EbeanServer getEbeanServer() {
return null;
}
@Override
public Transaction getTransaction() {
return null;
}
@Override
public Query<T> getQuery() {
return null;
}
@Override
public boolean isMultiValueIdSupported() {
return supported;
}
@Override
public boolean isMultiValueSupported(Class<?> valueType) {
return supported;
}
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import java.util.Arrays;
@@ -10,7 +9,6 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
public class InQueryExpressionTest extends BaseExpressionTest {
@NotNull
private InQueryExpression exp(String propertyName, boolean not, String sql, Object... bindValues) {
return new InQueryExpression(propertyName, not, sql, Arrays.asList(bindValues));
}
@@ -23,7 +23,7 @@ public class IsEmptyExpressionQueryTest extends BaseTestCase {
.query();
query.findList();
assertThat(sqlOf(query)).contains("select t0.id from o_customer t0 where not exists (select 1 from contact where customer_id = t0.id");
assertThat(sqlOf(query)).contains("select t0.id from o_customer t0 where not exists (select 1 from contact x where x.customer_id = t0.id");
}
@Test
@@ -37,7 +37,7 @@ public class IsEmptyExpressionQueryTest extends BaseTestCase {
.query();
query.findList();
assertThat(sqlOf(query)).contains("select t0.id from o_customer t0 where exists (select 1 from contact where customer_id = t0.id");
assertThat(sqlOf(query)).contains("select t0.id from o_customer t0 where exists (select 1 from contact x where x.customer_id = t0.id");
}
@Test
@@ -51,7 +51,7 @@ public class IsEmptyExpressionQueryTest extends BaseTestCase {
.query();
query.findList();
assertThat(sqlOf(query)).contains("select t0.id from contact t0 where not exists (select 1 from contact_note where contact_id = t0.id");
assertThat(sqlOf(query)).contains("select t0.id from contact t0 where not exists (select 1 from contact_note x where x.contact_id = t0.id");
}
@Test
@@ -65,7 +65,7 @@ public class IsEmptyExpressionQueryTest extends BaseTestCase {
.query();
query.findList();
assertThat(sqlOf(query)).contains("select t0.id from contact t0 where exists (select 1 from contact_note where contact_id = t0.id");
assertThat(sqlOf(query)).contains("select t0.id from contact t0 where exists (select 1 from contact_note x where x.contact_id = t0.id");
}
@@ -95,10 +95,10 @@ public class IsEmptyExpressionQueryTest extends BaseTestCase {
query.findList();
if (isPostgres()) {
assertThat(sqlOf(query)).contains("select distinct on (t0.id) t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where not exists (select 1 from contact_note where contact_id = u1.id)");
assertThat(sqlOf(query)).contains("select distinct on (t0.id) t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where not exists (select 1 from contact_note x where x.contact_id = u1.id)");
} else {
assertThat(sqlOf(query)).contains("select distinct t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where not exists (select 1 from contact_note where contact_id = u1.id)");
assertThat(sqlOf(query)).contains("select distinct t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where not exists (select 1 from contact_note x where x.contact_id = u1.id)");
}
}
@@ -114,10 +114,10 @@ public class IsEmptyExpressionQueryTest extends BaseTestCase {
query.findList();
if (isPostgres()) {
assertThat(sqlOf(query)).contains("select distinct on (t0.id) t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where exists (select 1 from contact_note where contact_id = u1.id)");
assertThat(sqlOf(query)).contains("select distinct on (t0.id) t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where exists (select 1 from contact_note x where x.contact_id = u1.id)");
} else {
assertThat(sqlOf(query)).contains("select distinct t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where exists (select 1 from contact_note where contact_id = u1.id)");
assertThat(sqlOf(query)).contains("select distinct t0.id from o_customer t0 join contact u1 on u1.customer_id = t0.id where exists (select 1 from contact_note x where x.contact_id = u1.id)");
}
}
@@ -1,13 +1,11 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class JsonPathExpressionTest extends BaseExpressionTest {
@NotNull
private JsonPathExpression exp(String propertyName, String path, Op operator, Object value) {
return new JsonPathExpression(propertyName, path, operator, value);
}
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.expression;
import io.ebean.LikeType;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
@@ -9,7 +8,6 @@ import static org.assertj.core.api.StrictAssertions.assertThat;
public class LikeExpressionTest extends BaseExpressionTest {
@NotNull
private LikeExpression exp(String propertyName, String value, boolean caseInsensitive, LikeType type) {
return new LikeExpression(propertyName, value, caseInsensitive, type);
}
@@ -1,13 +1,11 @@
package io.ebeaninternal.server.expression;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class RawExpressionTest extends BaseExpressionTest {
@NotNull
private RawExpression exp(String sql, Object... values) {
return new RawExpression(sql, values);
}
@@ -78,9 +78,14 @@ public class TDSpiExpressionRequest implements SpiExpressionRequest {
public void appendLike() {
}
@Override
public String escapeLikeString(String value) {
return value;
}
@Override
public void appendInExpression(boolean not, Object[] bindValues) {
}
}
@@ -4,9 +4,9 @@ import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.Query;
import io.ebeaninternal.api.SpiQuery;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
@@ -167,7 +167,7 @@ public class EqlParserTest extends BaseTestCase {
Query<Customer> query = parse("where name in ('Rob','Jim')");
query.findList();
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
platformAssertIn(query.getGeneratedSql(),"where t0.name");
}
@Test
@@ -178,7 +178,7 @@ public class EqlParserTest extends BaseTestCase {
query.setParameter("two", "Bar");
query.findList();
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
platformAssertIn(query.getGeneratedSql(),"where t0.name");
}
@Test
@@ -189,7 +189,7 @@ public class EqlParserTest extends BaseTestCase {
query.setParameter("two", "Bar");
query.findList();
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
platformAssertIn(query.getGeneratedSql(),"where t0.name");
}
@Test
@@ -200,7 +200,7 @@ public class EqlParserTest extends BaseTestCase {
query.setParameter("two", "Bar");
query.findList();
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ? )");
platformAssertIn(query.getGeneratedSql(),"where t0.name");
}
@Test
@@ -210,7 +210,7 @@ public class EqlParserTest extends BaseTestCase {
query.setParameter("names", Arrays.asList("Baz", "Maz", "Jim"));
query.findList();
assertThat(query.getGeneratedSql()).contains("where t0.name in (?, ?, ? )");
platformAssertIn(query.getGeneratedSql(),"where t0.name");
}
@Test
@@ -1,7 +1,5 @@
package io.ebeaninternal.server.query;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -53,7 +51,6 @@ public class RawSqlQueryPlanKeyTest {
assertThat(key("select foo", true, true, "").hashCode()).isNotEqualTo(key("select foo", true, true, "a").hashCode());
}
@NotNull
private RawSqlQueryPlanKey key(String sql, boolean rawSql, boolean rowNumberIncluded, String logWhereSql) {
return new RawSqlQueryPlanKey(sql, rawSql, rowNumberIncluded, logWhereSql);
}
@@ -3,9 +3,11 @@ package io.ebeaninternal.server.querydefn;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.OrmQueryRequest;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.Order;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -91,7 +93,11 @@ public class DefaultOrmQueryTest extends BaseTestCase {
}
private void prepare(DefaultOrmQuery<?> q1, DefaultOrmQuery<?> q2) {
q1.prepare(null);
q2.prepare(null);
OrmQueryRequest<?> r1 = createQueryRequest(SpiQuery.Type.LIST, q1, null);
q1.prepare(r1);
OrmQueryRequest<?> r2 = createQueryRequest(SpiQuery.Type.LIST, q2, null);
q2.prepare(r2);
}
}
@@ -11,7 +11,6 @@ import io.ebeaninternal.server.deploy.meta.DeployTableJoin;
import io.ebeaninternal.server.deploy.meta.DeployTableJoinColumn;
import io.ebeaninternal.server.expression.BaseExpressionTest;
import org.tests.model.basic.Customer;
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -73,7 +72,6 @@ public class OrmQueryPlanKeyTest extends BaseExpressionTest {
assertSame(key1, key2);
}
@NotNull
private TableJoin tableJoin(String table, String col1, String col2) {
DeployTableJoin deploy = new DeployTableJoin();
deploy.setTable(table);
@@ -160,4 +160,26 @@ public class TestRawSqlColumnParsing extends TestCase {
}
public void test_doubleColon() {
ColumnMapping columnMapping = DRawSqlColumnsParser.parse("a,MD5(id::text) as b,c");
Map<String, Column> mapping = columnMapping.mapping();
Column c = mapping.get("a");
assertEquals("a", c.getDbColumn());
assertEquals(0, c.getIndexPos());
assertEquals("a", c.getPropertyName());
c = mapping.get("b");
assertEquals("MD5(id::text)", c.getDbColumn());
assertEquals(1, c.getIndexPos());
assertEquals("b", c.getPropertyName());
c = mapping.get("c");
assertEquals("c", c.getDbColumn());
assertEquals(2, c.getIndexPos());
assertEquals("c", c.getPropertyName());
}
}
@@ -1,12 +1,24 @@
package io.ebeaninternal.server.rawsql;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.RawSql;
import io.ebeaninternal.server.rawsql.SpiRawSql.Sql;
import io.ebean.RawSqlBuilder;
import junit.framework.TestCase;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import io.ebeaninternal.server.rawsql.SpiRawSql.Sql;
import org.junit.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
public class TestRawSqlParsing extends TestCase {
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertTrue;
public class TestRawSqlParsing extends BaseTestCase {
@Test
public void test() {
String sql
@@ -24,6 +36,25 @@ public class TestRawSqlParsing extends TestCase {
String s = rs.toString();
assertTrue(s, s.contains("[order_id, sum"));
}
@Test
@ForPlatform(Platform.POSTGRES)
public void testDoubleColon() {
ResetBasicData.reset();
String sql = "select id, name from o_customer where name=:name and MD5(id::text) BETWEEN '00000000000000000000000000000000' AND 'ffffffffffffffffffffffffffffffff'";
RawSql rawSql = RawSqlBuilder
.parse(sql)
.create();
List<Customer> list = Ebean.createQuery(Customer.class)
.setRawSql(rawSql)
.setParameter("name", "Rob")
.findList();
assertThat(list).isNotEmpty();
}
}
@@ -32,4 +32,19 @@ public class BindParamsParserTest {
assertEquals("delete from foo where id in (?,?,?,?)", sql1);
}
@Test
public void findNameStart() {
assertEquals(5, BindParamsParser.findNameStart("some :name = ?", 0));
}
@Test
public void findNameStart_doubleColon() {
assertEquals(-1, BindParamsParser.findNameStart("some ::name = ?", 0));
}
@Test
public void findNameStart_doubleColonSkip() {
assertEquals(10, BindParamsParser.findNameStart("some ::na :a = ?", 0));
}
}
@@ -14,7 +14,6 @@ import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import org.ebeantest.LoggedSqlCollector;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert;
import org.junit.Test;
@@ -96,7 +95,6 @@ public class TunedQueryInfoTest extends BaseTestCase {
Assert.assertTrue(trimSql(loggedSql.get(1), 1).contains("select t0.id, t0.status,"));
}
@NotNull
private TunedQueryInfo createTunedQueryInfo(OrmQueryDetail tunedDetail) {
Origin origin = new Origin();
origin.setDetail(tunedDetail.asString());
@@ -79,7 +79,7 @@ public class TestBatchLazyWithCacheHits extends BaseTestCase {
// batch lazy loading into cache
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("from uuone t0 where t0.name like ");
assertThat(sql.get(1)).contains("from uuone t0 where t0.id in (?,");
platformAssertIn(sql.get(1), "from uuone t0 where t0.id");
statistics = beanCache.getStatistics(true);
assertThat(statistics.getSize()).isGreaterThan(3);

Some files were not shown because too many files have changed in this diff Show More