Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	ebean-bom/pom.xml
#	ebean-core/src/main/java/io/ebeaninternal/server/persist/BatchedPstmt.java
#	ebean-ddl-generator/src/main/java/io/ebeaninternal/dbmigration/ddlgeneration/platform/DB2Ddl.java
#	ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql
#	ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql
#	ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations
#	ebean-test/src/test/java/org/tests/basic/TestMetaAnnotation.java
#	ebean-test/src/test/java/org/tests/model/basic/cache/TestCacheViaComplexNaturalKey3.java
#	tests/test-java16/pom.xml
#	tests/test-kotlin/pom.xml
This commit is contained in:
Roland Praml committed 2022-02-02 09:43:41 +01:00
commit 090f3cddb8
56 files changed
+593 -597

No files matched your search

+1 -1
View File
@@ -130,7 +130,7 @@
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.24</version>
<version>42.3.2</version>
<optional>true</optional>
</dependency>
@@ -8,15 +8,15 @@ import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.util.UrlHelper;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.io.InputStream;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.*;
import java.net.URL;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import static java.util.Objects.requireNonNull;
final class DScriptRunner implements ScriptRunner {
private static final String NEWLINE = "\n";
@@ -30,8 +30,8 @@ final class DScriptRunner implements ScriptRunner {
}
@Override
public void run(String path) {
run(path, null);
public void run(String resourcePath) {
run(resourcePath, null);
}
@Override
@@ -49,27 +49,42 @@ final class DScriptRunner implements ScriptRunner {
run(resource, null, placeholderMap);
}
@Override
public void run(Path file) {
run(file, null);
}
@Override
public void run(Path file, Map<String, String> placeholderMap) {
requireNonNull(file);
String scriptName = file.toFile().getName();
String content = fileContent(file);
runScript(content, scriptName, placeholderMap, false);
}
private void run(URL resource, String scriptName, Map<String, String> placeholderMap) {
if (resource == null) {
throw new IllegalArgumentException("resource is null?");
}
requireNonNull(resource);
if (scriptName == null) {
scriptName = resource.getFile();
}
String content = content(resource);
runScript(content, scriptName, placeholderMap, false);
}
private String content(URL resource) {
if (resource == null) {
throw new IllegalArgumentException("resource is null?");
}
try (InputStream inputStream = UrlHelper.openNoCache(resource);
Reader reader = IOUtils.newReader(inputStream)) {
private String fileContent(Path file) {
try (InputStream inputStream = new FileInputStream(file.toFile());
Reader reader = IOUtils.newReader(inputStream)) {
return readContent(reader);
} catch (IOException e) {
throw new PersistenceException("Failed to read script content", e);
}
}
private String content(URL resource) {
requireNonNull(resource);
try (InputStream inputStream = UrlHelper.openNoCache(resource);
Reader reader = IOUtils.newReader(inputStream)) {
return readContent(reader);
} catch (IOException e) {
throw new PersistenceException("Failed to read script content", e);
}
@@ -85,10 +100,9 @@ final class DScriptRunner implements ScriptRunner {
*/
private void runScript(String content, String scriptName, Map<String, String> placeholderMap, boolean useAutoCommit) {
try {
if (placeholderMap != null) {
if (placeholderMap != null && !placeholderMap.isEmpty()) {
content = ScriptTransform.build(null, placeholderMap).transform(content);
}
try (Connection connection = obtainConnection()) {
DdlRunner runner = new DdlRunner(useAutoCommit, scriptName, platformName);
runner.runAll(content, connection);
@@ -110,7 +124,6 @@ final class DScriptRunner implements ScriptRunner {
}
private String readContent(Reader reader) throws IOException {
StringBuilder buf = new StringBuilder();
try (LineNumberReader lineReader = new LineNumberReader(reader)) {
String line;
@@ -86,6 +86,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
private final ConcurrentHashMap<String, ElPropertyDeploy> elDeployCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, ElComparator<T>> comparatorCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, STreeProperty> dynamicProperty = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Map<String,String>> pathMaps = new ConcurrentHashMap<>();
private final Map<String, SpiRawSql> namedRawSql;
private final Map<String, String> namedQuery;
private final boolean multiValueSupported;
@@ -2775,6 +2777,22 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return draftableElement;
}
@Override
public Map<String, String> pathMap(String prefix) {
return pathMaps.computeIfAbsent(prefix, s -> {
HashMap<String, String> m = new HashMap<>();
for (STreePropertyAssocMany many : propsMany()) {
String name = many.name();
m.put(name, prefix + "." + name);
}
for (STreePropertyAssocOne one : propsOne()) {
String name = one.name();
m.put(name, prefix + "." + name);
}
return m.isEmpty() ? Collections.emptyMap() : m;
});
}
@Override
public boolean isEmbeddedPath(String propertyPath) {
ElPropertyDeploy elProp = elPropertyDeploy(propertyPath);
@@ -6,8 +6,6 @@ import io.ebeaninternal.api.SpiTransaction;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -23,18 +21,12 @@ import java.util.List;
*/
public final class BatchedPstmt implements SpiProfileTransactionEvent {
/**
* The underlying statement.
*/
private static final DB2GetKeys DB2_HACK = new DB2GetKeys();
private PreparedStatement pstmt;
/**
* True if an insert that uses generated keys.
*/
private final boolean isGenKeys;
/**
* The list of BatchPostExecute used to perform post processing.
* The list of BatchPostExecute used to perform post-processing.
*/
private final List<BatchPostExecute> list = new ArrayList<>();
private final String sql;
@@ -44,18 +36,6 @@ public final class BatchedPstmt implements SpiProfileTransactionEvent {
private int[] results;
private List<InputStream> inputStreams;
private static Class<? extends PreparedStatement> DB2_PREPARED_STATEMENT;
private static Method GET_DB_GENERATED_KEYS;
static {
try {
DB2_PREPARED_STATEMENT = (Class<? extends PreparedStatement>) Class.forName("com.ibm.db2.jcc.DB2PreparedStatement");
GET_DB_GENERATED_KEYS = DB2_PREPARED_STATEMENT.getDeclaredMethod("getDBGeneratedKeys");
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException cnf) {
// NOP
}
}
/**
* Create with a given statement.
*/
@@ -183,40 +163,15 @@ public final class BatchedPstmt implements SpiProfileTransactionEvent {
}
}
protected void getGeneratedKeys() throws SQLException {
int index = 0;
if (DB2_PREPARED_STATEMENT != null) {
PreparedStatement db2Stmt = null;
if (DB2_PREPARED_STATEMENT.isInstance(pstmt)) {
db2Stmt = pstmt;
} else if (pstmt.isWrapperFor(DB2_PREPARED_STATEMENT)) {
db2Stmt = pstmt.unwrap(DB2_PREPARED_STATEMENT);
}
if (db2Stmt != null) {
// WTF: https://stackoverflow.com/questions/41725492/how-to-get-auto-generated-keys-of-batch-insert-statement
ResultSet[] result;
try {
result = (ResultSet[]) GET_DB_GENERATED_KEYS.invoke(db2Stmt);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new SQLException("Could not get generated keys for DB2", e);
}
for (int i = 0; i < result.length; i++) {
while (result[i].next()) {
ResultSet rset = result[i];
Object idValue = rset.getObject(1);
list.get(index).setGeneratedKey(idValue);
index++;
}
}
return;
}
private void getGeneratedKeys() throws SQLException {
if (DB2_HACK.getGeneratedKeys(pstmt, list)) {
return;
}
int index = 0;
try (ResultSet rset = pstmt.getGeneratedKeys()) {
while (rset.next()) {
Object idValue = rset.getObject(1);
list.get(index).setGeneratedKey(idValue);
index++;
list.get(index++).setGeneratedKey(idValue);
}
}
}
@@ -0,0 +1,55 @@
package io.ebeaninternal.server.persist;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* Horrible DB2 hack.
*/
final class DB2GetKeys {
private static Class<? extends PreparedStatement> DB2_PREPARED_STATEMENT;
private static Method GET_DB_GENERATED_KEYS;
static {
try {
DB2_PREPARED_STATEMENT = (Class<? extends PreparedStatement>) Class.forName("com.ibm.db2.jcc.DB2PreparedStatement");
GET_DB_GENERATED_KEYS = DB2_PREPARED_STATEMENT.getDeclaredMethod("getDBGeneratedKeys");
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException cnf) {
// NOP
}
}
static boolean getGeneratedKeys(PreparedStatement pstmt, List<BatchPostExecute> list) throws SQLException {
if (DB2_PREPARED_STATEMENT != null) {
PreparedStatement db2Stmt = null;
if (DB2_PREPARED_STATEMENT.isInstance(pstmt)) {
db2Stmt = pstmt;
} else if (pstmt.isWrapperFor(DB2_PREPARED_STATEMENT)) {
db2Stmt = pstmt.unwrap(DB2_PREPARED_STATEMENT);
}
if (db2Stmt != null) {
// WTF: https://stackoverflow.com/questions/41725492/how-to-get-auto-generated-keys-of-batch-insert-statement
ResultSet[] result;
try {
result = (ResultSet[]) GET_DB_GENERATED_KEYS.invoke(db2Stmt);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new SQLException("Could not get generated keys for DB2", e);
}
int index = 0;
for (ResultSet resultSet : result) {
while (resultSet.next()) {
Object idValue = resultSet.getObject(1);
list.get(index++).setGeneratedKey(idValue);
}
}
return true;
}
}
return false;
}
}
@@ -581,20 +581,17 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
@Override
public void registerBeanInherit(BeanPropertyAssocOne<?> property, EntityBeanIntercept ebi) {
String path = getPath(property.name());
request.loadContext().register(path, ebi, property);
request.loadContext().register(path(property.name()), ebi, property);
}
@Override
public void register(String path, EntityBeanIntercept ebi) {
path = getPath(path);
request.loadContext().register(path, ebi);
request.loadContext().register(path(path), ebi);
}
@Override
public void register(BeanPropertyAssocMany<?> many, BeanCollection<?> bc) {
String path = getPath(many.name());
request.loadContext().register(path, many, bc);
request.loadContext().register(path(many.name()), many, bc);
}
/**
@@ -663,18 +660,14 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
return autoTuneProfiling && query.isUsageProfiling();
}
private String getPath(String propertyName) {
private String path(String propertyName) {
if (currentPrefix == null) {
return propertyName;
} else if (propertyName == null) {
return currentPrefix;
}
String path = currentPathMap.get(propertyName);
if (path != null) {
return path;
} else {
return currentPrefix + "." + propertyName;
}
return path != null ? path : currentPrefix + "." + propertyName;
}
@Override
@@ -7,6 +7,8 @@ import io.ebeaninternal.server.deploy.DbReadContext;
import io.ebeaninternal.server.deploy.InheritInfo;
import io.ebeaninternal.server.deploy.id.IdBinder;
import java.util.Map;
/**
* Bean type interface for Sql query tree.
*/
@@ -136,4 +138,8 @@ public interface STreeType {
*/
void markAsDeleted(EntityBean bean);
/**
* Return the "path map" to toMany or toOne properties using the given prefix.
*/
Map<String, String> pathMap(String prefix);
}
@@ -46,12 +46,12 @@ class SqlTreeLoadBean implements SqlTreeLoad {
this.temporalMode = node.temporalMode;
this.temporalVersions = node.temporalVersions;
this.nodeBeanProp = node.nodeBeanProp;
this.readId = node.readId;//!aggregationRoot && withId && desc.hasId();
this.readId = node.readId;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = node.disableLazyLoad;// disableLazyLoad || !readIdNormal || desc.isRawSqlBased();
this.partialObject = node.partialObject;//props.isPartialObject();
this.properties = node.properties;//props.getProps();
this.pathMap = node.pathMap;//createPathMap(prefix, desc);
this.disableLazyLoad = node.disableLazyLoad;
this.partialObject = node.partialObject;
this.properties = node.properties;
this.pathMap = node.pathMap;
this.children = node.createLoadChildren();
}
@@ -15,14 +15,13 @@ import java.util.*;
class SqlTreeNodeBean implements SqlTreeNode {
private static final SqlTreeLoad[] NO_LOAD_CHILDREN = new SqlTreeLoad[0];
private static final SqlTreeNode[] NO_CHILDREN = new SqlTreeNode[0];
final STreeType desc;
final IdBinder idBinder;
/**
* The children which will be other SelectBean or SelectProxyBean.
*/
final SqlTreeNode[] children;
final List<SqlTreeNode> children;
/**
* Set to true if this is a partial object fetch.
*/
@@ -94,8 +93,8 @@ class SqlTreeNodeBean implements SqlTreeNode {
this.disableLazyLoad = disableLazyLoad || !readIdNormal || desc.isRawSqlBased();
this.partialObject = props.isPartialObject();
this.properties = props.getProps();
this.children = myChildren == null ? NO_CHILDREN : myChildren.toArray(new SqlTreeNode[0]);
pathMap = createPathMap(prefix, desc);
this.children = myChildren == null ? Collections.emptyList() : myChildren;
this.pathMap = createPathMap(prefix, desc);
}
@Override
@@ -104,10 +103,10 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
protected SqlTreeLoad[] createLoadChildren() {
if (children.length == 0) {
if (children.isEmpty()) {
return NO_LOAD_CHILDREN;
}
List<SqlTreeLoad> loadChildren = new ArrayList<>(children.length);
List<SqlTreeLoad> loadChildren = new ArrayList<>(children.size());
for (SqlTreeNode child : children) {
SqlTreeLoad load = child.createLoad();
if (load != null) {
@@ -119,24 +118,15 @@ class SqlTreeNodeBean implements SqlTreeNode {
@Override
public final boolean isSingleProperty() {
return properties != null && properties.length == 1 && children.length == 0;
return properties != null && properties.length == 1 && children.isEmpty();
}
private Map<String, String> createPathMap(String prefix, STreeType desc) {
HashMap<String, String> m = new HashMap<>();
for (STreePropertyAssocMany many : desc.propsMany()) {
String name = many.name();
m.put(name, getPath(prefix, name));
}
return m;
return prefix == null ? Collections.emptyMap() : desc.pathMap(prefix);
}
private String getPath(String prefix, String propertyName) {
if (prefix == null) {
return propertyName;
} else {
return prefix + "." + propertyName;
}
private String path(String prefix, String propertyName) {
return prefix == null ? propertyName : prefix + "." + propertyName;
}
@Override
@@ -144,7 +134,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
if (readId) {
if (inheritInfo != null) {
// discriminator column always proceeds id column
selectChain.add(getPath(prefix, inheritInfo.getDiscriminatorColumn()));
selectChain.add(path(prefix, inheritInfo.getDiscriminatorColumn()));
}
idBinder.buildRawSqlSelectChain(prefix, selectChain);
}
@@ -399,7 +389,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
}
return false;
}
@Override
public void unselectLobs() {
@@ -101,6 +101,7 @@ public final class DefaultTypeManager implements TypeManager {
private final TypeJsonManager jsonManager;
private final boolean offlineMigrationGeneration;
private final EnumType defaultEnumType;
private final DatabasePlatform databasePlatform;
// OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath
@@ -138,7 +139,8 @@ public final class DefaultTypeManager implements TypeManager {
this.typeMap = new ConcurrentHashMap<>();
this.nativeMap = new ConcurrentHashMap<>();
this.logicalMap = new ConcurrentHashMap<>();
this.postgres = isPostgres(config.getDatabasePlatform());
this.databasePlatform = config.getDatabasePlatform();
this.postgres = isPostgresCompatible(config.getDatabasePlatform());
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.getJsonMutationDetection()) : null;
@@ -205,8 +207,13 @@ public final class DefaultTypeManager implements TypeManager {
}
}
private boolean isPostgres(DatabasePlatform databasePlatform) {
return databasePlatform.getPlatform().base() == Platform.POSTGRES;
private boolean isPostgresCompatible(DatabasePlatform databasePlatform) {
return databasePlatform.isPlatform(Platform.POSTGRES)
|| databasePlatform.isPlatform(Platform.YUGABYTE);
}
private boolean hstoreSupport() {
return databasePlatform.isPlatform(Platform.POSTGRES);
}
/**
@@ -310,7 +317,7 @@ public final class DefaultTypeManager implements TypeManager {
@Override
public ScalarType<?> getDbMapScalarType() {
return (postgres) ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR, false);
return hstoreSupport() ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR, false);
}
@Override
@@ -748,7 +755,7 @@ public final class DefaultTypeManager implements TypeManager {
jsonNodeVarchar = new ScalarTypeJsonNode.Varchar(mapper);
jsonNodeJson = jsonNodeClob; // Default for non-Postgres databases
jsonNodeJsonb = jsonNodeClob; // Default for non-Postgres databases
if (isPostgres(config.getDatabasePlatform())) {
if (postgres) {
jsonNodeJson = new ScalarTypeJsonNodePostgres.JSON(mapper);
jsonNodeJsonb = new ScalarTypeJsonNodePostgres.JSONB(mapper);
}