mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df7b04877d | ||
|
|
4683877e0b | ||
|
|
d0020ab8ce | ||
|
|
9b3b8ad46a | ||
|
|
ca3c02bc1c |
@@ -3,7 +3,7 @@ package io.ebean;
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
/**
|
||||
* Thrown when a foreign key constraint is enforced.
|
||||
* Thrown when a foreign key constraint is enforced or a field is too large.
|
||||
*/
|
||||
public class DataIntegrityException extends PersistenceException {
|
||||
private static final long serialVersionUID = -6740171949170180970L;
|
||||
@@ -14,4 +14,11 @@ public class DataIntegrityException extends PersistenceException {
|
||||
public DataIntegrityException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with message only.
|
||||
*/
|
||||
public DataIntegrityException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,6 +540,7 @@ public class DatabaseConfig {
|
||||
|
||||
private String dumpMetricsOptions;
|
||||
|
||||
private LengthCheck lengthCheck = LengthCheck.OFF;
|
||||
private Function<String, String> metricNaming = MetricNamingMatch.INSTANCE;
|
||||
|
||||
/**
|
||||
@@ -2919,6 +2920,7 @@ public class DatabaseConfig {
|
||||
jdbcFetchSizeFindEach = p.getInt("jdbcFetchSizeFindEach", jdbcFetchSizeFindEach);
|
||||
jdbcFetchSizeFindList = p.getInt("jdbcFetchSizeFindList", jdbcFetchSizeFindList);
|
||||
databasePlatformName = p.get("databasePlatformName", databasePlatformName);
|
||||
lengthCheck = p.getEnum(LengthCheck.class, "lengthCheck", lengthCheck);
|
||||
|
||||
uuidVersion = p.getEnum(UuidVersion.class, "uuidVersion", uuidVersion);
|
||||
uuidStateFile = p.get("uuidStateFile", uuidStateFile);
|
||||
@@ -3419,6 +3421,20 @@ public class DatabaseConfig {
|
||||
this.metricNaming = metricNaming;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length check mode.
|
||||
*/
|
||||
public LengthCheck getLengthCheck() {
|
||||
return lengthCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the length check mode.
|
||||
*/
|
||||
public void setLengthCheck(LengthCheck lengthCheck) {
|
||||
this.lengthCheck = lengthCheck;
|
||||
}
|
||||
|
||||
public enum UuidVersion {
|
||||
VERSION4,
|
||||
VERSION1,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package io.ebean.config;
|
||||
|
||||
/**
|
||||
* Defines the length-check mode.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public enum LengthCheck {
|
||||
/**
|
||||
* By default, length checking is off. This means, strings/jsons and files are passed to the DB and the DB might or might not check the length.
|
||||
* The DB has to check the data length. Note this is not possible for certain datatypes (e.g. clob without size)
|
||||
*/
|
||||
OFF,
|
||||
/**
|
||||
* When enabling length check, ebean validates strings/json strings and files before saving them to DB.
|
||||
*/
|
||||
ON,
|
||||
/**
|
||||
* Same as "ON", but take the UTF8-bytelength for validation. This may be useful, if you have an UTF8 based charset (default for DB2)
|
||||
*/
|
||||
UTF8
|
||||
}
|
||||
@@ -174,4 +174,9 @@ public interface DataBinder {
|
||||
*/
|
||||
String popJson();
|
||||
|
||||
/**
|
||||
* Returns the last bound object (e.g. for BindValidation). Note for InputStreams you'll get an InputStreamInfo.
|
||||
*/
|
||||
Object popLastObject();
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package io.ebean.core.type;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Helper to transports length info of DataBind.setBinaryStream(stream, length) to BindValidation
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public final class InputStreamInfo {
|
||||
|
||||
private final InputStream stream;
|
||||
private final long length;
|
||||
|
||||
public InputStreamInfo(InputStream stream, long length) {
|
||||
this.stream = stream;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public InputStream stream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
public long length() {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
package io.ebeaninternal.server.bind;
|
||||
|
||||
import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.InputStreamInfo;
|
||||
import io.ebeaninternal.api.CoreLog;
|
||||
import io.ebeaninternal.server.core.timezone.DataTimeZone;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
@@ -23,6 +27,8 @@ public class DataBind implements DataBinder {
|
||||
protected int pos;
|
||||
private String json;
|
||||
|
||||
private Object lastObject = null;
|
||||
|
||||
public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt, Connection connection) {
|
||||
this.dataTimeZone = dataTimeZone;
|
||||
this.pstmt = pstmt;
|
||||
@@ -65,16 +71,19 @@ public class DataBind implements DataBinder {
|
||||
@Override
|
||||
public void setObject(Object value) throws SQLException {
|
||||
pstmt.setObject(++pos, value);
|
||||
lastObject = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setObject(Object value, int sqlType) throws SQLException {
|
||||
pstmt.setObject(++pos, value, sqlType);
|
||||
lastObject = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNull(int jdbcType) throws SQLException {
|
||||
pstmt.setNull(++pos, jdbcType);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,7 +105,7 @@ public class DataBind implements DataBinder {
|
||||
}
|
||||
}
|
||||
|
||||
private void closeInputStreams() {
|
||||
public void closeInputStreams() {
|
||||
if (inputStreams != null) {
|
||||
for (InputStream inputStream : inputStreams) {
|
||||
try {
|
||||
@@ -117,36 +126,43 @@ public class DataBind implements DataBinder {
|
||||
@Override
|
||||
public void setString(String value) throws SQLException {
|
||||
pstmt.setString(++pos, value);
|
||||
lastObject = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setInt(int value) throws SQLException {
|
||||
pstmt.setInt(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setLong(long value) throws SQLException {
|
||||
pstmt.setLong(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setShort(short value) throws SQLException {
|
||||
pstmt.setShort(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setFloat(float value) throws SQLException {
|
||||
pstmt.setFloat(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setDouble(double value) throws SQLException {
|
||||
pstmt.setDouble(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setBigDecimal(BigDecimal value) throws SQLException {
|
||||
pstmt.setBigDecimal(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -157,6 +173,7 @@ public class DataBind implements DataBinder {
|
||||
} else {
|
||||
pstmt.setDate(++pos, value);
|
||||
}
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,6 +184,7 @@ public class DataBind implements DataBinder {
|
||||
} else {
|
||||
pstmt.setTimestamp(++pos, value);
|
||||
}
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,26 +195,31 @@ public class DataBind implements DataBinder {
|
||||
} else {
|
||||
pstmt.setTime(++pos, value);
|
||||
}
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBoolean(boolean value) throws SQLException {
|
||||
pstmt.setBoolean(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBytes(byte[] value) throws SQLException {
|
||||
pstmt.setBytes(++pos, value);
|
||||
lastObject = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByte(byte value) throws SQLException {
|
||||
pstmt.setByte(++pos, value);
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setChar(char value) throws SQLException {
|
||||
pstmt.setString(++pos, String.valueOf(value));
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -211,21 +234,31 @@ public class DataBind implements DataBinder {
|
||||
}
|
||||
inputStreams.add(inputStream);
|
||||
pstmt.setBinaryStream(++pos, inputStream, length);
|
||||
lastObject = new InputStreamInfo(inputStream, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlob(byte[] bytes) throws SQLException {
|
||||
pstmt.setBinaryStream(++pos, new ByteArrayInputStream(bytes), bytes.length);
|
||||
lastObject = bytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClob(String content) throws SQLException {
|
||||
pstmt.setCharacterStream(++pos, new StringReader(content), content.length());
|
||||
lastObject = content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setArray(String arrayType, Object[] elements) throws SQLException {
|
||||
pstmt.setArray(++pos, connection.createArrayOf(arrayType, elements));
|
||||
lastObject = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object popLastObject() {
|
||||
Object ret = lastObject;
|
||||
lastObject = null;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
private final String asOfViewSuffix;
|
||||
private final boolean jacksonCorePresent;
|
||||
private final int queryPlanTTLSeconds;
|
||||
private final BindMaxLength bindMaxLength;
|
||||
private int entityBeanCount;
|
||||
private List<BeanDescriptor<?>> immutableDescriptorList;
|
||||
/**
|
||||
@@ -159,6 +160,19 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
this.changeLogListener = config.changeLogListener(bootupClasses.getChangeLogListener());
|
||||
this.changeLogRegister = config.changeLogRegister(bootupClasses.getChangeLogRegister());
|
||||
this.jacksonCorePresent = config.isJacksonCorePresent();
|
||||
this.bindMaxLength = initMaxLength();
|
||||
}
|
||||
|
||||
BindMaxLength initMaxLength() {
|
||||
LengthCheck lengthCheck = this.config.getLengthCheck();
|
||||
switch (lengthCheck) {
|
||||
case OFF:
|
||||
return null;
|
||||
case UTF8:
|
||||
return BindMaxLength.ofUtf8();
|
||||
default:
|
||||
return BindMaxLength.ofStandard();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1502,6 +1516,10 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
return list;
|
||||
}
|
||||
|
||||
public BindMaxLength bindMaxLength() {
|
||||
return bindMaxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparator to sort the BeanDescriptors by name.
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.ebean.DataIntegrityException;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.MutableValueInfo;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.config.EncryptKey;
|
||||
import io.ebean.config.LengthCheck;
|
||||
import io.ebean.config.dbplatform.DbEncryptFunction;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.config.dbplatform.ExtraDbTypes;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.DocPropertyType;
|
||||
import io.ebean.core.type.InputStreamInfo;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebean.plugin.Property;
|
||||
import io.ebean.text.StringParser;
|
||||
@@ -46,6 +50,7 @@ import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
@@ -166,6 +171,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
*/
|
||||
private final String dbComment;
|
||||
private final DbEncryptFunction dbEncryptFunction;
|
||||
private final BindMaxLength bindMaxLength;
|
||||
private int deployOrder;
|
||||
final boolean jsonSerialize;
|
||||
final boolean jsonDeserialize;
|
||||
@@ -258,6 +264,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
}
|
||||
this.jsonSerialize = deploy.isJsonSerialize();
|
||||
this.jsonDeserialize = deploy.isJsonDeserialize();
|
||||
this.bindMaxLength = deploy.bindMaxLength();
|
||||
}
|
||||
|
||||
private String tableAliasIntern(BeanDescriptor<?> descriptor, String s, boolean dbEncrypted, String dbColumn) {
|
||||
@@ -345,6 +352,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
this.elPlaceHolderEncrypted = override.replace(source.elPlaceHolderEncrypted, source.dbColumn);
|
||||
this.jsonSerialize = source.jsonSerialize;
|
||||
this.jsonDeserialize = source.jsonDeserialize;
|
||||
this.bindMaxLength = source.bindMaxLength;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -554,6 +562,18 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bind(DataBind b, Object value) throws SQLException {
|
||||
scalarType.bind(b, value);
|
||||
if (bindMaxLength != null) {
|
||||
Object obj = b.popLastObject();
|
||||
long length = bindMaxLength.length(dbLength, obj);
|
||||
if (length > dbLength) {
|
||||
b.closeInputStreams();
|
||||
String s = String.valueOf(value); // take original bind value here.
|
||||
if (s.length() > 50) {
|
||||
s = s.substring(0, 47) + "...";
|
||||
}
|
||||
throw new DataIntegrityException("Cannot bind value '" + s + "' (effective length=" + length + ") to column '" + dbColumn + "' (length=" + dbLength + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings(value = "unchecked")
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.core.type.InputStreamInfo;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public interface BindMaxLength {
|
||||
|
||||
/**
|
||||
* Return a UTF8 based implementation.
|
||||
*/
|
||||
static BindMaxLength ofUtf8() {
|
||||
return new UTF8();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a standard implementation.
|
||||
*/
|
||||
static BindMaxLength ofStandard() {
|
||||
return new Standard();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the length of the object.
|
||||
*/
|
||||
long length(int dbLength, Object obj);
|
||||
|
||||
final class UTF8 implements BindMaxLength {
|
||||
|
||||
@Override
|
||||
public long length(int dbLength, Object obj) {
|
||||
if (obj instanceof String) {
|
||||
String s = (String) obj;
|
||||
int stringLength = s.length();
|
||||
if (stringLength > dbLength) {
|
||||
return stringLength;
|
||||
} else if (stringLength * 4 <= dbLength) {
|
||||
return -1;
|
||||
} else {
|
||||
return s.getBytes(StandardCharsets.UTF_8).length;
|
||||
}
|
||||
|
||||
} else if (obj instanceof byte[]) {
|
||||
return ((byte[]) obj).length;
|
||||
} else if (obj instanceof InputStreamInfo) {
|
||||
return ((InputStreamInfo) obj).length();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class Standard implements BindMaxLength {
|
||||
|
||||
@Override
|
||||
public long length(int dbLength, Object obj) {
|
||||
if (obj instanceof String) {
|
||||
return ((String) obj).length();
|
||||
} else if (obj instanceof byte[]) {
|
||||
return ((byte[]) obj).length;
|
||||
} else if (obj instanceof InputStreamInfo) {
|
||||
return ((InputStreamInfo) obj).length();
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,6 +139,10 @@ public class DeployBeanDescriptor<T> {
|
||||
this.beanType = beanType;
|
||||
}
|
||||
|
||||
public BindMaxLength bindMaxLength() {
|
||||
return manager.bindMaxLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the IdClass to use.
|
||||
*/
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package io.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import io.avaje.lang.Nullable;
|
||||
import io.ebean.annotation.*;
|
||||
import io.ebean.config.ScalarTypeConverter;
|
||||
import io.ebean.config.dbplatform.DbDefaultValue;
|
||||
import io.ebean.config.dbplatform.DbEncrypt;
|
||||
import io.ebean.config.dbplatform.DbEncryptFunction;
|
||||
import io.ebean.config.dbplatform.ExtraDbTypes;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
import io.ebeaninternal.server.core.InternString;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BindMaxLength;
|
||||
import io.ebeaninternal.server.deploy.DbMigrationInfo;
|
||||
import io.ebeaninternal.server.deploy.DeployDocPropertyOptions;
|
||||
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
@@ -1133,4 +1136,20 @@ public class DeployBeanProperty {
|
||||
boolean isJsonType() {
|
||||
return mutationDetection != null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BindMaxLength bindMaxLength() {
|
||||
if (dbLength == 0) {
|
||||
return null;
|
||||
}
|
||||
switch (dbType) {
|
||||
case Types.VARCHAR:
|
||||
case Types.BLOB:
|
||||
case ExtraDbTypes.JSON:
|
||||
return desc.bindMaxLength();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package org.tests.basic;
|
||||
|
||||
import io.ebean.DB;
|
||||
import io.ebean.DataIntegrityException;
|
||||
import io.ebean.xtest.BaseTestCase;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.tests.model.json.EBasicJsonList;
|
||||
import org.tests.model.json.EBasicJsonMap;
|
||||
import org.tests.model.types.SomeFileBean;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
/**
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
public class TestLength extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
void testFileSize() throws IOException {
|
||||
File f1 = File.createTempFile("testfile", "tmp");
|
||||
byte[] buf = new byte[1024];
|
||||
try (FileOutputStream fos = new FileOutputStream(f1)) {
|
||||
for (int i = 0; i < 100; i++) {
|
||||
fos.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
SomeFileBean sfb1 = new SomeFileBean();
|
||||
sfb1.setContent(f1);
|
||||
DB.save(sfb1);
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(f1)) {
|
||||
for (int i = 0; i < 101; i++) {
|
||||
fos.write(buf);
|
||||
}
|
||||
}
|
||||
|
||||
SomeFileBean sfb2 = new SomeFileBean();
|
||||
sfb2.setContent(f1);
|
||||
assertThatThrownBy(() -> DB.save(sfb2)).isInstanceOf(DataIntegrityException.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The property 'EBasicJsonMap.content' is annotated with @DbJson(length=5000). So we assume, that we cannot save Json-objects
|
||||
* where the serialized form exceed that limit and we would expect an error on save.
|
||||
* The length check works for platforms like h2, as H2 uses a 'varchar(5000)'. So it is impossible to save such long jsons,
|
||||
* but it won't work for SqlServer, as here 'nvarchar(max)' is used. No validation happens at DB level and you might get very
|
||||
* large Json objects in your database. This mostly happens unintentionally (programming error, misconfiguration)
|
||||
* So they are in the database and they cannot be accessed by ebean any more, because there are new limits in Jackson:
|
||||
* - Max 5 Meg per string in 2.15.0
|
||||
* - Max 20 Meg per string in 2.15.1
|
||||
* see https://github.com/FasterXML/jackson-core/issues/1014
|
||||
*/
|
||||
@Test
|
||||
void testLongString() {
|
||||
// s is so big, that it could not be deserialized by jackson
|
||||
String s = new String(new char[20_000_001]).replace('\0', 'x');
|
||||
|
||||
EBasicJsonMap bean = new EBasicJsonMap();
|
||||
bean.setName("b1");
|
||||
bean.setContent(Map.of("string", s));
|
||||
|
||||
assertThatThrownBy(() -> {
|
||||
// we expect, that we can NOT save the bean, this is ensured by the bind validator.
|
||||
DB.save(bean);
|
||||
}).isInstanceOf(DataIntegrityException.class);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests the UTF8 validation.
|
||||
*/
|
||||
@Test
|
||||
void testUtf8() {
|
||||
|
||||
String s = new String(new char[40]).replace('\0', '€');
|
||||
|
||||
EBasicJsonList bean = new EBasicJsonList();
|
||||
bean.setName("b1");
|
||||
bean.setTags(List.of(s));
|
||||
|
||||
if (isDb2() || isOracle()) {
|
||||
// by default, DB2 && oracle uses bytes in varchar, so an '€' symbol needs 3 bytes
|
||||
assertThatThrownBy(() -> {
|
||||
// we expect, that we can NOT save the bean, this is ensured by the bind validator.
|
||||
DB.save(bean);
|
||||
}).isInstanceOf(DataIntegrityException.class);
|
||||
} else {
|
||||
DB.save(bean);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.tests.json;
|
||||
|
||||
import io.ebean.DB;
|
||||
import io.ebean.DataIntegrityException;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.tests.model.json.EBasicJsonMap;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
class TestDbJsonLength {
|
||||
|
||||
|
||||
/**
|
||||
* The property 'EBasicJsonMap.content' is annotated with @DbJson(length=5000). So we assume, that we cannot save Json-objects
|
||||
* where the serialized form exceed that limit and we would expect an error on save.
|
||||
* The length check works for platforms like h2, as H2 uses a 'varchar(5000)'. So it is impossible to save such long jsons,
|
||||
* but it won't work for SqlServer, as here 'nvarchar(max)' is used. No validation happens at DB level and you might get very
|
||||
* large Json objects in your database. This mostly happens unintentionally (programming error, misconfiguration)
|
||||
* So they are in the database and they cannot be accessed by ebean any more, because there are new limits in Jackson:
|
||||
* - Max 5 Meg per string in 2.15.0
|
||||
* - Max 20 Meg per string in 2.15.1
|
||||
* see https://github.com/FasterXML/jackson-core/issues/1014
|
||||
*/
|
||||
@Test
|
||||
void testLongString() {
|
||||
// s is so big, that it could not be deserialized by jackson
|
||||
String s = new String(new char[20_000_001]).replace('\0', 'x');
|
||||
|
||||
EBasicJsonMap bean = new EBasicJsonMap();
|
||||
bean.setName("b1");
|
||||
bean.setContent(Map.of("string", s));
|
||||
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
|
||||
softly.assertThatThrownBy(() -> {
|
||||
DB.save(bean);
|
||||
}).isInstanceOf(DataIntegrityException.class);
|
||||
|
||||
|
||||
if (bean.getId() != null) {
|
||||
// we expect, that we could NOT save the bean, but this is not true for sqlServer.
|
||||
// we will get a javax.persistence.PersistenceException: Error loading on org.tests.model.json.EBasicJsonMap.content
|
||||
// when we try to load the bean back from DB
|
||||
DB.find(EBasicJsonMap.class, bean.getId());
|
||||
}
|
||||
|
||||
softly.assertAll();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class EBasicJsonMap {
|
||||
|
||||
String name;
|
||||
|
||||
@DbJson
|
||||
@DbJson(length = 5000)
|
||||
Map<String, Object> content;
|
||||
|
||||
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.tests.model.types;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Lob;
|
||||
import javax.persistence.Version;
|
||||
import javax.persistence.*;
|
||||
import java.io.File;
|
||||
|
||||
@Entity
|
||||
@@ -18,6 +15,7 @@ public class SomeFileBean {
|
||||
String name;
|
||||
|
||||
@Lob
|
||||
@Column(length = 100 * 1024) // limit to 100kb
|
||||
File content;
|
||||
|
||||
public Long getId() {
|
||||
|
||||
@@ -5,3 +5,4 @@ ebean.test.username=admin
|
||||
ebean.test.password=admin
|
||||
datasource.default=db2-11
|
||||
ebean.db2-11.databasePlatformName=db2luw
|
||||
ebean.lengthCheck=utf8
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
ebean.test.platform=oracle
|
||||
ebean.test.dbName=test_eb
|
||||
datasource.default=oracle
|
||||
ebean.lengthCheck=utf8
|
||||
|
||||
@@ -7,6 +7,7 @@ ebean.test.sqlserver.port=9434
|
||||
ebean.test.sqlserver.url=jdbc:sqlserver://localhost:9434;databaseName=test_ebean;sendTimeAsDateTime=false;integratedSecurity=false;trustServerCertificate=true
|
||||
datasource.default=sqlserver2019
|
||||
ebean.sqlserver2019.databasePlatformName=sqlserver17
|
||||
ebean.lengthCheck=on
|
||||
|
||||
## A case sensitive collation example:
|
||||
#ebean.test.sqlserver.collation=LATIN1_GENERAL_100_CI_AS_SC_UTF8
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
<nexus.staging.keepStagingRepositoryOnFailure>true</nexus.staging.keepStagingRepositoryOnFailure>
|
||||
<nexus.staging.skipStagingRepositoryClose>true</nexus.staging.skipStagingRepositoryClose>
|
||||
<nexus.staging.autoReleaseAfterClose>false</nexus.staging.autoReleaseAfterClose>
|
||||
<jackson.version>2.15.0</jackson.version>
|
||||
<jackson.version>2.15.2</jackson.version>
|
||||
<h2database.version>2.1.214</h2database.version>
|
||||
<ebean-persistence-api.version>3.0</ebean-persistence-api.version>
|
||||
<ebean-types.version>3.0</ebean-types.version>
|
||||
|
||||
Reference in New Issue
Block a user