Compare commits

...
Author SHA1 Message Date
rbygrave ee615836ba [maven-release-plugin] prepare release ebean-parent-12.11.0 2021-08-06 14:56:09 +12:00
rbygrave fea7de42d5 #2290 - Add MetaTimedMetric locationHash() which is a crc32 checksum of package + type + method (excludes line number) 2021-08-06 14:48:42 +12:00
Rob BygraveandGitHub 4787303160 Merge pull request #2289 from ebean-orm/feature/meta-api
Refactor io.ebean.meta API, method rename with deprecation
2021-08-06 13:17:08 +12:00
rbygrave 801d419f51 Refactor io.ebean.meta API, method rename with deprecation 2021-08-06 13:14:29 +12:00
Rob BygraveandGitHub 3579557200 Merge pull request #2288 from ebean-orm/feature/sql-hash
Change query metric hash from MD5 of sql + name + loc to Checksum of sql (+ rename to sqlHash)
2021-08-06 12:52:42 +12:00
rbygrave 202d785710 Rename hash to sqlHash to better reflect what it is (CRC32 checksum of the sql) 2021-08-06 12:06:43 +12:00
rbygrave 7ca72a39b1 Change query metric hash from MD5 of sql + name + loc to Checksum of sql 2021-08-06 11:52:38 +12:00
rbygrave f34e0f5f2b #2287 - ebean-migration: For multiple platforms automatically run the correct migrations 2021-08-05 23:03:02 +12:00
rbygrave d98a31fa6b Bump to 12.11.0-SNAPSHOT 2021-08-05 16:53:51 +12:00
rbygrave 9a82dbb3e6 Bump parent to java8-oss 3.2 with GPG maven plugin active only with "release" profile
The GPG maven plugin is now only active with the "release" profile
use -P release when we want to release with GPG signing
2021-08-05 13:17:32 +12:00
rbygrave a76442641b Tidy test only - rename ServerConfigTest to DatabaseConfigTest and tidy 2021-08-05 13:14:00 +12:00
rbygrave 2043fdf7e1 #2274 Add DatabaseConfig jsonMutationDetection property setting the global default mode 2021-08-05 13:12:18 +12:00
rbygrave a06f7bea41 Tidy javadoc for MutableValueInfo 2021-08-05 12:55:42 +12:00
rbygrave db34afe867 #2284 - ENH: ddl-generator - add optional generation of idx_<platform>.migrations file 2021-08-03 00:03:02 +12:00
rbygrave 589ee7a5ea #2284 - ENH: ddl-generator - add optional generation of idx_<platform>.migrations file 2021-08-02 23:59:49 +12:00
rbygrave 4ba26ee7b7 #2283 - ddl-generator - Refactor tidy DefaultDbMigration etc 2021-08-02 20:49:30 +12:00
rbygrave 71223895ac #2283 - ddl-generator - Refactor tidy whitespace etc 2021-08-02 20:38:05 +12:00
rbygrave 928b90122f #2283 - ddl-generator - Add DbMigration.addPlatform(Platform) with default sub-directory based on Platform 2021-08-02 20:15:51 +12:00
rbygrave d601a88d82 #2278 - Move null to empty json collections check to BeanPropertyJsonBasic 2021-07-30 23:53:28 +12:00
rbygrave 443e826f61 #2278 - Embedded bean override with DbJson property 2021-07-30 23:22:06 +12:00
rbygrave 2691221168 #2278 - Do not override empty json collections, when database value is null 2021-07-30 22:53:51 +12:00
rbygrave e62bb3dc6b Merge branch 'pr/bugfix/null_to_empty_json_collection' of https://github.com/FOCONIS/ebean into FOCONIS-pr/bugfix/null_to_empty_json_collection 2021-07-30 22:51:40 +12:00
Rob BygraveandGitHub 604099429e Merge pull request #2282 from ebean-orm/feature/json-mutable-collections
Treat json Jackson collections the same wrt mutation detection
2021-07-30 21:15:04 +12:00
Rob BygraveandGitHub 11dc7f64ba Merge pull request #2274 from ebean-orm/feature/json-dirty-detection-md5
Add support for JSON bean mutation detection via HASH and SOURCE of JSON string content
2021-07-30 21:11:42 +12:00
rbygrave ceea705a76 Treat json Jackson collections the same wrt mutation detection 2021-07-30 20:20:03 +12:00
rbygrave d9f7531e81 Tidy tests only - TestDbJson_Jackson3 TestJacksonPlainBean 2021-07-30 17:26:46 +12:00
rbygrave 7bd7640c63 Update test TestDbJson_Jackson3 showing HASH mode is used even on ModifyAwareType 2021-07-30 17:18:28 +12:00
rbygrave ca1636ff9e Fix when json/jackson bean inserted as null and not changed
Expectation is that it is not included in update (still null, no change)
2021-07-30 17:16:05 +12:00
rbygrave ea741d0169 Update TestDbJson_Jackson3 with comments 2021-07-30 15:41:52 +12:00
rbygrave c43e2e6956 Update TestDbJson_Jackson3 2021-07-30 15:34:54 +12:00
rbygrave 03004452ac ebean.mf set back to synthetic: true 2021-07-30 14:53:32 +12:00
rbygrave a470877017 Update kotlin-querybean-generator pom to make kotlin in IntelliJ happy 2021-07-30 14:52:49 +12:00
rbygrave 777d322222 #2279 - Remove _ebean_getMarker() from EntityBean interface - not actually required 2021-07-30 14:51:38 +12:00
Jonas Pöhler 1f0ac1f1c1 ADD: Testcases for List and Map as well 2021-07-29 16:50:08 +02:00
Jonas Pöhler (JPo) 1fad6e910c FIX: do not override empty (json) collections with null
Original: https://github.com/ebean-orm/ebean/commit/5551c35ec2496a9d62ab0afcaecbed739f17aa7c
Signed-off-by: Jonas Pöhler (JPo) <jonas.poehler@foconis.de>
2021-07-29 15:53:44 +02:00
rbygrave 822fb7325a Due to mutableNext handling, with checkMutableProperties() even when known dirty go into beanProperty.checkMutable()
As per rPraml's PR and comment

Due to handling of mutableNext we need  checkMutableProperties() to call into what is now beanProperty.checkMutable() even when we already know it's dirty.
2021-07-29 23:37:29 +12:00
rbygrave 20e0eb1021 Re-enable TestDbJson_Jackson3 via adding mutationDetection = HASH to beanList property
@DbJsonB(mutationDetection = HASH)
  List<PlainBean> beanList;
2021-07-29 23:17:11 +12:00
rbygrave 20becf951d Use MutationDetection replacing dirtyDetection and keepSource
Also adds NoMutationDetection to support NONE
2021-07-29 23:11:42 +12:00
rbygrave 5aa8557168 Update tests only - TestJacksonPlainBean with dirtyDetection = false property 2021-07-29 16:29:10 +12:00
rbygrave c66d248e96 Add MutableValueNext to replace mutableContent such that hash compute is only done once
Adds MutableValueInfo.nextDirty() to replace the isEqualToJson() method. The next is computed once and stored. BindablePropertyJsonUpdate makes the .mutableNext(propertyIndex) call to move the next MutableValueInfo and return the json content.
2021-07-29 15:42:16 +12:00
rbygrave 0d8e7c852b Change Checksum to use CRC32 2021-07-29 15:32:06 +12:00
rbygrave 991c6d6b5c No effective change - tidy FactoryProperty 2021-07-29 13:30:15 +12:00
rbygrave 2bc5d3d325 Refactor rename MutableHash to MutableValueInfo (and subsequent rename on methods etc) 2021-07-29 13:20:30 +12:00
rbygrave 5ad8e7ead4 Use new @DbJson dirtyDetection and keepSource attributes
- Bump ebean-annotation with new @DbJson dirtyDetection and keepSource attributes
- Get those to BeanPropertyJsonMapper to chose MutableHash implementation
- Modify MD5MutableHash to include check for isDirty()
- EBasicJsonList needs keepSource=true to pass that test with oldValue
2021-07-29 12:47:10 +12:00
rbygrave 8832bbc57f No effective change - format EntityBeanIntercept only
Plus adjust timing on SqlQueryCancelTest
2021-07-29 12:43:37 +12:00
rbygrave 86483ff947 Use scalarType.format(value) removing scalarType.jsonMapper(value)
Remove scalarType.jsonMapper(value) as we can just use format(value) instead
2021-07-29 11:52:15 +12:00
rbygrave 92d7a7562f Tidy only - no functional change to MutableHash and BeanPropertyJsonMapper 2021-07-29 11:49:01 +12:00
rbygrave 52fe3cf3c8 Change from MD5 to Checksum (Adler32) for json content dirty detection 2021-07-29 11:40:05 +12:00
rbygrave 94fb6414fa Refactor move createMutableHash() method from ScalarType to BeanProperty / BeanPropertyJsonMapper 2021-07-29 11:28:25 +12:00
rbygrave d0270dbc6a Temporarily disable TestDbJson_Jackson3 updateIncludesJsonColumn_when_list_loadedAndNotDirtyAware()
BeanState state = DB.getBeanState(found);
    assertThat(state.getChangedProps()).containsExactlyInAnyOrder("beanList");
    // this test fails, because we have a OmList instead of a GenericObject
    // TODO: Can/Should we enhance the @DbJson/@DbJsonB annotations with a property "dirtyDetection"
2021-07-29 11:21:38 +12:00
rbygrave 11a047bf8e Merge branch 'FOCONIS-feature/json-dirty-detection-md5' into feature/json-dirty-detection-md5 2021-07-29 10:27:28 +12:00
Roland Praml eb70c7e196 some javadoc and refactored getOrigValue 2021-07-28 17:36:53 +02:00
Roland Praml b497b8635b some refactor and tidying 2021-07-28 17:22:29 +02:00
Roland Praml 5e595ec5f8 Proof of concept to support also bean change detection / changelog on mutable properties 2021-07-28 14:54:56 +02:00
rbygrave 49b86d07d3 Modify SqlBeanLoad to use readSet()
Note that we don't need to use readSetIntercept() now as there is no property change listener support
2021-07-28 22:48:29 +12:00
rbygrave 11eed4cfe9 Add BeanPropertyJsonMapper for JSON dirty detection 2021-07-28 21:43:57 +12:00
rbygrave 4aef0ad459 #2275 - Change Query<?> to Query<T> on ExtendedServer exists() method 2021-07-28 17:23:37 +12:00
Rob BygraveandGitHub 21cd74d0ca Merge pull request #2273 from ebean-orm/feature/2272
#2272 Fix for collection with orphanRemoval (such that equals/hashCode isn't use)
2021-07-28 17:14:09 +12:00
rbygrave 6c262f1df5 JSON bean dirty detection via MD5 of JSON string content
- MD5 of json content stored on EntityBeanIntercept for dirty detection
- Only convert to JSON once (at dirty detection time). Store this json content on EntityBeanIntercept to later push to ScalarTypeJsonObjectMapper for bind

Should consider alternative to extend BeanProperty rather than have these if blocks.
2021-07-23 16:50:24 +12:00
rbygrave f32ace4e5a Refactor EntityBeanIntercept replacing owner._ebean_getPropertyNames().length with flags.length 2021-07-23 13:54:29 +12:00
rbygrave e54b7052e0 #2272 Fix for collection with orphanRemoval (such that equals/hashCode isn't use)
- Changes ModifyHolder to use IdentityHashMap such that equals/hashcode isn't invoked
- Change SaveManyBeans such that M2M intersection deletes execute before inserts

The SaveManyBeans change is required to pass TestM2MModifyTest. With that test we get a delete/insert pair that we somewhat expect because we clear() the collection. Previously equals/hashCode meant we didn't get that delete/insert pair.

The test changes from containsExactly() to containsOnly() as we now can't guarantee the ordering with IdentityHashMap.
2021-07-23 12:18:06 +12:00
rbygrave b40d448955 #2272 No effective change, tidy test TestM2MModifyTest only
Noting that this test fails with the fix for 2272 as M2M intersection deletes are currently executed after inserts. We need a fix to SaveManyBeans to change that so that deletes to intersection table execute after inserts.
2021-07-23 12:01:36 +12:00
rbygrave 33bccd3bcb [maven-release-plugin] prepare for next development iteration 2021-07-22 19:03:54 +12:00
178 changed files with 2793 additions and 1541 deletions
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.10.0</version>
<version>12.11.0</version>
</parent>
<name>ebean api</name>
@@ -50,7 +50,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>7.0</version>
<version>7.2</version>
</dependency>
<dependency>
@@ -67,7 +67,7 @@ public interface ExtendedServer {
*
* @return True if the query finds a matching row in the database
*/
<T> boolean exists(Query<?> ormQuery, Transaction transaction);
<T> boolean exists(Query<T> ormQuery, Transaction transaction);
/**
* Return the number of 'top level' or 'root' entities this query should return.
@@ -45,6 +45,14 @@ public interface ProfileLocation {
*/
String label();
/**
* Return a hash of the location that intentionally excludes the line number.
* <p>
* The hash is expected to be stable regardless of line number in the source file
* so that is identifies the class and method location over a long time.
*/
long hash();
/**
* Return the full location.
*/
@@ -27,17 +27,6 @@ public interface EntityBean extends Serializable {
throw new NotEnhancedException();
}
/**
* Return the enhancement marker value.
* <p>
* This is the class name of the enhanced class and used to check that all
* entity classes are enhanced (specifically not just a super class).
* </p>
*/
default String _ebean_getMarker() {
throw new NotEnhancedException();
}
/**
* Create and return a new entity bean instance.
*/
@@ -51,6 +51,11 @@ public final class EntityBeanIntercept implements Serializable {
*/
private static final byte FLAG_ORIG_VALUE_SET = 8;
/**
* Flags indicating if the mutable hash is set.
*/
private static final byte FLAG_MUTABLE_HASH_SET = 16;
private transient final ReentrantLock lock = new ReentrantLock();
private transient NodeUsageCollector nodeUsageCollector;
private transient PersistenceContext persistenceContext;
@@ -91,6 +96,17 @@ public final class EntityBeanIntercept implements Serializable {
private Object ownerId;
private int sortOrder;
/**
* Holds information of json loaded jackson beans (e.g. the original json or checksum).
*/
private MutableValueInfo[] mutableInfo;
/**
* Holds json content determined at point of dirty check.
* Stored here on dirty check such that we only convert to json once.
*/
private MutableValueNext[] mutableNext;
/**
* Create a intercept with a given entity.
*/
@@ -228,6 +244,17 @@ public final class EntityBeanIntercept implements Serializable {
* if any embedded beans are either new or dirty (and hence need saving).
*/
public boolean isDirty() {
if (dirty) {
return true;
}
if (mutableInfo != null) {
for (int i = 0; i < mutableInfo.length; i++) {
if (mutableInfo[i] != null && !mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
dirty = true;
break;
}
}
}
return dirty;
}
@@ -368,8 +395,9 @@ public final class EntityBeanIntercept implements Serializable {
this.owner._ebean_setEmbeddedLoaded();
this.lazyLoadProperty = -1;
this.origValues = null;
this.mutableNext = null;
for (int i = 0; i < flags.length; i++) {
flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
flags[i] &= ~(FLAG_CHANGED_PROP | FLAG_ORIG_VALUE_SET);
}
this.dirty = false;
}
@@ -442,6 +470,10 @@ public final class EntityBeanIntercept implements Serializable {
* Return the original value that was changed via an update.
*/
public Object getOrigValue(int propertyIndex) {
if ((flags[propertyIndex] & (FLAG_ORIG_VALUE_SET | FLAG_MUTABLE_HASH_SET)) == FLAG_MUTABLE_HASH_SET) {
// mutable hash set, but not ORIG_VALUE
setOriginalValue(propertyIndex, mutableInfo[propertyIndex].get());
}
if (origValues == null) {
return null;
}
@@ -476,7 +508,7 @@ public final class EntityBeanIntercept implements Serializable {
* Return the number of properties.
*/
public int getPropertyLength() {
return owner._ebean_getPropertyNames().length;
return flags.length;
}
/**
@@ -564,7 +596,7 @@ public final class EntityBeanIntercept implements Serializable {
private void setOriginalValue(int propertyIndex, Object value) {
if (origValues == null) {
origValues = new Object[owner._ebean_getPropertyNames().length];
origValues = new Object[flags.length];
}
if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
@@ -577,7 +609,7 @@ public final class EntityBeanIntercept implements Serializable {
*/
private void setOriginalValueForce(int propertyIndex, Object value) {
if (origValues == null) {
origValues = new Object[owner._ebean_getPropertyNames().length];
origValues = new Object[flags.length];
}
origValues[propertyIndex] = value;
}
@@ -638,7 +670,7 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyNames(Set<String> props, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
if (isChangedProp(i)) {
// the property has been changed on this bean
props.add((prefix == null ? getProperty(i) : prefix + getProperty(i)));
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
@@ -656,7 +688,7 @@ public final class EntityBeanIntercept implements Serializable {
String[] names = owner._ebean_getPropertyNames();
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
if (isChangedProp(i)) {
if (propertyNames.contains(names[i])) {
return true;
}
@@ -684,7 +716,7 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyValues(Map<String, ValuePair> dirtyValues, String prefix) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
if (isChangedProp(i)) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
Object newVal = owner._ebean_getField(i);
@@ -706,7 +738,7 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
if (isChangedProp(i)) {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
@@ -741,7 +773,7 @@ public final class EntityBeanIntercept implements Serializable {
}
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) { // we do not check against mutablecontent here.
sb.append(i).append(',');
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
@@ -1112,7 +1144,7 @@ public final class EntityBeanIntercept implements Serializable {
*/
public void setLoadError(int propertyIndex, Exception t) {
if (loadErrors == null) {
loadErrors = new Exception[owner._ebean_getPropertyNames().length];
loadErrors = new Exception[flags.length];
}
loadErrors[propertyIndex] = t;
flags[propertyIndex] |= FLAG_LOADED_PROP;
@@ -1138,4 +1170,62 @@ public final class EntityBeanIntercept implements Serializable {
}
return ret;
}
private boolean isChangedProp(int i) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
return true;
} else if (mutableInfo == null || mutableInfo[i] == null || mutableInfo[i].isEqualToObject(owner._ebean_getField(i))) {
return false;
} else {
// mark for change
flags[i] |= FLAG_CHANGED_PROP;
dirty = true; // this makes the bean automatically dirty!
return true;
}
}
/**
* Return the MutableValueInfo for the given property or null.
*/
public MutableValueInfo mutableInfo(int propertyIndex) {
return mutableInfo == null ? null : mutableInfo[propertyIndex];
}
/**
* Set the MutableValueInfo for the given property.
*/
public void mutableInfo(int propertyIndex, MutableValueInfo info) {
if (mutableInfo == null) {
mutableInfo = new MutableValueInfo[flags.length];
}
flags[propertyIndex] |= FLAG_MUTABLE_HASH_SET;
mutableInfo[propertyIndex] = info;
}
/**
* Dirty detection set the next mutable property content and info .
* <p>
* Set here as the mutable property dirty detection is based on json content comparison.
* We only want to perform the json serialisation once so storing it here as part of
* dirty detection so that we can get it back to bind in insert or update etc.
*/
public void mutableNext(int propertyIndex, MutableValueNext next) {
if (mutableNext == null) {
mutableNext = new MutableValueNext[flags.length];
}
mutableNext[propertyIndex] = next;
}
/**
* Update the 'next' mutable info returning the content that was obtained via dirty detection.
*/
public String mutableNext(int propertyIndex) {
if (mutableNext == null) {
return null;
}
final MutableValueNext next = mutableNext[propertyIndex];
mutableInfo(propertyIndex, next.info());
return next.content();
}
}
@@ -0,0 +1,42 @@
package io.ebean.bean;
/**
* Holds information on mutable values (like plain beans stored as json).
* <p>
* Used internally in EntityBeanIntercept for dirty detection on mutable values.
* Typically, mutation detection is based on a hash/checksum of json content or the
* original json content itself.
* <p>
* Refer to the mapping options {@code @DbJson(mutationDetection)}.
*/
public interface MutableValueInfo {
/**
* Compares the given json returning null if deemed unchanged or returning
* the MutableValueNext to use if deemed dirty/changed.
* <p>
* Returning MutableValueNext allows an implementation based on hash/checksum
* to only perform that computation once.
*
* @return Null if deemed unchanged or the MutableValueNext if deemed changed.
*/
MutableValueNext nextDirty(String json);
/**
* Compares the given object to an internal value.
* <p>
* This is used to support changelog/beanState. The implementation can serialize the
* object into json form and compare it against the original json.
*/
boolean isEqualToObject(Object obj);
/**
* Creates a new instance from the internal json string.
* <p>
* This is used to provide an original/old value for change logging / persist listeners.
* This is only available for properties that have {@code @DbJson(keepSource=true)}.
*/
default Object get() {
return null;
}
}
@@ -0,0 +1,17 @@
package io.ebean.bean;
/**
* Represents a next value to use for mutable content properties (DbJson with jackson beans).
*/
public interface MutableValueNext {
/**
* Return the next content to use. Provided such that we serialise to json once.
*/
String content();
/**
* Return the next MutableValueInfo to use after an update.
*/
MutableValueInfo info();
}
@@ -3,9 +3,7 @@ package io.ebean.common;
import io.ebean.bean.EntityBean;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.*;
/**
* Holds sets of additions and deletions from a 'owner' List Set or Map.
@@ -23,19 +21,19 @@ class ModifyHolder<E> implements Serializable {
/**
* Deletions list for manyToMany persistence.
*/
private Set<E> modifyDeletions = new LinkedHashSet<>();
private Map<E,Object> modifyDeletions = new IdentityHashMap<>();
/**
* Additions list for manyToMany persistence.
*/
private Set<E> modifyAdditions = new LinkedHashSet<>();
private Map<E,Object> modifyAdditions = new IdentityHashMap<>();
private boolean touched;
void reset() {
touched = false;
modifyDeletions = new LinkedHashSet<>();
modifyAdditions = new LinkedHashSet<>();
modifyDeletions = new IdentityHashMap<>();
modifyAdditions = new IdentityHashMap<>();
}
/**
@@ -50,51 +48,46 @@ class ModifyHolder<E> implements Serializable {
}
private boolean undoDeletion(E bean) {
return (bean != null) && modifyDeletions.remove(bean);
return (bean != null) && modifyDeletions.remove(bean) != null;
}
void modifyAddition(E bean) {
if (bean != null) {
touched = true;
if (bean instanceof EntityBean) {
((EntityBean) bean)._ebean_getIntercept().setDeletedFromCollection(false);
}
// If it is to delete then just remove the deletion
if (!undoDeletion(bean)) {
// Insert
modifyAdditions.add(bean);
modifyAdditions.put(bean, bean);
}
}
}
private boolean undoAddition(Object bean) {
return (bean != null) && modifyAdditions.remove(bean);
return (bean != null) && modifyAdditions.remove(bean) != null;
}
@SuppressWarnings("unchecked")
void modifyRemoval(Object bean) {
if (bean != null) {
touched = true;
if (bean instanceof EntityBean) {
((EntityBean) bean)._ebean_getIntercept().setDeletedFromCollection(true);
}
// If it is to be added then just remove the addition
if (!undoAddition(bean)) {
modifyDeletions.add((E) bean);
modifyDeletions.put((E) bean, bean);
}
}
}
Set<E> getModifyAdditions() {
return modifyAdditions;
return modifyAdditions.keySet();
}
Set<E> getModifyRemovals() {
return modifyDeletions;
return modifyDeletions.keySet();
}
/**
@@ -7,9 +7,7 @@ import io.ebean.EbeanVersion;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.annotation.Encrypted;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.annotation.*;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbEncrypt;
@@ -194,10 +192,9 @@ public class DatabaseConfig {
private JsonConfig.Include jsonInclude = JsonConfig.Include.ALL;
/**
* When true then by default DbJson beans are assumed to be dirty.
* I believe we want to change this default to false in the future.
* The default mode used for {@code @DbJson} with Jackson ObjectMapper.
*/
private boolean jsonDirtyByDefault = true;
private MutationDetection jsonMutationDetection = MutationDetection.HASH;
/**
* The database platform name. Used to imply a DatabasePlatform to use.
@@ -744,23 +741,21 @@ public class DatabaseConfig {
}
/**
* Return true if DbJson beans are assumed dirty by default.
* <p>
* That is, when true beans that do not implement ModifyAwareType are by
* default assumed to be dirty and included in updates.
* Return the default MutableDetection to use with {@code @DbJson} using Jackson.
*
* @see DbJson#mutationDetection()
*/
public boolean isJsonDirtyByDefault() {
return jsonDirtyByDefault;
public MutationDetection getJsonMutationDetection() {
return jsonMutationDetection;
}
/**
* Set to false if we want DbJson beans to not be assumed to be dirty.
* <p>
* That is, when true beans that do not implement ModifyAwareType are by
* default assumed to be dirty and included in updates.
* Set the default MutableDetection to use with {@code @DbJson} using Jackson.
*
* @see DbJson#mutationDetection()
*/
public void setJsonDirtyByDefault(boolean jsonDirtyByDefault) {
this.jsonDirtyByDefault = jsonDirtyByDefault;
public void setJsonMutationDetection(MutationDetection jsonMutationDetection) {
this.jsonMutationDetection = jsonMutationDetection;
}
/**
@@ -2935,7 +2930,7 @@ public class DatabaseConfig {
jsonInclude = p.getEnum(JsonConfig.Include.class, "jsonInclude", jsonInclude);
jsonDateTime = p.getEnum(JsonConfig.DateTime.class, "jsonDateTime", jsonDateTime);
jsonDate = p.getEnum(JsonConfig.Date.class, "jsonDate", jsonDate);
jsonDirtyByDefault = p.getBoolean("jsonDirtyByDefault", jsonDirtyByDefault);
jsonMutationDetection = p.getEnum(MutationDetection.class, "jsonMutationDetection", jsonMutationDetection);
runMigration = p.getBoolean("migration.run", runMigration);
ddlGenerate = p.getBoolean("ddl.generate", ddlGenerate);
@@ -18,22 +18,22 @@ public abstract class AbstractMetricVisitor implements MetricVisitor {
}
@Override
public boolean isReset() {
public boolean reset() {
return reset;
}
@Override
public boolean isCollectTransactionMetrics() {
public boolean collectTransactionMetrics() {
return collectTransactionMetrics;
}
@Override
public boolean isCollectQueryMetrics() {
public boolean collectQueryMetrics() {
return collectQueryMetrics;
}
@Override
public boolean isCollectL2Metrics() {
public boolean collectL2Metrics() {
return collectL2Metrics;
}
@@ -27,17 +27,17 @@ public class BasicMetricVisitor extends AbstractMetricVisitor implements ServerM
}
@Override
public List<MetaTimedMetric> getTimedMetrics() {
public List<MetaTimedMetric> timedMetrics() {
return timed;
}
@Override
public List<MetaQueryMetric> getQueryMetrics() {
public List<MetaQueryMetric> queryMetrics() {
return query;
}
@Override
public List<MetaCountMetric> getCountMetrics() {
public List<MetaCountMetric> countMetrics() {
return count;
}
@@ -8,6 +8,13 @@ public interface MetaCountMetric extends MetaMetric {
/**
* Return the total count.
*/
long getCount();
long count();
/**
* Migrate to count()
*/
@Deprecated
default long getCount() {
return count();
}
}
@@ -8,6 +8,13 @@ public interface MetaMetric {
/**
* Return the metric name.
*/
String getName();
String name();
/**
* Migrate to name().
*/
@Deprecated
default String getName() {
return name();
}
}
@@ -8,21 +8,52 @@ public interface MetaQueryMetric extends MetaTimedMetric {
/**
* The type of entity or DTO bean.
*/
Class<?> getType();
Class<?> type();
/**
* Migrate to type().
*/
@Deprecated
default Class<?> getType() {
return type();
}
/**
* The label for the query (can be null).
*/
String getLabel();
String label();
/**
* Migrate to label().
*/
@Deprecated
default String getLabel() {
return label();
}
/**
* The actual SQL of the query.
*/
String getSql();
String sql();
/**
* Return the hash of the plan.
* Migrate to sql().
*/
String getHash();
@Deprecated
default String getSql() {
return sql();
}
/**
* Return the hash of the sql.
*/
long sqlHash();
/**
* Migrate to sqlHash().
*/
@Deprecated
default long getSqlHash() {
return sqlHash();
}
}
@@ -10,45 +10,45 @@ public interface MetaQueryPlan {
/**
* Return the bean type for the query.
*/
Class<?> getBeanType();
Class<?> beanType();
/**
* Return the label of the query.
*/
String getLabel();
String label();
/**
* Return the profile location for the query.
*/
ProfileLocation getProfileLocation();
ProfileLocation profileLocation();
/**
* Return the sql of the query.
*/
String getSql();
String sql();
/**
* Return the hash of the plan.
*/
String getHash();
long sqlHash();
/**
* Return a description of the bind values.
*/
String getBind();
String bind();
/**
* Return the raw plan.
*/
String getPlan();
String plan();
/**
* Return the query execution time associated with the bind values capture.
*/
long getQueryTimeMicros();
long queryTimeMicros();
/**
* Return the total count of times bind capture has occurred.
*/
long getCaptureCount();
long captureCount();
}
@@ -6,30 +6,79 @@ package io.ebean.meta;
*/
public interface MetaTimedMetric extends MetaMetric {
/**
* Return the metric location hash if defined.
* <p>
* This hash excludes line number with the intention of being stable over time
* as code changes move the source line (but the method is the same).
*/
long locationHash();
/**
* Return the metric location if defined.
*/
String getLocation();
String location();
/**
* Migrate to location()
*/
@Deprecated
default String getLocation() {
return location();
}
/**
* Return the total count.
*/
long getCount();
long count();
/**
* Migrate to count()
*/
@Deprecated
default long getCount() {
return count();
}
/**
* Return the total execution time in micros.
*/
long getTotal();
long total();
/**
* Migrate to total()
*/
@Deprecated
default long getTotal() {
return total();
}
/**
* Return the max execution time in micros.
*/
long getMax();
long max();
/**
* Migrate to max()
*/
@Deprecated
default long getMax() {
return max();
}
/**
* Return the mean execution time in micros.
*/
long getMean();
long mean();
/**
* Migrate to mean()
*/
@Deprecated
default long getMean() {
return mean();
}
/**
* Return true if this is the first metrics collection for this query.
@@ -6,7 +6,7 @@ package io.ebean.meta;
public class MetricData {
private String name;
private String hash;
private long sqlHash;
private String loc;
private String sql;
@@ -14,6 +14,7 @@ public class MetricData {
private Long mean;
private Long max;
private Long total;
private long locHash;
public MetricData(String name) {
this.name = name;
@@ -30,12 +31,20 @@ public class MetricData {
this.name = name;
}
public String getHash() {
return hash;
public long getSqlHash() {
return sqlHash;
}
public void setHash(String hash) {
this.hash = hash;
public void setSqlHash(long sqlHash) {
this.sqlHash = sqlHash;
}
public void setLocHash(long locHash) {
this.locHash = locHash;
}
public long getLocHash() {
return locHash;
}
public String getLoc() {
@@ -8,22 +8,22 @@ public interface MetricVisitor {
/**
* Return true if the metrics should be reset.
*/
boolean isReset();
boolean reset();
/**
* Return true if we should visit the transaction metrics.
*/
boolean isCollectTransactionMetrics();
boolean collectTransactionMetrics();
/**
* Return true if we should visit the ORM and SQL query metrics.
*/
boolean isCollectQueryMetrics();
boolean collectQueryMetrics();
/**
* Return true if we should visit the L2 cache metrics.
*/
boolean isCollectL2Metrics();
boolean collectL2Metrics();
/**
* Visit has started.
@@ -10,7 +10,7 @@ public class QueryPlanInit {
private boolean all;
private Set<String> hashes = new HashSet<>();
private Set<Long> hashes = new HashSet<>();
private long thresholdMicros;
@@ -32,7 +32,7 @@ public class QueryPlanInit {
* Return the query execution time threshold which must be exceeded to initiate
* query plan collection.
*/
public long getThresholdMicros() {
public long thresholdMicros() {
return thresholdMicros;
}
@@ -40,28 +40,28 @@ public class QueryPlanInit {
* Set the query execution time threshold which must be exceeded to initiate
* query plan collection.
*/
public void setThresholdMicros(long thresholdMicros) {
public void thresholdMicros(long thresholdMicros) {
this.thresholdMicros = thresholdMicros;
}
/**
* Return true if the query plan should be initiated based on it's hash.
*/
public boolean includeHash(String hash) {
return all || hashes.contains(hash);
public boolean includeHash(long sqlHash) {
return all || hashes.contains(sqlHash);
}
/**
* Return the specific hashes that we want to collect query plans on.
*/
public Set<String> getHashes() {
public Set<Long> sqlHashes() {
return hashes;
}
/**
* Set the specific hashes that we want to collect query plans on.
*/
public void setHashes(Set<String> hashes) {
public void sqlHashes(Set<Long> hashes) {
this.hashes = hashes;
}
}
@@ -18,7 +18,7 @@ public class QueryPlanRequest {
* have been around for a while (e.g. 5 mins) and so reasonably represent
* bind values that match the slowest execution for this query plan.
*/
public long getSince() {
public long since() {
return since;
}
@@ -28,14 +28,14 @@ public class QueryPlanRequest {
*
* @param since The minimum age of the bind values capture.
*/
public void setSince(long since) {
public void since(long since) {
this.since = since;
}
/**
* Return the maximum number of plans to capture.
*/
public int getMaxCount() {
public int maxCount() {
return maxCount;
}
@@ -45,7 +45,7 @@ public class QueryPlanRequest {
* Use this to limit how much query plan capturing is done as query
* plan capture is actual database load.
*/
public void setMaxCount(int maxCount) {
public void maxCount(int maxCount) {
this.maxCount = maxCount;
}
@@ -54,7 +54,7 @@ public class QueryPlanRequest {
* <p>
* Query plan collection will stop once this time is exceeded.
*/
public long getMaxTimeMillis() {
public long maxTimeMillis() {
return maxTimeMillis;
}
@@ -65,7 +65,7 @@ public class QueryPlanRequest {
* this to ensure the query plan capture does not use excessive amount
* of time - put too much load on the database.
*/
public void setMaxTimeMillis(long maxTimeMillis) {
public void maxTimeMillis(long maxTimeMillis) {
this.maxTimeMillis = maxTimeMillis;
}
}
@@ -10,16 +10,39 @@ public interface ServerMetrics {
/**
* Return timed metrics for Transactions, labelled SqlQuery, labelled SqlUpdate.
*/
List<MetaTimedMetric> getTimedMetrics();
List<MetaTimedMetric> timedMetrics();
/**
* Migrate to timedMetrics().
*/
@Deprecated
default List<MetaTimedMetric> getTimedMetrics() {
return timedMetrics();
}
/**
* Return the query metrics.
*/
List<MetaQueryMetric> getQueryMetrics();
List<MetaQueryMetric> queryMetrics();
/**
* Migrate to queryMetrics().
*/
@Deprecated
default List<MetaQueryMetric> getQueryMetrics() {
return queryMetrics();
}
/**
* Return the Counter metrics.
*/
List<MetaCountMetric> getCountMetrics();
List<MetaCountMetric> countMetrics();
/**
* Migrate to countMetrics().
*/
@Deprecated
default List<MetaCountMetric> getCountMetrics() {
return countMetrics();
}
}
@@ -8,12 +8,12 @@ import java.util.Comparator;
public interface ServerMetricsAsJson {
/**
* Set to false to exclude profile location and sql.
* Set to false in order to exclude profile location and sql.
*/
ServerMetricsAsJson withExtraAttributes(boolean withLocation);
/**
* Set to false to exclude SQL hash.
* Set to false in order to exclude SQL hash.
*/
ServerMetricsAsJson withHash(boolean withHash);
@@ -32,7 +32,7 @@ public class SortMetric {
@Override
public int compare(MetaCountMetric o1, MetaCountMetric o2) {
return stringCompare(o1.getName(), o2.getName());
return stringCompare(o1.name(), o2.name());
}
}
@@ -43,8 +43,8 @@ public class SortMetric {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
int i = stringCompare(o1.getName(), o2.getName());
return i != 0 ? i : Long.compare(o1.getCount(), o2.getCount());
int i = stringCompare(o1.name(), o2.name());
return i != 0 ? i : Long.compare(o1.count(), o2.count());
}
}
@@ -55,7 +55,7 @@ public class SortMetric {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getCount(), o1.getCount());
return Long.compare(o2.count(), o1.count());
}
}
@@ -66,7 +66,7 @@ public class SortMetric {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getTotal(), o1.getTotal());
return Long.compare(o2.total(), o1.total());
}
}
@@ -77,7 +77,7 @@ public class SortMetric {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getMean(), o1.getMean());
return Long.compare(o2.mean(), o1.mean());
}
}
@@ -88,7 +88,7 @@ public class SortMetric {
@Override
public int compare(MetaTimedMetric o1, MetaTimedMetric o2) {
return Long.compare(o2.getMax(), o1.getMax());
return Long.compare(o2.max(), o1.max());
}
}
}
@@ -12,6 +12,11 @@ public interface TimedMetricStats extends MetaTimedMetric {
*/
void setLocation(String location);
/**
* Additionally set the location hash.
*/
void setLocationHash(long locationHash);
/**
* Override the name based on profile location.
*/
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.10.0</version>
<version>12.11.0</version>
</parent>
<!-- <parent>-->
<!-- <groupId>org.avaje</groupId>-->
@@ -14,7 +14,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.10.0</tag>
<tag>ebean-parent-12.11.0</tag>
</scm>
<name>ebean autotune</name>
@@ -26,7 +26,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
<scope>provided</scope>
</dependency>
+19 -19
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.10.0</version>
<version>12.11.0</version>
</parent>
<name>ebean bom</name>
@@ -14,12 +14,12 @@
<properties>
<ebean-ddl-runner.version>1.0</ebean-ddl-runner.version>
<ebean-migration-auto.version>1.0</ebean-migration-auto.version>
<ebean-migration.version>12.4.0</ebean-migration.version>
<ebean-migration-auto.version>1.1</ebean-migration-auto.version>
<ebean-migration.version>12.11.0</ebean-migration.version>
<ebean-test-docker.version>4.1</ebean-test-docker.version>
<ebean-datasource.version>7.0</ebean-datasource.version>
<ebean-agent.version>12.10.0</ebean-agent.version>
<ebean-maven-plugin.version>12.10.0</ebean-maven-plugin.version>
<ebean-agent.version>12.11.0</ebean-agent.version>
<ebean-maven-plugin.version>12.11.0</ebean-maven-plugin.version>
</properties>
<dependencyManagement>
@@ -81,88 +81,88 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-xml</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-autotune</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.10.0</version>
<version>12.11.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
@@ -163,4 +163,15 @@ public interface DataBinder {
* Bind an array value.
*/
void setArray(String arrayType, Object[] elements) throws SQLException;
/**
* Push json from dirty detection to be available for binding.
*/
void pushJson(String json);
/**
* Pop json made during dirty detection for scalarType binding.
*/
String popJson();
}
@@ -48,4 +48,14 @@ public interface DataReader {
Object getObject() throws SQLException;
InputStream getBinaryStream() throws SQLException;
/**
* Push json from dirty detection to be available for binding.
*/
void pushJson(String json);
/**
* Pop json made during dirty detection for scalarType binding.
*/
String popJson();
}
@@ -2,6 +2,7 @@ package io.ebean.core.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.text.StringFormatter;
import io.ebean.text.StringParser;
@@ -34,6 +35,10 @@ import java.sql.SQLException;
*/
public interface ScalarType<T> extends StringParser, StringFormatter, ScalarDataReader<T> {
default boolean isJsonMapper() {
return false;
}
/**
* Return true if this is a binary type and can not support parse() and format() from/to string.
* This allows Ebean to optimise marshalling types to string.
+7 -7
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>12.10.0</version>
<version>12.11.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -15,7 +15,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-parent-12.10.0</tag>
<tag>ebean-parent-12.11.0</tag>
</scm>
<profiles>
@@ -52,13 +52,13 @@
<dependency>
<groupId>io.avaje</groupId>
<artifactId>classpath-scanner</artifactId>
<version>4.2</version>
<version>6.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration-auto</artifactId>
<version>1.0</version>
<version>1.1</version>
</dependency>
<!-- keep testing in core using ebean-ddl-generator & ebean-migration -->
@@ -87,19 +87,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-externalmapping-api</artifactId>
<version>12.10.0</version>
<version>12.11.0</version>
</dependency>
<dependency>
@@ -18,9 +18,9 @@ public interface SpiQueryPlan {
String getName();
/**
* The hash for the query plan.
* The hash of the sql.
*/
String getHash();
long getSqlHash();
/**
* The SQL for the query plan.
@@ -18,8 +18,8 @@ class DefaultQueryPlanListener implements QueryPlanListener {
String dbName = capture.getDatabase().getName();
for (MetaQueryPlan plan : capture.getPlans()) {
log.info("queryPlan db:{} label:{} queryTimeMicros:{} loc:{} sql:{} bind:{} plan:{}",
dbName, plan.getLabel(), plan.getQueryTimeMicros(), plan.getProfileLocation(),
plan.getSql(), plan.getBind(), plan.getPlan());
dbName, plan.label(), plan.queryTimeMicros(), plan.profileLocation(),
plan.sql(), plan.bind(), plan.plan());
}
}
}
@@ -397,6 +397,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (dbSchema != null) {
migrationRunner.setDefaultDbSchema(dbSchema);
}
migrationRunner.setPlatform(config.getDatabasePlatform().getPlatform().base().name().toLowerCase());
migrationRunner.loadProperties(config.getProperties());
migrationRunner.run(config.getDataSource());
}
@@ -415,8 +416,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private void collectQueryPlans() {
QueryPlanRequest request = new QueryPlanRequest();
request.setMaxCount(config.getQueryPlanCaptureMaxCount());
request.setMaxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis());
request.maxCount(config.getQueryPlanCaptureMaxCount());
request.maxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis());
// obtains query explain plans ...
List<MetaQueryPlan> plans = metaInfoManager.queryPlanCollectNow(request);
@@ -1283,14 +1284,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
@Override
public <T> boolean exists(Query<?> ormQuery, Transaction transaction) {
Query<?> ormQueryCopy = ormQuery.copy();
ormQueryCopy.setMaxRows(1);
public <T> boolean exists(Query<T> ormQuery, Transaction transaction) {
Query<T> ormQueryCopy = ormQuery.copy().setMaxRows(1);
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, ormQueryCopy, transaction);
try {
request.initTransIfRequired();
List<Object> ids = request.findIds();
return !ids.isEmpty();
return !request.findIds().isEmpty();
} finally {
request.endTransIfRequired();
}
@@ -2329,13 +2328,13 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public void visitMetrics(MetricVisitor visitor) {
visitor.visitStart();
if (visitor.isCollectTransactionMetrics()) {
if (visitor.collectTransactionMetrics()) {
transactionManager.visitMetrics(visitor);
}
if (visitor.isCollectL2Metrics()) {
if (visitor.collectL2Metrics()) {
serverCacheManager.visitMetrics(visitor);
}
if (visitor.isCollectQueryMetrics()) {
if (visitor.collectQueryMetrics()) {
beanDescriptorManager.visitMetrics(visitor);
dtoBeanManager.visitMetrics(visitor);
relationalQueryEngine.visitMetrics(visitor);
@@ -2352,7 +2351,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {
if (initRequest.isAll()) {
queryPlanManager.setDefaultThreshold(initRequest.getThresholdMicros());
queryPlanManager.setDefaultThreshold(initRequest.thresholdMicros());
}
return beanDescriptorManager.queryPlanInit(initRequest);
}
@@ -78,11 +78,11 @@ class DumpMetrics {
out("-- Dumping metrics for " + server.getName() + " -- ");
ServerMetrics serverMetrics = server.getMetaInfoManager().collectMetrics();
for (MetaTimedMetric metric : serverMetrics.getTimedMetrics()) {
for (MetaTimedMetric metric : serverMetrics.timedMetrics()) {
log(metric);
}
List<MetaCountMetric> countMetrics = serverMetrics.getCountMetrics();
List<MetaCountMetric> countMetrics = serverMetrics.countMetrics();
if (!countMetrics.isEmpty()) {
out("\n-- Counters --");
countMetrics.sort(SortMetric.COUNT_NAME);
@@ -91,7 +91,7 @@ class DumpMetrics {
}
}
List<MetaQueryMetric> queryMetrics = serverMetrics.getQueryMetrics();
List<MetaQueryMetric> queryMetrics = serverMetrics.queryMetrics();
if (!queryMetrics.isEmpty()) {
out("\n-- Queries --");
queryMetrics.sort(sortBy);
@@ -104,8 +104,8 @@ class DumpMetrics {
private void logCount(MetaCountMetric metric) {
StringBuilder sb = new StringBuilder();
sb.append(padNameTimed(metric.getName())).append(" ");
sb.append(" count:").append(pad(metric.getCount()));
sb.append(padNameTimed(metric.name())).append(" ");
sb.append(" count:").append(pad(metric.count()));
out(sb.toString());
}
@@ -120,38 +120,39 @@ class DumpMetrics {
appendQueryName(metric, sb);
appendCounters(metric, sb);
if (dumpHash) {
sb.append("\n hash:").append(metric.getHash());
sb.append("\n sqlHash:").append(metric.sqlHash());
}
appendProfileAndSql(metric, sb);
out(sb.toString());
}
private void appendQueryName(MetaQueryMetric metric, StringBuilder sb) {
sb.append("query:").append(padName(metric.getName())).append(" ");
sb.append("query:").append(padName(metric.name())).append(" ");
}
private void appendProfileAndSql(MetaQueryMetric metric, StringBuilder sb) {
String location = metric.getLocation();
String location = metric.location();
if (dumpLoc && location != null) {
sb.append("\n loc:").append(location);
sb.append("\n locHash:").append(metric.locationHash());
}
if (dumpSql) {
sb.append(" \n\n sql:").append(metric.getSql()).append("\n\n");
sb.append(" \n\n sql:").append(metric.sql()).append("\n\n");
}
}
private void log(MetaTimedMetric metric) {
StringBuilder sb = new StringBuilder();
sb.append(padNameTimed(metric.getName())).append(" ");
sb.append(padNameTimed(metric.name())).append(" ");
appendCounters(metric, sb);
out(sb.toString());
}
private void appendCounters(MetaTimedMetric timedMetric, StringBuilder sb) {
sb.append(" count:").append(pad(timedMetric.getCount()))
.append(" total:").append(pad(timedMetric.getTotal()))
.append(" mean:").append(pad(timedMetric.getMean()))
.append(" max:").append(pad(timedMetric.getMax()));
sb.append(" count:").append(pad(timedMetric.count()))
.append(" total:").append(pad(timedMetric.total()))
.append(" mean:").append(pad(timedMetric.mean()))
.append(" max:").append(pad(timedMetric.max()));
}
private String padName(String name) {
@@ -31,9 +31,9 @@ class DumpMetricsData {
private void collect(ServerMetrics serverMetrics) {
final List<MetaTimedMetric> timedMetrics = serverMetrics.getTimedMetrics();
final List<MetaCountMetric> countMetrics = serverMetrics.getCountMetrics();
final List<MetaQueryMetric> queryMetrics = serverMetrics.getQueryMetrics();
final List<MetaTimedMetric> timedMetrics = serverMetrics.timedMetrics();
final List<MetaCountMetric> countMetrics = serverMetrics.countMetrics();
final List<MetaQueryMetric> queryMetrics = serverMetrics.queryMetrics();
for (MetaTimedMetric metric : timedMetrics) {
add(metric);
@@ -47,7 +47,7 @@ class DumpMetricsData {
}
private MetricData create(MetaMetric metric) {
MetricData data = new MetricData(metric.getName());
MetricData data = new MetricData(metric.name());
list.add(data);
return data;
}
@@ -55,30 +55,32 @@ class DumpMetricsData {
private void add(MetaTimedMetric metric) {
final MetricData data = create(metric);
appendCounters(data, metric);
data.setLoc(metric.getLocation());
data.setLoc(metric.location());
data.setLocHash(metric.locationHash());
}
private void addCount(MetaCountMetric metric) {
final MetricData data = create(metric);
data.setCount(metric.getCount());
data.setCount(metric.count());
}
private void addQuery(MetaQueryMetric metric) {
final MetricData data = create(metric);
appendCounters(data, metric);
appendLocationAndSql(data, metric);
data.setHash(metric.getHash());
data.setSqlHash(metric.sqlHash());
}
private void appendLocationAndSql(MetricData data, MetaQueryMetric metric) {
data.setLoc(metric.getLocation());
data.setSql(metric.getSql());
data.setLocHash(metric.locationHash());
data.setLoc(metric.location());
data.setSql(metric.sql());
}
private void appendCounters(MetricData data, MetaTimedMetric timedMetric) {
data.setCount(timedMetric.getCount());
data.setTotal(timedMetric.getTotal());
data.setMean(timedMetric.getMean());
data.setMax(timedMetric.getMax());
data.setCount(timedMetric.count());
data.setTotal(timedMetric.total());
data.setMean(timedMetric.mean());
data.setMax(timedMetric.max());
}
}
@@ -84,11 +84,11 @@ class DumpMetricsJson implements ServerMetricsAsJson {
private void collect(ServerMetrics serverMetrics) {
try {
start();
for (MetaTimedMetric metric : serverMetrics.getTimedMetrics()) {
for (MetaTimedMetric metric : serverMetrics.timedMetrics()) {
logTimed(metric);
}
List<MetaCountMetric> countMetrics = serverMetrics.getCountMetrics();
List<MetaCountMetric> countMetrics = serverMetrics.countMetrics();
if (!countMetrics.isEmpty()) {
if (sortBy != null) {
countMetrics.sort(SortMetric.COUNT_NAME);
@@ -98,7 +98,7 @@ class DumpMetricsJson implements ServerMetricsAsJson {
}
}
List<MetaQueryMetric> queryMetrics = serverMetrics.getQueryMetrics();
List<MetaQueryMetric> queryMetrics = serverMetrics.queryMetrics();
if (!queryMetrics.isEmpty()) {
if (sortBy != null) {
queryMetrics.sort(sortBy);
@@ -170,7 +170,7 @@ class DumpMetricsJson implements ServerMetricsAsJson {
}
objStart();
key("name");
val(metric.getName());
val(metric.name());
}
private void metricEnd() throws IOException {
@@ -180,7 +180,7 @@ class DumpMetricsJson implements ServerMetricsAsJson {
private void logCount(MetaCountMetric metric) throws IOException {
metricStart(metric);
key("count");
val(metric.getCount());
val(metric.count());
metricEnd();
}
@@ -188,7 +188,8 @@ class DumpMetricsJson implements ServerMetricsAsJson {
metricStart(metric);
appendTiming(metric);
if (isIncludeDetail(metric)) {
appendExtra("loc", metric.getLocation());
keyVal("locHash", metric.locationHash());
appendExtra("loc", metric.location());
}
metricEnd();
}
@@ -197,11 +198,12 @@ class DumpMetricsJson implements ServerMetricsAsJson {
metricStart(metric);
appendTiming(metric);
if (withHash) {
appendExtra("hash", metric.getHash());
keyVal("sqlHash", metric.sqlHash());
keyVal("locHash", metric.locationHash());
}
if (isIncludeDetail(metric)) {
appendExtra("loc", metric.getLocation());
appendExtra("sql", metric.getSql());
appendExtra("loc", metric.location());
appendExtra("sql", metric.sql());
}
metricEnd();
}
@@ -218,13 +220,14 @@ class DumpMetricsJson implements ServerMetricsAsJson {
}
private void appendTiming(MetaTimedMetric timedMetric) throws IOException {
key("count");
val(timedMetric.getCount());
key("total");
val(timedMetric.getTotal());
key("mean");
val(timedMetric.getMean());
key("max");
val(timedMetric.getMax());
keyVal("count", timedMetric.count());
keyVal("total", timedMetric.total());
keyVal("mean", timedMetric.mean());
keyVal("max", timedMetric.max());
}
private void keyVal(String key, long value) throws IOException {
key(key);
val(value);
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.core.bootup;
import io.avaje.classpath.scanner.ClassFilter;
import io.ebean.annotation.DocStore;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.IdGenerator;
@@ -27,27 +26,23 @@ import javax.persistence.Embeddable;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
/**
* Interesting classes for a EbeanServer such as Embeddable, Entity,
* ScalarTypes, Finders, Listeners and Controllers.
*/
public class BootupClasses implements ClassFilter {
public class BootupClasses implements Predicate<Class<?>> {
private static final Logger logger = LoggerFactory.getLogger(BootupClasses.class);
private final List<Class<?>> embeddableList = new ArrayList<>();
private final List<Class<?>> entityList = new ArrayList<>();
private final List<Class<? extends ScalarType<?>>> scalarTypeList = new ArrayList<>();
private final List<Class<? extends ScalarTypeConverter<?, ?>>> scalarConverterList = new ArrayList<>();
private final List<Class<? extends AttributeConverter<?, ?>>> attributeConverterList = new ArrayList<>();
// The following objects are instantiated on first request
@@ -55,19 +50,12 @@ public class BootupClasses implements ClassFilter {
// instance list, that holds the instance. Once a class is instantiated
// (or added) it will get removed from the candidate list
private final List<Class<? extends IdGenerator>> idGeneratorCandidates = new ArrayList<>();
private final List<Class<? extends BeanPersistController>> beanPersistControllerCandidates = new ArrayList<>();
private final List<Class<? extends BeanPostLoad>> beanPostLoadCandidates = new ArrayList<>();
private final List<Class<? extends BeanPostConstructListener>> beanPostConstructListenerCandidates = new ArrayList<>();
private final List<Class<? extends BeanFindController>> beanFindControllerCandidates = new ArrayList<>();
private final List<Class<? extends BeanPersistListener>> beanPersistListenerCandidates = new ArrayList<>();
private final List<Class<? extends BeanQueryAdapter>> beanQueryAdapterCandidates = new ArrayList<>();
private final List<Class<? extends ServerConfigStartup>> serverConfigStartupCandidates = new ArrayList<>();
private final List<IdGenerator> idGeneratorInstances = new ArrayList<>();
@@ -98,7 +86,7 @@ public class BootupClasses implements ClassFilter {
public BootupClasses(List<Class<?>> list) {
if (list != null) {
for (Class<?> cls : list) {
isMatch(cls);
test(cls);
}
}
}
@@ -188,13 +176,11 @@ public class BootupClasses implements ClassFilter {
}
public void addChangeLogInstances(DatabaseConfig config) {
readAuditPrepare = config.getReadAuditPrepare();
readAuditLogger = config.getReadAuditLogger();
changeLogPrepare = config.getChangeLogPrepare();
changeLogListener = config.getChangeLogListener();
changeLogRegister = config.getChangeLogRegister();
// if not already set create the implementations found
// via classpath scanning
if (readAuditPrepare == null && readAuditPrepareClass != null) {
@@ -341,18 +327,14 @@ public class BootupClasses implements ClassFilter {
}
@Override
public boolean isMatch(Class<?> cls) {
public boolean test(Class<?> cls) {
if (isEmbeddable(cls)) {
embeddableList.add(cls);
} else if (isEntity(cls)) {
entityList.add(cls);
} else {
return isInterestingInterface(cls);
}
return true;
}
@@ -364,7 +346,6 @@ public class BootupClasses implements ClassFilter {
*/
@SuppressWarnings("unchecked")
private boolean isInterestingInterface(Class<?> cls) {
if (Modifier.isAbstract(cls.getModifiers())) {
// do not include abstract classes as we can
// not instantiate them
@@ -1558,8 +1558,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
void queryPlanInit(QueryPlanInit request, List<MetaQueryPlan> list) {
for (CQueryPlan queryPlan : queryPlanCache.values()) {
if (request.includeHash(queryPlan.getHash())) {
queryPlan.queryPlanInit(request.getThresholdMicros());
if (request.includeHash(queryPlan.getSqlHash())) {
queryPlan.queryPlanInit(request.thresholdMicros());
list.add(queryPlan.createMeta(null, null));
}
}
@@ -1572,7 +1572,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
iudMetrics.visit(visitor);
for (CQueryPlan queryPlan : queryPlanCache.values()) {
if (!queryPlan.isEmptyStats()) {
visitor.visitQuery(queryPlan.getSnapshot(visitor.isReset()));
visitor.visitQuery(queryPlan.getSnapshot(visitor.reset()));
}
}
}
@@ -2022,7 +2022,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
public boolean isTableManaged(String tableName) {
return owner.isTableManaged(tableName);
}
/**
* Return the order column property.
*/
@@ -3198,9 +3198,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
public void checkMutableProperties(EntityBeanIntercept ebi) {
for (BeanProperty beanProperty : propertiesMutable) {
int propertyIndex = beanProperty.getPropertyIndex();
if (!ebi.isDirtyProperty(propertyIndex) && ebi.isLoadedProperty(propertyIndex)) {
if (ebi.isLoadedProperty(propertyIndex)) {
Object value = beanProperty.getValue(ebi.getOwner());
if (value != null && beanProperty.isDirtyValue(value)) {
if (beanProperty.checkMutable(value, ebi.isDirtyProperty(propertyIndex), ebi)) {
// mutable scalar value which is considered dirty so mark
// it as such so that it is included in an update
ebi.markPropertyAsChanged(propertyIndex);
@@ -42,11 +42,7 @@ class BeanEmbeddedMetaFactory {
int dbScale = dbScale(column, sourceProperties[i]);
String colDefn = getDbColumnDefn(column, sourceProperties[i]);
BeanPropertyOverride overrides = new BeanPropertyOverride(dbColumn, dbNullable, dbLength, dbScale, colDefn);
if (sourceProperties[i] instanceof BeanPropertyAssocOne) {
embeddedProperties[i] = new BeanPropertyAssocOne((BeanPropertyAssocOne)sourceProperties[i], overrides);
} else {
embeddedProperties[i] = new BeanProperty(sourceProperties[i], overrides);
}
embeddedProperties[i] = sourceProperties[i].override(overrides);
}
return new BeanEmbeddedMeta(embeddedProperties);
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonToken;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.MutableValueInfo;
import io.ebean.bean.PersistenceContext;
import io.ebean.config.EncryptKey;
import io.ebean.config.dbplatform.DbEncryptFunction;
@@ -13,6 +14,7 @@ import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarType;
import io.ebean.plugin.Property;
import io.ebean.text.StringParser;
import io.ebean.text.TextException;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
@@ -31,11 +33,7 @@ import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.LocalEncryptedType;
import io.ebeaninternal.server.type.ScalarTypeBoolean;
import io.ebeaninternal.server.type.ScalarTypeEnum;
import io.ebeaninternal.server.type.ScalarTypeLogicalType;
import io.ebeaninternal.server.type.*;
import io.ebeaninternal.util.ValueUtil;
import io.ebeanservice.docstore.api.mapping.DocMappingBuilder;
import io.ebeanservice.docstore.api.mapping.DocPropertyMapping;
@@ -52,6 +50,7 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -366,13 +365,14 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return InternString.intern(s);
}
public BeanProperty override(BeanPropertyOverride override) {
return new BeanProperty(this, override);
}
/**
* Create a Matching BeanProperty with some attributes overridden.
* <p>
* Primarily for supporting Embedded beans with overridden dbColumn
* mappings.
* Create a Matching BeanProperty with some attributes overridden for Embedded beans.
*/
public BeanProperty(BeanProperty source, BeanPropertyOverride override) {
protected BeanProperty(BeanProperty source, BeanPropertyOverride override) {
this.descriptor = source.descriptor;
this.propertyIndex = source.propertyIndex;
this.name = source.getName();
@@ -642,15 +642,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
}
public Object readSet(DbReadContext ctx, EntityBean bean) throws SQLException {
try {
Object value = scalarType.read(ctx.getDataReader());
if (bean != null) {
setValue(bean, value);
}
return value;
} catch (Exception e) {
throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
}
return readSet(ctx.getDataReader(), bean);
}
@SuppressWarnings("unchecked")
@@ -828,6 +820,13 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return scalarType.parse(value);
}
/**
* creates a mutableHash for the given JSON value.
*/
public MutableValueInfo createMutableInfo(String json) {
throw new UnsupportedOperationException();
}
/**
* Read the value for this property from L2 cache entry and set it to the bean.
* <p>
@@ -909,7 +908,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the name of the property.
*/
@Override @Nonnull
@Override
@Nonnull
public String getName() {
return name;
}
@@ -1018,8 +1018,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Return true if the mutable value is considered dirty.
* This is only used for 'mutable' scalar types like hstore etc.
*/
boolean isDirtyValue(Object value) {
return scalarType.isDirty(value);
boolean checkMutable(Object value, boolean alreadyDirty, EntityBeanIntercept ebi) {
return alreadyDirty || value != null && scalarType.isDirty(value);
}
/**
@@ -1281,7 +1281,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
@Override
public Object localEncrypt(Object value) {
return ((LocalEncryptedType)scalarType).localEncrypt(value);
return ((LocalEncryptedType) scalarType).localEncrypt(value);
}
/**
@@ -1394,7 +1394,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the property type.
*/
@Override @Nonnull
@Override
@Nonnull
public Class<?> getPropertyType() {
return propertyType;
}
@@ -94,10 +94,12 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
/**
* Copy constructor for ManyToOne inside Embeddable.
*/
public BeanPropertyAssocOne(BeanPropertyAssocOne source, BeanPropertyOverride override) {
@Override
public BeanPropertyAssocOne<?> override(BeanPropertyOverride override) {
return new BeanPropertyAssocOne<>(this, override);
}
protected BeanPropertyAssocOne(BeanPropertyAssocOne<T> source, BeanPropertyOverride override) {
super(source, override);
primaryKeyExport = source.primaryKeyExport;
primaryKeyJoin = source.primaryKeyJoin;
@@ -255,12 +257,8 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
private SqlUpdate deleteByParentIdList(List<Object> parentIds) {
StringBuilder sb = new StringBuilder(100);
sb.append(deleteByParentIdInSql);
sb.append(targetIdBinder.getIdInValueExpr(false, parentIds.size()));
DefaultSqlUpdate delete = new DefaultSqlUpdate(sb.toString());
String sql = deleteByParentIdInSql + targetIdBinder.getIdInValueExpr(false, parentIds.size());
DefaultSqlUpdate delete = new DefaultSqlUpdate(sql);
bindParentIds(delete, parentIds);
return delete;
}
@@ -501,7 +499,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
return targetDescriptor.getIdProperty();
}
ScalarType getIdScalarType() {
ScalarType<?> getIdScalarType() {
return targetDescriptor.getIdProperty().scalarType;
}
@@ -0,0 +1,58 @@
package io.ebeaninternal.server.deploy;
import io.ebean.bean.EntityBean;
import io.ebean.core.type.DataReader;
import io.ebean.text.TextException;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Map;
/**
* A DbJson property that does not use Jackson ObjectMapper.
*/
public class BeanPropertyJsonBasic extends BeanProperty {
public BeanPropertyJsonBasic(BeanDescriptor<?> descriptor, DeployBeanProperty deploy) {
super(descriptor, deploy);
}
protected BeanPropertyJsonBasic(BeanProperty source, BeanPropertyOverride override) {
super(source, override);
}
@Override
public BeanProperty override(BeanPropertyOverride override) {
return new BeanPropertyJsonBasic(this, override);
}
protected Object checkForEmpty(EntityBean bean) {
final Object value = getValue(bean);
if (value instanceof Collection && ((Collection<?>) value).isEmpty()
|| value instanceof Map && ((Map<?, ?>) value).isEmpty()) {
return value;
}
return null;
}
@Override
public Object readSet(DataReader reader, EntityBean bean) throws SQLException {
try {
Object value = scalarType.read(reader);
if (value == null) {
value = checkForEmpty(bean);
}
if (bean != null) {
setValue(bean, value);
}
return value;
} catch (TextException e) {
throw e;
} catch (Exception e) {
throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
}
}
}
@@ -0,0 +1,208 @@
package io.ebeaninternal.server.deploy;
import io.ebean.annotation.MutationDetection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.MutableValueInfo;
import io.ebean.bean.MutableValueNext;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.ScalarType;
import io.ebean.text.TextException;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.util.Checksum;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.Objects;
/**
* Handle json property with MutationDetection of SOURCE or HASH only.
*/
public class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
private final boolean sourceDetection;
public BeanPropertyJsonMapper(BeanDescriptor<?> desc, DeployBeanProperty deployProp) {
super(desc, deployProp);
this.sourceDetection = deployProp.getMutationDetection() == MutationDetection.SOURCE;
}
private BeanPropertyJsonMapper(BeanPropertyJsonMapper source, BeanPropertyOverride override) {
super(source, override);
this.sourceDetection = source.sourceDetection;
}
@Override
public BeanProperty override(BeanPropertyOverride override) {
return new BeanPropertyJsonMapper(this, override);
}
@Override
public MutableValueInfo createMutableInfo(String json) {
if (sourceDetection) {
return new SourceMutableValue(scalarType, json);
} else {
return new ChecksumMutableValue(scalarType, json);
}
}
/**
* Next when no prior MutableValueInfo.
*/
private MutableValueNext next(String json) {
if (sourceDetection) {
return new SourceMutableValue(scalarType, json);
} else {
return new NextPair(json, new ChecksumMutableValue(scalarType, json));
}
}
/**
* Return true if the json property is considered dirty.
*/
@Override
boolean checkMutable(Object value, boolean alreadyDirty, EntityBeanIntercept ebi) {
// mutation detection based on json content or checksum of json content
// only perform serialisation to json once
final String json = scalarType.format(value);
final MutableValueInfo oldHash = ebi.mutableInfo(propertyIndex);
if (oldHash == null) {
if (value == null) {
return false; // no change, still null
}
ebi.mutableNext(propertyIndex, next(json));
return true;
}
// only perform compute of checksum/hash once (if checksum based)
final MutableValueNext next = oldHash.nextDirty(json);
if (next != null) {
ebi.mutableNext(propertyIndex, next);
return true;
}
return false;
}
@Override
public Object readSet(DataReader reader, EntityBean bean) throws SQLException {
try {
Object value = scalarType.read(reader);
if (value == null) {
value = checkForEmpty(bean);
}
if (bean != null) {
setValue(bean, value);
String json = reader.popJson();
if (json != null) {
final MutableValueInfo hash = createMutableInfo(json);
bean._ebean_getIntercept().mutableInfo(propertyIndex, hash);
}
}
return value;
} catch (TextException e) {
throw e;
} catch (Exception e) {
throw new PersistenceException("Error readSet on " + descriptor + "." + name, e);
}
}
private static final class NextPair implements MutableValueNext {
private final String json;
private final MutableValueInfo next;
NextPair(String json, MutableValueInfo next) {
this.json = json;
this.next = next;
}
@Override
public String content() {
return json;
}
@Override
public MutableValueInfo info() {
return next;
}
}
/**
* Hold checksum of json source content to use for dirty detection.
* <p>
* Does not support rebuilding 'oldValue' as no original json content.
*/
private static final class ChecksumMutableValue implements MutableValueInfo {
private final ScalarType<?> parent;
private final long checksum;
ChecksumMutableValue(ScalarType<?> parent, String json) {
this.parent = parent;
this.checksum = Checksum.checksum(json);
}
/**
* Create with pre-computed checksum.
*/
ChecksumMutableValue(ScalarType<?> parent, long checksum) {
this.parent = parent;
this.checksum = checksum;
}
@Override
public MutableValueNext nextDirty(String json) {
final long nextChecksum = Checksum.checksum(json);
return nextChecksum == checksum ? null : new NextPair(json, new ChecksumMutableValue(parent, nextChecksum));
}
@Override
public boolean isEqualToObject(Object obj) {
return Checksum.checksum(parent.format(obj)) == checksum;
}
@Override
public Object get() {
return null; // cannot create object from json
}
}
/**
* Hold json source content. This supports rebuilding the 'oldValue'.
*/
private static final class SourceMutableValue implements MutableValueInfo, MutableValueNext {
private final String originalJson;
private final ScalarType<?> parent;
SourceMutableValue(ScalarType<?> parent, String json) {
this.parent = parent;
this.originalJson = json;
}
@Override
public MutableValueNext nextDirty(String json) {
return Objects.equals(originalJson, json) ? null : new SourceMutableValue(parent, json);
}
@Override
public boolean isEqualToObject(Object obj) {
return Objects.equals(originalJson, parent.format(obj));
}
@Override
public Object get() {
// rebuild the 'oldValue' for change log etc
return parent.parse(originalJson);
}
@Override
public String content() {
return originalJson;
}
@Override
public MutableValueInfo info() {
return this;
}
}
}
@@ -28,11 +28,6 @@ class ElementEntityBean implements EntityBean {
return properties[pos];
}
@Override
public String _ebean_getMarker() {
return null;
}
@Override
public Object _ebean_newInstance() {
return new ElementEntityBean(properties);
@@ -1,18 +1,6 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.annotation.CreatedTimestamp;
import io.ebean.annotation.DocCode;
import io.ebean.annotation.DocProperty;
import io.ebean.annotation.DocSortable;
import io.ebean.annotation.Formula;
import io.ebean.annotation.Platform;
import io.ebean.annotation.SoftDelete;
import io.ebean.annotation.UpdatedTimestamp;
import io.ebean.annotation.WhenCreated;
import io.ebean.annotation.WhenModified;
import io.ebean.annotation.Where;
import io.ebean.annotation.WhoCreated;
import io.ebean.annotation.WhoModified;
import io.ebean.annotation.*;
import io.ebean.config.ScalarTypeConverter;
import io.ebean.config.dbplatform.DbDefaultValue;
import io.ebean.config.dbplatform.DbEncrypt;
@@ -39,7 +27,6 @@ import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -109,6 +96,7 @@ public class DeployBeanProperty {
private boolean jsonSerialize = true;
private boolean jsonDeserialize = true;
private MutationDetection mutationDetection;
private boolean dbEncrypted;
private DbEncryptFunction dbEncryptFunction;
@@ -327,6 +315,14 @@ public class DeployBeanProperty {
this.jsonDeserialize = jsonDeserialize;
}
public MutationDetection getMutationDetection() {
return mutationDetection;
}
public void setMutationDetection(MutationDetection dirtyDetection) {
this.mutationDetection = dirtyDetection;
}
/**
* Return the sortOrder for the properties.
*/
@@ -1201,4 +1197,11 @@ public class DeployBeanProperty {
return false;
}
boolean isJsonMapper() {
return scalarType != null && scalarType.isJsonMapper();
}
boolean isJsonType() {
return mutationDetection != null;
}
}
@@ -1,15 +1,7 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorMap;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.BeanPropertyIdClass;
import io.ebeaninternal.server.deploy.BeanPropertyOrderColumn;
import io.ebeaninternal.server.deploy.BeanPropertySimpleCollection;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.*;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.properties.BeanPropertySetter;
import io.ebeaninternal.server.type.ScalarTypeString;
@@ -30,47 +22,28 @@ public class DeployBeanPropertyLists {
private static final NoopSetter NOOP_SETTER = new NoopSetter();
private BeanProperty versionProperty;
private BeanProperty unmappedJson;
private BeanProperty draft;
private BeanProperty draftDirty;
private BeanProperty tenant;
private final BeanDescriptor<?> desc;
private final LinkedHashMap<String, BeanProperty> propertyMap;
private BeanProperty id;
private final List<BeanProperty> local = new ArrayList<>();
private final List<BeanProperty> mutable = new ArrayList<>();
private final List<BeanPropertyAssocMany<?>> manys = new ArrayList<>();
private final List<BeanProperty> nonManys = new ArrayList<>();
private final List<BeanProperty> aggs = new ArrayList<>();
private final List<BeanPropertyAssocOne<?>> ones = new ArrayList<>();
private final List<BeanPropertyAssocOne<?>> onesImported = new ArrayList<>();
private final List<BeanPropertyAssocOne<?>> embedded = new ArrayList<>();
private final List<BeanProperty> baseScalar = new ArrayList<>();
private final List<BeanProperty> transients = new ArrayList<>();
private final List<BeanProperty> nonTransients = new ArrayList<>();
private final BeanPropertyAssocOne<?> unidirectional;
private final BeanProperty orderColumn;
@SuppressWarnings({"unchecked"})
public DeployBeanPropertyLists(BeanDescriptorMap owner, BeanDescriptor<?> desc, DeployBeanDescriptor<?> deploy) {
this.desc = desc;
@@ -86,7 +59,7 @@ public class DeployBeanPropertyLists {
this.orderColumn = deployOrderColumn != null ? new BeanPropertyOrderColumn(desc, deployOrderColumn) : null;
DeployBeanPropertyAssocOne<?> deployUnidirectional = deploy.getUnidirectional();
this.unidirectional = deployUnidirectional == null ? null : new BeanPropertyAssocOne(owner, desc, deployUnidirectional);
this.unidirectional = deployUnidirectional == null ? null : new BeanPropertyAssocOne<>(owner, desc, deployUnidirectional);
this.propertyMap = new LinkedHashMap<>();
@@ -127,7 +100,7 @@ public class DeployBeanPropertyLists {
}
if (orderColumn != null) {
orderColumn.setDeployOrder(order++);
orderColumn.setDeployOrder(order);
allocateToList(orderColumn);
propertyMap.put(orderColumn.getName(), orderColumn);
}
@@ -154,7 +127,6 @@ public class DeployBeanPropertyLists {
}
private void setImportedPrimaryKeysFor(DeployBeanDescriptor<?> deploy, DeployBeanPropertyAssocOne<?> id) {
for (DeployBeanProperty prop : id.getTargetDeploy().properties()) {
DeployBeanProperty match = findImported(deploy, prop);
if (match != null) {
@@ -164,7 +136,6 @@ public class DeployBeanPropertyLists {
}
private DeployBeanProperty findImported(DeployBeanDescriptor<?> deploy, DeployBeanProperty embeddedScalar) {
// the logical name and db column we are looking for a match on
String name = embeddedScalar.getName();
String dbColumn = embeddedScalar.getDbColumn();
@@ -180,7 +151,6 @@ public class DeployBeanPropertyLists {
return assocOne;
}
}
return null;
}
@@ -368,7 +338,6 @@ public class DeployBeanPropertyLists {
}
public BeanProperty getSoftDeleteProperty() {
for (BeanProperty prop : nonManys) {
if (prop.isSoftDelete()) {
return prop;
@@ -385,7 +354,6 @@ public class DeployBeanPropertyLists {
* Return the properties set via generated values on insert.
*/
public BeanProperty[] getGeneratedInsert() {
List<BeanProperty> list = new ArrayList<>();
for (BeanProperty prop : nonTransients) {
GeneratedProperty gen = prop.getGeneratedProperty();
@@ -400,7 +368,6 @@ public class DeployBeanPropertyLists {
* Return the properties set via generated values on update.
*/
public BeanProperty[] getGeneratedUpdate() {
List<BeanProperty> list = new ArrayList<>();
for (BeanProperty prop : nonTransients) {
GeneratedProperty gen = prop.getGeneratedProperty();
@@ -438,8 +405,7 @@ public class DeployBeanPropertyLists {
}
}
}
return (BeanPropertyAssocOne[]) list.toArray(new BeanPropertyAssocOne[0]);
return list.toArray(new BeanPropertyAssocOne<?>[0]);
}
private BeanPropertyAssocMany<?>[] getMany2Many() {
@@ -449,8 +415,7 @@ public class DeployBeanPropertyLists {
list.add(prop);
}
}
return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[0]);
return list.toArray(new BeanPropertyAssocMany<?>[0]);
}
private BeanPropertyAssocMany<?>[] getMany(Mode mode) {
@@ -471,25 +436,26 @@ public class DeployBeanPropertyLists {
break;
}
}
return (BeanPropertyAssocMany[]) list.toArray(new BeanPropertyAssocMany[0]);
return list.toArray(new BeanPropertyAssocMany<?>[0]);
}
@SuppressWarnings({"unchecked", "rawtypes"})
private BeanProperty createBeanProperty(BeanDescriptorMap owner, DeployBeanProperty deployProp) {
if (deployProp instanceof DeployBeanPropertyAssocOne) {
return new BeanPropertyAssocOne(owner, desc, (DeployBeanPropertyAssocOne) deployProp);
}
if (deployProp instanceof DeployBeanPropertySimpleCollection<?>) {
return new BeanPropertySimpleCollection(desc, (DeployBeanPropertySimpleCollection) deployProp);
}
if (deployProp instanceof DeployBeanPropertyAssocMany) {
return new BeanPropertyAssocMany(desc, (DeployBeanPropertyAssocMany) deployProp);
}
if (deployProp.isJsonMapper()) {
return new BeanPropertyJsonMapper(desc, deployProp);
}
if (deployProp.isJsonType()) {
return new BeanPropertyJsonBasic(desc, deployProp);
}
return new BeanProperty(desc, deployProp);
}
@@ -1,10 +1,6 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.DbArray;
import io.ebean.annotation.DbJson;
import io.ebean.annotation.DbJsonB;
import io.ebean.annotation.DbJsonType;
import io.ebean.annotation.DbMap;
import io.ebean.annotation.*;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.EncryptDeploy;
import io.ebean.config.EncryptDeployManager;
@@ -209,26 +205,22 @@ public class DeployUtil {
}
}
/**
* This property is marked as a Lob object.
*/
void setDbJsonType(DeployBeanProperty prop, DbJson dbJsonType) {
int dbType = getDbJsonStorage(dbJsonType.storage());
setDbJsonType(prop, dbType, dbJsonType.length());
setDbJsonType(prop, dbType, dbJsonType.length(), dbJsonType.mutationDetection());
}
void setDbJsonBType(DeployBeanProperty prop, DbJsonB dbJsonB) {
setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length());
setDbJsonType(prop, DbPlatformType.JSONB, dbJsonB.length(), dbJsonB.mutationDetection());
}
private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) {
private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength, MutationDetection mutationDetection) {
prop.setDbType(dbType);
prop.setMutationDetection(mutationDetection);
ScalarType<?> scalarType = typeManager.getJsonScalarType(prop, dbType, dbLength);
if (scalarType == null) {
throw new RuntimeException("No ScalarType for JSON property [" + prop + "] [" + dbType + "]");
}
prop.setDbType(dbType);
prop.setScalarType(scalarType);
if (dbType == Types.VARCHAR || dbLength > 0) {
// determine the db column size
@@ -295,7 +295,16 @@ public class SaveManyBeans extends SaveManyBase {
}
transaction.depth(+1);
if (deletions != null && !deletions.isEmpty()) {
for (Object other : deletions) {
EntityBean otherDelete = (EntityBean) other;
// the object from the 'other' side of the ManyToMany
// build a intersection row for 'delete'
IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherDelete, publish);
SpiSqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD);
persister.executeOrQueue(sqlDelete, transaction, queue);
}
}
if (additions != null && !additions.isEmpty()) {
for (Object other : additions) {
EntityBean otherBean = (EntityBean) other;
@@ -318,16 +327,6 @@ public class SaveManyBeans extends SaveManyBase {
}
}
}
if (deletions != null && !deletions.isEmpty()) {
for (Object other : deletions) {
EntityBean otherDelete = (EntityBean) other;
// the object from the 'other' side of the ManyToMany
// build a intersection row for 'delete'
IntersectionRow intRow = many.buildManyToManyMapBean(parentBean, otherDelete, publish);
SpiSqlUpdate sqlDelete = intRow.createDelete(server, DeleteMode.HARD);
persister.executeOrQueue(sqlDelete, transaction, queue);
}
}
// decrease the depth back to what it was
transaction.depth(-1);
}
@@ -63,6 +63,11 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
}
}
@Override
public void pushJson(String json) {
dataBind.pushJson(json);
}
@Override
public long now() {
return now;
@@ -0,0 +1,42 @@
package io.ebeaninternal.server.persist.dmlbind;
import io.ebean.bean.EntityBean;
import io.ebean.bean.MutableValueInfo;
import io.ebeaninternal.server.deploy.BeanProperty;
import java.sql.SQLException;
/**
* For JSON Jackson properties - dirty detection via MD5 of json content.
*/
class BindablePropertyJsonInsert extends BindableProperty {
private final int propertyIndex;
BindablePropertyJsonInsert(BeanProperty prop) {
super(prop);
this.propertyIndex = prop.getPropertyIndex();
}
/**
* Normal binding of a property value from the bean.
*/
@Override
public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException {
if (bean == null) {
request.bind(null, prop);
} else {
Object value = prop.getValue(bean);
if (value == null) {
request.bind(null, prop);
} else {
// on insert store hash and push json
final String json = prop.format(value);
final MutableValueInfo hash = prop.createMutableInfo(json);
bean._ebean_getIntercept().mutableInfo(propertyIndex, hash);
request.pushJson(json);
request.bind(value, prop);
}
}
}
}
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.persist.dmlbind;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.BeanProperty;
import java.sql.SQLException;
/**
* For JSON Jackson properties - dirty detection via MD5 of json content.
*/
class BindablePropertyJsonUpdate extends BindableProperty {
private final int propertyIndex;
BindablePropertyJsonUpdate(BeanProperty prop) {
super(prop);
this.propertyIndex = prop.getPropertyIndex();
}
/**
* Normal binding of a property value from the bean.
*/
@Override
public void dmlBind(BindableRequest request, EntityBean bean) throws SQLException {
if (bean == null) {
request.bind(null, prop);
} else {
// update mutableInfo and push json
request.pushJson(bean._ebean_getIntercept().mutableNext(propertyIndex));
request.bind(prop.getValue(bean), prop);
}
}
}
@@ -57,4 +57,9 @@ public interface BindableRequest {
* Return true if this is an update request.
*/
boolean isUpdate();
/**
* Push json content for scalarType bind().
*/
void pushJson(String json);
}
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.persist.dmlbind;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.BeanPropertyJsonMapper;
import io.ebeaninternal.server.persist.dml.DmlMode;
/**
@@ -23,14 +24,12 @@ class FactoryProperty {
* Create a Bindable for the property given the mode and withLobs flag.
*/
public Bindable create(BeanProperty prop, DmlMode mode, boolean withLobs, boolean allowManyToOne) {
if (DmlMode.INSERT == mode && !prop.isDbInsertable()) {
return null;
}
if (DmlMode.UPDATE == mode && !prop.isDbUpdatable()) {
return null;
}
if (prop.isLob() && !withLobs) {
// Lob exclusion
return null;
@@ -38,11 +37,16 @@ class FactoryProperty {
if (prop.isDbEncrypted()){
return new BindableEncryptedProperty(prop, bindEncryptDataFirst);
}
if (allowManyToOne && prop instanceof BeanPropertyAssocOne) {
return new BindableAssocOne((BeanPropertyAssocOne<?>)prop);
}
if (prop instanceof BeanPropertyJsonMapper) {
if (DmlMode.INSERT == mode) {
return new BindablePropertyJsonInsert(prop);
} else if (DmlMode.UPDATE == mode) {
return new BindablePropertyJsonUpdate(prop);
}
}
return new BindableProperty(prop);
}
}
@@ -10,9 +10,11 @@ final class BasicProfileLocation implements ProfileLocation {
private final String fullLocation;
private final String location;
private final String label;
private final long hash;
BasicProfileLocation(String fullLocation) {
this.fullLocation = fullLocation;
this.hash = UtilLocation.hash(fullLocation);
this.location = shortDesc(fullLocation);
this.label = UtilLocation.label(location);
}
@@ -42,6 +44,11 @@ final class BasicProfileLocation implements ProfileLocation {
return location;
}
@Override
public long hash() {
return hash;
}
@Override
public String fullLocation() {
return fullLocation;
@@ -49,7 +49,7 @@ class DCountMetric implements CountMetric {
@Override
public void visit(MetricVisitor visitor) {
long val = visitor.isReset() ? count.sumThenReset() : count.sum();
long val = visitor.reset() ? count.sumThenReset() : count.sum();
if (val > 0) {
visitor.visitCount(new DCountMetricStats(name, val));
}
@@ -66,12 +66,12 @@ class DCountMetric implements CountMetric {
}
@Override
public String getName() {
public String name() {
return name;
}
@Override
public long getCount() {
public long count() {
return count;
}
}
@@ -8,17 +8,14 @@ import io.ebean.ProfileLocation;
class DProfileLocation implements ProfileLocation {
private static final String IO_EBEAN = "io.ebean";
private static final String UNKNOWN = "unknown";
private String fullLocation;
private String location;
private String label;
private long hash;
private final int lineNumber;
private int traceCount;
DProfileLocation() {
@@ -53,6 +50,7 @@ class DProfileLocation implements ProfileLocation {
label = UtilLocation.label(shortDesc);
location = shortDesc;
fullLocation = loc;
hash = UtilLocation.hash(loc);
initWith(label);
return true;
}
@@ -71,6 +69,11 @@ class DProfileLocation implements ProfileLocation {
return location;
}
@Override
public long hash() {
return hash;
}
@Override
public String fullLocation() {
return fullLocation;
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.profile;
import io.ebean.ProfileLocation;
import io.ebeaninternal.server.util.Md5;
import io.ebeaninternal.server.util.Checksum;
class DQueryPlanMeta {
@@ -10,7 +10,7 @@ class DQueryPlanMeta {
private final ProfileLocation profileLocation;
private final String name;
private final String sql;
private final String hash;
private final long sqlHash;
DQueryPlanMeta(Class<?> type, String label, ProfileLocation profileLocation, String sql) {
this.type = type;
@@ -22,23 +22,15 @@ class DQueryPlanMeta {
name += "_" + label;
}
this.name = name;
this.hash = initHash();
}
private String initHash() {
StringBuilder sb = new StringBuilder(sql).append("|").append(name);
if (profileLocation != null) {
sb.append("|").append(profileLocation.location());
}
return Md5.hash(sb.toString());
this.sqlHash = Checksum.checksum(sql);
}
public Class<?> getType() {
return type;
}
public String getHash() {
return hash;
public long getSqlHash() {
return sqlHash;
}
public String getName() {
@@ -57,6 +49,10 @@ class DQueryPlanMeta {
return (profileLocation == null) ? null : profileLocation.location();
}
public long getLocationHash() {
return (profileLocation == null) ? 0 : profileLocation.hash();
}
public String getSql() {
return sql;
}
@@ -19,7 +19,7 @@ class DQueryPlanMetric implements QueryPlanMetric {
@Override
public void visit(MetricVisitor visitor) {
TimedMetricStats stats = metric.collect(visitor.isReset());
TimedMetricStats stats = metric.collect(visitor.reset());
if (stats != null) {
visitor.visitQuery(new Stats(meta, stats, collected));
collected = true;
@@ -45,11 +45,11 @@ class DQueryPlanMetric implements QueryPlanMetric {
@Override
public String toString() {
return meta + " " + stats + " sql:" + getSql();
return meta + " " + stats + " sql:" + sql();
}
@Override
public Class<?> getType() {
public Class<?> type() {
return meta.getType();
}
@@ -59,48 +59,53 @@ class DQueryPlanMetric implements QueryPlanMetric {
}
@Override
public String getHash() {
return meta.getHash();
public long sqlHash() {
return meta.getSqlHash();
}
@Override
public String getLabel() {
public String label() {
return meta.getLabel();
}
@Override
public String getSql() {
public String sql() {
return meta.getSql();
}
@Override
public String getName() {
public String name() {
return meta.getName();
}
@Override
public String getLocation() {
public String location() {
return meta.getLocation();
}
@Override
public long getCount() {
return stats.getCount();
public long locationHash() {
return meta.getLocationHash();
}
@Override
public long getTotal() {
return stats.getTotal();
public long count() {
return stats.count();
}
@Override
public long getMax() {
return stats.getMax();
public long total() {
return stats.total();
}
@Override
public long getMean() {
return stats.getMean();
public long max() {
return stats.max();
}
@Override
public long mean() {
return stats.mean();
}
}
}
@@ -7,18 +7,15 @@ import io.ebean.metric.TimedMetricStats;
*/
class DTimeMetricStats implements TimedMetricStats {
private String name;
private final boolean collected;
private String location;
private final long count;
private final long total;
private final long max;
private String name;
private String location;
private long locationHash;
DTimeMetricStats(String name, boolean collected, long count, long total, long max) {
this.name = name;
this.collected = collected;
@@ -40,6 +37,7 @@ class DTimeMetricStats implements TimedMetricStats {
.append(" max:").append(max);
if (location != null) {
sb.append(" loc:").append(location);
sb.append(" locHash:").append(locationHash);
}
return sb.toString();
}
@@ -49,6 +47,11 @@ class DTimeMetricStats implements TimedMetricStats {
this.location = location;
}
@Override
public void setLocationHash(long locationHash) {
this.locationHash = locationHash;
}
@Override
public boolean initialCollection() {
return !collected;
@@ -60,20 +63,25 @@ class DTimeMetricStats implements TimedMetricStats {
}
@Override
public String getName() {
public String name() {
return name;
}
@Override
public String getLocation() {
public String location() {
return location;
}
@Override
public long locationHash() {
return locationHash;
}
/**
* Return the count of values collected.
*/
@Override
public long getCount() {
public long count() {
return count;
}
@@ -81,7 +89,7 @@ class DTimeMetricStats implements TimedMetricStats {
* Return the total of all the values.
*/
@Override
public long getTotal() {
public long total() {
return total;
}
@@ -89,7 +97,7 @@ class DTimeMetricStats implements TimedMetricStats {
* Return the Max value collected.
*/
@Override
public long getMax() {
public long max() {
return max;
}
@@ -97,7 +105,7 @@ class DTimeMetricStats implements TimedMetricStats {
* Return the mean value rounded up.
*/
@Override
public long getMean() {
public long mean() {
return (count < 1) ? 0L : Math.round((double)(total / count));
}
@@ -71,7 +71,7 @@ class DTimedMetric implements TimedMetric {
@Override
public void visit(MetricVisitor visitor) {
DTimeMetricStats metric = collect(visitor.isReset());
DTimeMetricStats metric = collect(visitor.reset());
if (metric != null) {
visitor.visitTimed(metric);
}
@@ -48,7 +48,7 @@ class DTimedProfileLocation extends DProfileLocation implements TimedProfileLoca
@Override
public void visit(MetricVisitor visitor) {
TimedMetricStats collect = timedMetric.collect(visitor.isReset());
TimedMetricStats collect = timedMetric.collect(visitor.reset());
if (collect != null) {
if (overrideMetricName) {
collect.setName(fullName);
@@ -1,7 +1,21 @@
package io.ebeaninternal.server.profile;
import io.ebeaninternal.server.util.Checksum;
final class UtilLocation {
/**
* Return a hash of the full description excluding the source line number.
*/
static long hash(String full) {
final int pos = full.lastIndexOf('(');
if (pos > -1) {
return Checksum.checksum(full.substring(0, pos));
} else {
return Checksum.checksum(full);
}
}
static String label(String shortDescription) {
int pos = shortDescription.indexOf("(");
if (pos == -1) {
@@ -21,5 +35,4 @@ final class UtilLocation {
}
return desc;
}
}
@@ -13,12 +13,12 @@ import io.ebeaninternal.api.SpiQueryBindCapture;
import io.ebeaninternal.api.SpiQueryPlan;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.util.Checksum;
import io.ebeaninternal.server.util.Str;
import io.ebeaninternal.server.query.CQueryPlanStats.Snapshot;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.DataBindCapture;
import io.ebeaninternal.server.type.RsetDataReader;
import io.ebeaninternal.server.util.Md5;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -54,46 +54,32 @@ public class CQueryPlan implements SpiQueryPlan {
static final String RESULT_SET_BASED_RAW_SQL = "--ResultSetBasedRawSql";
private final SpiEbeanServer server;
private final ProfileLocation profileLocation;
private final String location;
private final long locationHash;
private final String label;
private final String name;
private final CQueryPlanKey planKey;
private final boolean rawSql;
private final String sql;
private final String hash;
private final long sqlHash;
private final String logWhereSql;
private final SqlTree sqlTree;
/**
* Encrypted properties required additional binding.
*/
private final STreeProperty[] encryptedProps;
private final CQueryPlanStats stats;
private final Class<?> beanType;
final DataTimeZone dataTimeZone;
private final int asOfTableCount;
/**
* Key used to identify the query plan in audit logging.
*/
private volatile String auditQueryHash;
private final Set<String> dependentTables;
private final SpiQueryBindCapture bindCapture;
/**
@@ -106,9 +92,10 @@ public class CQueryPlan implements SpiQueryPlan {
this.planKey = request.getQueryPlanKey();
SpiQuery<?> query = request.getQuery();
this.profileLocation = query.getProfileLocation();
this.location = (profileLocation == null) ? null : profileLocation.location();
this.locationHash = (profileLocation == null) ? 0 : profileLocation.hash();
this.label = query.getPlanLabel();
this.name = deriveName(label, query.getType(), request.getBeanDescriptor().getSimpleName());
this.location = location();
this.asOfTableCount = query.getAsOfTableCount();
this.sql = sqlRes.getSql();
this.sqlTree = sqlTree;
@@ -118,7 +105,7 @@ public class CQueryPlan implements SpiQueryPlan {
this.stats = new CQueryPlanStats(this);
this.dependentTables = sqlTree.dependentTables();
this.bindCapture = initBindCapture(query);
this.hash = md5Hash();
this.sqlHash = Checksum.checksum(sql);
}
/**
@@ -130,9 +117,10 @@ public class CQueryPlan implements SpiQueryPlan {
this.beanType = request.getBeanDescriptor().getBeanType();
SpiQuery<?> query = request.getQuery();
this.profileLocation = query.getProfileLocation();
this.location = (profileLocation == null) ? null : profileLocation.location();
this.locationHash = (profileLocation == null) ? 0 : profileLocation.hash();
this.label = query.getPlanLabel();
this.name = deriveName(label, query.getType(), request.getBeanDescriptor().getSimpleName());
this.location = location();
this.planKey = buildPlanKey(sql, logWhereSql);
this.asOfTableCount = 0;
this.sql = sql;
@@ -143,7 +131,7 @@ public class CQueryPlan implements SpiQueryPlan {
this.stats = new CQueryPlanStats(this);
this.dependentTables = sqlTree.dependentTables();
this.bindCapture = initBindCaptureRaw(sql, query);
this.hash = md5Hash();
this.sqlHash = Checksum.checksum(sql);
}
private String deriveName(String label, SpiQuery.Type type, String simpleName) {
@@ -169,10 +157,6 @@ public class CQueryPlan implements SpiQueryPlan {
return sql.equals(RESULT_SET_BASED_RAW_SQL) || query.getType().isUpdate() ? SpiQueryBindCapture.NOOP : server.createQueryBindCapture(this);
}
private String location() {
return (profileLocation == null) ? null : profileLocation.location();
}
private CQueryPlanKey buildPlanKey(String sql, String logWhereSql) {
return new RawSqlQueryPlanKey(sql, false, logWhereSql);
}
@@ -193,8 +177,8 @@ public class CQueryPlan implements SpiQueryPlan {
}
@Override
public String getHash() {
return hash;
public long getSqlHash() {
return sqlHash;
}
@Override
@@ -219,6 +203,10 @@ public class CQueryPlan implements SpiQueryPlan {
return location;
}
public long getLocationHash() {
return locationHash;
}
@Override
public void queryPlanInit(long thresholdMicros) {
bindCapture.queryPlanInit(thresholdMicros);
@@ -226,7 +214,7 @@ public class CQueryPlan implements SpiQueryPlan {
@Override
public DQueryPlanOutput createMeta(String bind, String planString) {
return new DQueryPlanOutput(getBeanType(), name, hash, sql, profileLocation, bind, planString);
return new DQueryPlanOutput(getBeanType(), name, sqlHash, sql, profileLocation, bind, planString);
}
public DataReader createDataReader(ResultSet rset) {
@@ -273,22 +261,7 @@ public class CQueryPlan implements SpiQueryPlan {
private String calcAuditQueryKey() {
// rawSql needs to include the MD5 hash of the sql
return rawSql ? planKey.getPartialKey() + "_" + hash : planKey.getPartialKey();
}
/**
* Return the MD5 hash of the sql.
*/
private String md5Hash() {
StringBuilder sb = new StringBuilder(sql)
.append("|").append(name)
.append("|").append(location);
try {
return Md5.hash(sb.toString());
} catch (Exception e) {
logger.error("Failed to MD5 hash the query", e);
return "error";
}
return rawSql ? planKey.getPartialKey() + "_" + sqlHash : planKey.getPartialKey();
}
SqlTree getSqlTree() {
@@ -24,10 +24,10 @@ class CQueryPlanRequest {
CQueryPlanRequest(Connection connection, QueryPlanRequest req, Iterator<CQueryBindCapture> iterator) {
this.connection = connection;
this.iterator = iterator;
this.maxCount = req.getMaxCount();
long reqSince = req.getSince();
this.maxCount = req.maxCount();
long reqSince = req.since();
this.since = (reqSince == 0) ? Long.MAX_VALUE: reqSince;
long maxTimeMillis = req.getMaxTimeMillis();
long maxTimeMillis = req.maxTimeMillis();
this.maxTime = maxTimeMillis > 0 ? System.currentTimeMillis() + maxTimeMillis : 0;
}
@@ -83,56 +83,61 @@ public final class CQueryPlanStats {
@Override
public String toString() {
return "label:" + getLabel() + " location:" + getLocation() + " metrics:" + metrics + " sql:" + getSql();
return "label:" + label() + " location:" + location() + " metrics:" + metrics + " sql:" + sql();
}
@Override
public Class<?> getType() {
public Class<?> type() {
return queryPlan.getBeanType();
}
@Override
public String getLabel() {
public String label() {
return queryPlan.getLabel();
}
@Override
public String getName() {
public String name() {
return queryPlan.getName();
}
@Override
public String getLocation() {
public String location() {
return queryPlan.getLocation();
}
@Override
public long getCount() {
return metrics.getCount();
public long locationHash() {
return queryPlan.getLocationHash();
}
@Override
public long getTotal() {
return metrics.getTotal();
public long count() {
return metrics.count();
}
@Override
public long getMax() {
return metrics.getMax();
public long total() {
return metrics.total();
}
@Override
public long getMean() {
return metrics.getMean();
public long max() {
return metrics.max();
}
@Override
public String getHash() {
return queryPlan.getHash();
public long mean() {
return metrics.mean();
}
@Override
public String getSql() {
public long sqlHash() {
return queryPlan.getSqlHash();
}
@Override
public String sql() {
return queryPlan.getSql();
}
@@ -16,15 +16,14 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
private final String sql;
private final String bind;
private final String plan;
private String hash;
private final long sqlHash;
private long queryTimeMicros;
private long captureCount;
DQueryPlanOutput(Class<?> beanType, String label, String hash, String sql, ProfileLocation profileLocation, String bind, String plan) {
DQueryPlanOutput(Class<?> beanType, String label, long sqlHash, String sql, ProfileLocation profileLocation, String bind, String plan) {
this.beanType = beanType;
this.label = label;
this.hash = hash;
this.sqlHash = sqlHash;
this.sql = sql;
this.profileLocation = profileLocation;
this.bind = bind;
@@ -32,15 +31,15 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
}
@Override
public String getHash() {
return hash;
public long sqlHash() {
return sqlHash;
}
/**
* Return the associated bean.
*/
@Override
public Class<?> getBeanType() {
public Class<?> beanType() {
return beanType;
}
@@ -48,12 +47,12 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
* Return the query label if set.
*/
@Override
public String getLabel() {
public String label() {
return label;
}
@Override
public ProfileLocation getProfileLocation() {
public ProfileLocation profileLocation() {
return profileLocation;
}
@@ -61,7 +60,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
* Return the sql of query.
*/
@Override
public String getSql() {
public String sql() {
return sql;
}
@@ -69,7 +68,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
* Return a description of the bind values used.
*/
@Override
public String getBind() {
public String bind() {
return bind;
}
@@ -77,7 +76,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
* Return the query plan.
*/
@Override
public String getPlan() {
public String plan() {
return plan;
}
@@ -86,7 +85,7 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
* to build the query plan.
*/
@Override
public long getQueryTimeMicros() {
public long queryTimeMicros() {
return queryTimeMicros;
}
@@ -94,13 +93,13 @@ class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
* Return the total count of times bind capture has occurred.
*/
@Override
public long getCaptureCount() {
public long captureCount() {
return captureCount;
}
@Override
public String toString() {
return " BeanType:" + ((beanType == null) ? "" : beanType.getSimpleName()) + " planHash:" + hash + " label:" + label + " queryTimeMicros:" + queryTimeMicros + " captureCount:" + captureCount + "\n SQL:" + sql + "\nBIND:" + bind + "\nPLAN:" + plan;
return " BeanType:" + ((beanType == null) ? "" : beanType.getSimpleName()) + " planHash:" + sqlHash + " label:" + label + " queryTimeMicros:" + queryTimeMicros + " captureCount:" + captureCount + "\n SQL:" + sql + "\nBIND:" + bind + "\nPLAN:" + plan;
}
/**
@@ -18,19 +18,15 @@ public class SqlBeanLoad {
private final DbReadContext ctx;
private final EntityBean bean;
private final EntityBeanIntercept ebi;
private final Class<?> type;
private final boolean lazyLoading;
private final boolean refreshLoading;
private final boolean rawSql;
SqlBeanLoad(DbReadContext ctx, Class<?> type, EntityBean bean, Mode queryMode) {
this.ctx = ctx;
this.rawSql = ctx.isRawSql();
this.type = type;
this.lazyLoading = queryMode == Mode.LAZYLOAD_BEAN;
this.refreshLoading = queryMode == Mode.REFRESH_BEAN;
this.bean = bean;
this.ebi = bean == null ? null : bean._ebean_getIntercept();
}
@@ -50,34 +46,21 @@ public class SqlBeanLoad {
}
public Object load(BeanProperty prop) {
if (!rawSql && !prop.isLoadProperty(ctx.isDraftQuery())) {
return null;
}
if ((bean == null)
|| (lazyLoading && ebi.isLoadedProperty(prop.getPropertyIndex()))
|| (type != null && !prop.isAssignableFrom(type))) {
// ignore this property
// ... null: bean already in persistence context
// ... lazyLoading: partial bean that is lazy loading
// ... type: inheritance and not assignable to this instance
prop.loadIgnore(ctx);
return null;
}
try {
Object dbVal = prop.read(ctx);
if (!refreshLoading) {
prop.setValue(bean, dbVal);
} else {
prop.setValueIntercept(bean, dbVal);
}
return dbVal;
return prop.readSet(ctx, bean);
} catch (Exception e) {
bean._ebean_getIntercept().setLoadError(prop.getPropertyIndex(), e);
ctx.handleLoadError(prop.getFullBeanName(), e);
@@ -89,10 +72,6 @@ public class SqlBeanLoad {
* Load the given value into the property.
*/
public void load(BeanProperty target, Object dbVal) {
if (!refreshLoading) {
target.setValue(bean, dbVal);
} else {
target.setValueIntercept(bean, dbVal);
}
target.setValue(bean, dbVal);
}
}
@@ -36,6 +36,7 @@ public class DataBind implements DataBinder {
private List<InputStream> inputStreams;
protected int pos;
private String json;
public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt, Connection connection) {
this.dataTimeZone = dataTimeZone;
@@ -43,6 +44,19 @@ public class DataBind implements DataBinder {
this.connection = connection;
}
@Override
public void pushJson(String json) {
assert this.json == null; // we can only push one value
this.json = json;
}
@Override
public String popJson() {
String ret = json;
json = null;
return ret;
}
@Override
public StringBuilder append(Object entry) {
return bindLog.append(entry);
@@ -2,7 +2,6 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.annotation.*;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.JsonConfig;
@@ -135,7 +134,7 @@ public final class DefaultTypeManager implements TypeManager {
this.postgres = isPostgres(config.getDatabasePlatform());
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.isJsonDirtyByDefault()) : null;
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.getJsonMutationDetection()) : null;
this.extraTypeFactory = new DefaultTypeFactory(config);
this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform());
this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform());
@@ -426,7 +425,7 @@ public final class DefaultTypeManager implements TypeManager {
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, (AnnotatedField) prop.getJacksonField(), dbType, docType);
return ScalarTypeJsonObjectMapper.createTypeFor(jsonManager, prop, dbType, docType);
}
/**
@@ -557,7 +556,7 @@ public final class DefaultTypeManager implements TypeManager {
// no override or further mapping required
return scalarType;
}
ScalarTypeEnum<?> scalarEnum = (ScalarTypeEnum<?>)scalarType;
ScalarTypeEnum<?> scalarEnum = (ScalarTypeEnum<?>) scalarType;
if (scalarEnum != null && !scalarEnum.isOverrideBy(type)) {
if (type != null && !scalarEnum.isCompatible(type)) {
throw new IllegalStateException("Error mapping Enum type:" + enumType + " It is mapped using 2 different modes when only one is supported (ORDINAL, STRING or an Ebean mapping)");
@@ -674,7 +673,7 @@ public final class DefaultTypeManager implements TypeManager {
private Object initObjectMapper(DatabaseConfig config) {
Object objectMapper = config.getObjectMapper();
if (objectMapper == null) {
objectMapper = new ObjectMapper();
objectMapper = InitObjectMapper.init();
config.setObjectMapper(objectMapper);
}
return objectMapper;
@@ -0,0 +1,22 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Initialise the Jackson ObjectMapper.
*/
class InitObjectMapper {
/**
* Create and return the default ObjectMapper.
*/
static Object init() {
SimpleModule module = new SimpleModule();
module.addAbstractTypeMapping(Set.class, LinkedHashSet.class);
return new ObjectMapper().registerModule(module);
}
}
@@ -20,22 +20,29 @@ import java.util.Calendar;
public class RsetDataReader implements DataReader {
private static final int bufferSize = 512;
static final int clobBufferSize = 512;
static final int stringInitialSize = 512;
private final DataTimeZone dataTimeZone;
private final ResultSet rset;
protected int pos;
private String json;
public RsetDataReader(DataTimeZone dataTimeZone, ResultSet rset) {
this.dataTimeZone = dataTimeZone;
this.rset = rset;
}
@Override
public void pushJson(String json) {
this.json = json;
}
@Override
public String popJson() {
return json;
}
@Override
public void close() throws SQLException {
rset.close();
@@ -7,14 +7,13 @@ import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.annotation.MutationDetection;
import io.ebean.core.type.DataBinder;
import io.ebean.core.type.DataReader;
import io.ebean.core.type.DocPropertyType;
import io.ebean.core.type.ScalarType;
import io.ebean.text.TextException;
import io.ebeaninternal.json.ModifyAwareList;
import io.ebeaninternal.json.ModifyAwareMap;
import io.ebeaninternal.json.ModifyAwareSet;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.PersistenceException;
import java.io.DataInput;
@@ -22,9 +21,6 @@ import java.io.DataOutput;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Supports @DbJson properties using Jackson ObjectMapper.
@@ -34,81 +30,85 @@ class ScalarTypeJsonObjectMapper {
/**
* Create and return the appropriate ScalarType.
*/
static ScalarType<?> createTypeFor(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
Class<?> type = field.getRawType();
if (Set.class.equals(type)) {
return new OmSet(jsonManager, field, dbType, docType);
static ScalarType<?> createTypeFor(TypeJsonManager jsonManager, DeployBeanProperty prop, int dbType, DocPropertyType docType) {
AnnotatedField field = (AnnotatedField) prop.getJacksonField();
MutationDetection mode = prop.getMutationDetection();
if (mode == MutationDetection.NONE) {
return new NoMutationDetection(jsonManager, field, dbType, docType);
} else if (mode != MutationDetection.DEFAULT) {
return new GenericObject(jsonManager, field, dbType, docType);
}
if (List.class.equals(type)) {
return new OmList(jsonManager, field, dbType, docType);
}
if (Map.class.equals(type)) {
return new OmMap(jsonManager, field, dbType);
}
return new GenericObject(jsonManager, field, dbType, type);
// using the global default MutationDetection mode (defaults to HASH)
prop.setMutationDetection(jsonManager.mutationDetection());
return new GenericObject(jsonManager, field, dbType, docType);
}
/**
* Maps any type (Object) using Jackson ObjectMapper.
* No mutation detection on this json property.
*/
private static class NoMutationDetection extends Base<Object> {
NoMutationDetection(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Object.class, jsonManager, field, dbType, docType);
}
@Override
public boolean isMutable() {
return false;
}
@Override
public boolean isDirty(Object value) {
return false;
}
}
/**
* Supports HASH and SOURCE dirty detection modes.
*/
private static class GenericObject extends Base<Object> {
GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, Class<?> rawType) {
super(Object.class, jsonManager, field, dbType, DocPropertyType.OBJECT, rawType);
}
}
/**
* Type for Sets wrapping the ObjectMapper Set as a ModifyAwareSet.
*/
@SuppressWarnings("rawtypes")
private static class OmSet extends Base<Set> {
OmSet(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Set.class, jsonManager, field, dbType, docType);
GenericObject(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(Object.class, jsonManager, field, dbType, docType);
}
@Override
@SuppressWarnings("unchecked")
public Set read(DataReader reader) throws SQLException {
Set value = super.read(reader);
return value == null ? null : new ModifyAwareSet(value);
}
}
/**
* Type for Lists wrapping the ObjectMapper List as a ModifyAwareList.
*/
@SuppressWarnings("rawtypes")
private static class OmList extends Base<List> {
OmList(TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
super(List.class, jsonManager, field, dbType, docType);
public boolean isJsonMapper() {
return true;
}
@Override
@SuppressWarnings("unchecked")
public List read(DataReader reader) throws SQLException {
List value = super.read(reader);
return value == null ? null : new ModifyAwareList(value);
}
}
/**
* Type for Map wrapping the ObjectMapper Map as a ModifyAwareMap.
*/
@SuppressWarnings("rawtypes")
private static class OmMap extends Base<Map> {
OmMap(TypeJsonManager jsonManager, AnnotatedField field, int dbType) {
super(Map.class, jsonManager, field, dbType, DocPropertyType.OBJECT);
public Object read(DataReader reader) throws SQLException {
String json = reader.getString();
if (json == null || json.isEmpty()) {
return null;
}
// pushJson such that we MD5 and store on EntityBeanIntercept later
reader.pushJson(json);
try {
return objectReader.readValue(json, deserType);
} catch (IOException e) {
throw new TextException("Failed to parse JSON [{}] as " + deserType, json, e);
}
}
@Override
@SuppressWarnings("unchecked")
public Map read(DataReader reader) throws SQLException {
Map value = super.read(reader);
return value == null ? null : new ModifyAwareMap(value);
public void bind(DataBinder binder, Object value) throws SQLException {
// popJson as dirty detection already converted to json string
String rawJson = binder.popJson();
if (rawJson == null && value != null) {
rawJson = formatValue(value); // not expected, need to check?
}
if (pgType != null) {
binder.setObject(PostgresHelper.asObject(pgType, rawJson));
} else {
if (value == null) {
// use varchar, otherwise SqlServer/db2 will fail with 'Invalid JDBC data type 5.001.'
binder.setNull(Types.VARCHAR);
} else {
binder.setString(rawJson);
}
}
}
}
@@ -118,44 +118,27 @@ class ScalarTypeJsonObjectMapper {
*/
private static abstract class Base<T> extends ScalarTypeBase<T> {
private final ObjectWriter objectWriter;
private final ObjectMapper objectReader;
private final JavaType deserType;
private final String pgType;
protected final ObjectWriter objectWriter;
protected final ObjectMapper objectReader;
protected final JavaType deserType;
protected final String pgType;
private final DocPropertyType docType;
private final TypeJsonManager.DirtyHandler dirtyHandler;
Base(Class<T> cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType) {
this(cls, jsonManager, field, dbType, docType, cls);
}
Base(Class<T> cls, TypeJsonManager jsonManager, AnnotatedField field, int dbType, DocPropertyType docType, Class<?> rawType) {
super(cls, false, dbType);
this.objectReader = jsonManager.objectMapper();
this.pgType = jsonManager.postgresType(dbType);
this.docType = docType;
this.dirtyHandler = jsonManager.dirtyHandler(cls, rawType);
final JacksonTypeHelper helper = new JacksonTypeHelper(field, objectReader);
this.deserType = helper.type();
this.objectWriter = helper.objectWriter();
}
/**
* Consider as a mutable type. Use the isDirty() method to check for dirty state.
*/
@Override
public boolean isMutable() {
return true;
}
/**
* Return true if the value should be considered dirty (and included in an update).
*/
@Override
public boolean isDirty(Object value) {
return dirtyHandler.isDirty(value);
}
@Override
public T read(DataReader reader) throws SQLException {
String json = reader.getString();
@@ -2,25 +2,23 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.ModifyAwareType;
import io.ebean.config.DatabaseConfig;
import io.ebean.annotation.MutationDetection;
import io.ebean.config.dbplatform.DbPlatformType;
class TypeJsonManager {
interface DirtyHandler {
boolean isDirty(Object value);
}
private final boolean postgres;
private final ObjectMapper objectMapper;
private final DirtyHandler defaultHandler;
private final DirtyHandler modifyAwareHandler;
private final MutationDetection mutationDetection;
TypeJsonManager(boolean postgres, Object objectMapper, boolean defaultDirty) {
TypeJsonManager(boolean postgres, Object objectMapper, MutationDetection mutationDetection) {
this.postgres = postgres;
this.objectMapper = (ObjectMapper) objectMapper;
this.defaultHandler = new DefaultHandler(defaultDirty);
this.modifyAwareHandler = new ModifyAwareHandler();
this.mutationDetection = mutationDetection;
}
MutationDetection mutationDetection() {
return mutationDetection;
}
ObjectMapper objectMapper() {
@@ -39,17 +37,6 @@ class TypeJsonManager {
return null;
}
/**
* Return the DirtyHandler to use.
*/
DirtyHandler dirtyHandler(Class<?> cls, Class<?> rawType) {
if (!Object.class.equals(cls) || ModifyAwareType.class.isAssignableFrom(rawType)) {
// Set, List and Map are modify aware
return modifyAwareHandler;
}
return defaultHandler;
}
/**
* Return true if the value should be considered dirty (and included in an update).
*/
@@ -71,28 +58,4 @@ class TypeJsonManager {
}
}
static final class ModifyAwareHandler implements DirtyHandler {
@Override
public boolean isDirty(Object value) {
return checkModifyAware(value);
}
}
/**
* Effectively constant based on {@link DatabaseConfig#isJsonDirtyByDefault()}
*/
static final class DefaultHandler implements DirtyHandler {
private final boolean dirty;
DefaultHandler(boolean dirty) {
this.dirty = dirty;
}
@Override
public boolean isDirty(Object value) {
return dirty;
}
}
}
@@ -0,0 +1,20 @@
package io.ebeaninternal.server.util;
import java.nio.charset.StandardCharsets;
import java.util.zip.CRC32;
/**
* Compute a checksum for String content. Use when we desire cheaper option than MD5.
*/
public final class Checksum {
/**
* Return the checksum for the given String input.
*/
public static long checksum(String input) {
CRC32 checksum = new CRC32();
final byte[] bytes = input.getBytes(StandardCharsets.UTF_8);
checksum.update(bytes, 0, bytes.length);
return checksum.getValue();
}
}
@@ -1,32 +0,0 @@
package io.ebeaninternal.server.util;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public final class Md5 {
/**
* Return the MD5 hash of the underlying sql.
*/
public static String hash(String content) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return digestToHex(md.digest(content.getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) {
throw new RuntimeException("MD5 hashing failed", e);
}
}
/**
* Convert the digest into a hex value.
*/
private static String digestToHex(byte[] digest) {
StringBuilder sb = new StringBuilder();
for (byte aDigest : digest) {
sb.append(Integer.toString((aDigest & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
}
@@ -90,14 +90,14 @@ public abstract class BaseTestCase {
}
protected List<MetaTimedMetric> visitTimedMetrics() {
return collectMetrics().getTimedMetrics();
return collectMetrics().timedMetrics();
}
protected List<MetaTimedMetric> sqlMetrics() {
List<MetaTimedMetric> timedMetrics = visitTimedMetrics();
return timedMetrics.stream()
.filter((it) -> it.getName().startsWith("sql.") || it.getName().startsWith("orm."))
.filter((it) -> it.name().startsWith("sql.") || it.name().startsWith("orm."))
.collect(Collectors.toList());
}
@@ -191,13 +191,13 @@ public class DtoQuery2Test extends BaseTestCase {
BasicMetricVisitor basic = new BasicMetricVisitor(false, true, true, true);
server().getMetaInfoManager().visitMetrics(basic);
List<MetaQueryMetric> stats = basic.getQueryMetrics();
List<MetaQueryMetric> stats = basic.queryMetrics();
assertThat(stats).hasSize(1);
MetaQueryMetric queryMetric = stats.get(0);
assertThat(queryMetric.getLabel()).isEqualTo("basic");
assertThat(queryMetric.getCount()).isEqualTo(3);
assertThat(queryMetric.getName()).isEqualTo("dto.DCust_basic");
assertThat(queryMetric.label()).isEqualTo("basic");
assertThat(queryMetric.count()).isEqualTo(3);
assertThat(queryMetric.name()).isEqualTo("dto.DCust_basic");
server().findDto(DCust.class, "select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name")
@@ -207,7 +207,7 @@ public class DtoQuery2Test extends BaseTestCase {
BasicMetricVisitor metric2 = server().getMetaInfoManager().visitBasic();
stats = metric2.getQueryMetrics();
stats = metric2.queryMetrics();
assertThat(stats).hasSize(2);
log.info("stats " + stats);
@@ -27,12 +27,12 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
@AfterClass
public static void reportStats() {
ServerMetrics metrics = DB.getDefault().getMetaInfoManager().collectMetrics();
for (MetaQueryMetric metric : metrics.getQueryMetrics()) {
for (MetaQueryMetric metric : metrics.queryMetrics()) {
System.out.println(metric);
}
System.out.println("-- transaction metrics --");
for (MetaTimedMetric metric : metrics.getTimedMetrics()) {
for (MetaTimedMetric metric : metrics.timedMetrics()) {
System.out.println(metric);
}
}
@@ -59,15 +59,15 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
ServerMetrics metrics = collectMetrics();
List<MetaQueryMetric> stats = metrics.getQueryMetrics();
List<MetaQueryMetric> stats = metrics.queryMetrics();
for (MetaQueryMetric stat : stats) {
long meanMicros = stat.getMean();
long meanMicros = stat.mean();
assertThat(meanMicros).isLessThan(900_000);
assertThat(stat.getLocation()).isSameAs(loc0.location());
assertThat(stat.location()).isSameAs(loc0.location());
}
assertThat(stats).hasSize(1);
assertThat(stats.get(0).getCount()).isEqualTo(4);
assertThat(stats.get(0).count()).isEqualTo(4);
}
@ForPlatform(Platform.H2)
@@ -42,14 +42,14 @@ public class DtoQueryTest extends BaseTestCase {
ServerMetrics metrics = collectMetrics();
List<MetaQueryMetric> stats = metrics.getQueryMetrics();
List<MetaQueryMetric> stats = metrics.queryMetrics();
for (MetaQueryMetric stat : stats) {
long meanMicros = stat.getMean();
long meanMicros = stat.mean();
assertThat(meanMicros).isLessThan(900_000);
}
assertThat(stats).hasSize(1);
assertThat(stats.get(0).getCount()).isEqualTo(1);
assertThat(stats.get(0).count()).isEqualTo(1);
}
@Test
@@ -283,13 +283,13 @@ public class DtoQueryTest extends BaseTestCase {
BasicMetricVisitor basic = new BasicMetricVisitor(false, true, true, true);
server().getMetaInfoManager().visitMetrics(basic);
List<MetaQueryMetric> stats = basic.getQueryMetrics();
List<MetaQueryMetric> stats = basic.queryMetrics();
assertThat(stats).hasSize(1);
MetaQueryMetric queryMetric = stats.get(0);
assertThat(queryMetric.getLabel()).isEqualTo("basic");
assertThat(queryMetric.getCount()).isEqualTo(3);
assertThat(queryMetric.getName()).isEqualTo("dto.DCust_basic");
assertThat(queryMetric.label()).isEqualTo("basic");
assertThat(queryMetric.count()).isEqualTo(3);
assertThat(queryMetric.name()).isEqualTo("dto.DCust_basic");
server().findDto(DCust.class, "select c4.id, c4.name from o_customer c4 where lower(c4.name) = :name")
@@ -299,7 +299,7 @@ public class DtoQueryTest extends BaseTestCase {
ServerMetrics metric2 = server().getMetaInfoManager().collectMetrics();
stats = metric2.getQueryMetrics();
stats = metric2.queryMetrics();
assertThat(stats).hasSize(2);
log.info("stats " + stats);
@@ -11,7 +11,7 @@ import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
public class EbeanServer_refresh {
@@ -39,8 +39,11 @@ public class EbeanServer_refresh {
assertEquals(rows, 1);
basic.setName("modify");
assertTrue(DB.getBeanState(basic).isDirty());
server.refresh(basic);
assertEquals(basic.getStatus(), EBasic.Status.ACTIVE);
assertFalse(DB.getBeanState(basic).isDirty());
}
@Test
@@ -39,10 +39,10 @@ public class UpdateQueryTest extends BaseTestCase {
assertSql(query).contains("update o_customer set status=?, updtime=? where status = ? and id > ?");
ServerMetrics metrics = collectMetrics();
List<MetaQueryMetric> ormQueryMetrics = metrics.getQueryMetrics();
List<MetaQueryMetric> ormQueryMetrics = metrics.queryMetrics();
assertThat(ormQueryMetrics).hasSize(1);
assertThat(ormQueryMetrics.get(0).getType()).isEqualTo(Customer.class);
assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateActive");
assertThat(ormQueryMetrics.get(0).type()).isEqualTo(Customer.class);
assertThat(ormQueryMetrics.get(0).label()).isEqualTo("updateActive");
}
@Test
@@ -69,10 +69,10 @@ public class UpdateQueryTest extends BaseTestCase {
assertSql(sql.get(0)).contains("update o_customer set status = status");
ServerMetrics metrics = collectMetrics();
List<MetaQueryMetric> ormQueryMetrics = metrics.getQueryMetrics();
List<MetaQueryMetric> ormQueryMetrics = metrics.queryMetrics();
assertThat(ormQueryMetrics).hasSize(1);
assertThat(ormQueryMetrics.get(0).getType()).isEqualTo(Customer.class);
assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateAll");
assertThat(ormQueryMetrics.get(0).type()).isEqualTo(Customer.class);
assertThat(ormQueryMetrics.get(0).label()).isEqualTo("updateAll");
}
@Test
@@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -13,9 +12,9 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanListTest {
private Object object1 = new Object();
private Object object2 = new Object();
private Object object3 = new Object();
private final Object object1 = new Object();
private final Object object2 = new Object();
private final Object object3 = new Object();
private List<Object> all() {
List<Object> all = new ArrayList<>();
@@ -33,7 +32,7 @@ public class BeanListTest {
}
@Test
public void test_setModifyListening_null() throws Exception {
public void test_setModifyListening_null() {
BeanList<Object> list = new BeanList<>();
list.setModifyListening(null);
@@ -45,7 +44,7 @@ public class BeanListTest {
}
@Test
public void test_setModifyListening_none() throws Exception {
public void test_setModifyListening_none() {
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.NONE);
@@ -57,28 +56,28 @@ public class BeanListTest {
}
@Test
public void testAdd() throws Exception {
public void testAdd() {
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.add(object1);
assertThat(list.getModifyAdditions()).containsExactly(object1);
assertThat(list.getModifyAdditions()).containsOnly(object1);
assertThat(list.getModifyRemovals()).isEmpty();
list.add(object1);
assertThat(list.getModifyAdditions()).containsExactly(object1);
assertThat(list.getModifyAdditions()).containsOnly(object1);
list.add(object2);
assertThat(list.getModifyAdditions()).containsExactly(object1, object2);
assertThat(list.getModifyAdditions()).containsOnly(object1, object2);
list.remove(object1);
assertThat(list.getModifyAdditions()).containsExactly(object2);
assertThat(list.getModifyAdditions()).containsOnly(object2);
assertThat(list.getModifyRemovals()).isEmpty();
}
@Test
public void testAddAll_given_emptyStart() throws Exception {
public void testAddAll_given_emptyStart() {
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -86,12 +85,12 @@ public class BeanListTest {
// act
list.addAll(all());
assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3);
assertThat(list.getModifyRemovals()).isEmpty();
}
@Test
public void test_removals_DeleteThenAddBack_expect_noChange() throws Exception {
public void test_removals_DeleteThenAddBack_expect_noChange() {
BeanList<Object> list = new BeanList<>(some());
list.setModifyListening(BeanCollection.ModifyListenMode.REMOVALS);
@@ -106,33 +105,33 @@ public class BeanListTest {
}
@Test
public void test_sort_whenAll_expect_noChange() throws Exception {
public void test_sort_whenAll_expect_noChange() {
BeanList<Object> list = new BeanList<>(all());
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
Collections.sort(list, Comparator.comparingInt(Object::hashCode));
list.sort(Comparator.comparingInt(Object::hashCode));
assertThat(list.getModifyRemovals()).isEmpty();
assertThat(list.getModifyAdditions()).isEmpty();
}
@Test
public void test_sort_whenRemovals_expect_noChange() throws Exception {
public void test_sort_whenRemovals_expect_noChange() {
BeanList<Object> list = new BeanList<>(all());
list.setModifyListening(BeanCollection.ModifyListenMode.REMOVALS);
// act
Collections.sort(list, Comparator.comparingInt(Object::hashCode));
list.sort(Comparator.comparingInt(Object::hashCode));
assertThat(list.getModifyRemovals()).isEmpty();
assertThat(list.getModifyAdditions()).isEmpty();
}
@Test
public void testAdd_given_someAlreadyIn() throws Exception {
public void testAdd_given_someAlreadyIn() {
BeanList<Object> list = new BeanList<>(some());
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -143,12 +142,12 @@ public class BeanListTest {
assertThat(list.contains(object2)).isTrue();
list.add(object2); // object2 added as List allows duplicates
assertThat(list.getModifyAdditions()).containsExactly(object1, object2);
assertThat(list.getModifyAdditions()).containsOnly(object1, object2);
assertThat(list.getModifyRemovals()).isEmpty();
}
@Test
public void testAddSome_given_someAlreadyIn() throws Exception {
public void testAddSome_given_someAlreadyIn() {
BeanList<Object> list = new BeanList<>(some());
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -156,43 +155,43 @@ public class BeanListTest {
// act
list.addAll(all());
assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3);
assertThat(list.getModifyRemovals()).isEmpty();
}
@Test
public void testRemove_given_beansInAdditions() throws Exception {
public void testRemove_given_beansInAdditions() {
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all());
assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
list.remove(object2);
list.remove(object3);
assertThat(list.getModifyAdditions()).containsExactly(object1);
assertThat(list.getModifyAdditions()).containsOnly(object1);
assertThat(list.getModifyRemovals()).isEmpty();
}
@Test
public void testRemoveAll_given_beansInAdditions() throws Exception {
public void testRemoveAll_given_beansInAdditions() {
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all());
assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
list.removeAll(some());
assertThat(list.getModifyAdditions()).containsExactly(object1);
assertThat(list.getModifyAdditions()).containsOnly(object1);
assertThat(list.getModifyRemovals()).isEmpty();
}
@Test
public void testRemove_given_beansNotInAdditions() throws Exception {
public void testRemove_given_beansNotInAdditions() {
BeanList<Object> list = new BeanList<>(all());
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -203,11 +202,11 @@ public class BeanListTest {
// assert
assertThat(list.getModifyAdditions()).isEmpty();
assertThat(list.getModifyRemovals()).containsExactly(object2, object3);
assertThat(list.getModifyRemovals()).containsOnly(object2, object3);
}
@Test
public void testRemoveAll_given_beansNotInAdditions() throws Exception {
public void testRemoveAll_given_beansNotInAdditions() {
BeanList<Object> list = new BeanList<>(all());
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -217,11 +216,11 @@ public class BeanListTest {
// assert
assertThat(list.getModifyAdditions()).isEmpty();
assertThat(list.getModifyRemovals()).containsExactly(object2, object3);
assertThat(list.getModifyRemovals()).containsOnly(object2, object3);
}
@Test
public void testClear() throws Exception {
public void testClear() {
BeanList<Object> list = new BeanList<>(all());
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -230,12 +229,12 @@ public class BeanListTest {
list.clear();
//assert
assertThat(list.getModifyRemovals()).containsExactly(object1, object2, object3);
assertThat(list.getModifyRemovals()).containsOnly(object1, object2, object3);
assertThat(list.getModifyAdditions()).isEmpty();
}
@Test
public void testClear_given_someBeansInAdditions() throws Exception {
public void testClear_given_someBeansInAdditions() {
BeanList<Object> list = new BeanList<>();
list.add(object1);
@@ -247,27 +246,27 @@ public class BeanListTest {
list.clear();
//assert
assertThat(list.getModifyRemovals()).containsExactly(object1);
assertThat(list.getModifyRemovals()).containsOnly(object1);
assertThat(list.getModifyAdditions()).isEmpty();
}
@Test
public void testRetainAll_given_beansInAdditions() throws Exception {
public void testRetainAll_given_beansInAdditions() {
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all());
assertThat(list.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(list.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
list.retainAll(some());
assertThat(list.getModifyAdditions()).containsExactly(object2, object3);
assertThat(list.getModifyAdditions()).containsOnly(object2, object3);
assertThat(list.getModifyRemovals()).isEmpty();
}
@Test
public void testRetainAll_given_someBeansInAdditions() throws Exception {
public void testRetainAll_given_someBeansInAdditions() {
BeanList<Object> list = new BeanList<>();
list.add(object1);
@@ -278,12 +277,12 @@ public class BeanListTest {
// act
list.retainAll(some());
assertThat(list.getModifyAdditions()).containsExactly(object3);
assertThat(list.getModifyRemovals()).containsExactly(object1);
assertThat(list.getModifyAdditions()).containsOnly(object3);
assertThat(list.getModifyRemovals()).containsOnly(object1);
}
@Test
public void testRetainAll_given_noBeansInAdditions() throws Exception {
public void testRetainAll_given_noBeansInAdditions() {
BeanList<Object> list = new BeanList<>(all());
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -291,7 +290,7 @@ public class BeanListTest {
// act
list.retainAll(some());
assertThat(list.getModifyRemovals()).containsExactly(object1);
assertThat(list.getModifyRemovals()).containsOnly(object1);
}
@Test
@@ -34,30 +34,30 @@ public class BeanMapTest {
}
@Test
public void testAdd() throws Exception {
public void testAdd() {
BeanMap<String, Object> map = new BeanMap<>();
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
map.put("1", object1);
map.put("4", null);
assertThat(map.getModifyAdditions()).containsExactly(object1);
assertThat(map.getModifyAdditions()).containsOnly(object1);
assertThat(map.getModifyRemovals()).isEmpty();
map.put("1", object1);
map.put("4", null);
assertThat(map.getModifyAdditions()).containsExactly(object1);
assertThat(map.getModifyAdditions()).containsOnly(object1);
map.put("2", object2);
assertThat(map.getModifyAdditions()).containsExactly(object1, object2);
assertThat(map.getModifyAdditions()).containsOnly(object1, object2);
map.remove("1");
assertThat(map.getModifyAdditions()).containsExactly(object2);
assertThat(map.getModifyAdditions()).containsOnly(object2);
assertThat(map.getModifyRemovals()).isEmpty();
}
@Test
public void testAddAll_given_emptyStart() throws Exception {
public void testAddAll_given_emptyStart() {
BeanMap<String, Object> set = new BeanMap<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -65,28 +65,28 @@ public class BeanMapTest {
// act
set.putAll(all());
assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testAdd_given_someAlreadyIn() throws Exception {
public void testAdd_given_someAlreadyIn() {
BeanMap<String, Object> map = new BeanMap<>(some());
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
assertThat(map.values().contains(object1)).isFalse();
assertThat(map.containsValue(object1)).isFalse();
map.put("1", object1);
assertThat(map.values().contains(object2)).isTrue();
assertThat(map.containsValue(object2)).isTrue();
map.put("2", object2);
assertThat(map.getModifyAdditions()).containsExactly(object1);
assertThat(map.getModifyAdditions()).containsOnly(object1);
assertThat(map.getModifyRemovals()).isEmpty();
}
@Test
public void testAddSome_given_someAlreadyIn() throws Exception {
public void testAddSome_given_someAlreadyIn() {
BeanMap<String, Object> map = new BeanMap<>(some());
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -94,44 +94,44 @@ public class BeanMapTest {
// act
map.putAll(all());
assertThat(map.getModifyAdditions()).containsExactly(object1);
assertThat(map.getModifyAdditions()).containsOnly(object1);
assertThat(map.getModifyRemovals()).isEmpty();
}
@Test
public void testRemove_given_beansInAdditions() throws Exception {
public void testRemove_given_beansInAdditions() {
BeanMap<String, Object> map = new BeanMap<>();
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
map.putAll(all());
assertThat(map.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(map.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
map.remove("2");
map.remove("3");
assertThat(map.getModifyAdditions()).containsExactly(object1);
assertThat(map.getModifyAdditions()).containsOnly(object1);
assertThat(map.getModifyRemovals()).isEmpty();
}
@Test
public void testRemoveAll_given_beansInAdditions() throws Exception {
public void testRemoveAll_given_beansInAdditions() {
BeanMap<String, Object> map = new BeanMap<>();
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
map.putAll(all());
assertThat(map.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(map.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
map.remove("2");
map.remove("3");
assertThat(map.getModifyAdditions()).containsExactly(object1);
assertThat(map.getModifyAdditions()).containsOnly(object1);
assertThat(map.getModifyRemovals()).isEmpty();
}
@Test
public void testRemove_given_beansNotInAdditions() throws Exception {
public void testRemove_given_beansNotInAdditions() {
BeanMap<String, Object> map = new BeanMap<>(all());
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -142,11 +142,11 @@ public class BeanMapTest {
// assert
assertThat(map.getModifyAdditions()).isEmpty();
assertThat(map.getModifyRemovals()).containsExactly(object2, object3);
assertThat(map.getModifyRemovals()).containsOnly(object2, object3);
}
@Test
public void testRemoveAll_given_beansNotInAdditions() throws Exception {
public void testRemoveAll_given_beansNotInAdditions() {
BeanMap<String, Object> map = new BeanMap<>(all());
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -157,11 +157,11 @@ public class BeanMapTest {
// assert
assertThat(map.getModifyAdditions()).isEmpty();
assertThat(map.getModifyRemovals()).containsExactly(object2, object3);
assertThat(map.getModifyRemovals()).containsOnly(object2, object3);
}
@Test
public void testClear() throws Exception {
public void testClear() {
BeanMap<String, Object> map = new BeanMap<>(all());
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -170,12 +170,12 @@ public class BeanMapTest {
map.clear();
//assert
assertThat(map.getModifyRemovals()).containsExactly(object1, object2, object3);
assertThat(map.getModifyRemovals()).containsOnly(object1, object2, object3);
assertThat(map.getModifyAdditions()).isEmpty();
}
@Test
public void testClear_given_someBeansInAdditions() throws Exception {
public void testClear_given_someBeansInAdditions() {
BeanMap<String, EBasic> map = newModifyListeningMap();
map.put("2", object2);
@@ -185,7 +185,7 @@ public class BeanMapTest {
map.clear();
//assert
assertThat(map.getModifyRemovals()).containsExactly(object1);
assertThat(map.getModifyRemovals()).containsOnly(object1);
assertThat(map.getModifyAdditions()).isEmpty();
}
@@ -228,7 +228,7 @@ public class BeanMapTest {
assertThat(map).doesNotContainKeys("1");
assertThat(map.get("1")).isNull();
assertThat(map.getModifyRemovals()).containsExactly(object1);
assertThat(map.getModifyRemovals()).containsOnly(object1);
}
@Test
@@ -245,7 +245,7 @@ public class BeanMapTest {
assertThat(map).isEmpty();
assertThat(keySet).isEmpty();
assertThat(map.getModifyRemovals()).containsExactly(object1, object2);
assertThat(map.getModifyRemovals()).containsOnly(object1, object2);
}
@Test
@@ -258,20 +258,14 @@ public class BeanMapTest {
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
final Set<String> keySet = map.keySet();
final Iterator<String> iterator = keySet.iterator();
while (iterator.hasNext()) {
final String key = iterator.next();
if (key.equals("2")) {
iterator.remove();
}
}
keySet.removeIf(key -> key.equals("2"));
assertThat(map).hasSize(2);
assertThat(keySet).hasSize(2);
assertThat(keySet).containsExactly("1", "3");
assertThat(map).containsKeys("1", "3");
assertThat(map.getModifyRemovals()).containsExactly(object2);
assertThat(map.getModifyRemovals()).containsOnly(object2);
}
@Test
@@ -294,7 +288,7 @@ public class BeanMapTest {
assertThat(keySet).containsExactly("1", "4");
assertThat(map).containsKeys("1", "4");
assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5);
assertThat(map.getModifyRemovals()).containsOnly(object2, object3, object5);
}
@@ -318,7 +312,7 @@ public class BeanMapTest {
assertThat(keySet).containsExactly("2", "3", "5");
assertThat(map).containsKeys("2", "3", "5");
assertThat(map.getModifyRemovals()).containsExactly(object1, object4);
assertThat(map.getModifyRemovals()).containsOnly(object1, object4);
}
@Test(expected = UnsupportedOperationException.class)
@@ -348,7 +342,7 @@ public class BeanMapTest {
assertThat(entries).isEmpty();
assertThat(map).isEmpty();
assertThat(map.getModifyRemovals()).containsExactly(object1);
assertThat(map.getModifyRemovals()).containsOnly(object1);
}
@Test
@@ -365,7 +359,7 @@ public class BeanMapTest {
assertThat(existed22).isFalse();
assertThat(map).hasSize(4);
assertThat(map.getModifyRemovals()).containsExactly(object1);
assertThat(map.getModifyRemovals()).containsOnly(object1);
}
@Test
@@ -395,7 +389,7 @@ public class BeanMapTest {
}
assertThat(map).hasSize(3);
assertThat(entries).hasSize(3);
assertThat(map.getModifyRemovals()).containsExactly(object2, object5);
assertThat(map.getModifyRemovals()).containsOnly(object2, object5);
}
@Test
@@ -406,7 +400,7 @@ public class BeanMapTest {
entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(3);
assertThat(entries).hasSize(3);
assertThat(map.getModifyRemovals()).containsExactly(object1, object4);
assertThat(map.getModifyRemovals()).containsOnly(object1, object4);
}
@Test
@@ -417,7 +411,7 @@ public class BeanMapTest {
entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(2);
assertThat(entries).hasSize(2);
assertThat(map.getModifyRemovals()).containsExactly(object2, object3, object5);
assertThat(map.getModifyRemovals()).containsOnly(object2, object3, object5);
}
private BeanMap<String, EBasic> newModifyListeningMap() {
@@ -31,28 +31,28 @@ public class BeanSetTest {
}
@Test
public void testAdd() throws Exception {
public void testAdd() {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.add(object1);
assertThat(set.getModifyAdditions()).containsExactly(object1);
assertThat(set.getModifyAdditions()).containsOnly(object1);
assertThat(set.getModifyRemovals()).isEmpty();
set.add(object1);
assertThat(set.getModifyAdditions()).containsExactly(object1);
assertThat(set.getModifyAdditions()).containsOnly(object1);
set.add(object2);
assertThat(set.getModifyAdditions()).containsExactly(object1, object2);
assertThat(set.getModifyAdditions()).containsOnly(object1, object2);
set.remove(object1);
assertThat(set.getModifyAdditions()).containsExactly(object2);
assertThat(set.getModifyAdditions()).containsOnly(object2);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testAddAll_given_emptyStart() throws Exception {
public void testAddAll_given_emptyStart() {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -60,12 +60,12 @@ public class BeanSetTest {
// act
set.addAll(all());
assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testAdd_given_someAlreadyIn() throws Exception {
public void testAdd_given_someAlreadyIn() {
BeanSet<Object> set = new BeanSet<>(some());
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -76,12 +76,12 @@ public class BeanSetTest {
assertThat(set.contains(object2)).isTrue();
set.add(object2);
assertThat(set.getModifyAdditions()).containsExactly(object1);
assertThat(set.getModifyAdditions()).containsOnly(object1);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testAddSome_given_someAlreadyIn() throws Exception {
public void testAddSome_given_someAlreadyIn() {
BeanSet<Object> set = new BeanSet<>(some());
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -89,43 +89,43 @@ public class BeanSetTest {
// act
set.addAll(all());
assertThat(set.getModifyAdditions()).containsExactly(object1);
assertThat(set.getModifyAdditions()).containsOnly(object1);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testRemove_given_beansInAdditions() throws Exception {
public void testRemove_given_beansInAdditions() {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.addAll(all());
assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
set.remove(object2);
set.remove(object3);
assertThat(set.getModifyAdditions()).containsExactly(object1);
assertThat(set.getModifyAdditions()).containsOnly(object1);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testRemoveAll_given_beansInAdditions() throws Exception {
public void testRemoveAll_given_beansInAdditions() {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.addAll(all());
assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
set.removeAll(some());
assertThat(set.getModifyAdditions()).containsExactly(object1);
assertThat(set.getModifyAdditions()).containsOnly(object1);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testRemove_given_beansNotInAdditions() throws Exception {
public void testRemove_given_beansNotInAdditions() {
BeanSet<Object> set = new BeanSet<>(all());
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -136,11 +136,11 @@ public class BeanSetTest {
// assert
assertThat(set.getModifyAdditions()).isEmpty();
assertThat(set.getModifyRemovals()).containsExactly(object2, object3);
assertThat(set.getModifyRemovals()).containsOnly(object2, object3);
}
@Test
public void testRemoveAll_given_beansNotInAdditions() throws Exception {
public void testRemoveAll_given_beansNotInAdditions() {
BeanSet<Object> set = new BeanSet<>(all());
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -150,11 +150,11 @@ public class BeanSetTest {
// assert
assertThat(set.getModifyAdditions()).isEmpty();
assertThat(set.getModifyRemovals()).containsExactly(object2, object3);
assertThat(set.getModifyRemovals()).containsOnly(object2, object3);
}
@Test
public void testClear() throws Exception {
public void testClear() {
BeanSet<Object> set = new BeanSet<>(all());
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -163,12 +163,12 @@ public class BeanSetTest {
set.clear();
//assert
assertThat(set.getModifyRemovals()).containsExactly(object1, object2, object3);
assertThat(set.getModifyRemovals()).containsOnly(object1, object2, object3);
assertThat(set.getModifyAdditions()).isEmpty();
}
@Test
public void testClear_given_someBeansInAdditions() throws Exception {
public void testClear_given_someBeansInAdditions() {
BeanSet<Object> set = new BeanSet<>();
set.add(object1);
@@ -180,27 +180,27 @@ public class BeanSetTest {
set.clear();
//assert
assertThat(set.getModifyRemovals()).containsExactly(object1);
assertThat(set.getModifyRemovals()).containsOnly(object1);
assertThat(set.getModifyAdditions()).isEmpty();
}
@Test
public void testRetainAll_given_beansInAdditions() throws Exception {
public void testRetainAll_given_beansInAdditions() {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
set.addAll(all());
assertThat(set.getModifyAdditions()).containsExactly(object1, object2, object3);
assertThat(set.getModifyAdditions()).containsOnly(object1, object2, object3);
// act
set.retainAll(some());
assertThat(set.getModifyAdditions()).containsExactly(object2, object3);
assertThat(set.getModifyAdditions()).containsOnly(object2, object3);
assertThat(set.getModifyRemovals()).isEmpty();
}
@Test
public void testRetainAll_given_someBeansInAdditions() throws Exception {
public void testRetainAll_given_someBeansInAdditions() {
BeanSet<Object> set = new BeanSet<>();
set.add(object1);
@@ -211,12 +211,12 @@ public class BeanSetTest {
// act
set.retainAll(some());
assertThat(set.getModifyAdditions()).containsExactly(object3);
assertThat(set.getModifyRemovals()).containsExactly(object1);
assertThat(set.getModifyAdditions()).containsOnly(object3);
assertThat(set.getModifyRemovals()).containsOnly(object1);
}
@Test
public void testRetainAll_given_noBeansInAdditions() throws Exception {
public void testRetainAll_given_noBeansInAdditions() {
BeanSet<Object> set = new BeanSet<>(all());
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
@@ -224,7 +224,7 @@ public class BeanSetTest {
// act
set.retainAll(some());
assertThat(set.getModifyRemovals()).containsExactly(object1);
assertThat(set.getModifyRemovals()).containsOnly(object1);
}
}
@@ -0,0 +1,194 @@
package io.ebean.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.annotation.MutationDetection;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.dbplatform.IdType;
import io.ebean.datasource.DataSourceConfig;
import org.junit.Test;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class DatabaseConfigTest {
@Test
public void testLoadFromEbeanProperties() {
DatabaseConfig config = new DatabaseConfig();
config.loadFromProperties();
assertEquals(PersistBatch.NONE, config.getPersistBatch());
assertNotNull(config.getProperties());
}
@Test
public void evalPropertiesInput() {
String home = System.getenv("HOME");
Properties props = new Properties();
props.setProperty("ddl.initSql", "${HOME}/initSql");
DatabaseConfig config = new DatabaseConfig();
config.loadFromProperties(props);
String ddlInitSql = config.getDdlInitSql();
assertThat(ddlInitSql).isEqualTo(home+"/initSql");
}
@Test
public void testLoadWithProperties() {
DatabaseConfig config = new DatabaseConfig();
config.setPersistBatch(PersistBatch.NONE);
config.setPersistBatchOnCascade(PersistBatch.NONE);
config.setAutoReadOnlyDataSource(false);
config.setReadOnlyDataSource(null);
config.setReadOnlyDataSourceConfig(new DataSourceConfig());
Properties props = new Properties();
props.setProperty("persistBatch", "ALL");
props.setProperty("persistBatchOnCascade", "ALL");
props.setProperty("dbuuid", "binary");
props.setProperty("jdbcFetchSizeFindEach", "42");
props.setProperty("jdbcFetchSizeFindList", "43");
props.setProperty("backgroundExecutorShutdownSecs", "98");
props.setProperty("backgroundExecutorSchedulePoolSize", "4");
props.setProperty("dbOffline", "true");
props.setProperty("jsonDateTime", "MILLIS");
props.setProperty("jsonDate", "MILLIS");
props.setProperty("jsonMutationDetection", "NONE");
props.setProperty("autoReadOnlyDataSource", "true");
props.setProperty("disableL2Cache", "true");
props.setProperty("notifyL2CacheInForeground", "true");
props.setProperty("idType", "SEQUENCE");
props.setProperty("mappingLocations", "classpath:/foo;bar");
props.setProperty("namingConvention", "io.ebean.config.MatchingNamingConvention");
props.setProperty("idGeneratorAutomatic", "true");
props.setProperty("enabledL2Regions", "r0,users,orgs");
props.setProperty("caseSensitiveCollation", "false");
props.setProperty("loadModuleInfo", "true");
props.setProperty("forUpdateNoKey", "true");
props.setProperty("defaultServer", "false");
props.setProperty("queryPlan.enable", "true");
props.setProperty("queryPlan.thresholdMicros", "10000");
props.setProperty("queryPlan.capture", "true");
props.setProperty("queryPlan.capturePeriodSecs", "42");
props.setProperty("queryPlan.captureMaxTimeMillis", "560");
props.setProperty("queryPlan.captureMaxCount", "7");
config.loadFromProperties(props);
assertFalse(config.isDefaultServer());
assertTrue(config.isDisableL2Cache());
assertTrue(config.isNotifyL2CacheInForeground());
assertTrue(config.isDbOffline());
assertTrue(config.isAutoReadOnlyDataSource());
assertTrue(config.isAutoLoadModuleInfo());
assertTrue(config.isIdGeneratorAutomatic());
assertFalse(config.getPlatformConfig().isCaseSensitiveCollation());
assertTrue(config.getPlatformConfig().isForUpdateNoKey());
assertThat(config.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class);
assertEquals(MutationDetection.NONE, config.getJsonMutationDetection());
config.setJsonMutationDetection(MutationDetection.SOURCE);
assertEquals(MutationDetection.SOURCE, config.getJsonMutationDetection());
assertEquals(IdType.SEQUENCE, config.getIdType());
assertEquals(PersistBatch.ALL, config.getPersistBatch());
assertEquals(PersistBatch.ALL, config.getPersistBatchOnCascade());
assertEquals(PlatformConfig.DbUuid.BINARY, config.getPlatformConfig().getDbUuid());
assertEquals(JsonConfig.DateTime.MILLIS, config.getJsonDateTime());
assertEquals(JsonConfig.Date.MILLIS, config.getJsonDate());
assertEquals("r0,users,orgs", config.getEnabledL2Regions());
assertEquals(42, config.getJdbcFetchSizeFindEach());
assertEquals(43, config.getJdbcFetchSizeFindList());
assertEquals(4, config.getBackgroundExecutorSchedulePoolSize());
assertEquals(98, config.getBackgroundExecutorShutdownSecs());
assertTrue(config.isQueryPlanEnable());
assertEquals(10000, config.getQueryPlanThresholdMicros());
assertTrue(config.isQueryPlanCapture());
assertEquals(42, config.getQueryPlanCapturePeriodSecs());
assertEquals(560, config.getQueryPlanCaptureMaxTimeMillis());
assertEquals(7, config.getQueryPlanCaptureMaxCount());
assertThat(config.getMappingLocations()).containsExactly("classpath:/foo","bar");
config.setPersistBatch(PersistBatch.NONE);
config.setPersistBatchOnCascade(PersistBatch.NONE);
Properties props1 = new Properties();
props1.setProperty("ebean.persistBatch", "ALL");
props1.setProperty("ebean.persistBatchOnCascade", "ALL");
config.setNotifyL2CacheInForeground(true);
config.setDisableL2Cache(true);
props1.setProperty("ebean.disableL2Cache", "false");
props1.setProperty("ebean.notifyL2CacheInForeground", "false");
config.loadFromProperties(props1);
assertFalse(config.isDisableL2Cache());
assertFalse(config.isNotifyL2CacheInForeground());
assertEquals(PersistBatch.ALL, config.getPersistBatch());
assertEquals(PersistBatch.ALL, config.getPersistBatchOnCascade());
config.setEnabledL2Regions("r0,orgs");
assertEquals("r0,orgs", config.getEnabledL2Regions());
}
@Test
public void test_defaults() {
DatabaseConfig config = new DatabaseConfig();
assertTrue(config.isIdGeneratorAutomatic());
assertTrue(config.isDefaultServer());
assertFalse(config.isAutoPersistUpdates());
config.setIdGeneratorAutomatic(false);
assertFalse(config.isIdGeneratorAutomatic());
assertEquals(JsonConfig.DateTime.ISO8601, config.getJsonDateTime());
assertEquals(JsonConfig.Date.ISO8601, config.getJsonDate());
assertEquals(MutationDetection.HASH, config.getJsonMutationDetection());
assertTrue(config.getPlatformConfig().isCaseSensitiveCollation());
assertTrue(config.isAutoLoadModuleInfo());
assertFalse(config.isQueryPlanEnable());
assertEquals(Long.MAX_VALUE, config.getQueryPlanThresholdMicros());
assertFalse(config.isQueryPlanCapture());
assertEquals(600, config.getQueryPlanCapturePeriodSecs());
assertEquals(10000L, config.getQueryPlanCaptureMaxTimeMillis());
assertEquals(10, config.getQueryPlanCaptureMaxCount());
config.setLoadModuleInfo(false);
assertFalse(config.isAutoLoadModuleInfo());
config.setAutoPersistUpdates(true);
assertTrue(config.isAutoPersistUpdates());
}
@Test
public void test_putServiceObject() {
ObjectMapper objectMapper = new ObjectMapper();
DatabaseConfig config = new DatabaseConfig();
config.putServiceObject(objectMapper);
ObjectMapper mapper0 = config.getServiceObject(ObjectMapper.class);
ObjectMapper mapper1 = (ObjectMapper)config.getServiceObject("objectMapper");
assertThat(objectMapper).isSameAs(mapper0);
assertThat(objectMapper).isSameAs(mapper1);
}
}
@@ -1,193 +0,0 @@
package io.ebean.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.dbplatform.IdType;
import io.ebean.datasource.DataSourceConfig;
import org.junit.Test;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class ServerConfigTest {
@Test
public void testLoadFromEbeanProperties() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.loadFromProperties();
assertEquals(PersistBatch.NONE, serverConfig.getPersistBatch());
assertNotNull(serverConfig.getProperties());
}
@Test
public void evalPropertiesInput() {
String home = System.getenv("HOME");
Properties props = new Properties();
props.setProperty("ddl.initSql", "${HOME}/initSql");
ServerConfig serverConfig = new ServerConfig();
serverConfig.loadFromProperties(props);
String ddlInitSql = serverConfig.getDdlInitSql();
assertThat(ddlInitSql).isEqualTo(home+"/initSql");
}
@Test
public void testLoadWithProperties() {
ServerConfig serverConfig = new ServerConfig();
serverConfig.setPersistBatch(PersistBatch.NONE);
serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
serverConfig.setAutoReadOnlyDataSource(false);
serverConfig.setReadOnlyDataSource(null);
serverConfig.setReadOnlyDataSourceConfig(new DataSourceConfig());
Properties props = new Properties();
props.setProperty("persistBatch", "ALL");
props.setProperty("persistBatchOnCascade", "ALL");
props.setProperty("dbuuid", "binary");
props.setProperty("jdbcFetchSizeFindEach", "42");
props.setProperty("jdbcFetchSizeFindList", "43");
props.setProperty("backgroundExecutorShutdownSecs", "98");
props.setProperty("backgroundExecutorSchedulePoolSize", "4");
props.setProperty("dbOffline", "true");
props.setProperty("jsonDateTime", "MILLIS");
props.setProperty("jsonDate", "MILLIS");
props.setProperty("jsonDirtyByDefault", "false");
props.setProperty("autoReadOnlyDataSource", "true");
props.setProperty("disableL2Cache", "true");
props.setProperty("notifyL2CacheInForeground", "true");
props.setProperty("idType", "SEQUENCE");
props.setProperty("mappingLocations", "classpath:/foo;bar");
props.setProperty("namingConvention", "io.ebean.config.MatchingNamingConvention");
props.setProperty("idGeneratorAutomatic", "true");
props.setProperty("enabledL2Regions", "r0,users,orgs");
props.setProperty("caseSensitiveCollation", "false");
props.setProperty("loadModuleInfo", "true");
props.setProperty("forUpdateNoKey", "true");
props.setProperty("defaultServer", "false");
props.setProperty("queryPlan.enable", "true");
props.setProperty("queryPlan.thresholdMicros", "10000");
props.setProperty("queryPlan.capture", "true");
props.setProperty("queryPlan.capturePeriodSecs", "42");
props.setProperty("queryPlan.captureMaxTimeMillis", "560");
props.setProperty("queryPlan.captureMaxCount", "7");
serverConfig.loadFromProperties(props);
assertFalse(serverConfig.isDefaultServer());
assertTrue(serverConfig.isDisableL2Cache());
assertTrue(serverConfig.isNotifyL2CacheInForeground());
assertTrue(serverConfig.isDbOffline());
assertTrue(serverConfig.isAutoReadOnlyDataSource());
assertTrue(serverConfig.isAutoLoadModuleInfo());
assertTrue(serverConfig.isIdGeneratorAutomatic());
assertFalse(serverConfig.getPlatformConfig().isCaseSensitiveCollation());
assertTrue(serverConfig.getPlatformConfig().isForUpdateNoKey());
assertThat(serverConfig.getNamingConvention()).isInstanceOf(MatchingNamingConvention.class);
assertEquals(IdType.SEQUENCE, serverConfig.getIdType());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
assertEquals(PlatformConfig.DbUuid.BINARY, serverConfig.getPlatformConfig().getDbUuid());
assertEquals(JsonConfig.DateTime.MILLIS, serverConfig.getJsonDateTime());
assertEquals(JsonConfig.Date.MILLIS, serverConfig.getJsonDate());
assertFalse(serverConfig.isJsonDirtyByDefault());
serverConfig.setJsonDirtyByDefault(true);
assertTrue(serverConfig.isJsonDirtyByDefault());
assertEquals("r0,users,orgs", serverConfig.getEnabledL2Regions());
assertEquals(42, serverConfig.getJdbcFetchSizeFindEach());
assertEquals(43, serverConfig.getJdbcFetchSizeFindList());
assertEquals(4, serverConfig.getBackgroundExecutorSchedulePoolSize());
assertEquals(98, serverConfig.getBackgroundExecutorShutdownSecs());
assertTrue(serverConfig.isQueryPlanEnable());
assertEquals(10000, serverConfig.getQueryPlanThresholdMicros());
assertTrue(serverConfig.isQueryPlanCapture());
assertEquals(42, serverConfig.getQueryPlanCapturePeriodSecs());
assertEquals(560, serverConfig.getQueryPlanCaptureMaxTimeMillis());
assertEquals(7, serverConfig.getQueryPlanCaptureMaxCount());
assertThat(serverConfig.getMappingLocations()).containsExactly("classpath:/foo","bar");
serverConfig.setPersistBatch(PersistBatch.NONE);
serverConfig.setPersistBatchOnCascade(PersistBatch.NONE);
Properties props1 = new Properties();
props1.setProperty("ebean.persistBatch", "ALL");
props1.setProperty("ebean.persistBatchOnCascade", "ALL");
serverConfig.setNotifyL2CacheInForeground(true);
serverConfig.setDisableL2Cache(true);
props1.setProperty("ebean.disableL2Cache", "false");
props1.setProperty("ebean.notifyL2CacheInForeground", "false");
serverConfig.loadFromProperties(props1);
assertFalse(serverConfig.isDisableL2Cache());
assertFalse(serverConfig.isNotifyL2CacheInForeground());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatchOnCascade());
serverConfig.setEnabledL2Regions("r0,orgs");
assertEquals("r0,orgs", serverConfig.getEnabledL2Regions());
}
@Test
public void test_defaults() {
ServerConfig serverConfig = new ServerConfig();
assertTrue(serverConfig.isIdGeneratorAutomatic());
assertTrue(serverConfig.isDefaultServer());
assertFalse(serverConfig.isAutoPersistUpdates());
serverConfig.setIdGeneratorAutomatic(false);
assertFalse(serverConfig.isIdGeneratorAutomatic());
assertEquals(JsonConfig.DateTime.ISO8601, serverConfig.getJsonDateTime());
assertEquals(JsonConfig.Date.ISO8601, serverConfig.getJsonDate());
assertTrue(serverConfig.isJsonDirtyByDefault());
assertTrue(serverConfig.getPlatformConfig().isCaseSensitiveCollation());
assertTrue(serverConfig.isAutoLoadModuleInfo());
assertFalse(serverConfig.isQueryPlanEnable());
assertEquals(Long.MAX_VALUE, serverConfig.getQueryPlanThresholdMicros());
assertFalse(serverConfig.isQueryPlanCapture());
assertEquals(600, serverConfig.getQueryPlanCapturePeriodSecs());
assertEquals(10000L, serverConfig.getQueryPlanCaptureMaxTimeMillis());
assertEquals(10, serverConfig.getQueryPlanCaptureMaxCount());
serverConfig.setLoadModuleInfo(false);
assertFalse(serverConfig.isAutoLoadModuleInfo());
serverConfig.setAutoPersistUpdates(true);
assertTrue(serverConfig.isAutoPersistUpdates());
}
@Test
public void test_putServiceObject() {
ObjectMapper objectMapper = new ObjectMapper();
ServerConfig config = new ServerConfig();
config.putServiceObject(objectMapper);
ObjectMapper mapper0 = config.getServiceObject(ObjectMapper.class);
ObjectMapper mapper1 = (ObjectMapper)config.getServiceObject("objectMapper");
assertThat(objectMapper).isSameAs(mapper0);
assertThat(objectMapper).isSameAs(mapper1);
}
}
@@ -650,7 +650,7 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
}
@Override
public <T> boolean exists(Query<?> ormQuery, Transaction transaction) {
public <T> boolean exists(Query<T> ormQuery, Transaction transaction) {
return false;
}
@@ -23,11 +23,11 @@ public class BeanIudMetricsTest {
BasicMetricVisitor basic = new BasicMetricVisitor();
iudMetrics.visit(basic);
List<MetaTimedMetric> timed = basic.getTimedMetrics();
List<MetaTimedMetric> timed = basic.timedMetrics();
assertThat(timed).hasSize(1);
assertThat(timed.get(0).getCount()).isEqualTo(4);
assertThat(timed.get(0).getName()).isEqualTo("iud.one.insertBatch");
assertThat(timed.get(0).count()).isEqualTo(4);
assertThat(timed.get(0).name()).isEqualTo("iud.one.insertBatch");
iudMetrics.addBatch(PersistRequest.Type.UPDATE, startNanos, 1);
iudMetrics.addBatch(PersistRequest.Type.DELETE_SOFT, startNanos, 2);
@@ -37,15 +37,15 @@ public class BeanIudMetricsTest {
basic = new BasicMetricVisitor();
iudMetrics.visit(basic);
timed = basic.getTimedMetrics();
timed = basic.timedMetrics();
assertThat(timed).hasSize(3);
assertThat(timed.get(0).getCount()).isEqualTo(16);
assertThat(timed.get(0).getName()).isEqualTo("iud.one.insertBatch");
assertThat(timed.get(1).getCount()).isEqualTo(3);
assertThat(timed.get(1).getName()).isEqualTo("iud.one.updateBatch");
assertThat(timed.get(2).getCount()).isEqualTo(12);
assertThat(timed.get(2).getName()).isEqualTo("iud.one.deleteBatch");
assertThat(timed.get(0).count()).isEqualTo(16);
assertThat(timed.get(0).name()).isEqualTo("iud.one.insertBatch");
assertThat(timed.get(1).count()).isEqualTo(3);
assertThat(timed.get(1).name()).isEqualTo("iud.one.updateBatch");
assertThat(timed.get(2).count()).isEqualTo(12);
assertThat(timed.get(2).name()).isEqualTo("iud.one.deleteBatch");
}
@Test
@@ -63,15 +63,15 @@ public class BeanIudMetricsTest {
BasicMetricVisitor basic = new BasicMetricVisitor();
iudMetrics.visit(basic);
List<MetaTimedMetric> timed = basic.getTimedMetrics();
List<MetaTimedMetric> timed = basic.timedMetrics();
assertThat(timed).hasSize(3);
assertThat(timed.get(0).getCount()).isEqualTo(1);
assertThat(timed.get(0).getName()).isEqualTo("iud.one.insert");
assertThat(timed.get(1).getCount()).isEqualTo(2);
assertThat(timed.get(1).getName()).isEqualTo("iud.one.update");
assertThat(timed.get(2).getCount()).isEqualTo(2);
assertThat(timed.get(2).getName()).isEqualTo("iud.one.delete");
assertThat(timed.get(0).count()).isEqualTo(1);
assertThat(timed.get(0).name()).isEqualTo("iud.one.insert");
assertThat(timed.get(1).count()).isEqualTo(2);
assertThat(timed.get(1).name()).isEqualTo("iud.one.update");
assertThat(timed.get(2).count()).isEqualTo(2);
assertThat(timed.get(2).name()).isEqualTo("iud.one.delete");
}
}
@@ -21,17 +21,17 @@ public class DTimedMetricMapTest {
BasicMetricVisitor visitor = new BasicMetricVisitor();
metricMap.visit(visitor);
MetaTimedMetric timedMetric = visitor.getTimedMetrics().get(0);
assertThat(timedMetric.getCount()).isEqualTo(1);
assertThat(timedMetric.getTotal()).isGreaterThan(10);
MetaTimedMetric timedMetric = visitor.timedMetrics().get(0);
assertThat(timedMetric.count()).isEqualTo(1);
assertThat(timedMetric.total()).isGreaterThan(10);
metricMap.addSinceNanos("some", nanos);
visitor = new BasicMetricVisitor();
metricMap.visit(visitor);
timedMetric = visitor.getTimedMetrics().get(0);
assertThat(timedMetric.getCount()).isEqualTo(1);
assertThat(timedMetric.getTotal()).isGreaterThan(10);
timedMetric = visitor.timedMetrics().get(0);
assertThat(timedMetric.count()).isEqualTo(1);
assertThat(timedMetric.total()).isGreaterThan(10);
}
}
@@ -17,16 +17,16 @@ public class DTimedMetricTest {
metric.addSinceNanos(start);
DTimeMetricStats stats = metric.collect(true);
assertThat(stats.getCount()).isEqualTo(1);
assertThat(stats.getTotal()).isGreaterThan(10);
assertThat(stats.getMax()).isEqualTo(stats.getTotal());
assertThat(stats.count()).isEqualTo(1);
assertThat(stats.total()).isGreaterThan(10);
assertThat(stats.max()).isEqualTo(stats.total());
metric.addSinceNanos(start);
stats = metric.collect(true);
assertThat(stats.getCount()).isEqualTo(1);
assertThat(stats.getTotal()).isGreaterThan(10);
assertThat(stats.getMax()).isEqualTo(stats.getTotal());
assertThat(stats.count()).isEqualTo(1);
assertThat(stats.total()).isGreaterThan(10);
assertThat(stats.max()).isEqualTo(stats.total());
}
@Test
@@ -40,16 +40,16 @@ public class DTimedMetricTest {
metric.addBatchSince(start, 5);
DTimeMetricStats stats = metric.collect(true);
assertThat(stats.getCount()).isEqualTo(5);
assertThat(stats.getTotal()).isGreaterThan(10000);
assertThat(stats.getMax()).isEqualTo(stats.getTotal() / 5);
assertThat(stats.getMax()).isGreaterThan(10000 / 5);
assertThat(stats.count()).isEqualTo(5);
assertThat(stats.total()).isGreaterThan(10000);
assertThat(stats.max()).isEqualTo(stats.total() / 5);
assertThat(stats.max()).isGreaterThan(10000 / 5);
metric.addBatchSince(start, 2);
stats = metric.collect(true);
assertThat(stats.getCount()).isEqualTo(2);
assertThat(stats.getTotal()).isGreaterThan(10000);
assertThat(stats.getMax()).isEqualTo(stats.getTotal() / 2);
assertThat(stats.count()).isEqualTo(2);
assertThat(stats.total()).isGreaterThan(10000);
assertThat(stats.max()).isEqualTo(stats.total() / 2);
}
}
@@ -26,7 +26,7 @@ public class SortMetricTest {
list.add(create("a"));
list.sort(sortMetric);
String names = list.stream().map(DTimeMetricStats::getName).collect(Collectors.joining());
String names = list.stream().map(DTimeMetricStats::name).collect(Collectors.joining());
assertEquals("nullabcd", names);
}
@@ -8,8 +8,14 @@ public class UtilLocationTest {
@Test
public void label() {
assertThat(UtilLocation.label("foo")).isEqualTo("foo");
assertThat(UtilLocation.label("ProfileLocationTest$Other.<init>(ProfileLocationTest.java:47)")).isEqualTo("ProfileLocationTest$Other.init");
}
@Test
public void hash() {
assertThat(UtilLocation.hash("org.foo.MyFoo.doIt(MyFoo.java:12)")).isEqualTo(396279222L);
assertThat(UtilLocation.hash("org.foo.MyFoo.doIt(MyFoo.java:13)")).isEqualTo(396279222L);
assertThat(UtilLocation.hash("org.foo.MyFoo.doIt(MyFoo.java:945)")).isEqualTo(396279222L);
}
}
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.util;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class ChecksumTest {
@Test
public void checksum() {
final long val = Checksum.checksum("Hello world");
assertThat(val).isEqualTo(2346098258L);
assertThat(Checksum.checksum("Hello world")).isEqualTo(val);
assertThat(Checksum.checksum("hello world")).isNotEqualTo(val);
}
@Test
public void checksum_shortString() {
final long val0 = Checksum.checksum("2012-01-11");
final long val1 = Checksum.checksum("2012-10-02");
assertThat(val0).isNotEqualTo(val1);
}
}
@@ -1,18 +0,0 @@
package io.ebeaninternal.server.util;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class Md5Test {
@Test
public void hash() throws Exception {
String content = "some random content we wish to hash";
String hash1 = Md5.hash(content);
String hash2 = Md5.hash(content);
assertEquals(hash1, hash2);
}
}
@@ -80,15 +80,15 @@ public class TestBatchInsertFlush extends BaseTestCase {
}
ServerMetrics metrics = collectMetrics();
List<MetaTimedMetric> txnStats = metrics.getTimedMetrics();
List<MetaTimedMetric> txnStats = metrics.timedMetrics();
for (MetaTimedMetric txnMetric : txnStats) {
System.out.println(txnMetric);
}
assertThat(txnStats).hasSize(4);
assertThat(txnStats.get(0).getName()).isEqualTo("txn.main");
assertThat(txnStats.get(1).getName()).isEqualTo("txn.named.TestBatchInsertFlush.no_cascade");
assertThat(txnStats.get(2).getName()).isEqualTo("iud.TSDetail.insertBatch");
assertThat(txnStats.get(3).getName()).isEqualTo("iud.TSMaster.insertBatch");
assertThat(txnStats.get(0).name()).isEqualTo("txn.main");
assertThat(txnStats.get(1).name()).isEqualTo("txn.named.TestBatchInsertFlush.no_cascade");
assertThat(txnStats.get(2).name()).isEqualTo("iud.TSDetail.insertBatch");
assertThat(txnStats.get(3).name()).isEqualTo("iud.TSMaster.insertBatch");
}
@Test
@@ -1,7 +1,11 @@
package org.tests.json;
import io.ebean.BaseTestCase;
import io.ebean.BeanState;
import io.ebean.DB;
import io.ebean.ValuePair;
import io.ebean.event.BeanPersistAdapter;
import io.ebean.event.BeanPersistRequest;
import io.ebeantest.LoggedSql;
import org.junit.Test;
import org.tests.model.json.EBasicJsonJackson3;
@@ -11,11 +15,33 @@ import org.tests.model.json.PlainBeanDirtyAware;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class TestDbJson_Jackson3 extends BaseTestCase {
public static class EBasicJsonListPersistController extends BeanPersistAdapter {
private static Map<String, ValuePair> updatedValues;
@Override
public boolean isRegisterFor(Class<?> cls) {
return EBasicJsonList.class.isAssignableFrom(cls);
}
@Override
public boolean preInsert(BeanPersistRequest<?> request) {
updatedValues = request.getUpdatedValues();
return true;
}
@Override
public boolean preUpdate(BeanPersistRequest<?> request) {
updatedValues = request.getUpdatedValues();
return true;
}
}
@Test
public void updateIncludesJsonColumn_when_explicit_isMarkedDirty() {
@@ -24,6 +50,7 @@ public class TestDbJson_Jackson3 extends BaseTestCase {
EBasicJsonJackson3 bean = new EBasicJsonJackson3();
bean.setName("b1");
bean.setPlainValue(contentBean);
bean.setPlainValue2(contentBean);
bean.save();
@@ -32,20 +59,15 @@ public class TestDbJson_Jackson3 extends BaseTestCase {
LoggedSql.start();
found.save();
List<String> sql = LoggedSql.collect();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update ebasic_json_jackson3 set name=?, version=? where id=? and version=?");
expectedSql(0, "update ebasic_json_jackson3 set name=?, version=? where id=? and version=?");
found.setName("b1-mod2");
found.getPlainValue().setName("b");
found.getPlainValue().setMarkedDirty(true);
// found.getPlainValue().setMarkedDirty(true); // Irrelevant for SOURCE or HASH based mutation detection
found.save();
sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update ebasic_json_jackson3 set name=?, plain_value=?, version=? where id=? and version=?");
expectedSql(0, "update ebasic_json_jackson3 set name=?, plain_value=?, version=? where id=? and version=?");
LoggedSql.stop();
final EBasicJsonJackson3 found2 = DB.find(EBasicJsonJackson3.class, bean.getId());
@@ -69,11 +91,138 @@ public class TestDbJson_Jackson3 extends BaseTestCase {
found.setName("p1-mod");
found.setBeanList(null);
BeanState state = DB.getBeanState(found);
assertThat(state.getChangedProps()).containsExactlyInAnyOrder("name", "beanList");
ValuePair pair = state.getDirtyValues().get("name");
assertThat(pair.getNewValue()).isEqualTo("p1-mod");
assertThat(pair.getOldValue()).isEqualTo("p1");
pair = state.getDirtyValues().get("beanList");
assertThat(pair.getNewValue()).isEqualTo(null);
assertThat((List<PlainBean>)pair.getOldValue()).hasSize(1)
.extracting(PlainBean::getName).containsExactly("a");
LoggedSql.start();
DB.save(found);
final List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
assertThat(sql.get(0)).contains("update ebasic_json_list set name=?, bean_list=?, plain_bean=?, version=? where id=?");
// plain_bean=?, no longer included with MD5 dirty detection
expectedSql(0, "update ebasic_json_list set name=?, bean_list=?, version=? where id=?");
assertThat(EBasicJsonListPersistController.updatedValues.entrySet())
.extracting(Map.Entry::toString)
.containsExactlyInAnyOrder("beanList=null,[name:a]","name=p1-mod,p1","version=2,1");
assertThat(DB.getBeanState(found).isDirty()).isFalse();
found.getPlainBean().setName("b");
assertThat(DB.getBeanState(found).isDirty()).isTrue();
state = DB.getBeanState(found);
assertThat(state.getChangedProps()).containsExactlyInAnyOrder("plainBean");
pair = state.getDirtyValues().get("plainBean");
assertThat(pair.getNewValue()).hasToString("name:b");
assertThat(pair.getOldValue()).hasToString("name:a");
LoggedSql.start();
DB.save(found);
// plain_bean=?, no longer included with MD5 dirty detection
expectedSql(0, "update ebasic_json_list set plain_bean=?, version=? where id=?");
assertThat(EBasicJsonListPersistController.updatedValues.entrySet())
.extracting(Map.Entry::toString)
.containsExactlyInAnyOrder("plainBean=name:b,name:a", "version=3,2");
LoggedSql.stop();
}
@Test
public void updateIncludesJsonColumn_when_list_loadedAndNotDirtyAware() {
PlainBean contentBean = new PlainBean("a", 42);
EBasicJsonList bean = new EBasicJsonList();
bean.setName("p1");
bean.setPlainBean(contentBean);
bean.setBeanList(Arrays.asList(contentBean));
DB.save(bean);
final EBasicJsonList found = DB.find(EBasicJsonList.class, bean.getId());
found.getBeanList().get(0).setName("p1-mod");
BeanState state = DB.getBeanState(found);
assertThat(state.getChangedProps()).containsExactlyInAnyOrder("beanList");
}
@Test
public void update_with_differentDbJsonSettings() {
PlainBeanDirtyAware contentBean1 = new PlainBeanDirtyAware("x", 42);
PlainBeanDirtyAware contentBean2 = new PlainBeanDirtyAware("y", 43);
PlainBeanDirtyAware contentBean3 = new PlainBeanDirtyAware("z", 44);
EBasicJsonJackson3 bean = new EBasicJsonJackson3();
bean.setName("b1");
bean.setPlainValue(contentBean1);
bean.setPlainValue2(contentBean2);
bean.setPlainValue3(contentBean3);
BeanState state = DB.getBeanState(bean);
// a new bean is not considered as dirty (thus have no changed props)
assertThat(state.isDirty()).isFalse();
assertThat(state.isNewOrDirty()).isTrue();
assertThat(state.getChangedProps()).isEmpty();
bean.save();
bean = DB.find(EBasicJsonJackson3.class, bean.getId());
state = DB.getBeanState(bean);
// a fresh loaded bean is also not considered as dirty
assertThat(state.isDirty()).isFalse();
assertThat(state.isNewOrDirty()).isFalse();
assertThat(state.getChangedProps()).isEmpty();
bean.getPlainValue().setName("a"); // has SOURCE
assertThat(state.isDirty()).isTrue();
assertThat(state.getChangedProps()).containsExactly("plainValue");
bean.getPlainValue2().setName("b");
assertThat(state.getChangedProps()).containsExactlyInAnyOrder("plainValue", "plainValue2");
bean.getPlainValue3().setName("c"); // has mutationDetection = NONE
Map<String, ValuePair> dirtyValues = state.getDirtyValues();
assertThat(dirtyValues).hasSize(2).containsKeys("plainValue", "plainValue2");
assertThat(dirtyValues.get("plainValue")).hasToString("name:a,name:x"); // SOURCE -> origValue present
assertThat(dirtyValues.get("plainValue2")).hasToString("name:b,null"); // without SOURCE no origValue present
LoggedSql.start();
bean.save();
expectedSql(0, "update ebasic_json_jackson3 set plain_value=?, plain_value2=?, version=? where id=?");
bean = DB.find(EBasicJsonJackson3.class, bean.getId());
LoggedSql.collect(); // ignore the select
assertThat(bean.getPlainValue().getName()).isEqualTo("a");
assertThat(bean.getPlainValue2().getName()).isEqualTo("b");
assertThat(bean.getPlainValue3().getName()).isEqualTo("z"); // value is not updated
bean.getPlainValue3().setName("c");
bean.getPlainValue3().setMarkedDirty(true); // This is ignored because it is MutationDetection.NONE
bean.save();
// no update as plainValue3 has MutationDetection.NONE (ModifyAwareType = NONE isn't an expected combination to me)
assertThat(LoggedSql.collect()).isEmpty();
bean.getPlainValue2().setName("b2"); // effectively HASH mode mutation detection
bean.save();
expectedSql(0, "update ebasic_json_jackson3 set plain_value2=?, version=? where id=? and version=?");
LoggedSql.stop();
}
private void expectedSql(int i, String s) {
assertThat(LoggedSql.collect().get(i)).contains(s);
}
}
@@ -76,10 +76,11 @@ public class TestDbJson_List extends BaseTestCase {
update_when_dirty();
update_when_dirty_flags();
update_when_dirty_SetListMap();
DB.delete(found);
}
//@Test//(dependsOnMethods = "insert")
public void json_parse_format() {
private void json_parse_format() {
String asJson = DB.json().toJson(found);
assertThat(asJson).contains("\"tags\":[\"one\",\"two\"]");
@@ -104,8 +105,7 @@ public class TestDbJson_List extends BaseTestCase {
assertThat(fromJson.getBeanMap()).hasSize(2);
}
//@Test//(dependsOnMethods = "insert")
public void update_when_notDirty() {
private void update_when_notDirty() {
found.setName("mod");
LoggedSqlCollector.start();
@@ -113,10 +113,11 @@ public class TestDbJson_List extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
// we don't update the phone numbers (as they are not dirty)
assertSql(sql.get(0)).contains("update ebasic_json_list set name=?, plain_bean=?, version=? where");
// plain_bean=?, no longer included with MD5 dirty detection
assertSql(sql.get(0)).contains("update ebasic_json_list set name=?, version=? where");
}
public void update_when_dirty() {
private void update_when_dirty() {
//found.setName("modAgain");
found.getTags().add("three");
@@ -126,10 +127,11 @@ public class TestDbJson_List extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
// we don't update the phone numbers (as they are not dirty)
assertSql(sql.get(0)).contains("update ebasic_json_list set plain_bean=?, tags=?, version=? where id=? and version=?");
// plain_bean=? not included using MD5 dirty detection
assertSql(sql.get(0)).contains("update ebasic_json_list set tags=?, version=? where id=? and version=?");
}
public void update_when_dirty_flags() {
private void update_when_dirty_flags() {
//found.setName("modAgain");
found.getFlags().remove(42L);
@@ -139,10 +141,11 @@ public class TestDbJson_List extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
// we don't update the phone numbers (as they are not dirty)
assertSql(sql.get(0)).contains("update ebasic_json_list set plain_bean=?, flags=?, version=? where id=? and version=?;");
// plain_bean=? not included with MD5 dirty detection
assertSql(sql.get(0)).contains("update ebasic_json_list set flags=?, version=? where id=? and version=?;");
}
public void update_when_dirty_SetListMap() {
private void update_when_dirty_SetListMap() {
//found.setName("modAgain");
found.getBeanSet().clear();
@@ -154,7 +157,8 @@ public class TestDbJson_List extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
// we don't update the phone numbers (as they are not dirty)
assertSql(sql.get(0)).contains("update ebasic_json_list set beans=?, bean_list=?, bean_map=?, plain_bean=?, version=? where id=? and version=?");
// plain_bean=? not included with MD5 dirty detection
assertSql(sql.get(0)).contains("update ebasic_json_list set beans=?, bean_list=?, bean_map=?, version=? where id=? and version=?");
}
@Test
@@ -221,4 +225,19 @@ public class TestDbJson_List extends BaseTestCase {
DB.delete(bean);
}
@Test
public void testNullToEmpty() {
EBasicJsonList bean = new EBasicJsonList();
bean.setFlags(null);
bean.setTags(null);
bean.setBeanMap(null);
DB.save(bean);
bean = DB.find(EBasicJsonList.class).setId(bean.getId()).findOne();
assertThat(bean.getFlags()).isEmpty();
assertThat(bean.getTags()).isEmpty();
assertThat(bean.getBeanMap()).isEmpty();
}
}
@@ -1,14 +1,15 @@
package org.tests.m2m;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.DB;
import org.junit.Test;
import org.tests.model.basic.MRole;
import org.tests.model.basic.MUser;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestM2MModifyTest extends BaseTestCase {
@Test
@@ -19,8 +20,8 @@ public class TestM2MModifyTest extends BaseTestCase {
MRole r1 = new MRole("r1");
// Save r1 and r2
Ebean.save(r0);
Ebean.save(r1);
DB.save(r0);
DB.save(r1);
// Create a new user
MUser u0 = new MUser("usr0");
@@ -28,32 +29,27 @@ public class TestM2MModifyTest extends BaseTestCase {
u0.addRole(r1);
// Save the user
Ebean.save(u0);
DB.save(u0);
List<MRole> roles = u0.getRoles();
Assert.assertTrue(roles.size() == 2);
assertThat(roles).hasSize(2);
u0 = Ebean.find(MUser.class, u0.getUserid());
u0 = DB.find(MUser.class, u0.getUserid());
roles = u0.getRoles();
int nrRoles = roles.size();
Assert.assertTrue(nrRoles == 2);
assertThat(roles).hasSize(2);
roles.clear();
roles.add(r0);
roles.add(r1);
roles.remove(r1);
Ebean.save(u0);
DB.save(u0);
u0 = Ebean.find(MUser.class, u0.getUserid());
u0 = DB.find(MUser.class, u0.getUserid());
roles = u0.getRoles();
nrRoles = roles.size();
Assert.assertTrue(nrRoles == 1);
assertThat(roles).hasSize(1);
}
}
@@ -28,7 +28,7 @@ public class TestM2mDeleteObject extends BaseTestCase {
List<MetaTimedMetric> sqlMetrics = sqlMetrics();
assertThat(sqlMetrics).hasSize(1);
assertThat(sqlMetrics.get(0).getName()).isEqualTo("orm.update.deleteAllPermissions");
assertThat(sqlMetrics.get(0).name()).isEqualTo("orm.update.deleteAllPermissions");
Tenant t = new Tenant("tenant");

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