Compare commits

...
Author SHA1 Message Date
rob bygrave 5b09443cdf [maven-release-plugin] prepare release ebean-11.22.7 2018-10-04 22:10:06 +13:00
rob bygrave 31093babe3 #1498 - findIds() with @SoftDelete bean returns all Ids (when soft deleted ones should not return)
And fix delete permanent that cascades
2018-10-04 20:49:30 +13:00
rob bygrave f8b9ca2034 #1498 - findIds() with @SoftDelete bean returns all Ids (when soft deleted ones should not return) 2018-10-04 20:26:00 +13:00
rob bygrave c27749ae2d #1497 - SQL Error with @History + @SoftDelete + @OneToOne fetch=FetchType.EAGER 2018-10-04 19:59:36 +13:00
rob bygrave 2c04430185 No effective change - add testSoftDelete_includeSoftDeletes_findOne 2018-10-04 17:14:53 +13:00
rob bygrave dcde47daf6 Merge branch 'master' of github.com:ebean-orm/ebean 2018-09-28 16:34:08 +12:00
rob bygrave 57056d7abd #1496 - Query label lost for findId() 2018-09-28 16:33:54 +12:00
Rob Bygrave 3409f264ec [maven-release-plugin] prepare for next development iteration 2018-09-26 23:38:57 +12:00
Rob Bygrave 6a61614430 [maven-release-plugin] prepare release ebean-11.22.6 2018-09-26 23:38:42 +12:00
Rob Bygrave 9734ae5787 #1490 - FIX: withQuote_when_match for different platforms 2018-09-26 21:33:27 +12:00
Roland PramlandRob Bygrave b817d5ee4d FIX: withQuote_when:_match for different platforms (#1490) 2018-09-26 21:26:29 +12:00
Rob Bygrave 0036473971 #1489 - removed the concat-operator "||" and replaced with "concat()" 2018-09-26 21:23:14 +12:00
Roland PramlandRob Bygrave a020bb15a1 removed the concat-operator "||" and replaced with "concat()" (#1489)
* removed the concat-operator "||" and replaced with "concat()"

* using || in postgres

* FIX: Assert-statement
2018-09-26 21:19:28 +12:00
Rob Bygrave efa86043bf #1492 - Update for NEW: collation support for orderBy 2018-09-26 21:15:41 +12:00
Roland PramlandRob Bygrave eb1ea5ddd2 NEW: collation support for orderBy (#1492) 2018-09-26 20:57:33 +12:00
Roland PramlandRob Bygrave a83a4fa1c4 FIX: check platform on multiple annotations like '@Formula(platform=...)' (#1495) 2018-09-26 20:55:39 +12:00
Roland PramlandRob Bygrave fe71c7cc4e FIX: tableName detection for native statements on SQLServer (#1494) 2018-09-26 20:54:22 +12:00
Roland PramlandRob Bygrave 5522d56e8d ignored tests for sqlserver, because there is no nested transaction support, yet (#1493) 2018-09-26 20:48:49 +12:00
Roland PramlandRob Bygrave 7e995efb62 Add methods th the plugin API to access type and inheritance information (#1491) 2018-09-26 20:48:19 +12:00
Roland PramlandRob Bygrave ef46bc86cc Original Value and bean change was not computed correctly in changelog (#1488)
* Bug in Changelog: It takes the first non null value as oldValue and does not detect if oldValue == newValue

* CHANGE: EntityBeanIntercept uses bitflags instead of separate boolean arrays - new flag 'origValueSet' introduced - getDirtyValues checks, if values are really different.
2018-09-26 20:40:56 +12:00
rob bygrave 0e75c50be5 #1487 - @OneToOne Postgres with different Id types - error PSQLException: ERROR: operator does not exist: uuid = character varying
Fix
2018-09-24 22:30:45 +12:00
rob bygrave cd1cdb027f #1487 - @OneToOne Postgres with different Id types - error PSQLException: ERROR: operator does not exist: uuid = character varying
Test
2018-09-24 22:30:28 +12:00
Roland PramlandRob Bygrave 66703a3a29 FIX: recursive BatchedPstmtHolder.flush, when BeanPersistController or ChangeLogFilter hit the database (#1486) 2018-09-21 15:38:21 +12:00
Roland PramlandRob Bygrave 56e65d7463 Fix issue in UUID-v1 generator on AWS Beanstalk (#1485) 2018-09-21 15:35:53 +12:00
Roland PramlandRob Bygrave eb7277aa13 Wrong behaviour in BeanMap.put/putAll (#1484) 2018-09-21 15:35:01 +12:00
Rob Bygrave 42f9e74d91 #1483 - Invalid SQL with allQuotedIdentifiers true - where t0.[t0.[COND]] in (?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) 2018-09-20 23:12:26 +12:00
Rob Bygrave be24e11b91 [maven-release-plugin] prepare for next development iteration 2018-09-20 21:35:24 +12:00
Rob Bygrave 42d3e9eb91 [maven-release-plugin] prepare release ebean-11.22.5 2018-09-20 21:35:08 +12:00
Rob Bygrave bb164a0763 OneToOne inserts with idType=SEQUENCE will fail #1480 2018-09-20 21:32:48 +12:00
Rob Bygrave 3a50c264b1 No effective change - add test with softDelete on bean with history 2018-09-20 21:10:41 +12:00
Rob Bygrave 0e7dc4209c #1482 - Improve error message for incorrect mapping - @JoinColumn without @ManyToOne etc 2018-09-20 21:09:01 +12:00
Roland PramlandRob Bygrave 72845acbad FIX: JunctionExpression.notExists (#1481) 2018-09-20 20:24:33 +12:00
Roland PramlandRob Bygrave 29d88d7de8 FIX: NPE in StalarTypeLocalTime and StalarTypeMonthDay (#1479) 2018-09-20 20:23:46 +12:00
rob bygrave c81d329100 [maven-release-plugin] prepare for next development iteration 2018-08-31 21:56:34 +12:00
rob bygrave 7c1c144af5 [maven-release-plugin] prepare release ebean-11.22.4 2018-08-31 21:56:19 +12:00
rob bygrave 7809858634 #1478 - MySql DDL, invalid comment included resulting in PersistenceException: Failed to run script 2018-08-31 21:54:48 +12:00
rob bygrave bd3cb3c547 [maven-release-plugin] prepare for next development iteration 2018-08-27 21:44:54 +12:00
rob bygrave db25cb7bfb [maven-release-plugin] prepare release ebean-11.22.3 2018-08-27 21:44:38 +12:00
rob bygrave 55a9ba7170 #1475 - @OneToOne child with @SoftDelete that is deleted is still loaded by parent 2018-08-27 21:35:00 +12:00
rob bygrave 2df2224286 No effective change - add a new test for OneToOne with fetchType LAZY 2018-08-27 20:00:44 +12:00
rob bygrave e1e8092144 [maven-release-plugin] prepare for next development iteration 2018-08-21 21:15:23 +12:00
rob bygrave 4d4886d808 [maven-release-plugin] prepare release ebean-11.22.2 2018-08-21 21:15:07 +12:00
rob bygrave d11bb2c4e5 No effective change - add a new test for OneToMany orphanRemoval=true 2018-08-21 21:08:17 +12:00
rob bygrave 663b1bb4aa #1474 - IllegalStateException: Table [some_table] does not exists in model? ... when applying drop table migration and table is no longer in the model 2018-08-21 20:45:31 +12:00
SnōwballandRob Bygrave f3d26e32ec Possible fix error with multiple uses of enum (#1473)
When an enum is exists in the type cache and isn't String, @DbArray
fails. This includes a test that should fail everywhere (order of
reading fields isn't guaranteed afaik and the column needs to be read
before the @DbArray for the test to fail).
2018-08-21 20:26:08 +12:00
rob bygrave 5f83584315 [maven-release-plugin] prepare for next development iteration 2018-08-16 00:05:31 +12:00
rob bygrave d28e3ee456 [maven-release-plugin] prepare release ebean-11.22.1 2018-08-16 00:05:16 +12:00
rob bygrave e1b7161b22 Bump version to 11.22.1-SNAPSHOT 2018-08-16 00:03:15 +12:00
rob bygrave f63a3cb926 Bump ebean-migration to 11.9.1 2018-08-16 00:02:10 +12:00
rob bygrave 29c5a17202 #1472 - Cascade insert with managed id ... results in update rather than insert statement 2018-08-15 23:12:06 +12:00
rob bygrave 340b100a1a No effective change - Add test for OneToOne orphanRemoval=true and String Ids (non generated ids) 2018-08-14 22:09:01 +12:00
rob bygrave 80975acfbf #1471 - Change stateless update on bean with no @Version property that updates 0 rows to throw EntityNotFoundException 2018-08-14 14:23:28 +12:00
rob bygrave 95dab97b1b No effective change - adjust test adding @Draft property to @Draftable Document 2018-08-14 14:16:08 +12:00
rob bygrave ce10dd79f9 [maven-release-plugin] prepare for next development iteration 2018-08-13 22:06:41 +12:00
rob bygrave 3f12739e71 [maven-release-plugin] prepare release ebean-11.21.1 2018-08-13 22:06:26 +12:00
rob bygrave cd1ae20015 Bump to 11.21.1-SNAPSHOT 2018-08-13 22:00:15 +12:00
rob bygrave 6d1e3a08e1 #1470 - Change OnQueryOnly to default to OnQueryOnly.COMMIT 2018-08-13 21:59:33 +12:00
rob bygrave 94e6fe90e5 No effective change - white space format 2018-08-13 21:54:19 +12:00
SnōwballandRob Bygrave 9bbe99657a Let enum arrays be saved as integers or strings (#1467)
* Let enums be saved as integers or strings as needed

* Add tests for int and varchar @DbArray enums
2018-08-13 21:26:14 +12:00
rob bygrave 0faf9f54ea [maven-release-plugin] prepare for next development iteration 2018-08-10 15:54:42 +12:00
rob bygrave 046a9b30b0 [maven-release-plugin] prepare release ebean-11.20.2 2018-08-10 15:54:29 +12:00
Rob BygraveandGitHub afb7679302 #1468 - ENH: Add built-in DDL generation for Postgres table partitioning (by day, week, month and year) (#1469) 2018-08-10 14:52:42 +12:00
rob bygrave db35f0f5ed #1466 - With ebean.dbSchema property (aka default db schema) we should not connection.setSchema() when running migrations 2018-08-09 21:34:23 +12:00
rob bygrave 19756421ad [maven-release-plugin] prepare for next development iteration 2018-08-08 23:34:11 +12:00
110 changed files with 2494 additions and 376 deletions
+3 -3
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.20.1</version>
<version>11.22.7</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -22,7 +22,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.20.1</tag>
<tag>ebean-11.22.7</tag>
</scm>
<profiles>
@@ -135,7 +135,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-migration</artifactId>
<version>11.8.1</version>
<version>11.9.1</version>
</dependency>
<dependency>
+59 -7
View File
@@ -1,9 +1,12 @@
package io.ebean;
import io.ebean.util.StringHelper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Represents an Order By for a Query.
@@ -74,6 +77,15 @@ public final class OrderBy<T> implements Serializable {
return query;
}
/**
* Add a property with ascending order to this OrderBy.
*/
public Query<T> asc(String propertyName, String collation) {
list.add(new Property(propertyName, true, collation));
return query;
}
/**
* Add a property with descending order to this OrderBy.
*/
@@ -83,6 +95,16 @@ public final class OrderBy<T> implements Serializable {
return query;
}
/**
* Add a property with descending order to this OrderBy.
*/
public Query<T> desc(String propertyName, String collation) {
list.add(new Property(propertyName, false, collation));
return query;
}
/**
* Return true if the property is known to be contained in the order by clause.
*/
@@ -231,6 +253,8 @@ public final class OrderBy<T> implements Serializable {
private boolean ascending;
private String collation;
private String nulls;
private String highLow;
@@ -247,17 +271,32 @@ public final class OrderBy<T> implements Serializable {
this.highLow = highLow;
}
public Property(String property, boolean ascending, String collation) {
this.property = property;
this.ascending = ascending;
this.collation = collation;
}
public Property(String property, boolean ascending, String collation, String nulls, String highLow) {
this.property = property;
this.ascending = ascending;
this.collation = collation;
this.nulls = nulls;
this.highLow = highLow;
}
/**
* Return a copy of this Property with the path trimmed.
*/
public Property copyWithTrim(String path) {
return new Property(property.substring(path.length() + 1), ascending, nulls, highLow);
return new Property(property.substring(path.length() + 1), ascending, collation, nulls, highLow);
}
@Override
public int hashCode() {
int hc = property.hashCode();
hc = hc * 92821 + (ascending ? 0 : 1);
hc = hc * 92821 + (collation == null ? 0 : collation.hashCode());
hc = hc * 92821 + (nulls == null ? 0 : nulls.hashCode());
hc = hc * 92821 + (highLow == null ? 0 : highLow.hashCode());
return hc;
@@ -274,8 +313,9 @@ public final class OrderBy<T> implements Serializable {
Property e = (Property) obj;
if (ascending != e.ascending) return false;
if (!property.equals(e.property)) return false;
if (nulls != null ? !nulls.equals(e.nulls) : e.nulls != null) return false;
return highLow != null ? highLow.equals(e.highLow) : e.highLow == null;
if (!Objects.equals(collation, e.collation)) return false;
if (!Objects.equals(nulls, e.nulls)) return false;
return Objects.equals(highLow, e.highLow);
}
@Override
@@ -284,7 +324,7 @@ public final class OrderBy<T> implements Serializable {
}
public String toStringFormat() {
if (nulls == null) {
if (nulls == null && collation == null) {
if (ascending) {
return property;
} else {
@@ -292,11 +332,23 @@ public final class OrderBy<T> implements Serializable {
}
} else {
StringBuilder sb = new StringBuilder();
sb.append(property);
if (collation != null) {
if (collation.contains("${}")) {
// this is a complex collation, e.g. DB2 - we must replace the property
sb.append(StringHelper.replaceString(collation, "${}", property));
} else {
sb.append(property);
sb.append(" collate ").append(collation);
}
} else {
sb.append(property);
}
if (!ascending) {
sb.append(" ").append("desc");
}
sb.append(" ").append(nulls).append(" ").append(highLow);
if (nulls != null) {
sb.append(" ").append(nulls).append(" ").append(highLow);
}
return sb.toString();
}
}
@@ -319,7 +371,7 @@ public final class OrderBy<T> implements Serializable {
* Return a copy of this property.
*/
public Property copy() {
return new Property(property, ascending, nulls, highLow);
return new Property(property, ascending, collation, nulls, highLow);
}
/**
@@ -71,21 +71,30 @@ public final class EntityBeanIntercept implements Serializable {
/**
* Used when a bean is partially filled.
*/
private final boolean[] loadedProps;
private boolean fullyLoadedBean;
private static final byte FLAG_LOADED_PROP = 1;
/**
* Set of changed properties.
*/
private boolean[] changedProps;
private static final byte FLAG_CHANGED_PROP = 2;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distingush
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private boolean[] embeddedDirty;
private static final byte FLAG_EMBEDDED_DIRTY = 4;
/**
* Flags indicating if a property is a dirty embedded bean. Used to distingush
* between an embedded bean being completely overwritten and one of its
* embedded properties being made dirty.
*/
private static final byte FLAG_ORIG_VALUE_SET = 8;
private final byte[] flags;
private boolean fullyLoadedBean;
private Object[] origValues;
@@ -102,7 +111,7 @@ public final class EntityBeanIntercept implements Serializable {
*/
public EntityBeanIntercept(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
this.loadedProps = new boolean[owner._ebean_getPropertyNames().length];
this.flags = new byte[owner._ebean_getPropertyNames().length];
}
/**
@@ -213,8 +222,8 @@ public final class EntityBeanIntercept implements Serializable {
* Check each property to see if the bean is partially loaded.
*/
public boolean isPartial() {
for (boolean loadedProp : loadedProps) {
if (!loadedProp) {
for (byte flag : flags) {
if ((flag & FLAG_LOADED_PROP) == 0) {
return true;
}
}
@@ -259,10 +268,10 @@ public final class EntityBeanIntercept implements Serializable {
* Return true if only the Id property has been loaded.
*/
public boolean hasIdOnly(int idIndex) {
for (int i = 0; i < loadedProps.length; i++) {
for (int i = 0; i < flags.length; i++) {
if (i == idIndex) {
if (!loadedProps[i]) return false;
} else if (loadedProps[i]) {
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
return false;
}
}
@@ -284,9 +293,9 @@ public final class EntityBeanIntercept implements Serializable {
if (idPos > -1) {
// For cases where properties are set on constructor
// set every non Id property to unloaded (for lazy loading)
for (int i = 0; i < loadedProps.length; i++) {
for (int i = 0; i < flags.length; i++) {
if (i != idPos) {
loadedProps[i] = false;
flags[i] &= ~FLAG_LOADED_PROP;
}
}
}
@@ -352,7 +361,9 @@ public final class EntityBeanIntercept implements Serializable {
this.owner._ebean_setEmbeddedLoaded();
this.lazyLoadProperty = -1;
this.origValues = null;
this.changedProps = null;
for (int i = 0; i < flags.length; i++) {
flags[i] &= ~(FLAG_CHANGED_PROP + FLAG_ORIG_VALUE_SET);
}
this.dirty = false;
}
@@ -475,7 +486,11 @@ public final class EntityBeanIntercept implements Serializable {
if (position == -1) {
throw new IllegalArgumentException("Property " + propertyName + " not found");
}
loadedProps[position] = loaded;
if (loaded) {
flags[position] |= FLAG_LOADED_PROP;
} else {
flags[position] &= ~FLAG_LOADED_PROP;
}
}
/**
@@ -483,22 +498,22 @@ public final class EntityBeanIntercept implements Serializable {
* constructor.
*/
public void setPropertyUnloaded(int propertyIndex) {
loadedProps[propertyIndex] = false;
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
}
/**
* Set the property to be loaded.
*/
public void setLoadedProperty(int propertyIndex) {
loadedProps[propertyIndex] = true;
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
/**
* Set all properties to be loaded (post insert).
*/
public void setLoadedPropertyAll() {
for (int i = 0; i < loadedProps.length; i++) {
loadedProps[i] = true;
for (int i = 0; i < flags.length; i++) {
flags[i] |= FLAG_LOADED_PROP;
}
}
@@ -506,14 +521,14 @@ public final class EntityBeanIntercept implements Serializable {
* Return true if the property is loaded.
*/
public boolean isLoadedProperty(int propertyIndex) {
return loadedProps[propertyIndex];
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
}
/**
* Return true if the property is considered changed.
*/
public boolean isChangedProperty(int propertyIndex) {
return (changedProps != null && changedProps[propertyIndex]);
return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
}
/**
@@ -521,8 +536,7 @@ public final class EntityBeanIntercept implements Serializable {
* embedded properties is dirty.
*/
public boolean isDirtyProperty(int propertyIndex) {
return (changedProps != null && changedProps[propertyIndex]
|| embeddedDirty != null && embeddedDirty[propertyIndex]);
return (flags[propertyIndex] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
}
/**
@@ -534,27 +548,22 @@ public final class EntityBeanIntercept implements Serializable {
}
public void setChangedProperty(int propertyIndex) {
if (changedProps == null) {
changedProps = new boolean[owner._ebean_getPropertyNames().length];
}
changedProps[propertyIndex] = true;
flags[propertyIndex] |= FLAG_CHANGED_PROP;
}
/**
* Set that an embedded bean has had one of its properties changed.
*/
private void setEmbeddedPropertyDirty(int propertyIndex) {
if (embeddedDirty == null) {
embeddedDirty = new boolean[owner._ebean_getPropertyNames().length];
}
embeddedDirty[propertyIndex] = true;
flags[propertyIndex] |= FLAG_EMBEDDED_DIRTY;
}
private void setOriginalValue(int propertyIndex, Object value) {
if (origValues == null) {
origValues = new Object[owner._ebean_getPropertyNames().length];
}
if (origValues[propertyIndex] == null) {
if ((flags[propertyIndex] & FLAG_ORIG_VALUE_SET) == 0) {
flags[propertyIndex] |= FLAG_ORIG_VALUE_SET;
origValues[propertyIndex] = value;
}
}
@@ -574,13 +583,9 @@ public final class EntityBeanIntercept implements Serializable {
*/
public void setNewBeanForUpdate() {
if (changedProps == null) {
changedProps = new boolean[owner._ebean_getPropertyNames().length];
}
for (int i = 0; i < loadedProps.length; i++) {
if (loadedProps[i]) {
changedProps[i] = true;
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
flags[i] |= FLAG_CHANGED_PROP;
}
}
setDirty(true);
@@ -594,8 +599,8 @@ public final class EntityBeanIntercept implements Serializable {
return null;
}
Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < loadedProps.length; i++) {
if (loadedProps[i]) {
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
props.add(getProperty(i));
}
}
@@ -609,12 +614,8 @@ public final class EntityBeanIntercept implements Serializable {
int len = getPropertyLength();
boolean[] dirties = new boolean[len];
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
dirties[i] = true;
} else if (embeddedDirty != null && embeddedDirty[i]) {
// an embedded property has been changed - recurse
dirties[i] = true;
}
// this, or an embedded property has been changed - recurse
dirties[i] = (flags[i] & (FLAG_CHANGED_PROP + FLAG_EMBEDDED_DIRTY)) != 0;
}
return dirties;
}
@@ -634,11 +635,11 @@ 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 (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
props.add(propName);
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyNames(props, getProperty(i) + ".");
@@ -654,12 +655,12 @@ public final class EntityBeanIntercept implements Serializable {
String[] names = owner._ebean_getPropertyNames();
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
if (propertyNames.contains(names[i])) {
return true;
}
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
if (propertyNames.contains(names[i])) {
return true;
}
@@ -683,15 +684,16 @@ 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 (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
String propName = (prefix == null ? getProperty(i) : prefix + getProperty(i));
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
if (!areEqual(oldVal, newVal)) {
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
}
dirtyValues.put(propName, new ValuePair(newVal, oldVal));
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + ".");
@@ -705,13 +707,15 @@ public final class EntityBeanIntercept implements Serializable {
public void addDirtyPropertyValues(BeanDiffVisitor visitor) {
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
// the property has been changed on this bean
Object newVal = owner._ebean_getField(i);
Object oldVal = getOrigValue(i);
visitor.visit(i, newVal, oldVal);
if (!areEqual(oldVal, newVal)) {
visitor.visit(i, newVal, oldVal);
}
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
visitor.visitPush(i);
@@ -739,9 +743,9 @@ public final class EntityBeanIntercept implements Serializable {
}
int len = getPropertyLength();
for (int i = 0; i < len; i++) {
if (changedProps != null && changedProps[i]) {
if ((flags[i] & FLAG_CHANGED_PROP) != 0) {
sb.append(i).append(',');
} else if (embeddedDirty != null && embeddedDirty[i]) {
} else if ((flags[i] & FLAG_EMBEDDED_DIRTY) != 0) {
// an embedded property has been changed - recurse
EntityBean embeddedBean = (EntityBean) owner._ebean_getField(i);
sb.append(i).append('[');
@@ -765,15 +769,12 @@ public final class EntityBeanIntercept implements Serializable {
return sb;
}
/**
* Return the set of property names for changed properties.
*/
public boolean[] getChanged() {
return changedProps;
}
public boolean[] getLoaded() {
return loadedProps;
boolean[] ret= new boolean[flags.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
}
return ret;
}
/**
@@ -821,7 +822,7 @@ public final class EntityBeanIntercept implements Serializable {
*/
private void loadBeanInternal(int loadProperty, BeanLoader loader) {
if (loadedProps == null || loadedProps[loadProperty]) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
}
@@ -887,7 +888,7 @@ public final class EntityBeanIntercept implements Serializable {
* Called when a BeanCollection is initialised automatically.
*/
public void initialisedMany(int propertyIndex) {
loadedProps[propertyIndex] = true;
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
private void preGetterCallback(int propertyIndex) {
+6 -3
View File
@@ -273,14 +273,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
checkReadOnly();
init();
if (modifyListening) {
Object oldBean = map.put(key, value);
E oldBean = map.put(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.put(key, value);
}
return map.put(key, value);
}
@Override
@@ -295,8 +297,9 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
modifyRemoval(oldBean);
}
}
} else {
map.putAll(puts);
}
map.putAll(puts);
}
@Override
@@ -107,6 +107,11 @@ public class DbMigrationConfig {
*/
protected String dbSchema;
/**
* Set to true if we consider this the 'default schema' (Postgres schema that matches DB username)
*/
protected boolean defaultDbSchema;
/**
* DB user used to run the DB migration.
*/
@@ -400,9 +405,15 @@ public class DbMigrationConfig {
* Set the Db schema if it hasn't already been defined.
*/
public void setDefaultDbSchema(String dbSchema) {
if (this.dbSchema == null) {
this.dbSchema = dbSchema;
}
this.defaultDbSchema = true;
this.dbSchema = dbSchema;
}
/**
* Return true if this is considered the default DB schema (Postgres schema matching DB username).
*/
public boolean isDefaultDbSchema() {
return defaultDbSchema;
}
/**
@@ -591,6 +602,9 @@ public class DbMigrationConfig {
runnerConfig.setDbUsername(getDbUsername());
runnerConfig.setDbPassword(getDbPassword());
runnerConfig.setDbSchema(getDbSchema());
if (defaultDbSchema) {
runnerConfig.setSetCurrentSchema(false);
}
runnerConfig.setClassLoader(classLoader);
if (patchInsertOn != null) {
runnerConfig.setPatchInsertOn(patchInsertOn);
@@ -1965,6 +1965,15 @@ public class ServerConfig {
* Return the UUID state file.
*/
public String getUuidStateFile() {
if (uuidStateFile == null || uuidStateFile.isEmpty()) {
// by default, add servername...
uuidStateFile = name + "-uuid.state";
// and store it in the user's home directory
String homeDir = System.getProperty("user.home");
if (homeDir != null && homeDir.isEmpty()) {
uuidStateFile = homeDir + "/.ebean/" + uuidStateFile;
}
}
return uuidStateFile;
}
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PartitionMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.CustomDbTypeMapping;
@@ -51,7 +52,7 @@ public class DatabasePlatform {
/**
* The behaviour used when ending a read only transaction at read committed isolation level.
*/
protected OnQueryOnly onQueryOnly = OnQueryOnly.ROLLBACK;
protected OnQueryOnly onQueryOnly = OnQueryOnly.COMMIT;
/**
* The open quote used by quoted identifiers.
@@ -63,8 +64,6 @@ public class DatabasePlatform {
*/
protected String closeQuote = "\"";
protected String concatOperator = "||";
/**
* When set to true all db column names and table names use quoted identifiers.
*/
@@ -454,13 +453,6 @@ public class DatabasePlatform {
return openQuote;
}
/**
* Return the DB concat operator.
*/
public String getConcatOperator() {
return concatOperator;
}
/**
* Return the JDBC type used to store booleans.
*/
@@ -706,6 +698,20 @@ public class DatabasePlatform {
}
}
/**
* Return true if partitions exist for the given table.
*/
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
return true;
}
/**
* Return the SQL to create an initial partition for the given table.
*/
public String tablePartitionInit(String tableName, PartitionMode mode, String property, String singlePrimaryKey) {
return null;
}
/**
* Escapes the like string for this DB-Platform
*/
@@ -2,6 +2,7 @@ package io.ebean.config.dbplatform.postgres;
import io.ebean.BackgroundExecutor;
import io.ebean.Query;
import io.ebean.annotation.PartitionMode;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -11,6 +12,10 @@ import io.ebean.config.dbplatform.PlatformIdGenerator;
import io.ebean.config.dbplatform.SqlErrorCodes;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
/**
@@ -116,4 +121,31 @@ public class PostgresPlatform extends DatabasePlatform {
return sql + " for update";
}
}
@Override
public boolean tablePartitionsExist(Connection connection, String table) throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("select count(*) from pg_inherits i WHERE i.inhparent = ?::regclass")) {
statement.setString(1, table);
try (ResultSet resultSet = statement.executeQuery()) {
return resultSet.next() && resultSet.getInt(1) > 0;
}
}
}
/**
* Return SQL using built in partition helper functions to create some initial partitions.
*
* Only use this if extra-dll doesn't have some initial partitions defined (which it should).
*/
public String tablePartitionInit(String tableName, PartitionMode mode, String property, String pkey) {
if (property == null) {
property = "";
}
if (pkey == null) {
pkey = "";
}
return "select partition('" + mode.name().toLowerCase() + "','" + tableName + "','" + pkey + "','" + property + "',1);";
}
}
@@ -98,6 +98,11 @@ public interface DbMigration {
*/
void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment);
/**
* Set this to false to exclude the builtin support for table partitioning (with @DbPartition).
*/
void setIncludeBuiltInPartitioning(boolean includeBuiltInPartitioning);
/**
* Set the header that is included in the generated DDL script.
*/
@@ -9,6 +9,10 @@ import io.ebean.event.BeanQueryAdapter;
import io.ebeanservice.docstore.api.mapping.DocumentMapping;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
/**
* Information and methods on BeanDescriptors made available to plugins.
@@ -18,6 +22,7 @@ public interface BeanType<T> {
/**
* Return the short name of the bean type.
*/
@Nonnull
String getName();
/**
@@ -28,11 +33,13 @@ public interface BeanType<T> {
/**
* Return the full name of the bean type.
*/
@Nonnull
String getFullName();
/**
* Return the class type this BeanDescriptor describes.
*/
@Nonnull
Class<T> getBeanType();
/**
@@ -43,6 +50,7 @@ public interface BeanType<T> {
/**
* Return all the properties for this bean type.
*/
@Nonnull
Collection<? extends Property> allProperties();
/**
@@ -197,6 +205,28 @@ public interface BeanType<T> {
*/
boolean hasInheritance();
/**
* Return true if this object is the root level object in its entity
* inheritance.
*/
boolean isInheritanceRoot();
/**
* Returns all direct children of this beantype
*/
List<BeanType<?>> getInheritanceChildren();
/**
* Returns the parent in inheritance hiearchy
*/
BeanType<?> getInheritanceParent();
/**
* Visit all children recursively
* @param visitor
*/
void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor);
/**
* Return the discriminator column.
*/
@@ -1,5 +1,7 @@
package io.ebean.plugin;
import javax.annotation.Nonnull;
/**
* Property of a entity bean that can be read.
*/
@@ -8,8 +10,15 @@ public interface Property {
/**
* Return the name of the property.
*/
@Nonnull
String getName();
/**
* Return the type of the property.
*/
@Nonnull
Class<?> getPropertyType();
/**
* Return the value of the property on the given bean.
*/
@@ -134,27 +134,40 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
/**
* Includes soft deletes rows in the result.
*/
SOFT_DELETED,
SOFT_DELETED(false),
/**
* Query runs against draft tables.
*/
DRAFT,
DRAFT(false),
/**
* Query runs against current data (normal).
*/
CURRENT,
CURRENT(false),
/**
* Query runs potentially returning many versions of the same bean.
*/
VERSIONS,
VERSIONS(true),
/**
* Query runs 'As Of' a given date time.
*/
AS_OF;
AS_OF(true);
private final boolean history;
TemporalMode(boolean history) {
this.history = history;
}
/**
* Return true if this is a history query.
*/
public boolean isHistory() {
return history;
}
/**
* Return the mode of the query of if null return CURRENT mode.
@@ -1,11 +1,14 @@
package io.ebeaninternal.dbmigration;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.migration.ddl.DdlRunner;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.model.CurrentModel;
import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import io.ebeaninternal.server.deploy.PartitionMeta;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -180,10 +183,50 @@ public class DdlGenerator {
String ignoreExtraDdl = System.getProperty("ebean.ignoreExtraDdl");
if (!"true".equalsIgnoreCase(ignoreExtraDdl) && jaxbPresent) {
if (currentModel.isTablePartitioning()) {
String extraPartitioning = ExtraDdlXmlReader.buildPartitioning(server.getDatabasePlatform().getName());
if (extraPartitioning != null && !extraPartitioning.isEmpty()) {
runScript(connection, false, extraPartitioning, "builtin-partitioning-dll");
}
}
String extraApply = ExtraDdlXmlReader.buildExtra(server.getDatabasePlatform().getName(), false);
if (extraApply != null) {
runScript(connection, false, extraApply, "extra-dll");
}
if (currentModel.isTablePartitioning()) {
checkInitialTablePartitions(connection);
}
}
}
/**
* Check if table partitions exist and if not create some. The expectation is that
* extra-dll.xml should have some partition initialisation but this helps people get going.
*/
private void checkInitialTablePartitions(Connection connection) {
DatabasePlatform databasePlatform = server.getDatabasePlatform();
try {
StringBuilder sb = new StringBuilder();
for (MTable table : currentModel.getPartitionedTables()) {
String tableName = table.getName();
if (!databasePlatform.tablePartitionsExist(connection, tableName)) {
log.info("No table partitions for table {}", tableName);
PartitionMeta meta = table.getPartitionMeta();
String initPart = databasePlatform.tablePartitionInit(tableName, meta.getMode(), meta.getProperty(), table.singlePrimaryKey());
sb.append(initPart).append("\n");
}
}
String initialPartitionSql = sb.toString();
if (!initialPartitionSql.isEmpty()) {
runScript(connection, false, initialPartitionSql, "initial table partitions");
}
} catch (SQLException e) {
log.error("Error checking initial table partitions", e);
}
}
@@ -100,6 +100,8 @@ public class DefaultDbMigration implements DbMigration {
protected String name;
protected String generatePendingDrop;
protected boolean includeBuiltInPartitioning = true;
/**
* Create for offline migration generation.
*/
@@ -181,6 +183,11 @@ public class DefaultDbMigration implements DbMigration {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
@Override
public void setIncludeBuiltInPartitioning(boolean includeBuiltInPartitioning) {
this.includeBuiltInPartitioning = includeBuiltInPartitioning;
}
@Override
public void setHeader(String header) {
this.header = header;
@@ -279,7 +286,7 @@ public class DefaultDbMigration implements DbMigration {
try {
Request request = createRequest();
if (platforms.isEmpty()) {
generateExtraDdl(request.migrationDir, databasePlatform);
generateExtraDdl(request.migrationDir, databasePlatform, request.isTablePartitioning());
}
String pendingVersion = generatePendingDrop();
@@ -314,24 +321,27 @@ public class DefaultDbMigration implements DbMigration {
* migration runner.
* </p>
*/
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform) throws IOException {
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, boolean tablePartitioning) throws IOException {
if (dbPlatform != null) {
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
generateExtraDdl(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
if (tablePartitioning && includeBuiltInPartitioning) {
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltinTablePartitioning());
}
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.readBuiltin());
generateExtraDdlFor(migrationDir, dbPlatform, ExtraDdlXmlReader.read());
}
}
private void generateExtraDdl(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
private void generateExtraDdlFor(File migrationDir, DatabasePlatform dbPlatform, ExtraDdl extraDdl) throws IOException {
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (!script.isDrop() && ExtraDdlXmlReader.matchPlatform(dbPlatform.getName(), script.getPlatforms())) {
writeExtraDdl(migrationDir, script);
}
writeExtraDdl(migrationDir, script);
}
}
}
}
/**
* Write (or override) the "repeatable" migration script.
@@ -349,8 +359,6 @@ public class DefaultDbMigration implements DbMigration {
}
}
private String repeatableMigrationName(boolean init, String scriptName) {
StringBuilder sb = new StringBuilder();
if (init) {
@@ -421,6 +429,10 @@ public class DefaultDbMigration implements DbMigration {
this.current = currentModel.read();
}
boolean isTablePartitioning() {
return current.isTablePartitioning();
}
/**
* Return the migration for the pending drops for a given version.
*/
@@ -528,7 +540,7 @@ public class DefaultDbMigration implements DbMigration {
File subPath = platformWriter.subPath(writePath, pair.prefix);
platformWriter.processMigration(dbMigration, platformBuffer, subPath, fullVersion);
generateExtraDdl(subPath, pair.platform);
generateExtraDdl(subPath, pair.platform, currentModel.isTablePartitioning());
}
}
@@ -60,6 +60,20 @@ public class CurrentModel {
this.platformTypes = platformTypes;
}
/**
* Return true if the model contains tables that are partitioned.
*/
public boolean isTablePartitioning() {
return model.isTablePartitioning();
}
/**
* Return the tables that have partitioning.
*/
public List<MTable> getPartitionedTables() {
return model.getPartitionedTables();
}
private static DbConstraintNaming.MaxLength maxLength(SpiEbeanServer server, DbConstraintNaming naming) {
if (naming.getMaxLength() != null) {
@@ -116,17 +130,7 @@ public class CurrentModel {
ddl.append(header).append('\n');
}
ExtraDdl extraDdl = ExtraDdlXmlReader.readBuiltin();
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (script.isInit() && ExtraDdlXmlReader.matchPlatform(server.getDatabasePlatform().getName(), script.getPlatforms())) {
ddl.append("-- init script " + script.getName()).append('\n');
ddl.append(script.getValue());
}
}
}
addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
@@ -136,6 +140,18 @@ public class CurrentModel {
return ddl.toString();
}
private void addExtraDdl(StringBuilder ddl, ExtraDdl extraDdl, String prefix) {
if (extraDdl != null) {
List<DdlScript> ddlScript = extraDdl.getDdlScript();
for (DdlScript script : ddlScript) {
if (script.isInit() && ExtraDdlXmlReader.matchPlatform(server.getDatabasePlatform().getName(), script.getPlatforms())) {
ddl.append(prefix + script.getName()).append('\n');
ddl.append(script.getValue());
}
}
}
}
/**
* Return the 'Drop' DDL.
*/
@@ -434,6 +434,13 @@ public class MTable {
return partitionMeta != null;
}
/**
* Return the partition meta for this table.
*/
public PartitionMeta getPartitionMeta() {
return partitionMeta;
}
public void setPkName(String pkName) {
this.pkName = pkName;
}
@@ -546,6 +553,17 @@ public class MTable {
return pk;
}
/**
* Return the primary key column if it is a simple primary key.
*/
public String singlePrimaryKey() {
List<MColumn> columns = primaryKeyColumns();
if (columns.size() == 1) {
return columns.get(0).getName();
}
return null;
}
private void checkTableName(String tableName) {
if (!name.equals(tableName)) {
throw new IllegalArgumentException("addColumn tableName [" + tableName + "] does not match [" + name + "]");
@@ -19,6 +19,7 @@ import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.Migration;
import io.ebeaninternal.dbmigration.migration.Sql;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -43,9 +44,25 @@ public class ModelContainer {
private final PendingDrops pendingDrops = new PendingDrops();
private final List<MTable> partitionedTables = new ArrayList<>();
public ModelContainer() {
}
/**
* Return true if the model contains tables that are partitioned.
*/
public boolean isTablePartitioning() {
return !partitionedTables.isEmpty();
}
/**
* Return the list of partitioned tables.
*/
public List<MTable> getPartitionedTables() {
return partitionedTables;
}
/**
* Adjust the FK references on all the draft tables.
*/
@@ -167,13 +184,12 @@ public class ModelContainer {
/**
* Unset the withHistory flag on the associated base table.
*/
private void applyChange(DropHistoryTable change) {
protected void applyChange(DropHistoryTable change) {
MTable table = tables.get(change.getBaseTable());
if (table == null) {
throw new IllegalStateException("Table [" + change.getBaseTable() + "] does not exist in model?");
if (table != null) {
table.setWithHistory(false);
}
table.setWithHistory(false);
}
private void applyChange(AddUniqueConstraint change) {
@@ -224,19 +240,14 @@ public class ModelContainer {
if (tables.containsKey(tableName)) {
throw new IllegalStateException("Table [" + tableName + "] already exists in model?");
}
MTable table = new MTable(createTable);
tables.put(tableName, table);
tables.put(tableName, new MTable(createTable));
}
/**
* Apply a DropTable change to the model.
*/
protected void applyChange(DropTable dropTable) {
String tableName = dropTable.getName();
if (!tables.containsKey(tableName)) {
throw new IllegalStateException("Table [" + tableName + "] does not exists in model?");
}
tables.remove(tableName);
tables.remove(dropTable.getName());
}
/**
@@ -247,22 +258,16 @@ public class ModelContainer {
if (indexes.containsKey(indexName)) {
throw new IllegalStateException("Index [" + indexName + "] already exists in model?");
}
MIndex index = new MIndex(createIndex);
indexes.put(createIndex.getIndexName(), index);
indexes.put(createIndex.getIndexName(), new MIndex(createIndex));
}
/**
* Apply a DropTable change to the model.
*/
protected void applyChange(DropIndex dropIndex) {
String name = dropIndex.getIndexName();
if (!indexes.containsKey(name)) {
throw new IllegalStateException("Index [" + name + "] does not exist in model?");
}
indexes.remove(name);
indexes.remove(dropIndex.getIndexName());
}
/**
* Apply a AddColumn change to the model.
*/
@@ -300,6 +305,9 @@ public class ModelContainer {
* Add a table (typically from reading EbeanServer meta data).
*/
public MTable addTable(MTable table) {
if (table.isPartitioned()) {
partitionedTables.add(table);
}
return tables.put(table.getName(), table);
}
@@ -26,9 +26,22 @@ public class ExtraDdlXmlReader {
public static String buildExtra(String platformName, boolean drops) {
ExtraDdl read = ExtraDdlXmlReader.read("/extra-ddl.xml");
return buildExtra(platformName, drops, read);
}
/**
* Return any extra DDL for supporting partitioning given the database platform.
*/
public static String buildPartitioning(String platformName) {
return buildExtra(platformName, false, readBuiltinTablePartitioning());
}
private static String buildExtra(String platformName, boolean drops, ExtraDdl read) {
if (read == null) {
return null;
}
StringBuilder sb = new StringBuilder(300);
for (DdlScript script : read.getDdlScript()) {
if (script.isDrop() == drops && matchPlatform(platformName, script.getPlatforms())) {
@@ -90,6 +103,13 @@ public class ExtraDdlXmlReader {
return read("/io/ebeaninternal/dbmigration/builtin-extra-ddl.xml");
}
/**
* Read the builtin extra ddl to support table partitioning.
*/
public static ExtraDdl readBuiltinTablePartitioning() {
return read("/io/ebeaninternal/dbmigration/builtin-extra-ddl-partitioning.xml");
}
/**
* Read the extra ddl.
*/
@@ -31,6 +31,7 @@ import io.ebeanservice.docstore.api.DocStoreUpdate;
import io.ebeanservice.docstore.api.DocStoreUpdateContext;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import javax.persistence.EntityNotFoundException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.io.IOException;
@@ -854,12 +855,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
@Override
public final void checkRowCount(int rowCount) {
if (ConcurrencyMode.VERSION == concurrencyMode && rowCount != 1) {
// fix for oracle.
// see: https://stackoverflow.com/questions/19022175/executebatch-method-return-array-of-value-2-in-java
if (rowCount != Statement.SUCCESS_NO_INFO) {
String m = Message.msg("persist.conc2", String.valueOf(rowCount));
throw new OptimisticLockException(m, null, bean);
if (rowCount != 1 && rowCount != Statement.SUCCESS_NO_INFO) {
if (ConcurrencyMode.VERSION == concurrencyMode) {
throw new OptimisticLockException(Message.msg("persist.conc2", String.valueOf(rowCount)), null, bean);
} else if (rowCount == 0 && type == Type.UPDATE) {
throw new EntityNotFoundException("No rows updated");
}
}
switch (type) {
@@ -1307,14 +1307,22 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Set the request flags indicating this is an insert.
*/
public void flagInsert() {
flags = Flags.setInsert(flags);
if (intercept.isNew()) {
flags = Flags.setInsertNormal(flags);
} else {
flags = Flags.setInsert(flags);
}
}
/**
* Unset the request insert flag indicating this is an update.
*/
public void flagUpdate() {
flags = Flags.unsetInsert(flags);
if (intercept.isLoaded()) {
flags = Flags.setUpdateNormal(flags);
} else {
flags = Flags.setUpdate(flags);
}
}
/**
@@ -7,8 +7,14 @@ import io.ebeaninternal.server.query.SqlJoinType;
*/
class AssocOneHelpRefExported extends AssocOneHelp {
public AssocOneHelpRefExported(BeanPropertyAssocOne<?> property) {
private final boolean softDelete;
private final String softDeletePredicate;
AssocOneHelpRefExported(BeanPropertyAssocOne<?> property) {
super(property);
this.softDelete = property.targetDescriptor.isSoftDelete();
this.softDeletePredicate = (softDelete) ? property.targetDescriptor.getSoftDeletePredicate("") : null;
}
/**
@@ -28,6 +34,10 @@ class AssocOneHelpRefExported extends AssocOneHelp {
void appendFrom(DbSqlContext ctx, SqlJoinType joinType) {
String relativePrefix = ctx.getRelativePrefix(property.getName());
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
if (softDelete && !ctx.isIncludeSoftDelete()) {
property.tableJoin.addJoin(joinType, relativePrefix, ctx, softDeletePredicate);
} else {
property.tableJoin.addJoin(joinType, relativePrefix, ctx);
}
}
}
@@ -100,6 +100,7 @@ import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -107,6 +108,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Describes Beans including their deployment information.
@@ -1133,6 +1136,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* Return true if this object is the root level object in its entity
* inheritance.
*/
@Override
public boolean isInheritanceRoot() {
return inheritInfo == null || inheritInfo.isRoot();
}
@@ -3475,4 +3479,29 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
public List<BeanProperty[]> getUniqueProps() {
return propertiesUnique;
}
@Override
public List<BeanType<?>> getInheritanceChildren() {
if (hasInheritance()) {
return getInheritInfo().getChildren()
.stream()
.map(InheritInfo::desc)
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
@Override
public BeanType<?> getInheritanceParent() {
return getInheritInfo() == null ? null : getInheritInfo().getParent().desc();
}
@Override
public void visitAllInheritanceChildren(Consumer<BeanType<?>> visitor) {
if (hasInheritance()) {
getInheritInfo().visitChildren(info -> visitor.accept(info.desc()));
}
}
}
@@ -979,6 +979,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the full name of this property.
*/
@Override
public String getFullBeanName() {
return descriptor.getFullName() + "." + name;
}
@@ -994,6 +995,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the scalarType.
*/
@Override
@SuppressWarnings(value = "unchecked")
public ScalarType<Object> getScalarType() {
return scalarType;
@@ -1369,6 +1371,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
/**
* Return the property type.
*/
@Override
public Class<?> getPropertyType() {
return propertyType;
}
@@ -318,11 +318,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
/**
* Find the Id's of detail beans given a parent Id or list of parent Id's.
*/
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds, boolean hard) {
if (parentId != null) {
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds);
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds, hard);
} else {
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds);
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds, hard);
}
}
@@ -127,14 +127,16 @@ class BeanPropertyAssocManySqlHelp<T> {
many.bindParentIdsIn(expr, parentIds, query);
}
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds, boolean hard) {
String rawWhere = deriveWhereParentIdSql(false, "");
SpiEbeanServer server = descriptor.getEbeanServer();
SpiQuery<?> q = many.newQuery(server);
many.bindParentIdEq(rawWhere, parentId, q);
if (hard) {
q.setIncludeSoftDeletes();
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
@@ -142,7 +144,7 @@ class BeanPropertyAssocManySqlHelp<T> {
return server.findIds(q, t);
}
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds, boolean hard) {
String rawWhere = deriveWhereParentIdSql(true, "");
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
@@ -153,7 +155,9 @@ class BeanPropertyAssocManySqlHelp<T> {
SpiQuery<?> q = many.newQuery(server);
//Query<?> q = server.find(propertyType);
many.bindParentIdsIn(expr, parentIds, q);
if (hard) {
q.setIncludeSoftDeletes();
}
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
}
@@ -261,7 +261,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t) {
String rawWhere = deriveWhereParentIdSql(true);
String inClause = targetIdBinder.getIdInValueExpr(false, parentIds.size());
String inClause = getIdBinder().getIdInValueExpr(false, parentIds.size());
String expr = rawWhere + inClause;
SpiEbeanServer server = server();
@@ -537,8 +537,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
BeanProperty[] emIds = targetDesc.propertiesBaseScalar();
try {
for (BeanProperty emId : emIds) {
ExportedProperty expProp = findMatch(true, emId);
list.add(expProp);
list.add(findMatch(true, emId));
}
} catch (PersistenceException e) {
// not found as individual scalar properties
@@ -547,8 +546,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
} else {
if (idProp != null) {
ExportedProperty expProp = findMatch(false, idProp);
list.add(expProp);
list.add(findMatch(false, idProp));
}
}
@@ -12,8 +12,6 @@ public interface DbSqlContext {
*/
void addJoin(String type, String table, TableJoinColumn[] cols, String a1, String a2, String inheritance);
void pushSecondaryTableAlias(String alias);
/**
* Push the current table alias onto the stack.
*/
@@ -91,11 +89,6 @@ public interface DbSqlContext {
*/
String getContent();
/**
* Return the current join node.
*/
String peekJoin();
/**
* Push a join node onto the stack.
*/
@@ -125,6 +118,11 @@ public interface DbSqlContext {
*/
void appendHistorySysPeriod();
/**
* Return true if the query includes soft deleted rows.
*/
boolean isIncludeSoftDelete();
/**
* Return true if the query is a 'asDraft' query.
*/
@@ -139,4 +137,5 @@ public interface DbSqlContext {
* Append 'for update' lock hints on FROM clause (sql server only).
*/
void appendFromForUpdate();
}
@@ -22,6 +22,11 @@ public abstract class DeployParser {
*/
protected static final char UNDERSCORE = '_';
protected static final char OPEN_SQUARE_BRACKET = '[';
protected static final char CLOSE_SQUARE_BRACKET = ']';
protected static final char DOUBLE_QUOTE = '\"';
protected static final char BACK_QUOTE = '`';
/**
* Used to determine when a column name terminates.
*/
@@ -183,10 +188,10 @@ public abstract class DeployParser {
wordBuffer.append(ch);
return false;
}
return Character.isLetterOrDigit(ch) || ch == UNDERSCORE || ch == PERIOD;
return Character.isLetterOrDigit(ch) || ch == UNDERSCORE || ch == PERIOD || ch == DOUBLE_QUOTE || ch == CLOSE_SQUARE_BRACKET || ch == BACK_QUOTE;
}
private boolean isWordStart(char ch) {
return Character.isLetter(ch) || ch == UNDERSCORE;
return Character.isLetter(ch) || ch == UNDERSCORE || ch == DOUBLE_QUOTE || ch == OPEN_SQUARE_BRACKET || ch == BACK_QUOTE;
}
}
@@ -126,6 +126,16 @@ public final class TableJoin {
return type;
}
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx, String predicate) {
String[] names = SplitName.split(prefix);
String a1 = ctx.getTableAlias(names[0]);
String a2 = ctx.getTableAlias(prefix);
SqlJoinType returnJoinType = addJoin(joinType, a1, a2, ctx);
ctx.append("and ").append(a2).append(predicate);
return returnJoinType;
}
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
String[] names = SplitName.split(prefix);
@@ -132,6 +132,9 @@ public class AnnotationAssocOnes extends AnnotationParser {
// check for manually defined joins
BeanTable beanTable = prop.getBeanTable();
for (JoinColumn joinColumn : getAll(prop, JoinColumn.class)) {
if (beanTable == null) {
throw new IllegalStateException("Looks like a missing @ManyToOne or @OneToOne on property " + prop.getFullBeanName()+" - no related 'BeanTable'");
}
prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable);
if (!joinColumn.updatable()) {
prop.setDbUpdateable(false);
@@ -146,6 +149,9 @@ public class AnnotationAssocOnes extends AnnotationParser {
JoinTable joinTable = get(prop, JoinTable.class);
if (joinTable != null) {
for (JoinColumn joinColumn : joinTable.joinColumns()) {
if (beanTable == null) {
throw new IllegalStateException("Looks like a missing @ManyToOne or @OneToOne on property " + prop.getFullBeanName()+" - no related 'BeanTable'");
}
prop.getTableJoin().addJoinColumn(false, joinColumn, beanTable);
if (!joinColumn.updatable()) {
prop.setDbUpdateable(false);
@@ -82,12 +82,12 @@ public abstract class AnnotationBase {
T a = null;
Field field = prop.getField();
if (field != null) {
a = AnnotationUtil.findAnnotation(field, annClass);
a = AnnotationUtil.findAnnotation(field, annClass, platform);
}
if (a == null) {
Method method = prop.getReadMethod();
if (method != null) {
a = AnnotationUtil.findAnnotation(method, annClass);
a = AnnotationUtil.findAnnotation(method, annClass, platform);
}
}
return a;
@@ -92,14 +92,7 @@ class InPairsExpression extends AbstractExpression {
return;
}
String concat = request.getDbPlatformHandler().getConcatOperator();
String concatFormula = "(" + property0 + concat + "'" + separator + "'" + concat + property1;
if (suffix != null && !suffix.isEmpty()) {
concatFormula += concat + "'" + suffix + "'";
}
concatFormula += ")";
request.append(concatFormula);
request.append(request.getDbPlatformHandler().concat(property0, separator, property1, suffix));
request.appendInExpression(not, concatBindValues);
}
@@ -667,7 +667,7 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
@Override
public ExpressionList<T> notExists(Query<?> subQuery) {
return exprList.exists(subQuery);
return exprList.notExists(subQuery);
}
@Override
@@ -8,17 +8,6 @@ import io.ebeaninternal.server.expression.BitwiseOp;
*/
abstract class BaseDbExpression implements DbExpressionHandler {
private final String concatOperator;
BaseDbExpression(String concatOperator) {
this.concatOperator = concatOperator;
}
@Override
public String getConcatOperator() {
return concatOperator;
}
@Override
public void bitwise(SpiExpressionRequest request, String propName, BitwiseOp operator, long flags, String compare, long match) {
@@ -57,4 +46,14 @@ abstract class BaseDbExpression implements DbExpressionHandler {
}
}
@Override
public String concat(String property0, String separator, String property1, String suffix) {
StringBuilder sb = new StringBuilder();
sb.append("concat(").append(property0).append(",'").append(separator).append("',").append(property1);
if (suffix != null && !suffix.isEmpty()) {
sb.append(",'").append(suffix).append('\'');
}
sb.append(')');
return sb.toString();
}
}
@@ -8,10 +8,6 @@ import io.ebeaninternal.server.expression.Op;
*/
public class BasicDbExpression extends BaseDbExpression {
BasicDbExpression(String concatOperator) {
super(concatOperator);
}
@Override
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
throw new RuntimeException("JSON expressions only supported on Postgres and Oracle");
@@ -9,11 +9,6 @@ import io.ebeaninternal.server.expression.Op;
*/
public interface DbExpressionHandler {
/**
* Return the DB concat operator (Usually SQL standard "||").
*/
String getConcatOperator();
/**
* Write the db platform specific json expression.
*/
@@ -33,4 +28,9 @@ public interface DbExpressionHandler {
* Add the bitwise expression.
*/
void bitwise(SpiExpressionRequest request, String propName, BitwiseOp operator, long flags, String compare, long match);
/**
* Performs a "CONCAT" operation for that platform.
*/
String concat(String property0, String separator, String property1, String suffix);
}
@@ -12,20 +12,21 @@ public class DbExpressionHandlerFactory {
public static DbExpressionHandler from(DatabasePlatform databasePlatform) {
Platform platform = databasePlatform.getPlatform();
String concatOperator = databasePlatform.getConcatOperator();
switch (platform) {
case H2:
return new H2DbExpression(concatOperator);
return new H2DbExpression();
case POSTGRES:
return new PostgresDbExpression(concatOperator);
return new PostgresDbExpression();
case MYSQL:
return new MySqlDbExpression(concatOperator);
return new MySqlDbExpression();
case ORACLE:
return new OracleDbExpression(concatOperator);
return new OracleDbExpression();
case SQLSERVER16:
case SQLSERVER17:
case SQLSERVER:
return new SqlServerDbExpression(concatOperator);
return new SqlServerDbExpression();
default:
return new BasicDbExpression(concatOperator);
return new BasicDbExpression();
}
}
}
@@ -8,10 +8,6 @@ import io.ebeaninternal.server.expression.BitwiseOp;
*/
class H2DbExpression extends BasicDbExpression {
H2DbExpression(String concatOperator) {
super(concatOperator);
}
@Override
public void bitwise(SpiExpressionRequest request, String propName, BitwiseOp operator, long flags, String compare, long match) {
@@ -5,8 +5,4 @@ package io.ebeaninternal.server.expression.platform;
*/
class MySqlDbExpression extends BasicDbExpression {
MySqlDbExpression(String concatOperator) {
super(concatOperator);
}
}
@@ -9,10 +9,6 @@ import io.ebeaninternal.server.expression.Op;
*/
public class OracleDbExpression extends BaseDbExpression {
OracleDbExpression(String concatOperator) {
super(concatOperator);
}
@Override
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
@@ -8,10 +8,6 @@ import io.ebeaninternal.server.expression.Op;
*/
public class PostgresDbExpression extends BaseDbExpression {
PostgresDbExpression(String concatOperator) {
super(concatOperator);
}
@Override
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
@@ -65,4 +61,16 @@ public class PostgresDbExpression extends BaseDbExpression {
request.append(" <> 0");
}
}
@Override
public String concat(String property0, String separator, String property1, String suffix) {
StringBuilder sb = new StringBuilder();
sb.append("(").append(property0).append("||'").append(separator).append("'||").append(property1);
if (suffix != null && !suffix.isEmpty()) {
sb.append("||'").append(suffix).append('\'');
}
sb.append(')');
return sb.toString();
}
}
@@ -8,10 +8,6 @@ import io.ebeaninternal.server.expression.Op;
*/
public class SqlServerDbExpression extends BaseDbExpression {
SqlServerDbExpression(String concatOperator) {
super(concatOperator);
}
@Override
public void json(final SpiExpressionRequest request, final String propName,
final String path, final Op operator, final Object value) {
@@ -82,8 +82,17 @@ public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
final Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces();
while (e.hasMoreElements()) {
NetworkInterface network = e.nextElement();
if (!network.isLoopback()) {
return network.getHardwareAddress();
try {
logger.trace("Probing interface {}", network);
if (!network.isLoopback()) {
byte[] addr = network.getHardwareAddress();
if (addr != null) {
logger.debug("Using interface {}", network);
return addr;
}
}
} catch (SocketException ex) {
logger.debug("Skipping {}", network, ex);
}
}
return null;
@@ -181,6 +190,10 @@ public class UuidV1IdGenerator extends UuidV1RndIdGenerator {
prop.setProperty("nodeId", getNodeIdentifier());
prop.setProperty("clockSeq", String.valueOf(clockSeq.get()));
prop.setProperty("timeStamp", String.valueOf(timeStamp.get()));
File dir = stateFile.getParentFile();
if (dir != null) {
dir.mkdirs();
}
try (OutputStream os = new FileOutputStream(stateFile)) {
prop.store(os, "ebean uuid state file");
logger.debug("Persisted state to '{}'", stateFile);
@@ -110,7 +110,15 @@ public class BatchedPstmtHolder {
// but still need to close PreparedStatements.
boolean isError = false;
for (BatchedPstmt bs : stmtMap.values()) {
// if there are Listeners/Controllers that interact with the database,
// the flush may get called recursively in executeBatch/postExecute.
// which leads that we process stmtMap.values() twice in the loop.
// So we copy the values, that we want to flush and clear it immediately.
BatchedPstmt[] values = stmtMap.values().toArray(new BatchedPstmt[stmtMap.values().size()]);
clear();
// this loop
for (BatchedPstmt bs : values) {
try {
if (!isError) {
bs.executeBatch(getGeneratedKeys);
@@ -139,8 +147,6 @@ public class BatchedPstmtHolder {
}
}
// clear the batch cache
clear();
if (firstError != null) {
String msg = "Error when batch flush on sql: " + errorSql;
@@ -456,8 +456,7 @@ public final class DefaultPersister implements Persister {
void saveRecurse(EntityBean bean, Transaction t, Object parentBean, int flags) {
// determine insert or update taking into account stateless updates
PersistRequestBean<?> request = createRequestRecurse(bean, t, parentBean, flags);
saveRecurse(request);
saveRecurse(createRequestRecurse(bean, t, parentBean, flags));
}
private void saveRecurse(PersistRequestBean<?> request) {
@@ -755,7 +754,7 @@ public final class DefaultPersister implements Persister {
executeSqlUpdate(sqlDelete, t);
} else {
// we need to fetch the Id's to delete (recurse or notify L2 cache)
List<Object> childIds = many.findIdsByParentId(id, idList, t, null);
List<Object> childIds = many.findIdsByParentId(id, idList, t, null, deleteMode.isHard());
if (!childIds.isEmpty()) {
delete(targetDesc, null, childIds, t, deleteMode);
}
@@ -1053,7 +1052,7 @@ public final class DefaultPersister implements Persister {
} else {
// Delete recurse using the Id values of the children
Object parentId = desc.getId(parentBean);
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds, deleteMode.isHard());
if (!idsByParentId.isEmpty()) {
deleteChildrenById(t, targetDesc, idsByParentId, deleteMode);
}
@@ -1229,8 +1228,8 @@ public final class DefaultPersister implements Persister {
BeanDescriptor<T> desc = mgr.getBeanDescriptor();
EntityBean entityBean = (EntityBean) bean;
PersistRequest.Type type;
if (Flags.isPublishOrMerge(flags)) {
// insert if it is a new bean (as publish created it)
if (Flags.isPublishMergeOrNormal(flags)) {
// just use bean state to determine insert or update
type = entityBean._ebean_getIntercept().isUpdate() ? Type.UPDATE : Type.INSERT;
} else {
// determine Insert or Update based on bean state and insert flag
@@ -5,7 +5,7 @@ package io.ebeaninternal.server.persist;
* <p>
* Allows passing of flag state when recursively persisting.
*/
public class Flags {
public final class Flags {
/**
* Indicates the bean is being inserted.
@@ -27,6 +27,11 @@ public class Flags {
*/
public static final int MERGE = 0x00000008;
/**
* Indicates Normal insert or update (not forced).
*/
public static final int NORMAL = 0x00000010;
/**
* No flags set.
*/
@@ -34,7 +39,9 @@ public class Flags {
public static final int PUBLISH_RECURSE = PUBLISH + RECURSE;
private static final int PUBLISH_MERGE = PUBLISH + MERGE;
private static final int PUBLISH_MERGE_NORMAL = PUBLISH + MERGE + NORMAL;
private static final int INSERT_NORMAL = INSERT + NORMAL;
/**
* Return true if the bean is being inserted.
@@ -65,10 +72,10 @@ public class Flags {
}
/**
* Return true if part of a Merge or Publish.
* Return true if part of a Merge or Publish or Normal (bean state matches persist).
*/
public static boolean isPublishOrMerge(long state) {
return (state & PUBLISH_MERGE) != 0;
public static boolean isPublishMergeOrNormal(int state) {
return (state & PUBLISH_MERGE_NORMAL) != 0;
}
/**
@@ -82,54 +89,70 @@ public class Flags {
* Set Insert flag.
*/
public static int setInsert(int state) {
return set(state, INSERT, true);
return set(state, INSERT);
}
/**
* Insert flag and normal in that bean is in NEW state (for insert).
*/
public static int setInsertNormal(int state) {
return set(state, INSERT_NORMAL);
}
/**
* Parent was not inserted.
*/
public static int unsetInsert(int state) {
return set(state, INSERT, false);
public static int setUpdate(int state) {
return unset(state, INSERT);
}
/**
* Not Insert and normal in that bean is in LOADED state (for update).
*/
public static int setUpdateNormal(int state) {
state &= ~INSERT;
state |= NORMAL;
return state;
}
/**
* Set Recurse flag.
*/
public static int setRecurse(int state) {
return set(state, RECURSE, true);
return set(state, RECURSE);
}
public static int unsetRecuse(int state) {
return set(state, RECURSE, false);
return unset(state, RECURSE);
}
/**
* Set Publish flag.
*/
public static int setPublish(int state) {
return set(state, PUBLISH, true);
return set(state, PUBLISH);
}
public static int unsetPublish(int state) {
return set(state, PUBLISH, false);
return unset(state, PUBLISH);
}
/**
* Set Merge flag.
*/
public static int setMerge(int state) {
return set(state, MERGE, true);
return set(state, MERGE);
}
public static int unsetMerge(int state) {
return set(state, MERGE, false);
return unset(state, MERGE);
}
private static int set(int state, int flag, boolean setFlag) {
if (setFlag) {
return (state |= flag);
} else {
return state &= ~flag;
}
private static int set(int state, int flag) {
return (state |= flag);
}
private static int unset(int state, int flag) {
return state &= ~flag;
}
}
@@ -7,7 +7,6 @@ import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.BatchedPstmt;
import io.ebeaninternal.server.persist.BatchedPstmtHolder;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.type.DataBind;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -138,11 +138,14 @@ class CQueryBuilder {
// wrap as - delete from table where id in (select id ...)
String sql = buildSql(null, request, predicates, sqlTree).getSql();
sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
String alias = (rootTableAlias == null) ? "t0" : rootTableAlias;
sql = aliasReplace(sql, alias);
sql = aliasReplace(sql, alias(rootTableAlias));
return sql;
}
private String alias(String rootTableAlias) {
return (rootTableAlias == null) ? "t0" : rootTableAlias;
}
private <T> String buildUpdateSql(OrmQueryRequest<T> request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {
StringBuilder sb = new StringBuilder(200);
@@ -160,8 +163,7 @@ class CQueryBuilder {
// wrap as - update table set ... where id in (select id ...)
String sql = buildSqlUpdate(null, request, predicates, sqlTree).getSql();
sql = updateClause + " " + request.getBeanDescriptor().getWhereIdInSql() + "in (" + sql + ")";
String alias = (rootTableAlias == null) ? "t0" : rootTableAlias;
sql = aliasReplace(sql, alias);
sql = aliasReplace(sql, alias(rootTableAlias));
return sql;
}
@@ -209,7 +211,12 @@ class CQueryBuilder {
*/
<T> CQueryFetchSingleAttribute buildFetchIdsQuery(OrmQueryRequest<T> request) {
request.getQuery().setSelectId();
SpiQuery<T> query = request.getQuery();
query.setSelectId();
BeanDescriptor<T> desc = request.getBeanDescriptor();
if (!query.isIncludeSoftDeletes() && desc.isSoftDelete()) {
query.addSoftDeletePredicate(desc.getSoftDeletePredicate(alias(query.getAlias())));
}
return buildFetchAttributeQuery(request);
}
@@ -217,7 +224,7 @@ class CQueryBuilder {
* Return the history support if this query needs it (is a 'as of' type query).
*/
<T> CQueryHistorySupport getHistorySupport(SpiQuery<T> query) {
return query.getTemporalMode() != SpiQuery.TemporalMode.CURRENT ? historySupport : null;
return query.getTemporalMode().isHistory() ? historySupport : null;
}
/**
@@ -417,7 +424,9 @@ class CQueryBuilder {
BeanDescriptor<?> desc = request.getBeanDescriptor();
try {
PreparedStatement statement = connection.prepareStatement(sql);
// For SqlServer we need either "selectMethod=cursor" in the connection string or fetch explicitly a cursorable
// statement here by specifying ResultSet.CONCUR_UPDATABLE
PreparedStatement statement = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE);
predicates.bind(statement, connection);
ResultSet resultSet = statement.executeQuery();
@@ -70,6 +70,11 @@ class DefaultDbSqlContext implements DbSqlContext {
this.fromForUpdate = fromForUpdate;
}
@Override
public boolean isIncludeSoftDelete() {
return alias.isIncludeSoftDelete();
}
@Override
public void appendFromForUpdate() {
if (fromForUpdate != null) {
@@ -99,11 +104,6 @@ class DefaultDbSqlContext implements DbSqlContext {
return encryptedProps.toArray(new BeanProperty[encryptedProps.size()]);
}
@Override
public String peekJoin() {
return joinStack.peek();
}
@Override
public void popJoin() {
joinStack.pop();
@@ -206,11 +206,6 @@ class DefaultDbSqlContext implements DbSqlContext {
return alias.getTableAliasManyWhere(prefix);
}
@Override
public void pushSecondaryTableAlias(String alias) {
tableAliasStack.push(alias);
}
@Override
public String getRelativePrefix(String propName) {
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.query;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiQuery;
import java.util.HashMap;
import java.util.HashSet;
@@ -17,6 +18,8 @@ class SqlTreeAlias {
private static final Pattern TABLE_ALIAS_REPLACE = Pattern.compile("${}", Pattern.LITERAL);
private final SpiQuery.TemporalMode temporalMode;
private int counter;
private int manyWhereCounter;
@@ -33,8 +36,9 @@ class SqlTreeAlias {
private final String rootTableAlias;
SqlTreeAlias(String rootTableAlias) {
SqlTreeAlias(String rootTableAlias, SpiQuery.TemporalMode temporalMode) {
this.rootTableAlias = rootTableAlias;
this.temporalMode = temporalMode;
}
/**
@@ -211,4 +215,8 @@ class SqlTreeAlias {
boolean isIncludeJoins() {
return !aliasMap.isEmpty() || !manyWhereAliasMap.isEmpty();
}
boolean isIncludeSoftDelete() {
return temporalMode == SpiQuery.TemporalMode.SOFT_DELETED;
}
}
@@ -110,7 +110,7 @@ public final class SqlTreeBuilder {
this.queryDetail = query.getDetail();
this.predicates = predicates;
this.alias = new SqlTreeAlias(request.getBaseTableAlias());
this.alias = new SqlTreeAlias(request.getBaseTableAlias(), temporalMode);
this.distinctOnPlatform = builder.isPlatformDistinctOn();
String fromForUpdate = builder.fromForUpdate(query);
@@ -768,6 +768,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
copy.timeout = timeout;
copy.mapKey = mapKey;
copy.id = id;
copy.label = label;
copy.useBeanCache = useBeanCache;
copy.useQueryCache = useQueryCache;
copy.readOnly = readOnly;
@@ -301,7 +301,7 @@ public class TransactionManager implements SpiTransactionManager {
}
// default to rollback if not defined on the platform
return dbPlatformOnQueryOnly == null ? OnQueryOnly.ROLLBACK : dbPlatformOnQueryOnly;
return dbPlatformOnQueryOnly == null ? OnQueryOnly.COMMIT : dbPlatformOnQueryOnly;
}
public String getServerName() {
@@ -112,14 +112,14 @@ interface ArrayElementConverter<T> {
@Override
public Object toElement(Object rawValue) {
return scalarType.parse(rawValue.toString());
return scalarType.toBeanType(rawValue);
}
@Override
public Object[] toDbArray(Object[] objects) {
Object[] dbArray = new Object[objects.length];
for (int i = 0; i < objects.length; i++) {
dbArray[i] = scalarType.format(objects[i]);
dbArray[i] = scalarType.toJdbcType(objects[i]);
}
return dbArray;
}
@@ -351,7 +351,7 @@ public final class DefaultTypeManager implements TypeManager {
if (type.equals(List.class)) {
if (arrayTypeListFactory != null) {
if (isEnumType(valueType)) {
return arrayTypeListFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), EnumType.STRING));
return arrayTypeListFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), null));
}
return arrayTypeListFactory.typeFor(valueType);
}
@@ -360,7 +360,7 @@ public final class DefaultTypeManager implements TypeManager {
} else if (type.equals(Set.class)) {
if (arrayTypeSetFactory != null) {
if (isEnumType(valueType)) {
return arrayTypeSetFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), EnumType.STRING));
return arrayTypeSetFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), null));
}
return arrayTypeSetFactory.typeFor(valueType);
}
@@ -60,7 +60,19 @@ public class ScalarTypeArrayList extends ScalarTypeJsonCollection<List> implemen
@Override
public ScalarTypeArrayList typeForEnum(ScalarType<?> scalarType) {
return new ScalarTypeArrayList("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType));
final String arrayType;
switch (scalarType.getJdbcType()) {
case Types.INTEGER:
arrayType = "integer";
break;
case Types.VARCHAR:
arrayType = "varchar";
break;
default:
throw new IllegalArgumentException("JdbcType [" + scalarType.getJdbcType() + "] not supported for @DbArray mapping on set.");
}
return new ScalarTypeArrayList(arrayType, scalarType.getDocType(), new ArrayElementConverter.EnumConverter(scalarType));
}
}
@@ -54,13 +54,24 @@ public class ScalarTypeArraySet<T> extends ScalarTypeJsonCollection<Set<T>> impl
if (valueType.equals(String.class)) {
return STRING;
}
throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping on set");
throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping on list");
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
public ScalarTypeArraySet typeForEnum(ScalarType<?> scalarType) {
return new ScalarTypeArraySet("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType));
final String arrayType;
switch (scalarType.getJdbcType()) {
case Types.INTEGER:
arrayType = "integer";
break;
case Types.VARCHAR:
arrayType = "varchar";
break;
default:
throw new IllegalArgumentException("JdbcType [" + scalarType.getJdbcType() + "] not supported for @DbArray mapping on set.");
}
return new ScalarTypeArraySet(arrayType, scalarType.getDocType(), new ArrayElementConverter.EnumConverter(scalarType));
}
}
@@ -68,9 +79,9 @@ public class ScalarTypeArraySet<T> extends ScalarTypeJsonCollection<Set<T>> impl
private final ArrayElementConverter<T> converter;
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
public ScalarTypeArraySet(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter<T> converter) {
super((Class)Set.class, Types.ARRAY, docPropertyType);
super((Class) Set.class, Types.ARRAY, docPropertyType);
this.arrayType = arrayType;
this.converter = converter;
}
@@ -7,6 +7,7 @@ import java.sql.SQLException;
import java.sql.Types;
import java.util.Set;
import java.util.UUID;
/**
* H2 database support for DB ARRAY.
*/
@@ -48,13 +49,13 @@ class ScalarTypeArraySetH2<T> extends ScalarTypeArraySet<T> {
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
public ScalarTypeArraySetH2 typeForEnum(ScalarType<?> scalarType) {
return new ScalarTypeArraySetH2("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType));
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({"unchecked", "rawtypes"})
private ScalarTypeArraySetH2(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) {
super(arrayType, docPropertyType, converter);
}
@@ -51,6 +51,7 @@ public class ScalarTypeLocalTime extends ScalarTypeBase<LocalTime> {
@Override
public LocalTime toBeanType(Object value) {
if (value instanceof LocalTime) return (LocalTime) value;
if (value == null) return null;
return BasicTypeConverter.toTime(value).toLocalTime();
}
@@ -39,6 +39,7 @@ public class ScalarTypeLocalTimeWithNanos extends ScalarTypeLocalTime {
@Override
public LocalTime toBeanType(Object value) {
if (value instanceof LocalTime) return (LocalTime) value;
if (value == null) return null;
return LocalTime.ofNanoOfDay(BasicTypeConverter.toLong(value));
}
@@ -72,6 +72,7 @@ public class ScalarTypeMonthDay extends ScalarTypeBase<MonthDay> {
@Override
public MonthDay toBeanType(Object value) {
if (value instanceof MonthDay) return (MonthDay) value;
if (value == null) return null;
return convertFromDate((Date) value);
}
@@ -0,0 +1,242 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<extra-ddl xmlns="http://ebean-orm.github.io/xml/ns/extraddl">
<ddl-script name="partition help" init="true" platforms="postgres">
-- partitioning helper functions (UTC based)
------------------------------------------------------------------------------------
-- Type: partition_meta
--
-- Type used to hold common partitioning parameters such as period start and end etc
------------------------------------------------------------------------------------
do $$
begin
if not exists (select 1 from pg_type where typname = 'partition_meta') THEN
create type partition_meta as
(
period_start timestamptz,
period_end timestamptz,
period_name text,
base_name text,
part_name text,
unique_column text,
index_column text
);
end if;
end$$;
------------------------------------------------------------------------------------
-- Function: _partition_create
--
-- Internal helper method to create a partition given meta data and
-- optional extra function to call (typically to create additional indexes)
------------------------------------------------------------------------------------
create or replace function _partition_create(meta partition_meta, extra text)
returns text
language plpgsql
set timezone to 'UTC'
as $$
begin
execute format('create table if not exists %I partition of %I for values from (''%s'') TO (''%s'')', meta.part_name, meta.base_name, meta.period_start, meta.period_end);
if (length(meta.unique_column) > 0) then
execute format('create unique index if not exists uq_%I ON %I (%I)', meta.part_name, meta.part_name, meta.unique_column);
end if;
if (length(meta.index_column) > 0) then
execute format('create index if not exists ix_%I_%s ON %I (%I)', meta.part_name, meta.index_column, meta.part_name, meta.index_column);
end if;
if (length(extra) > 0) then
execute 'select ' || extra || '($1)' using meta;
end if;
return meta.part_name;
end;
$$;
------------------------------------------------------------------------------------
-- Function: _partition_meta
--
-- Internal helper method to create and return meta data used to create a partition.
-- Helps work out start and end periods for day, week, month and year partitions.
------------------------------------------------------------------------------------
create or replace function _partition_meta(
mode text,
asOf date,
baseName text,
uniqueColumn text,
indexColumn text)
returns partition_meta
language plpgsql
set timezone to 'UTC'
as $$
declare
partName text;
meta partition_meta;
asOfUtc timestamptz;
begin
asOfUtc = timezone('utc', asOf);
if (mode = 'day') then
asOfUtc = date_trunc('day', asOfUtc);
partName = to_char(asOfUtc, 'YYYY_MM_DD');
select asOfUtc, asOfUtc + interval '1 days' into meta.period_start, meta.period_end;
elseif (mode = 'week') then
asOfUtc = date_trunc('week', asOfUtc);
partName = format('%s_w%s', extract(ISOYEAR FROM asOfUtc), extract(WEEK FROM asOfUtc));
select asOfUtc, asOfUtc + interval '7 days' into meta.period_start, meta.period_end;
elseif (mode = 'year') then
asOfUtc = date_trunc('year', asOfUtc);
partName = to_char(date_trunc('year', asOfUtc), 'YYYY');
select asOfUtc, asOfUtc + interval '1 year' into meta.period_start, meta.period_end;
else
asOfUtc = date_trunc('month', asOfUtc);
partName = to_char(asOfUtc, 'YYYY_MM');
select asOfUtc, asOfUtc + interval '1 month' into meta.period_start, meta.period_end;
end if;
select partName, baseName, format('%s_%s', baseName, partName), uniqueColumn, indexColumn
into meta.period_name, meta.base_name, meta.part_name, meta.unique_column, meta.index_column;
return meta;
end;
$$;
create or replace function _partition_meta_initdate(
meta partition_meta,
initDate date)
returns partition_meta
language plpgsql
set timezone to 'UTC'
as $$
begin
meta.period_start = initDate;
return meta;
end;
$$;
-- select _partition_over('week', current_date, 4);
------------------------------------------------------------------------------------
-- Function: _partition_over
--
-- Internal helper method to return a set/table of dates to ensure partitions exists for.
-- Typically we want to ensure some future partitions exist and this helps return dates
-- for which we loop to create partitions.
------------------------------------------------------------------------------------
create or replace function _partition_over(
mode text,
fromDate date default current_date,
_count integer default 0)
returns TABLE(of_date date)
language plpgsql
as $$
declare
endDate date;
begin
if (mode = 'day') then
endDate = fromDate + (interval '1 day' * _count);
fromDate = fromDate - interval '1 day'; -- allow for timezone
return query select s::date from generate_series(fromDate, endDate, '1 day') s;
elseif (mode = 'week') then
fromDate = date_trunc('week', fromDate);
endDate = fromDate + (interval '1 week' * _count);
return query select s::date from generate_series(fromDate, endDate, '1 week') s;
elseif (mode = 'year') then
fromDate = date_trunc('year', fromDate);
endDate = fromDate + (interval '1 year' * _count);
return query select s::date from generate_series(fromDate, endDate, '1 year') s;
else
fromDate = date_trunc('month', fromDate);
endDate = fromDate + (interval '1 month' * _count);
return query select s::date from generate_series(fromDate, endDate, '1 month') s;
end if;
end;
$$;
------------------------------------------------------------------------------------
-- Function: partition
--
-- Helper to ensure we create partitions into the future as needed for day, week, month
-- and year based partitioning. Typically we call this periodically (e.g. every day).
--
-- Examples:
--
-- select partition('week', 'trip', 'id', 'when_started', 4);
-- select partition('month', 'event', 'id', 'event_timestamp', 1);
--
------------------------------------------------------------------------------------
create or replace function partition(
mode text, -- one of 'day','week','month','year'
baseName text, -- base table name
uniqueColumn text, -- optional unique column
indexColumn text, -- optional column to index
partitionCount integer default 0, -- number of additional partitions
fromDate date default current_date, -- date to create first partition for
extra text default '') -- custom function to call per partition
returns text
language plpgsql
set timezone to 'UTC'
as $$
begin
perform _partition_create(_partition_meta(mode, poDate, baseName, uniqueColumn, indexColumn), extra)
from _partition_over(mode, fromDate, partitionCount) poDate;
return 'done';
end;
$$;
------------------------------------------------------------------------------------
-- Function: partition_init
--
-- Similar to partition but allows the first partition to be bigger with an explicit
-- initDate typically to allow back dated rows to go into the initial partition.
--
-- Examples:
--
-- select partition_init(date '2001-01-01', 'week', 'event', 'id', 'event_timestamp', 4);
--
------------------------------------------------------------------------------------
create or replace function partition_init(
initDate date, -- first partition period start date
mode text, -- one of 'day','week','month','year'
baseName text, -- base table name
uniqueColumn text, -- optional unique column
indexColumn text, -- optional column to index
partitionCount integer default 0, -- number of additional partitions
fromDate date default current_date, -- date to create first partition for
extra text default '') -- custom function to call per partition
returns text
language plpgsql
set timezone to 'UTC'
as $$
declare
meta partition_meta;
begin
-- override the period start for the first partition
meta = _partition_meta(mode, fromDate, baseName, uniqueColumn, indexColumn);
meta = _partition_meta_initdate(meta, initDate);
perform _partition_create(meta, extra);
if (partitionCount > 0) then
-- create additional migrations normally
perform _partition_create(_partition_meta(mode, poDate, baseName, uniqueColumn, indexColumn), extra)
from _partition_over(mode, fromDate, partitionCount) poDate;
end if;
return 'done';
end;
$$;
</ddl-script>
</extra-ddl>
@@ -13,16 +13,16 @@ if not exists (select name from sys.types where name = 'ebean_uniqueidentifier_
if not exists (select name from sys.types where name = 'ebean_nvarchar_tvp') create type ebean_nvarchar_tvp as table (c1 nvarchar(max));
delimiter $$
-----------------------------------------------------------
--
-- PROCEDURE: usp_ebean_drop_indices TABLE, COLUMN
-- deletes all indices referring to TABLE.COLUMN
-----------------------------------------------------------
--
CREATE OR ALTER PROCEDURE usp_ebean_drop_indices @tableName nvarchar(255), @columnName nvarchar(255)
AS SET NOCOUNT ON
declare @sql nvarchar(1000)
declare @indexName nvarchar(255)
BEGIN
DECLARE index_cursor CURSOR FOR SELECT i.name from sys.indexes i
DECLARE index_cursor CURSOR FOR SELECT i.name from sys.indexes i
join sys.index_columns ic on ic.object_id = i.object_id and ic.index_id = i.index_id
join sys.columns c on c.object_id = ic.object_id and c.column_id = ic.column_id
where i.object_id = OBJECT_ID(@tableName) AND c.name = @columnName;
@@ -41,10 +41,10 @@ END
$$
delimiter $$
--------------------------------------------------------------------
--
-- PROCEDURE: usp_ebean_drop_default_constraint TABLE, COLUMN
-- deletes the default constraint, which has a random name
--------------------------------------------------------------------
--
CREATE OR ALTER PROCEDURE usp_ebean_drop_default_constraint @tableName nvarchar(255), @columnName nvarchar(255)
AS SET NOCOUNT ON
declare @tmp nvarchar(1000)
@@ -58,16 +58,16 @@ END
$$
delimiter $$
--------------------------------------------------------------------
--
-- PROCEDURE: usp_ebean_drop_constraints TABLE, COLUMN
-- deletes constraints and foreign keys refering to TABLE.COLUMN
--------------------------------------------------------------------
--
CREATE OR ALTER PROCEDURE usp_ebean_drop_constraints @tableName nvarchar(255), @columnName nvarchar(255)
AS SET NOCOUNT ON
declare @sql nvarchar(1000)
declare @constraintName nvarchar(255)
BEGIN
DECLARE name_cursor CURSOR FOR
DECLARE name_cursor CURSOR FOR
SELECT cc.name from sys.check_constraints cc
join sys.columns c on c.object_id = cc.parent_object_id and c.column_id = cc.parent_column_id
where parent_object_id = OBJECT_ID(@tableName) AND c.name = @columnName
@@ -92,10 +92,10 @@ END
$$
delimiter $$
-------------------------------------------------------------------------------------
--
-- PROCEDURE: usp_ebean_drop_column TABLE, COLUMN
-- deletes the column annd ensures that all indices and constraints are dropped first
-------------------------------------------------------------------------------------
--
CREATE OR ALTER PROCEDURE usp_ebean_drop_column @tableName nvarchar(255), @columnName nvarchar(255)
AS SET NOCOUNT ON
declare @sql nvarchar(1000)
@@ -114,21 +114,21 @@ $$
DROP PROCEDURE IF EXISTS usp_ebean_drop_foreign_keys;
delimiter $$
------------------------------------------------------------------------------
--
-- PROCEDURE: usp_ebean_drop_foreign_keys TABLE, COLUMN
-- deletes all constraints and foreign keys referring to TABLE.COLUMN
------------------------------------------------------------------------------
--
CREATE PROCEDURE usp_ebean_drop_foreign_keys(IN p_table_name VARCHAR(255), IN p_column_name VARCHAR(255))
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE c_fk_name CHAR(255);
DECLARE curs CURSOR FOR SELECT CONSTRAINT_NAME from information_schema.KEY_COLUMN_USAGE
DECLARE curs CURSOR FOR SELECT CONSTRAINT_NAME from information_schema.KEY_COLUMN_USAGE
WHERE TABLE_SCHEMA = DATABASE() and TABLE_NAME = p_table_name and COLUMN_NAME = p_column_name
AND REFERENCED_TABLE_NAME IS NOT NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN curs;
read_loop: LOOP
FETCH curs INTO c_fk_name;
IF done THEN
@@ -138,7 +138,7 @@ BEGIN
PREPARE stmt FROM @sql;
EXECUTE stmt;
END LOOP;
CLOSE curs;
END
$$
@@ -146,10 +146,10 @@ $$
DROP PROCEDURE IF EXISTS usp_ebean_drop_column;
delimiter $$
-------------------------------------------------------------------------------------
--
-- PROCEDURE: usp_ebean_drop_column TABLE, COLUMN
-- deletes the column and ensures that all indices and constraints are dropped first
-------------------------------------------------------------------------------------
--
CREATE PROCEDURE usp_ebean_drop_column(IN p_table_name VARCHAR(255), IN p_column_name VARCHAR(255))
BEGIN
CALL usp_ebean_drop_foreign_keys(p_table_name, p_column_name);
@@ -1,6 +1,9 @@
package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropIndex;
import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.Migration;
import io.ebeaninternal.dbmigration.migrationreader.MigrationXmlReader;
import org.junit.Test;
@@ -79,6 +82,31 @@ public class ModelContainerTest {
assertThat(container.getPendingDrops()).isEmpty();
}
@Test
public void apply_dropTable_when_notInModel_then_ok() {
ModelContainer container = new ModelContainer();
container.apply(mig("5.0__dropTable.model.xml"), ver("5.0"));
assertThat(container.getTables()).isEmpty();
}
@Test
public void apply_drop_when_notInModel_then_ok() {
ModelContainer container = new ModelContainer();
DropTable dropTable = new DropTable();
dropTable.setName("DoesNotExist");
container.applyChange(dropTable);
DropIndex dropIndex = new DropIndex();
dropIndex.setIndexName("DoesNotExist");
container.applyChange(dropIndex);
DropHistoryTable dropHistoryTable = new DropHistoryTable();
dropHistoryTable.setBaseTable("DoesNotExist");
container.applyChange(dropHistoryTable);
}
private ModelContainer container_2_1() {
ModelContainer container = new ModelContainer();
container.apply(mig("2.0.model.xml"), ver("2.0"));
@@ -72,9 +72,9 @@ public class BeanPropertyAssocManyTest extends BaseTestCase {
customerIds.add(1L);
customerIds.add(2L);
List<Object> contactIdsForOne = contacts().findIdsByParentId(1L, null, null, null);
List<Object> contactIdsForOne = contacts().findIdsByParentId(1L, null, null, null, true);
List<Object> contactIdsForMultiple = contacts().findIdsByParentId(null, customerIds, null, null);
List<Object> contactIdsForMultiple = contacts().findIdsByParentId(null, customerIds, null, null, true);
assertThat(contactIdsForOne).isNotEmpty();
assertThat(contactIdsForMultiple).isNotEmpty();
@@ -1,8 +1,11 @@
package io.ebeaninternal.server.deploy;
import io.ebean.BaseTestCase;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import org.junit.Test;
import org.tests.model.basic.Address;
import org.tests.model.basic.BWithQIdent;
import org.tests.model.basic.Customer;
import static org.assertj.core.api.Assertions.assertThat;
@@ -14,6 +17,8 @@ public class DeployPropertyParserTest extends BaseTestCase {
private final BeanDescriptor<Address> addressBeanDescriptor = getBeanDescriptor(Address.class);
private final BeanDescriptor<BWithQIdent> bWithQIdentDescriptor = getBeanDescriptor(BWithQIdent.class);
@Test
public void from_prefix_expect_unchanged() {
assertThat(parser().parse("(select x from status join status)")).isEqualTo("(select x from status join status)");
@@ -49,6 +54,31 @@ public class DeployPropertyParserTest extends BaseTestCase {
assertThat(addressParser().parse("concat(line1, line2, '-EA')")).isEqualTo("concat(${}line_1, ${}line_2, '-EA')");
}
@Test
public void withExplicitQuote_all_platforms() {
assertThat(withQuoteParser().parse("t0.`CODE` like ?")).isEqualTo("t0.`CODE` like ?");
assertThat(withQuoteParser().parse("t0.[CODE] like ?")).isEqualTo("t0.[CODE] like ?");
assertThat(withQuoteParser().parse("t0.\"CODE\" like ?")).isEqualTo("t0.\"CODE\" like ?");
}
@Test
@ForPlatform(value = {Platform.H2, Platform.POSTGRES})
public void withQuote_when_match_h2() {
assertThat(withQuoteParser().parse("name like ?")).isEqualTo("${}\"Name\" like ?");
}
@Test
@ForPlatform(value = Platform.SQLSERVER)
public void withQuote_when_match_sqlserver() {
assertThat(withQuoteParser().parse("name like ?")).isEqualTo("${}[Name] like ?");
}
@Test
@ForPlatform(value = Platform.MYSQL)
public void withQuote_when_match_mysql() {
assertThat(withQuoteParser().parse("name like ?")).isEqualTo("${}`Name` like ?");
}
@Test
public void unknown_path() {
assertThat(parser().parse(" foo ")).isEqualTo(" foo ");
@@ -62,4 +92,8 @@ public class DeployPropertyParserTest extends BaseTestCase {
return addressBeanDescriptor.parser();
}
private DeployPropertyParser withQuoteParser() {
return bWithQIdentDescriptor.parser();
}
}
@@ -26,19 +26,60 @@ public class FlagsTest {
assertThat(Flags.isSet(state, Flags.PUBLISH)).isFalse();
assertThat(Flags.isSet(state, Flags.MERGE)).isTrue();
assertThat(Flags.isSet(state, Flags.INSERT)).isTrue();
}
@Test
public void insert() {
int state = 0;
state = Flags.setInsert(state);
assertThat(Flags.isSet(state, Flags.INSERT)).isTrue();
assertThat(Flags.isSet(state, Flags.NORMAL)).isFalse();
}
@Test
public void insertNormal() {
int state = 0;
state = Flags.setInsertNormal(state);
assertThat(Flags.isSet(state, Flags.INSERT)).isTrue();
assertThat(Flags.isSet(state, Flags.NORMAL)).isTrue();
}
@Test
public void update() {
int state = 0;
state = Flags.setUpdate(state);
assertThat(Flags.isSet(state, Flags.INSERT)).isFalse();
assertThat(Flags.isSet(state, Flags.NORMAL)).isFalse();
}
@Test
public void updateNormal() {
int state = 0;
state = Flags.setUpdateNormal(state);
assertThat(Flags.isSet(state, Flags.INSERT)).isFalse();
assertThat(Flags.isSet(state, Flags.NORMAL)).isTrue();
}
@Test
public void isPublishOrMerge() {
assertThat(Flags.isPublishOrMerge(0)).isFalse();
assertThat(Flags.isPublishOrMerge(Flags.INSERT)).isFalse();
assertThat(Flags.isPublishMergeOrNormal(0)).isFalse();
assertThat(Flags.isPublishMergeOrNormal(Flags.INSERT)).isFalse();
assertThat(Flags.isPublishMergeOrNormal(Flags.RECURSE)).isFalse();
assertThat(Flags.isPublishOrMerge(Flags.PUBLISH)).isTrue();
assertThat(Flags.isPublishOrMerge(Flags.MERGE)).isTrue();
assertThat(Flags.isPublishMergeOrNormal(Flags.PUBLISH)).isTrue();
assertThat(Flags.isPublishMergeOrNormal(Flags.MERGE)).isTrue();
assertThat(Flags.isPublishMergeOrNormal(Flags.NORMAL)).isTrue();
int mergePublish = Flags.setMerge(Flags.setPublish(0));
assertThat(Flags.isPublishOrMerge(mergePublish)).isTrue();
assertThat(Flags.isPublishMergeOrNormal(mergePublish)).isTrue();
}
}
@@ -72,7 +72,7 @@ public class TestQueryCacheTableDependency extends BaseTestCase {
.findCount();
assertThat(custs).isEqualTo(1);
Ebean.createSqlUpdate("update O_ADDRESS set line_2=? where line_2=?")
Ebean.createSqlUpdate("update o_address set line_2=? where line_2=?")
.setNextParameter("St Lucky3")
.setNextParameter("St Lucky2")
.execute();
@@ -5,11 +5,13 @@ import io.ebean.EbeanServerFactory;
import io.ebean.annotation.ChangeLog;
import io.ebean.config.ServerConfig;
import io.ebean.event.BeanPersistRequest;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeLogFilter;
import io.ebean.event.changelog.ChangeLogListener;
import io.ebean.event.changelog.ChangeLogPrepare;
import io.ebean.event.changelog.ChangeLogRegister;
import io.ebean.event.changelog.ChangeSet;
import io.ebean.event.changelog.ChangeType;
import io.ebeaninternal.api.SpiEbeanServer;
import org.tests.model.basic.EBasicChangeLog;
import com.fasterxml.jackson.annotation.JsonInclude;
@@ -18,8 +20,12 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
public class TestChangeLog extends BaseTestCase {
TDChangeLogPrepare changeLogPrepare = new TDChangeLogPrepare();
@@ -47,14 +53,77 @@ public class TestChangeLog extends BaseTestCase {
bean.setName("logBean");
bean.setShortDescription("hello");
server.save(bean);
BeanChange change = changeLogListener.changes.getChanges().get(0);
assertThat(change.getEvent()).isEqualTo(ChangeType.INSERT);
assertThat(change.getData())
.contains("\"name\":\"logBean\"")
.contains("\"shortDescription\":\"hello\"");
bean.setName("ChangedName");
server.save(bean);
change = changeLogListener.changes.getChanges().get(0);
assertThat(change.getEvent()).isEqualTo(ChangeType.UPDATE);
assertThat(change.getOldData()).contains("\"name\":\"logBean\"");
assertThat(change.getData()) .contains("\"name\":\"ChangedName\"");
server.delete(bean);
change = changeLogListener.changes.getChanges().get(0);
assertThat(change.getEvent()).isEqualTo(ChangeType.DELETE);
assertThat(change.getData()).isNull();
}
@Test
public void testWithNull() {
EBasicChangeLog bean = new EBasicChangeLog();
bean.setName(null);
bean.setShortDescription("hello");
server.save(bean);
BeanChange change = changeLogListener.changes.getChanges().get(0);
assertThat(change.getEvent()).isEqualTo(ChangeType.INSERT);
assertThat(change.getData())
.doesNotContain("\"name\"")
.contains("\"shortDescription\":\"hello\"");
bean.setName("log");
bean.setName("logBean");
bean.setShortDescription("world");
bean.setShortDescription("hello");
server.save(bean);
change = changeLogListener.changes.getChanges().get(0);
assertThat(change.getEvent()).isEqualTo(ChangeType.UPDATE);
assertThat(change.getOldData())
.contains("\"name\":null") // it was null
.doesNotContain("\"shortDescription\""); // it is unchanged
assertThat(change.getData())
.contains("\"name\":\"logBean\"")
.doesNotContain("\"shortDescription\""); // it is unchanged
server.delete(bean);
change = changeLogListener.changes.getChanges().get(0);
assertThat(change.getEvent()).isEqualTo(ChangeType.DELETE);
assertThat(change.getData()).isNull();
}
private SpiEbeanServer getServer() {
System.setProperty("ebean.ignoreExtraDdl", "true");
@@ -85,8 +85,8 @@ public class TestOnCascadeDeleteChildrenWithCompositeKeys extends BaseTestCase {
ids.add(1L);
ids.add(2L);
beanProperty.findIdsByParentId(null, ids, null, null);
beanProperty.findIdsByParentId(1L, null, null, null);
beanProperty.findIdsByParentId(null, ids, null, null, true);
beanProperty.findIdsByParentId(1L, null, null, null, true);
}
@Entity
@@ -18,6 +18,8 @@ public class EArrayBean {
ONE, TWO, THREE
}
IntEnum foo;
@Id
Long id;
@@ -38,12 +40,26 @@ public class EArrayBean {
@DbArray
List<Status> statuses;
@DbArray
List<VarcharEnum> vcEnums = new ArrayList<>();
@DbArray
List<IntEnum> intEnums = new ArrayList<>();
@DbArray
Set<Status> status2;
@Version
Long version;
public IntEnum getFoo() {
return foo;
}
public void setFoo(final IntEnum foo) {
this.foo = foo;
}
public Long getId() {
return id;
}
@@ -100,6 +116,21 @@ public class EArrayBean {
this.statuses = statuses;
}
public List<VarcharEnum> getVcEnums() {
return vcEnums;
}
public void setVcEnums(final List<VarcharEnum> vcEnums) {
this.vcEnums = vcEnums;
}
public List<IntEnum> getIntEnums() {
return intEnums;
}
public void setIntEnums(final List<IntEnum> intEnums) {
this.intEnums = intEnums;
}
public Set<Status> getStatus2() {
return status2;
@@ -0,0 +1,13 @@
package org.tests.model.array;
import io.ebean.annotation.DbEnumType;
import io.ebean.annotation.DbEnumValue;
public enum IntEnum {
ZERO, ONE, TWO;
@DbEnumValue(storage = DbEnumType.INTEGER)
public int dbValue() {
return 100 + ordinal();
}
}
@@ -1,19 +1,22 @@
package org.tests.model.array;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.Query;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.Query;
import io.ebean.SqlRow;
public class TestDbArray_basic extends BaseTestCase {
@@ -22,7 +25,7 @@ public class TestDbArray_basic extends BaseTestCase {
private EArrayBean found;
@Test
public void insert() {
public void insert() throws SQLException {
bean.setName("some stuff");
@@ -44,6 +47,10 @@ public class TestDbArray_basic extends BaseTestCase {
bean.setStatuses(new ArrayList<>());
bean.getStatuses().add(EArrayBean.Status.ONE);
bean.getStatuses().add(EArrayBean.Status.THREE);
bean.getVcEnums().add(VarcharEnum.ONE);
bean.getVcEnums().add(VarcharEnum.TWO);
bean.getIntEnums().add(IntEnum.ZERO);
bean.getIntEnums().add(IntEnum.TWO);
bean.setStatus2(new LinkedHashSet<>());
bean.getStatus2().add(EArrayBean.Status.TWO);
@@ -64,16 +71,23 @@ public class TestDbArray_basic extends BaseTestCase {
.arrayIsNotEmpty("phoneNumbers")
.arrayContains("statuses", EArrayBean.Status.ONE)
.arrayContains("status2", EArrayBean.Status.TWO)
.arrayContains("vcEnums", VarcharEnum.TWO)
.arrayContains("intEnums", IntEnum.ZERO)
.query();
List<EArrayBean> list = query.findList();
List<EArrayBean.Status> statuses = list.get(0).getStatuses();
Set<EArrayBean.Status> status2 = list.get(0).getStatus2();
List<IntEnum> intEnums = list.get(0).getIntEnums();
List<VarcharEnum> varcharEnums = list.get(0).getVcEnums();
assertThat(statuses).contains(EArrayBean.Status.ONE, EArrayBean.Status.THREE);
assertThat(status2).contains(EArrayBean.Status.ONE, EArrayBean.Status.TWO);
assertThat(intEnums).containsExactly(IntEnum.ZERO, IntEnum.TWO);
assertThat(varcharEnums).containsExactly(VarcharEnum.ONE, VarcharEnum.TWO);
assertThat(query.getGeneratedSql()).contains(" t0.other_ids @> array[?,?]::bigint[] ");
assertThat(query.getGeneratedSql()).contains(" t0.uids @> array[?] ");
assertThat(query.getGeneratedSql()).contains(" t0.phone_numbers @> array[?] ");
@@ -87,6 +101,15 @@ public class TestDbArray_basic extends BaseTestCase {
.query();
query.findList();
final SqlRow row = Ebean.createSqlQuery("select * from earray_bean").findOne();
final String[] vcs = (String[]) ((java.sql.Array) row.get("vc_enums")).getArray();
assertThat(vcs).containsExactly("xXxONE", "xXxTWO");
final Integer[] ints = (Integer[]) ((java.sql.Array) row.get("int_enums")).getArray();
assertThat(ints).containsExactly(100, 102);
assertThat(query.getGeneratedSql()).contains(" coalesce(cardinality(t0.other_ids),0) = 0");
assertThat(query.getGeneratedSql()).contains(" not (t0.uids @> array[?])");
}
@@ -0,0 +1,13 @@
package org.tests.model.array;
import io.ebean.annotation.DbEnumType;
import io.ebean.annotation.DbEnumValue;
public enum VarcharEnum {
ZERO, ONE, TWO;
@DbEnumValue(storage = DbEnumType.VARCHAR)
public String dbValue() {
return "xXx" + name();
}
}
@@ -5,7 +5,6 @@ import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.validation.constraints.Size;
import java.sql.Timestamp;
@Entity
@@ -18,6 +17,9 @@ public class BWithQIdent {
@Size(max = 191) // key must not exceed 767 Bytes, so max key len for mysql with utf8mb4 = 191*4 = 764 bytes
String name;
@Column(name = "`CODE`")
String CODE;
@Version
Timestamp lastUpdated;
@@ -37,6 +39,14 @@ public class BWithQIdent {
this.name = name;
}
public String getCODE() {
return CODE;
}
public void setCODE(String CODE) {
this.CODE = CODE;
}
public Timestamp getLastUpdated() {
return lastUpdated;
}
@@ -4,8 +4,6 @@ import io.ebean.BaseTestCase;
import io.ebean.CacheMode;
import io.ebean.Ebean;
import io.ebean.Pairs;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheManager;
import io.ebean.cache.ServerCacheStatistics;
@@ -251,7 +249,6 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase {
assertBeanCacheHitMiss(0, 0);
}
@IgnorePlatform({Platform.MYSQL, Platform.SQLSERVER})
@Test
public void findList_inPairs_standardConcat() {
@@ -280,14 +277,15 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase {
assertBeanCacheHitMiss(1, 0);
if (isH2()) {
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku||'-'||t0.code) in (?, ? ) order by t0.sku desc; --bind(def,Array[2]={2-1000,3-1000})");
} else {
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and concat(t0.sku,'-',t0.code) in (?, ? ) order by t0.sku desc; --bind(def,Array[2]={2-1000,3-1000})");
} else if (isPostgres()) {
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku||'-'||t0.code)");
} else {
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and concat(t0.sku,'-',t0.code)");
}
}
@IgnorePlatform({Platform.MYSQL, Platform.SQLSERVER})
@Test
public void findList_inPairs_userConcat() {
@@ -318,9 +316,11 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase {
assertBeanCacheHitMiss(1, 0);
if (isH2()) {
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku||':'||t0.code||'-foo') in (?, ? ) order by t0.sku desc; --bind(def,Array[2]={2:1000-foo,3:1000-foo})");
} else {
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and concat(t0.sku,':',t0.code,'-foo') in (?, ? ) order by t0.sku desc; --bind(def,Array[2]={2:1000-foo,3:1000-foo})");
} else if (isPostgres()){
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (t0.sku||':'||t0.code||'-foo')");
} else {
assertThat(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and concat(t0.sku,':',t0.code,'-foo')");
}
}
@@ -8,14 +8,14 @@ import java.util.Objects;
@Embeddable
public class CkeUserKey {
@Basic(optional = false)
@Column(name = "cod_cpny")
private int codCompany;
@Basic(optional = false)
@Column(name = "username")
private String username;
@Basic(optional = false)
@Column(name = "cod_cpny")
private int codCompany;
public CkeUserKey(int codCompany, String username) {
this.codCompany = codCompany;
this.username = username;
@@ -1,6 +1,7 @@
package org.tests.model.draftable;
import io.ebean.Finder;
import io.ebean.annotation.Draft;
import io.ebean.annotation.DraftOnly;
import io.ebean.annotation.Draftable;
@@ -25,6 +26,9 @@ public class Document extends BaseDomain {
String body;
@Draft
boolean draft;
@DraftOnly
Timestamp whenPublish;
@@ -54,6 +58,14 @@ public class Document extends BaseDomain {
this.body = body;
}
public boolean isDraft() {
return draft;
}
public void setDraft(boolean draft) {
this.draft = draft;
}
public Organisation getOrganisation() {
return organisation;
}
@@ -2,12 +2,12 @@ package org.tests.model.history;
import io.ebean.annotation.History;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.SoftDelete;
import org.tests.model.draftable.BaseDomain;
import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import java.util.List;
@History
@@ -21,6 +21,9 @@ public class HeLink extends BaseDomain {
String comments;
@SoftDelete
boolean deleted;
@HistoryExclude
@ManyToMany
List<HeDoc> docs;
@@ -33,6 +36,14 @@ public class HeLink extends BaseDomain {
public HeLink() {
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public String getName() {
return name;
}
@@ -0,0 +1,62 @@
package org.tests.model.history;
import io.ebean.annotation.History;
import io.ebean.annotation.SoftDelete;
import org.tests.model.draftable.BaseDomain;
import javax.persistence.Entity;
import javax.persistence.OneToOne;
@History
@Entity
public class HsdSetting extends BaseDomain {
String key;
String val;
@SoftDelete
boolean deleted;
@OneToOne
private HsdUser user;
public HsdSetting(String key) {
this.key = key;
}
public HsdSetting() {
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public String getVal() {
return val;
}
public void setVal(String val) {
this.val = val;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public HsdUser getUser() {
return user;
}
public void setUser(HsdUser user) {
this.user = user;
}
}
@@ -0,0 +1,54 @@
package org.tests.model.history;
import io.ebean.annotation.History;
import io.ebean.annotation.SoftDelete;
import org.tests.model.draftable.BaseDomain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToOne;
@History
@Entity
public class HsdUser extends BaseDomain {
String name;
@SoftDelete
boolean deleted;
@OneToOne(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
HsdSetting setting;
public HsdUser(String name) {
this.name = name;
}
public HsdUser() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public HsdSetting getSetting() {
return setting;
}
public void setSetting(HsdSetting setting) {
this.setting = setting;
}
}
@@ -7,8 +7,9 @@ import io.ebean.annotation.Platform;
import org.junit.Test;
import java.sql.Timestamp;
import java.util.List;
import static org.assertj.core.api.StrictAssertions.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
public class TestHistoryExclude extends BaseTestCase {
@@ -28,6 +29,38 @@ public class TestHistoryExclude extends BaseTestCase {
}
}
@Test
public void testSoftDelete_includeSoftDeletes_findList() {
HeLink l = new HeLink("two", "boo");
Ebean.save(l);
Ebean.delete(l);
List<HeLink> list = Ebean.find(HeLink.class)
.setIncludeSoftDeletes()
.findList();
assertThat(list).isNotEmpty();
}
@Test
public void testSoftDelete_includeSoftDeletes_findOne() {
HeLink l = new HeLink("three", "boo2");
Ebean.save(l);
Ebean.delete(l);
HeLink found = Ebean.find(HeLink.class)
.setId(l.getId())
.setIncludeSoftDeletes()
.findOne();
assertThat(found).isNotNull();
assertThat(found.getName()).isEqualTo("three");
}
@Test
public void testLazyLoad() {
@@ -0,0 +1,27 @@
package org.tests.model.history;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class TestHistorySoftDeleteOneToOne extends BaseTestCase {
@Test
public void findOne() {
HsdUser u1 = new HsdUser("u1");
Ebean.save(u1);
Ebean.delete(u1);
HsdUser one = Ebean.find(HsdUser.class)
.setId(u1.getId())
.setIncludeSoftDeletes()
.findOne();
assertThat(one).isNotNull();
}
}
@@ -0,0 +1,19 @@
package org.tests.model.onetoone;
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class OtoAone {
@Id
private String id;
private String description;
public OtoAone(String id, String description){
this.id = id;
this.description = description;
}
}
@@ -0,0 +1,47 @@
package org.tests.model.onetoone;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@Entity
public class OtoAtwo {
@Id
private String id;
private String description;
@OneToOne(orphanRemoval=true, cascade = CascadeType.ALL)
private OtoAone aone;
public OtoAtwo(String id, String description){
this.id = id;
this.description = description;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public OtoAone getAone() {
return aone;
}
public void setAone(OtoAone aone) {
this.aone = aone;
}
}
@@ -3,8 +3,8 @@ package org.tests.model.onetoone;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
@Entity
public class OtoBChild {
@@ -16,7 +16,7 @@ public class OtoBChild {
String child;
@OneToOne
@JoinColumn(name = "master_id", referencedColumnName = "id")
@PrimaryKeyJoinColumn
OtoBMaster master;
public Long getId() {
@@ -2,6 +2,7 @@ package org.tests.model.onetoone;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@@ -13,7 +14,7 @@ public class OtoBMaster {
String name;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "master")
@OneToOne(cascade = CascadeType.ALL, mappedBy = "master", fetch = FetchType.LAZY)
OtoBChild child;
public Long getId() {
@@ -0,0 +1,73 @@
package org.tests.model.onetoone;
import io.ebean.Finder;
import io.ebean.annotation.SoftDelete;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Version;
@Entity
public class OtoSdChild {
public static Finder<Long, OtoSdChild> find = new Finder<>(OtoSdChild.class);
@Id
long id;
String child;
@SoftDelete
boolean deleted;
@OneToOne
OtoSdMaster master;
@Version
long version;
public OtoSdChild(String child) {
this.child = child;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getChild() {
return child;
}
public void setChild(String child) {
this.child = child;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public OtoSdMaster getMaster() {
return master;
}
public void setMaster(OtoSdMaster master) {
this.master = master;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
@@ -0,0 +1,55 @@
package org.tests.model.onetoone;
import io.ebean.Finder;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Version;
@Entity
public class OtoSdMaster {
public static Finder<Long, OtoSdMaster> find = new Finder<>(OtoSdMaster.class);
@Id
long id;
String name;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "master")//, fetch = FetchType.LAZY)
OtoSdChild child;
@Version
long version;
public OtoSdMaster(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public OtoSdChild getChild() {
return child;
}
public void setChild(OtoSdChild child) {
this.child = child;
}
}
@@ -3,12 +3,53 @@ package org.tests.model.onetoone;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.Query;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestOneToOneImportedPkNative extends BaseTestCase {
@Test
public void findWithLazyOneToOne() {
OtoBChild child = new OtoBChild();
child.setChild("c1");
OtoBMaster master = new OtoBMaster();
master.setName("m2");
master.setChild(child);
Ebean.save(master);
Query<OtoBMaster> query = Ebean.find(OtoBMaster.class)
//.select("name")
.where().idEq(master.getId())
.query();
OtoBMaster one = query.findOne();
String sql = sqlOf(query);
assertThat(sql).contains("select t0.id, t0.name from oto_bmaster t0 where t0.id ");
assertThat(sql).doesNotContain("left join oto_bchild");
assertThat(one).isNotNull();
LoggedSqlCollector.start();
OtoBChild child1 = one.getChild();
assertThat(child1).isNotNull();
assertThat(child1.getChild()).isEqualTo("c1");
List<String> lazyLoadSql = LoggedSqlCollector.stop();
assertThat(lazyLoadSql).hasSize(2);
assertThat(lazyLoadSql.get(0)).contains("select t0.id, t0.name, t0.id from oto_bmaster t0 where t0.id = ?");
assertThat(lazyLoadSql.get(1)).contains("select t0.master_id, t0.child, t0.master_id from oto_bchild t0 where t0.master_id = ?");
}
@Test
public void native_with_o2oAndImportedPrimaryKey() {
@@ -23,12 +64,10 @@ public class TestOneToOneImportedPkNative extends BaseTestCase {
assertThat(m.getId()).isEqualTo(one.getId());
assertThat(m.getName()).isEqualTo(one.getName());
assertThat(m.getChild()).isNull();
OtoBMaster m2 = server.find(OtoBMaster.class, one.getId());
assertThat(m2.getId()).isEqualTo(one.getId());
assertThat(m2.getName()).isEqualTo(one.getName());
assertThat(m2.getChild()).isNull();
}
}
@@ -0,0 +1,66 @@
package org.tests.model.onetoone;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestOneToOneOrphanStringId extends BaseTestCase {
@Test
public void test_updateInsert() {
OtoAtwo b = new OtoAtwo("b1", "b test");
Ebean.save(b);
OtoAone a = new OtoAone("a1", "a test");
b.setAone(a);
LoggedSqlCollector.start();
Ebean.save(b);
List<String> update = LoggedSqlCollector.current();
assertThat(update).hasSize(2);
assertThat(update.get(0)).contains("insert into oto_aone");
assertThat(update.get(1)).contains("update oto_atwo set aone_id=? where id=?");
Ebean.delete(b);
List<String> deletes = LoggedSqlCollector.stop();
assertThat(deletes).hasSize(2);
assertThat(deletes.get(0)).contains("delete from oto_atwo");
assertThat(deletes.get(1)).contains("delete from oto_aone");
}
@Test
public void test_cascade() {
OtoAone a = new OtoAone("a2", "a test");
OtoAtwo b = new OtoAtwo("b2", "b test");
b.setAone(a);
LoggedSqlCollector.start();
Ebean.save(b);
List<String> inserts = LoggedSqlCollector.current();
assertThat(inserts).hasSize(2);
assertThat(inserts.get(0)).contains("insert into oto_aone");
assertThat(inserts.get(1)).contains("insert into oto_atwo");
Ebean.delete(b);
List<String> deletes = LoggedSqlCollector.stop();
assertThat(deletes).hasSize(2);
assertThat(deletes.get(0)).contains("delete from oto_atwo");
assertThat(deletes.get(1)).contains("delete from oto_aone");
}
}
@@ -0,0 +1,47 @@
package org.tests.model.onetoone;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class TestOneToOneSoftDeleteChild extends BaseTestCase {
@Test
public void deleteChild() {
OtoSdChild child = new OtoSdChild("c1");
OtoSdMaster master = new OtoSdMaster("m1");
master.setChild(child);
Ebean.save(master);
verifyBeforeDelete(master, child);
Ebean.delete(child);
verifyAfterDelete(master, child);
}
private void verifyBeforeDelete(OtoSdMaster parent, OtoSdChild child) {
assertThat(OtoSdMaster.find.byId(parent.getId()).getChild().getId())
.isEqualTo(child.getId());
assertThat(
OtoSdChild.find.byId(child.getId()).getMaster().getId())
.isEqualTo(parent.getId());
}
private void verifyAfterDelete(OtoSdMaster parent, OtoSdChild child) {
// After delete, finding child by id should return null
assertThat(OtoSdChild.find.byId(child.getId()))
.isNull();
// After delete, getting linked child from parent should return null
assertThat(OtoSdMaster.find.byId(parent.getId()).getChild())
.isNull();
}
}
@@ -0,0 +1,58 @@
package org.tests.model.orphanremoval;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Version;
@Entity
public class OrpDetail {
@Id
String id;
String detail;
@ManyToOne
OrpMaster master;
@Version
long version;
public OrpDetail(String id, String detail) {
this.id = id;
this.detail = detail;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public OrpMaster getMaster() {
return master;
}
public void setMaster(OrpMaster master) {
this.master = master;
}
public String getDetail() {
return detail;
}
public void setDetail(String detail) {
this.detail = detail;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
@@ -0,0 +1,60 @@
package org.tests.model.orphanremoval;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Version;
import java.util.List;
@Entity
public class OrpMaster {
@Id
String id;
String name;
@OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
List<OrpDetail> details;
@Version
long version;
public OrpMaster(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<OrpDetail> getDetails() {
return details;
}
public void setDetails(List<OrpDetail> details) {
this.details = details;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
@@ -0,0 +1,36 @@
package org.tests.model.orphanremoval;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.ebeantest.LoggedSqlCollector;
import org.junit.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class TestOrphanRemoveO2M extends BaseTestCase {
@Test
public void clear_expect_deletes() {
OrpMaster master = new OrpMaster("m","master");
master.getDetails().add(new OrpDetail("d1", "d1"));
master.getDetails().add(new OrpDetail("d2", "d2"));
Ebean.save(master);
master.getDetails().clear();
LoggedSqlCollector.start();
Ebean.save(master);
List<String> sql = LoggedSqlCollector.stop();
assertThat(Ebean.find(OrpDetail.class, "d1")).isNull();
assertThat(Ebean.find(OrpDetail.class, "d2")).isNull();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("delete from orp_detail where id=?");
}
}
@@ -6,10 +6,13 @@ import javax.persistence.Id;
import javax.persistence.Version;
import java.nio.file.Path;
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.MonthDay;
import java.time.OffsetDateTime;
import java.time.Period;
import java.time.Year;
@@ -39,6 +42,9 @@ public class SomeNewTypesBean {
@Column(name = "yr_mth")
YearMonth yearMonth;
@Column(name = "month_day")
MonthDay monthDay;
LocalDate localDate;
LocalDateTime localDateTime;
@@ -47,6 +53,8 @@ public class SomeNewTypesBean {
ZonedDateTime zonedDateTime;
LocalTime localTime;
Instant instant;
ZoneId zoneId;
@@ -57,6 +65,8 @@ public class SomeNewTypesBean {
Period period;
Duration duration;
public Long getId() {
return id;
}
@@ -105,6 +115,14 @@ public class SomeNewTypesBean {
this.yearMonth = yearMonth;
}
public MonthDay getMonthDay() {
return monthDay;
}
public void setMonthDay(MonthDay monthDay) {
this.monthDay = monthDay;
}
public LocalDate getLocalDate() {
return localDate;
}
@@ -137,6 +155,14 @@ public class SomeNewTypesBean {
this.zonedDateTime = zonedDateTime;
}
public LocalTime getLocalTime() {
return localTime;
}
public void setLocalTime(LocalTime localTime) {
this.localTime = localTime;
}
public Instant getInstant() {
return instant;
}
@@ -176,4 +202,12 @@ public class SomeNewTypesBean {
public void setPeriod(Period period) {
this.period = period;
}
public Duration getDuration() {
return duration;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
}
@@ -0,0 +1,66 @@
package org.tests.model.uuidsibling;
import org.junit.Test;
import java.util.UUID;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
public class SoftDeleteCascadeTest {
@Test
public void testDeleteParent() {
// Insert + link records
USibParent parent = new USibParent();
parent.save();
USibChild child = new USibChild(parent);
child.save();
USibChildSibling childSibling = new USibChildSibling(child);
childSibling.save();
Long parentId = parent.getId();
UUID childId = child.getId();
Long childSiblingId = childSibling.getId();
assertBefore(parent, child, childSibling);
parent.delete();
assertAfter(parentId, childId, childSiblingId);
}
private void assertBefore(USibParent parent, USibChild child, USibChildSibling childSibling) {
parent.refresh();
assertThat("Parent should have one child loaded",
parent.getChildren().size(),
is(1)
);
assertThat("Parent should have correct child loaded",
parent.getChildren().get(0).getId(),
is(child.getId())
);
assertThat("Child that was loaded should have its sibling available",
parent.getChildren().get(0).getChildSibling(),
not(nullValue())
);
assertThat(
"Child that was loaded should have loaded correct sibling",
parent.getChildren().get(0).getChildSibling().getId(),
is(childSibling.getId())
);
}
private void assertAfter(Long parentId, UUID childId, Long childSiblingId) {
assertThat(USibParent.find.byId(parentId), is(nullValue()));
assertThat(USibChild.find.byId(childId), is(nullValue()));
assertThat(USibChildSibling.find.byId(childSiblingId), is(nullValue()));
}
}
@@ -0,0 +1,70 @@
package org.tests.model.uuidsibling;
import io.ebean.Finder;
import io.ebean.Model;
import io.ebean.annotation.SoftDelete;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
@Entity
public class USibChild extends Model {
private static final long serialVersionUID = 738194912181571389L;
@Id
private UUID id;
@SoftDelete
private boolean deleted;
@OneToOne(mappedBy = "child", cascade = CascadeType.REMOVE)
private USibChildSibling childSibling;
@ManyToOne
private USibParent parent;
public static Finder<UUID, USibChild> find = new Finder<>(USibChild.class);
public USibChild() {}
public USibChild(USibParent parent) {
this.parent = parent;
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public USibChildSibling getChildSibling() {
return childSibling;
}
public USibChild setChildSibling(USibChildSibling childSibling) {
this.childSibling = childSibling;
return this;
}
public USibParent getParent() {
return parent;
}
public void setParent(USibParent parent) {
this.parent = parent;
}
}
@@ -0,0 +1,56 @@
package org.tests.model.uuidsibling;
import io.ebean.Finder;
import io.ebean.Model;
import io.ebean.annotation.SoftDelete;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@Entity
public class USibChildSibling extends Model {
private static final long serialVersionUID = 738194912181571389L;
@Id
private Long id;
@SoftDelete
private boolean deleted;
@OneToOne
private USibChild child;
public static Finder<Long, USibChildSibling> find = new Finder<>(USibChildSibling.class);
public USibChildSibling() {}
public USibChildSibling(USibChild child) {
this.child = child;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public USibChild getChild() {
return child;
}
public USibChildSibling setChild(USibChild child) {
this.child = child;
return this;
}
}
@@ -0,0 +1,52 @@
package org.tests.model.uuidsibling;
import io.ebean.Finder;
import io.ebean.Model;
import io.ebean.annotation.SoftDelete;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class USibParent extends Model {
private static final long serialVersionUID = 4116545858939406149L;
@Id
private Long id;
@SoftDelete
private boolean deleted;
@OneToMany(cascade = CascadeType.REMOVE, mappedBy = "parent")
private List<USibChild> children;
public static Finder<Long, USibParent> find = new Finder<>(USibParent.class);
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public List<USibChild> getChildren() {
return children;
}
public USibParent setChildren(List<USibChild> children) {
this.children = children;
return this;
}
}
@@ -0,0 +1,26 @@
package org.tests.quotedidentifier;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import org.junit.Test;
import org.tests.model.basic.BWithQIdent;
public class TestQuotedIdentifierQuery extends BaseTestCase {
@Test
public void test() {
BWithQIdent bean = new BWithQIdent();
bean.setName("foo");
bean.setCODE("bar");
Ebean.save(bean);
Ebean.find(BWithQIdent.class)
.where()
.eq("name", "foo")
.raw("t0.\"Name\" = ?", "foo")
.raw("t0.\"CODE\" = ?", "bar")
.findList();
}
}

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