Merge branch 'master' of github.com:ebean-orm/ebean

This commit is contained in:
rob bygrave
2017-10-09 21:13:32 +13:00
39 changed files with 406 additions and 56 deletions
-11
View File
@@ -1,16 +1,5 @@
package io.ebean;
import io.ebean.util.CamelCaseHelper;
import java.io.Serializable;
import java.sql.ResultSet;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Used to build object graphs based on a raw SQL statement (rather than
* generated by Ebean).
@@ -175,7 +175,7 @@ public class DatabasePlatform {
protected SqlExceptionTranslator exceptionTranslator = new SqlCodeTranslator();
protected char[] specialLikeCharacters = { '%', '_' };
protected char[] specialLikeCharacters = { '%', '_', '\\' };
/**
* Instantiates a new database platform.
@@ -26,7 +26,10 @@ public class DB2Platform extends DatabasePlatform {
this.dbIdentity.setSupportsGetGeneratedKeys(true);
this.dbIdentity.setSupportsSequence(true);
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.exceptionTranslator =
new SqlErrorCodes()
.addAcquireLock("40001","57033") // key -911/-913
@@ -46,6 +49,10 @@ public class DB2Platform extends DatabasePlatform {
persistBatchOnCascade = PersistBatch.NONE;
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
/**
* Return a DB2 specific sequence IdGenerator that supports batch fetching
* sequence values.
@@ -48,6 +48,9 @@ public class MySqlPlatform extends DatabasePlatform {
this.openQuote = "`";
this.closeQuote = "`";
// use pipe for escaping as it depends if mysql runs in no_backslash_escapes or not.
this.likeClause = "like binary ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.forwardOnlyHintOnFindIterate = true;
this.booleanDbType = Types.BIT;
@@ -76,4 +79,9 @@ public class MySqlPlatform extends DatabasePlatform {
// NOWAIT and SKIP LOCKED currently not supported with MySQL
return sql + " for update";
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
}
@@ -37,6 +37,9 @@ public class OraclePlatform extends DatabasePlatform {
this.treatEmptyStringsAsNull = true;
this.likeClause = "like ? escape '|'";
this.specialLikeCharacters = new char[] { '%', '_', '|' };
this.openQuote = "\"";
this.closeQuote = "\"";
@@ -78,4 +81,9 @@ public class OraclePlatform extends DatabasePlatform {
return sql + " for update";
}
}
@Override
protected void escapeLikeCharacter(char ch, StringBuilder sb) {
sb.append('|').append(ch);
}
}
@@ -577,7 +577,8 @@ public class BaseTableDdl implements TableDdl {
List<Column> columns = createTable.getColumn();
for (Column column : columns) {
if (hasValue(column.getUnique()) || hasValue(column.getUniqueOneToOne())) {
if (!Boolean.TRUE.equals(column.isPrimaryKey())
&& (hasValue(column.getUnique()) || hasValue(column.getUniqueOneToOne()))) {
if (Boolean.TRUE.equals(column.isNotnull()) || inlineUniqueWhenNullable) {
// normal mechanism for adding unique constraint
inlineUniqueConstraintSingle(apply, column);
@@ -22,6 +22,7 @@ public class DJsonService implements SpiJsonService {
/**
* Write the nested Map/List as json.
*/
@Override
public String write(Object object) throws IOException {
return EJsonWriter.write(object);
}
@@ -29,6 +30,7 @@ public class DJsonService implements SpiJsonService {
/**
* Write the nested Map/List as json to the writer.
*/
@Override
public void write(Object object, Writer writer) throws IOException {
EJsonWriter.write(object, writer);
}
@@ -36,6 +38,7 @@ public class DJsonService implements SpiJsonService {
/**
* Write the nested Map/List as json to the jsonGenerator.
*/
@Override
public void write(Object object, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.write(object, jsonGenerator);
}
@@ -43,6 +46,7 @@ public class DJsonService implements SpiJsonService {
/**
* Write the collection as json array to the jsonGenerator.
*/
@Override
public void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
EJsonWriter.writeCollection(collection, jsonGenerator);
}
@@ -51,6 +55,7 @@ public class DJsonService implements SpiJsonService {
* Parse the json and return as a Map additionally specifying if the returned map should
* be modify aware meaning that it can detect when it has been modified.
*/
@Override
public Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(json, modifyAware);
}
@@ -58,6 +63,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a Map.
*/
@Override
public Map<String, Object> parseObject(String json) throws IOException {
return EJsonReader.parseObject(json);
}
@@ -65,6 +71,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a Map taking a reader.
*/
@Override
public Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
return EJsonReader.parseObject(reader, modifyAware);
}
@@ -72,6 +79,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a Map taking a reader.
*/
@Override
public Map<String, Object> parseObject(Reader reader) throws IOException {
return EJsonReader.parseObject(reader);
}
@@ -79,6 +87,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a Map taking a JsonParser.
*/
@Override
public Map<String, Object> parseObject(JsonParser parser) throws IOException {
return EJsonReader.parseObject(parser);
}
@@ -89,6 +98,7 @@ public class DJsonService implements SpiJsonService {
* Used when the first token is checked to see if the value is null prior to calling this.
* </p>
*/
@Override
public Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
return EJsonReader.parseObject(parser, token);
}
@@ -96,6 +106,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a modify aware List.
*/
@Override
public <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
return EJsonReader.parseList(json, modifyAware);
}
@@ -103,6 +114,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a List.
*/
@Override
public List<Object> parseList(String json) throws IOException {
return EJsonReader.parseList(json);
}
@@ -110,6 +122,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a List taking a Reader.
*/
@Override
public List<Object> parseList(Reader reader) throws IOException {
return EJsonReader.parseList(reader);
}
@@ -117,6 +130,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a List taking a JsonParser.
*/
@Override
public List<Object> parseList(JsonParser parser) throws IOException {
return EJsonReader.parseList(parser, false);
}
@@ -124,6 +138,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json returning as a List taking into account the current token.
*/
@Override
@SuppressWarnings("unchecked")
public <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
return (List<T>) EJsonReader.parse(parser, currentToken, false);
@@ -132,6 +147,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(String json) throws IOException {
return EJsonReader.parse(json);
}
@@ -139,6 +155,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(Reader reader) throws IOException {
return EJsonReader.parse(reader);
}
@@ -146,6 +163,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json and return as a List or Map.
*/
@Override
public Object parse(JsonParser parser) throws IOException {
return EJsonReader.parse(parser);
}
@@ -153,6 +171,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json returning a Set that might be modify aware.
*/
@Override
public <T> Set<T> parseSet(String json, boolean modifyAware) throws IOException {
List<T> list = parseList(json, modifyAware);
if (list == null) {
@@ -169,6 +188,7 @@ public class DJsonService implements SpiJsonService {
/**
* Parse the json returning as a Set taking into account the current token.
*/
@Override
public <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return new LinkedHashSet<>(parseList(parser, currentToken));
}
@@ -288,7 +288,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements BeanQueryRe
*/
@Override
public void endTransIfRequired() {
if (createdTransaction) {
if (createdTransaction && transaction.isActive()) {
transaction.commit();
}
}
@@ -31,6 +31,7 @@ import io.ebeanservice.docstore.api.DocStoreUpdates;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -784,8 +785,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
@Override
public final void checkRowCount(int rowCount) {
if (ConcurrencyMode.VERSION == concurrencyMode && rowCount != 1) {
String m = Message.msg("persist.conc2", String.valueOf(rowCount));
throw new OptimisticLockException(m, null, bean);
// 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);
}
}
switch (type) {
case DELETE:
@@ -1038,6 +1038,13 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
if (prop.getMappedBy() == null) {
// if we are doc store only we are done
// this allowes the use of @OneToMany in @DocStore - Entities
if (info.getDescriptor().isDocStoreOnly()) {
prop.setUnidirectional();
return;
}
if (!findMappedBy(prop)) {
makeUnidirectional(info, prop);
return;
@@ -965,6 +965,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
/**
* Skip JSON write value for ToMany property.
*/
@Override
public void jsonWriteValue(SpiJsonWriter writeJson, Object value) throws IOException {
// do nothing, exclude ToMany properties
}
@@ -96,7 +96,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
if (!isTransient) {
//noinspection StatementWithEmptyBody
if (embedded) {
if (embedded || descriptor.isDocStoreOnly()) {
// no imported or exported information
} else if (!oneToOneExported) {
importedId = createImportedId(this, targetDescriptor, tableJoin);
@@ -1,7 +1,6 @@
package io.ebeaninternal.server.persist.dmlbind;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
import io.ebeaninternal.server.persist.dml.DmlMode;
/**
@@ -36,6 +36,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
/**
* An object that represents a SqlSelect statement.
@@ -421,6 +422,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
if (!moveToNextRow()) {
if (currentBean == null) {
nextBean = null;
return false;
} else {
// the last bean
@@ -508,6 +510,9 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
auditIterateNextBean();
}
hasNextCache = false;
if (nextBean == null) {
throw new NoSuchElementException();
}
return nextBean;
}
@@ -191,6 +191,7 @@ public class CQueryEngine {
*/
public <T> QueryIterator<T> findIterate(OrmQueryRequest<T> request) {
prepareForPaging(request);
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
@@ -326,18 +327,25 @@ public class CQueryEngine {
return historySupport.getSysPeriodLower(rootTableAlias);
}
/**
* deemed to be a be a paging query - check that the order by contains the id
* property to ensure unique row ordering for predicable paging but only in
* case, this is not a distinct query
*
* @param request
*/
private <T> void prepareForPaging(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
if (!query.isDistinct() && (query.getMaxRows() > 1 || query.getFirstRow() > 0)) {
request.getBeanDescriptor().appendOrderById(query);
}
}
/**
* Find a list/map/set of beans.
*/
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
SpiQuery<T> query = request.getQuery();
if (!query.isDistinct() && (query.getMaxRows() > 1 || query.getFirstRow() > 0)) {
// deemed to be a be a paging query - check that the order by contains
// the id property to ensure unique row ordering for predicable paging
// but only in case, this is not a distinct query
request.getBeanDescriptor().appendOrderById(query);
}
prepareForPaging(request);
CQuery<T> cquery = queryBuilder.buildQuery(request);
request.setCancelableQuery(cquery);
@@ -376,7 +384,7 @@ public class CQueryEngine {
if (cquery != null) {
cquery.close();
}
if (query.isFutureFetch()) {
if (request.getQuery().isFutureFetch()) {
// end the transaction for futureFindIds
// as it had it's own transaction
logger.debug("Future fetch completed!");
@@ -14,6 +14,8 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
private final CQuery<T> cquery;
private final OrmQueryRequest<T> request;
private boolean closed;
CQueryIteratorSimple(CQuery<T> cquery, OrmQueryRequest<T> request) {
this.cquery = cquery;
@@ -22,11 +24,17 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
@Override
public boolean hasNext() {
boolean ret = false;
try {
request.flushPersistenceContextOnIterate();
return cquery.hasNext();
ret = cquery.hasNext();
return ret;
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
} finally {
if (!ret) {
close();
}
}
}
@@ -38,9 +46,12 @@ class CQueryIteratorSimple<T> implements QueryIterator<T> {
@Override
public void close() {
cquery.updateExecutionStatisticsIterator();
cquery.close();
request.endTransIfRequired();
if (!closed) {
closed = true;
cquery.updateExecutionStatisticsIterator();
cquery.close();
request.endTransIfRequired();
}
}
@Override
@@ -342,28 +342,34 @@ public class WriteJson implements SpiJsonWriter {
}
}
@Override
public boolean isParentBean(Object bean) {
return !parentBeans.isEmpty() && parentBeans.contains(bean);
}
@Override
public void pushParentBeanMany(EntityBean parentBean) {
parentBeans.push(parentBean);
}
@Override
public void popParentBeanMany() {
parentBeans.pop();
}
@Override
public void beginAssocOne(String key, EntityBean bean) {
parentBeans.push(bean);
pathStack.pushPathKey(key);
}
@Override
public void endAssocOne() {
parentBeans.pop();
pathStack.pop();
}
@Override
public void beginAssocMany(String key) {
try {
pathStack.pushPathKey(key);
@@ -374,6 +380,7 @@ public class WriteJson implements SpiJsonWriter {
}
}
@Override
public void endAssocMany() {
try {
pathStack.pop();
@@ -407,6 +414,7 @@ public class WriteJson implements SpiJsonWriter {
return new WriteBean(desc, explicitAllProps, currentIncludeProps, bean, visitor);
}
@Override
public void writeValueUsingObjectMapper(String name, Object value) {
if (!isIncludeEmpty()) {
@@ -534,6 +542,7 @@ public class WriteJson implements SpiJsonWriter {
}
}
@Override
public Boolean includeMany(String key) {
if (fetchPath != null) {
String fullPath = pathStack.peekFullPath(key);
@@ -542,6 +551,7 @@ public class WriteJson implements SpiJsonWriter {
return null;
}
@Override
public void toJson(String name, Collection<?> c) {
try {
@@ -184,7 +184,7 @@ public class ScalarTypeJsonObjectMapper {
bind.setObject(PostgresHelper.asObject(pgType, rawJson));
} else {
if (value == null) {
bind.setNull(Types.LONGNVARCHAR); // use longvarchar, otherwise SqlServer will fail with 'Invalid JDBC data type 5.001.'
bind.setNull(Types.VARCHAR); // use varchar, otherwise SqlServer/db2 will fail with 'Invalid JDBC data type 5.001.'
} else {
try {
String json = objectMapper.writeValueAsString(value);