Compare commits

...
Author SHA1 Message Date
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
64 changed files with 1197 additions and 245 deletions
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.22.2</version>
<version>11.22.6</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.22.2</tag>
<tag>ebean-11.22.6</tag>
</scm>
<profiles>
+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
@@ -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;
}
@@ -64,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.
*/
@@ -455,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.
*/
@@ -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.
*/
@@ -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;
}
@@ -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;
@@ -417,7 +417,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);
@@ -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);
}
@@ -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,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();
}
}
@@ -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");
@@ -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;
@@ -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;
}
@@ -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,21 @@ public class TestHistoryExclude extends BaseTestCase {
}
}
@Test
public void testSoftDelete() {
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 testLazyLoad() {
@@ -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,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();
}
}
@@ -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();
}
}
@@ -139,7 +139,7 @@ public class TestNativeSqlBasic extends BaseTestCase {
* Oracle does not support getTableName() via JDBC resultSet meta data
*/
@Test
@IgnorePlatform({Platform.SQLSERVER, Platform.ORACLE}) // does only work in 'cursor' mode!
@IgnorePlatform({Platform.ORACLE})
public void partialAssoc() {
ResetBasicData.reset();
@@ -20,7 +20,7 @@ public class TestNestedSubTransaction extends BaseTestCase {
/**
* MySql only supports named savepoints - review.
*/
@IgnorePlatform(Platform.MYSQL)
@IgnorePlatform({Platform.MYSQL, Platform.SQLSERVER})
@Test
public void ebeanServer_commitTransaction_expect_sameAsTransactionCommit() {
@@ -65,7 +65,7 @@ public class TestNestedSubTransaction extends BaseTestCase {
}
@IgnorePlatform(Platform.MYSQL)
@IgnorePlatform({Platform.SQLSERVER, Platform.MYSQL})
@Test
public void nestedUseSavepoint_doubleNested_rollbackCommit() {
@@ -102,7 +102,7 @@ public class TestNestedSubTransaction extends BaseTestCase {
}
}
@IgnorePlatform(Platform.MYSQL)
@IgnorePlatform({Platform.SQLSERVER, Platform.MYSQL})
@Test
public void nestedUseSavepoint_doubleNested_commitRollback() {
@@ -139,7 +139,7 @@ public class TestNestedSubTransaction extends BaseTestCase {
}
}
@IgnorePlatform(Platform.MYSQL)
@IgnorePlatform({Platform.SQLSERVER, Platform.MYSQL})
@Test
public void nestedUseSavepoint_nested_RequiresNew() {
@@ -175,7 +175,7 @@ public class TestNestedSubTransaction extends BaseTestCase {
assertNull(after);
}
@IgnorePlatform(Platform.MYSQL)
@IgnorePlatform({Platform.SQLSERVER, Platform.MYSQL})
@Test
public void nestedUseSavepoint() {
@@ -11,10 +11,13 @@ import org.tests.model.types.SomeNewTypesBean;
import java.io.File;
import java.nio.file.Paths;
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;
@@ -37,6 +40,7 @@ public class TestNewTypes extends BaseTestCase {
bean.setLocalDateTime(LocalDateTime.now());
bean.setOffsetDateTime(OffsetDateTime.now());
bean.setZonedDateTime(ZonedDateTime.now());
bean.setLocalTime(LocalTime.now());
bean.setInstant(Instant.now());
bean.setYear(Year.now());
bean.setMonth(Month.APRIL);
@@ -44,8 +48,10 @@ public class TestNewTypes extends BaseTestCase {
bean.setZoneId(ZoneId.systemDefault());
bean.setZoneOffset(ZonedDateTime.now().getOffset());
bean.setYearMonth(YearMonth.of(2014, 9));
bean.setMonthDay(MonthDay.of(9,22));
bean.setPath(Paths.get(TEMP_PATH));
bean.setPeriod(Period.of(4,3,2));
bean.setDuration(Duration.ofMinutes(5));
Ebean.save(bean);
@@ -70,6 +76,9 @@ public class TestNewTypes extends BaseTestCase {
list = Ebean.find(SomeNewTypesBean.class).where().lt("zonedDateTime", ZonedDateTime.now()).findList();
assertTrue(!list.isEmpty());
list = Ebean.find(SomeNewTypesBean.class).where().le("localTime", LocalTime.now()).findList();
assertTrue(!list.isEmpty());
list = Ebean.find(SomeNewTypesBean.class).where().eq("zoneId", ZoneId.systemDefault().getId()).findList();
assertTrue(!list.isEmpty());
@@ -79,6 +88,9 @@ public class TestNewTypes extends BaseTestCase {
list = Ebean.find(SomeNewTypesBean.class).where().le("yearMonth", YearMonth.of(2014, 9)).findList();
assertTrue(!list.isEmpty());
list = Ebean.find(SomeNewTypesBean.class).where().le("monthDay", MonthDay.of(9,22)).findList();
assertTrue(!list.isEmpty());
list = Ebean.find(SomeNewTypesBean.class).where().le("year", Year.now()).findList();
assertTrue(!list.isEmpty());
@@ -91,6 +103,9 @@ public class TestNewTypes extends BaseTestCase {
list = Ebean.find(SomeNewTypesBean.class).where().eq("period", Period.of(4,3,2)).findList();
assertTrue(!list.isEmpty());
list = Ebean.find(SomeNewTypesBean.class).where().eq("duration", Duration.ofMinutes(5)).findList();
assertTrue(!list.isEmpty());
SomeNewTypesBean fetched = Ebean.find(SomeNewTypesBean.class, bean.getId());
assertEquals(bean.getZoneId(), fetched.getZoneId());
@@ -98,12 +113,15 @@ public class TestNewTypes extends BaseTestCase {
assertEquals(bean.getMonth(), fetched.getMonth());
assertEquals(bean.getYear(), fetched.getYear());
assertEquals(bean.getYearMonth(), fetched.getYearMonth());
assertEquals(bean.getMonthDay(), fetched.getMonthDay());
assertEquals(bean.getLocalDate(), fetched.getLocalDate());
assertThat(fetched.getLocalDateTime()).isEqualToIgnoringNanos(bean.getLocalDateTime());
assertThat(fetched.getOffsetDateTime()).isEqualToIgnoringNanos(bean.getOffsetDateTime());
assertThat(fetched.getLocalTime().toSecondOfDay()).isEqualTo(bean.getLocalTime().toSecondOfDay());
assertEquals(bean.getInstant().toEpochMilli() / 1000, fetched.getInstant().toEpochMilli() / 1000);
assertEquals(bean.getPath(), fetched.getPath());
assertEquals(bean.getPeriod(), fetched.getPeriod());
assertEquals(bean.getDuration(), fetched.getDuration());
String asJson = Ebean.json().toJson(fetched);
@@ -115,13 +133,16 @@ public class TestNewTypes extends BaseTestCase {
assertEquals(bean.getMonth(), toBean.getMonth());
assertEquals(bean.getYear(), toBean.getYear());
assertEquals(bean.getYearMonth(), toBean.getYearMonth());
assertEquals(bean.getMonthDay(), toBean.getMonthDay());
assertEquals(bean.getLocalDate(), toBean.getLocalDate());
assertThat(toBean.getLocalDateTime()).isEqualToIgnoringNanos(bean.getLocalDateTime());
assertThat(toBean.getOffsetDateTime()).isEqualToIgnoringNanos(bean.getOffsetDateTime());
assertEquals(bean.getLocalTime().toSecondOfDay(), toBean.getLocalTime().toSecondOfDay());
assertEquals(bean.getInstant().toEpochMilli() / 1000, toBean.getInstant().toEpochMilli() / 1000);
// FIXME: This test fails on Windows with: expected:<\tmp> but was:<C:\tmp>
assertEquals(bean.getPath(), toBean.getPath());
assertEquals(bean.getPeriod(), toBean.getPeriod());
assertEquals(bean.getDuration(), toBean.getDuration());
}
@@ -139,12 +160,15 @@ public class TestNewTypes extends BaseTestCase {
assertNull(fetched.getMonth());
assertNull(fetched.getYear());
assertNull(fetched.getYearMonth());
assertNull(fetched.getMonthDay());
assertNull(fetched.getLocalDate());
assertNull(fetched.getLocalDateTime());
assertNull(fetched.getOffsetDateTime());
assertNull(fetched.getLocalTime());
assertNull(fetched.getInstant());
assertNull(fetched.getPath());
assertNull(fetched.getPeriod());
assertNull(fetched.getDuration());
}
@Test
@@ -154,6 +178,7 @@ public class TestNewTypes extends BaseTestCase {
refBean.setLocalDateTime(LocalDateTime.now());
refBean.setOffsetDateTime(OffsetDateTime.now());
refBean.setZonedDateTime(ZonedDateTime.now());
refBean.setLocalTime(LocalTime.now());
refBean.setInstant(Instant.now());
refBean.setYear(Year.now());
refBean.setMonth(Month.APRIL);
@@ -161,8 +186,10 @@ public class TestNewTypes extends BaseTestCase {
refBean.setZoneId(ZoneId.systemDefault());
refBean.setZoneOffset(ZonedDateTime.now().getOffset());
refBean.setYearMonth(YearMonth.of(2014, 9));
refBean.setMonthDay(MonthDay.of( 9, 22));
refBean.setPath(Paths.get(TEMP_PATH));
refBean.setPeriod(Period.of(4,3,2));
refBean.setDuration(Duration.ofMinutes(5));
testSetGetPath(refBean);
}
@@ -179,6 +206,7 @@ public class TestNewTypes extends BaseTestCase {
ExpressionPath localDateTime = beanType.getExpressionPath("localDateTime");
ExpressionPath offsetDateTime = beanType.getExpressionPath("offsetDateTime");
ExpressionPath zonedDateTime = beanType.getExpressionPath("zonedDateTime");
ExpressionPath localTime = beanType.getExpressionPath("localTime");
ExpressionPath instant = beanType.getExpressionPath("instant");
ExpressionPath year = beanType.getExpressionPath("year");
ExpressionPath month = beanType.getExpressionPath("month");
@@ -186,8 +214,10 @@ public class TestNewTypes extends BaseTestCase {
ExpressionPath zoneId = beanType.getExpressionPath("zoneId");
ExpressionPath zoneOffset = beanType.getExpressionPath("zoneOffset");
ExpressionPath yearMonth = beanType.getExpressionPath("yearMonth");
ExpressionPath monthDay = beanType.getExpressionPath("monthDay");
ExpressionPath path = beanType.getExpressionPath("path");
ExpressionPath period = beanType.getExpressionPath("period");
ExpressionPath duration = beanType.getExpressionPath("duration");
localDate.pathSet(testBean, refBean.getLocalDate());
assertThat(localDate.pathGet(testBean)).isEqualTo(refBean.getLocalDate());
@@ -201,6 +231,9 @@ public class TestNewTypes extends BaseTestCase {
zonedDateTime.pathSet(testBean, refBean.getZonedDateTime());
assertThat(zonedDateTime.pathGet(testBean)).isEqualTo(refBean.getZonedDateTime());
localTime.pathSet(testBean, refBean.getLocalTime());
assertThat(localTime.pathGet(testBean)).isEqualTo(refBean.getLocalTime());
instant.pathSet(testBean, refBean.getInstant());
assertThat(instant.pathGet(testBean)).isEqualTo(refBean.getInstant());
@@ -222,12 +255,18 @@ public class TestNewTypes extends BaseTestCase {
yearMonth.pathSet(testBean, refBean.getYearMonth());
assertThat(yearMonth.pathGet(testBean)).isEqualTo(refBean.getYearMonth());
monthDay.pathSet(testBean, refBean.getMonthDay());
assertThat(monthDay.pathGet(testBean)).isEqualTo(refBean.getMonthDay());
path.pathSet(testBean, refBean.getPath());
assertThat(path.pathGet(testBean)).isEqualTo(refBean.getPath());
period.pathSet(testBean, refBean.getPeriod());
assertThat(period.pathGet(testBean)).isEqualTo(refBean.getPeriod());
duration.pathSet(testBean, refBean.getDuration());
assertThat(duration.pathGet(testBean)).isEqualTo(refBean.getDuration());
Ebean.save(refBean);
Ebean.save(testBean);
}
@@ -4,7 +4,10 @@ import io.ebean.BaseTestCase;
import io.ebean.OrderBy;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
/**
* Test the OrderBy object and especially its parsing.
@@ -153,4 +156,92 @@ public class TestOrderByParse extends BaseTestCase {
assertEquals("id, name", copy.toStringFormat());
}
@Test
public void testParsingWithCollation() {
OrderBy<Object> o1 = new OrderBy<>();
o1.asc("id", "latin_1");
assertTrue(o1.getProperties().size() == 1);
assertTrue(o1.getProperties().get(0).getProperty().equals("id"));
assertTrue(o1.getProperties().get(0).isAscending());
assertEquals("id collate latin_1", o1.toStringFormat());
o1 = new OrderBy<>();
o1.desc("id", "latin_1");
assertTrue(o1.getProperties().size() == 1);
assertTrue(o1.getProperties().get(0).getProperty().equals("id"));
assertTrue(!o1.getProperties().get(0).isAscending());
assertEquals("id collate latin_1 desc", o1.toStringFormat());
o1 = new OrderBy<>();
o1.desc("id", "latin_1");
o1.asc("date");
assertTrue(o1.getProperties().size() == 2);
assertTrue(o1.getProperties().get(0).getProperty().equals("id"));
assertTrue(o1.getProperties().get(1).getProperty().equals("date"));
assertTrue(!o1.getProperties().get(0).isAscending());
assertTrue(o1.getProperties().get(1).isAscending());
assertEquals("id collate latin_1 desc, date", o1.toStringFormat());
o1 = new OrderBy<>();
o1.desc("id", "latin_1");
o1.asc("name", "latin_2");
assertTrue(o1.getProperties().size() == 2);
assertTrue(o1.getProperties().get(0).getProperty().equals("id"));
assertTrue(o1.getProperties().get(1).getProperty().equals("name"));
assertTrue(!o1.getProperties().get(0).isAscending());
assertTrue(o1.getProperties().get(1).isAscending());
assertEquals("id collate latin_1 desc, name collate latin_2", o1.toStringFormat());
// functional (DB2) syntax
o1 = new OrderBy<>();
o1.desc("id", "COLLATION_KEY(${}, 'latin_1')");
assertTrue(o1.getProperties().size() == 1);
assertTrue(o1.getProperties().get(0).getProperty().equals("id"));
assertTrue(!o1.getProperties().get(0).isAscending());
assertEquals("COLLATION_KEY(id, 'latin_1') desc", o1.toStringFormat());
}
@Test
public void equals_with_nulls() {
OrderBy<Object> o1 = new OrderBy<>("id desc nulls high");
OrderBy<Object> o2 = new OrderBy<>("id desc nulls high");
OrderBy<Object> o3 = new OrderBy<>();
o3.add("id desc nulls high");
assertEquals(o1, o2);
assertEquals(o1, o3);
OrderBy<Object> o4 = new OrderBy<>("id desc");
OrderBy<Object> o5 = new OrderBy<>("oid desc nulls high");
OrderBy<Object> o6 = new OrderBy<>("id desc nulls low");
assertNotEquals(o1, o4);
assertNotEquals(o1, o5);
assertNotEquals(o1, o6);
}
@Test
public void equals_with_collation() {
OrderBy<Object> o1 = new OrderBy<>();
o1.asc("name", "latin_1");
OrderBy<Object> o2 = new OrderBy<>();
o2.asc("name", null);
OrderBy<Object> o3 = new OrderBy<>();
o2.asc("name", "bar");
assertNotEquals(o1, o2);
assertNotEquals(o1, o3);
OrderBy<Object> o4 = new OrderBy<>();
o4.asc("name", "latin_1");
assertEquals(o1, o4);
}
}
@@ -2,21 +2,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
@@ -26,7 +26,7 @@ BEGIN
PREPARE stmt FROM @sql;
EXECUTE stmt;
END LOOP;
CLOSE curs;
END
$$
@@ -34,10 +34,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);
@@ -10,16 +10,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;
@@ -38,10 +38,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)
@@ -55,16 +55,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
@@ -89,10 +89,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)