Compare commits

...
Author SHA1 Message Date
rob bygrave 166906a814 [maven-release-plugin] prepare release ebean-11.24.1 2018-11-01 17:22:22 +13:00
rob bygrave 1733d2382c Bump agent to 11.24.1 2018-11-01 17:20:41 +13:00
rob bygrave a6f9120905 #1525 - Check for presence of JAXB in classpath in CurrentModel (to support Java 11 without xml) 2018-11-01 13:31:08 +13:00
rob bygrave 32b599512b #1524 - @History on @OneToMany with asOf() query and fetch(many property) gives PersistenceException: Error with property[6] dt[93]data[2018-11-01 14:56:18.015][java.sql.Timestamp] 2018-11-01 00:48:46 +13:00
rob bygrave 34fd24d620 No effective change - add back SQL logging 2018-11-01 00:48:02 +13:00
Rob BygraveandGitHub f928923b5b #1519 - Memory leak when using @Cache(enableQueryCache=true) (#1523)
* #1519 - Memory leak when using @Cache(enableQueryCache=true)

* #1519 - Memory leak when using @Cache(enableQueryCache=true)

Remove unused TransactionEventBeans
2018-10-31 23:53:16 +13:00
rob bygrave 1f928e65a4 #1521 - Add API for using explicit transaction for UpdateQuery - e.g. ebeanServer.update(Customer.class)...update(activeTransaction); 2018-10-30 13:39:54 +13:00
rob bygrave d920d8eb76 #1522 - Refactor - Change io.ebean.Model to not be a annotated with @MappedSuperclass 2018-10-30 13:38:57 +13:00
Michael BenzandRob Bygrave 23d2c64046 JavaDoc Fix closing h3 tag in Transaction. no code change! (#1520) 2018-10-30 12:55:58 +13:00
rob bygrave 19d29cd836 #1514 - Add support for JacksonAnnotations at bean-properties
Fix where Jackson is not present on the classpath
2018-10-26 14:50:17 +13:00
rob bygrave f14c3e8cb4 Merge branch 'master' of github.com:ebean-orm/ebean 2018-10-26 01:34:36 +13:00
rob bygrave b9ca8be95a #1517 - Update for ebean-datasource maven groupId change to io.ebean 2018-10-26 01:34:16 +13:00
rob bygrave d627bb47e2 #1516 - More user friendly exceptions - BeanNotEnhancedException, BeanNotRegisteredException 2018-10-26 01:33:24 +13:00
rob bygrave d395c666fc #1516 - More user friendly exceptions - BeanNotEnhancedException, BeanNotRegisteredException 2018-10-26 01:32:22 +13:00
Roland PramlandRob Bygrave 939397acfc Add support for JacksonAnnotations at bean-properties (#1514) 2018-10-25 22:11:36 +13:00
Roland PramlandRob Bygrave fecb5fa731 There should be only one "Rob" in o_customer (#1515)
* FIX: CsvReader-Test

* Fixes for tests

* reversed changes

* tidy TestCsvReaderWithCallback
2018-10-25 10:35:02 +13:00
rob bygrave bdc9917811 [maven-release-plugin] prepare for next development iteration 2018-10-24 23:51:00 +13:00
rob bygrave afcd6859e0 [maven-release-plugin] prepare release ebean-11.23.1 2018-10-24 23:50:44 +13:00
rob bygrave fef87323b3 Bump to 11.23.1-SNAPSHOT 2018-10-24 23:49:26 +13:00
Rob BygraveandGitHub 6909b20a9e #1510 - Tidy DbMigrationConfig removing the properties that should only be with DbMigration generation (#1513) 2018-10-24 23:31:07 +13:00
rob bygrave c1a479e1f4 #1512 - Invalid SQL with Postgres + order by nulls first/last + where() expression on ManyToMany 2018-10-24 23:26:19 +13:00
rob bygrave e06967ff4a #1512 - Invalid SQL with Postgres + order by nulls first/last + where() expression on ManyToMany 2018-10-24 23:25:48 +13:00
rob bygrave 156469ae0f Update tests for running MySql 2018-10-24 22:59:48 +13:00
rob bygrave 1d1d479dda Fix concat_expectString test with explicit cast to string 2018-10-24 22:17:44 +13:00
rob bygrave d185ea5962 Updated db migration test for mysql 2018-10-24 22:12:42 +13:00
rob bygrave 2dbb5ea80d #1511 - Add SAP HANA support - format only changes 2018-10-24 21:49:48 +13:00
Jonathan BreglerandRob Bygrave 158ee9a081 Add SAP HANA support (#1511) 2018-10-24 21:38:17 +13:00
rob bygrave b1410cc660 [maven-release-plugin] prepare for next development iteration 2018-10-19 20:07:32 +13:00
rob bygrave 4c2e9ccacd [maven-release-plugin] prepare release ebean-11.22.10 2018-10-19 20:07:16 +13:00
rob bygrave 29aa395cd4 #1509 - DBMigration add ... List<String> getPendingDrops(); 2018-10-19 16:24:06 +13:00
rob bygrave 8160d1f01c #1508 - Invalid SQL used to fetch identity value for @Draftable with database platform that uses Identity but not getGeneratedKeys 2018-10-19 15:56:39 +13:00
rob bygrave 6fd76ff697 [maven-release-plugin] prepare for next development iteration 2018-10-15 22:35:28 +13:00
157 changed files with 4169 additions and 630 deletions
+15
View File
@@ -151,3 +151,18 @@ In `InsertHandler.getPstmt` the `meta.getIdentityDbColumns()` contains
TODO: not yet tested
'hana' platform
--------------------
Requires an installed SAP HANA server - e.g. https://store.docker.com/images/sap-hana-express-edition
- Create a user `EBEAN_TEST` with password `Eb3an_test` in the tenant database `HXE`
- Adjust the connection string and/or username in `ebean.properties`
run: `mvn clean test -Ddatasource.default=hana`
Current status: PASS
Tests run: 2486, Failures: 0, Errors: 0, Skipped: 84
+15 -8
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.22.9</version>
<version>11.24.1</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.9</tag>
<tag>ebean-11.24.1</tag>
</scm>
<profiles>
@@ -117,13 +117,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-annotation</artifactId>
<version>4.2</version>
<version>4.3</version>
</dependency>
<dependency>
<groupId>org.avaje</groupId>
<artifactId>avaje-datasource</artifactId>
<version>3.2.2</version>
<groupId>io.ebean</groupId>
<artifactId>ebean-datasource</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
@@ -229,7 +229,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-agent</artifactId>
<version>11.11.1</version>
<version>11.24.1</version>
<scope>test</scope>
</dependency>
@@ -271,6 +271,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sap.cloud.db.jdbc</groupId>
<artifactId>ngdbc</artifactId>
<version>2.3.48</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.avaje.composite</groupId>
<artifactId>avaje-composite-testing</artifactId>
@@ -302,7 +309,7 @@
<plugin>
<groupId>io.ebean</groupId>
<artifactId>ebean-maven-plugin</artifactId>
<version>11.11.1</version>
<version>11.24.1</version>
<executions>
<execution>
<id>test</id>
+11
View File
@@ -2,10 +2,12 @@ package io.ebean;
import io.ebean.annotation.TxIsolation;
import io.ebean.cache.ServerCacheManager;
import io.ebean.config.BeanNotEnhancedException;
import io.ebean.config.ServerConfig;
import io.ebean.plugin.Property;
import io.ebean.text.csv.CsvReader;
import io.ebean.text.json.JsonContext;
import io.ebean.datasource.DataSourceConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -160,6 +162,15 @@ public final class Ebean {
defaultServer = getWithCreate(defaultName.trim());
}
}
} catch (BeanNotEnhancedException e) {
throw e;
} catch (DataSourceConfigurationException e) {
String msg = "Configuration error creating DataSource for the default EbeanServer." +
" This typically means a missing application-test.yml or missing ebean-test-config dependency." +
" See https://ebean-orm.github.io/docs/trouble-shooting#datasource";
throw new DataSourceConfigurationException(msg, e);
} catch (Throwable e) {
logger.error("Error trying to create the default EbeanServer", e);
throw new RuntimeException(e);
@@ -166,6 +166,18 @@ public interface ExpressionList<T> {
*/
int delete();
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of rows that were deleted.
*/
int delete(Transaction transaction);
/**
* Execute as a update query.
*
@@ -174,6 +186,14 @@ public interface ExpressionList<T> {
*/
int update();
/**
* Execute as a update query with the given transaction.
*
* @return the number of rows that were updated.
* @see UpdateQuery
*/
int update(Transaction transaction);
/**
* Execute the query iterating over the results.
*
-1
View File
@@ -74,7 +74,6 @@ import javax.persistence.MappedSuperclass;
*
* }</pre>
*/
@MappedSuperclass
public abstract class Model {
/**
+20
View File
@@ -924,11 +924,31 @@ public interface Query<T> {
*/
int delete();
/**
* Execute as a delete query returning the number of rows deleted using the given transaction.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @return the number of beans/rows that were deleted.
*/
int delete(Transaction transaction);
/**
* Execute the UpdateQuery returning the number of rows updated.
*
* @return the number of beans/rows updated.
*/
int update();
/**
* Execute the UpdateQuery returning the number of rows updated using the given transaction.
*
* @return the number of beans/rows updated.
*/
int update(Transaction transaction);
/**
* Return the count of entities this query should return.
* <p>
+3 -3
View File
@@ -84,7 +84,7 @@ public interface Transaction extends AutoCloseable {
* <p>
* This is similar to commit() but leaves the transaction "Active".
* </p>
* <h3>Functions/h3>
* <h3>Functions</h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
@@ -100,7 +100,7 @@ public interface Transaction extends AutoCloseable {
* This performs commit and completes the transaction closing underlying resources and
* marking the transaction as "In active".
* </p>
* <h3>Functions/h3>
* <h3>Functions</h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
@@ -117,7 +117,7 @@ public interface Transaction extends AutoCloseable {
* <p>
* This performs rollback, closes underlying resources and marks the transaction as "In active".
* </p>
* <h3>Functions/h3>
* <h3>Functions</h3>
* <ul>
* <li>Call rollback on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
@@ -0,0 +1,13 @@
package io.ebean.config;
/**
* Throw when an processing an entity bean that is not bytecode enhanced.
*
* Refer: https://ebean-orm.github.io/docs/trouble-shooting#not-enhanced
*/
public class BeanNotEnhancedException extends IllegalStateException {
public BeanNotEnhancedException(String msg) {
super(msg);
}
}
@@ -0,0 +1,13 @@
package io.ebean.config;
/**
* Throw when an processing thinks a bean is not registered.
*
* Refer: https://ebean-orm.github.io/docs/trouble-shooting#not-registered
*/
public class BeanNotRegisteredException extends IllegalStateException {
public BeanNotRegisteredException(String msg) {
super(msg);
}
}
@@ -28,34 +28,6 @@ public class DbMigrationConfig {
*/
protected Platform platform;
/**
* Set to true if the DB migration should be generated on server start.
*/
protected boolean generate;
/**
* The migration version name (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
protected String version;
/**
* Description text that can be appended to the version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
*/
protected String name;
/**
* Resource path for the migration xml and sql.
*/
@@ -75,13 +47,6 @@ public class DbMigrationConfig {
protected String modelSuffix = ".model.xml";
protected boolean includeGeneratedFileComment;
/**
* The version of a pending drop that should be generated as the next migration.
*/
protected String generatePendingDrop;
/**
* For running migration the DB table that holds migration execution status.
*/
@@ -228,52 +193,6 @@ public class DbMigrationConfig {
this.applyPrefix = applyPrefix;
}
/**
* Return true if the generated file comment should be included.
*/
public boolean isIncludeGeneratedFileComment() {
return includeGeneratedFileComment;
}
/**
* Set to true if the generated file comment should be included.
*/
public void setIncludeGeneratedFileComment(boolean includeGeneratedFileComment) {
this.includeGeneratedFileComment = includeGeneratedFileComment;
}
/**
* Return the migration version (or "next") to generate pending drops for.
*/
public String getGeneratePendingDrop() {
return generatePendingDrop;
}
/**
* Set the migration version (or "next") to generate pending drops for.
*/
public void setGeneratePendingDrop(String generatePendingDrop) {
this.generatePendingDrop = generatePendingDrop;
}
/**
* Set the migration version.
* <p>
* Note that version set via System property or environment variable <code>ddl.migration.version</code> takes precedence.
*/
public void setVersion(String version) {
this.version = version;
}
/**
* Set the migration name.
* <p>
* Note that name set via System property or environment variable <code>ddl.migration.name</code> takes precedence.
*/
public void setName(String name) {
this.name = name;
}
/**
* Return the table name that holds the migration run details
* (used by DB Migration runner only).
@@ -504,14 +423,8 @@ public class DbMigrationConfig {
applyPrefix = properties.get("migration.applyPrefix", applyPrefix);
applySuffix = properties.get("migration.applySuffix", applySuffix);
modelSuffix = properties.get("migration.modelSuffix", modelSuffix);
includeGeneratedFileComment = properties.getBoolean("migration.includeGeneratedFileComment", includeGeneratedFileComment);
generatePendingDrop = properties.get("migration.generatePendingDrop", generatePendingDrop);
platform = properties.getEnum(Platform.class, "migration.platform", platform);
generate = properties.getBoolean("migration.generate", generate);
version = properties.get("migration.version", version);
name = properties.get("migration.name", name);
patchInsertOn = properties.get("migration.patchInsertOn", patchInsertOn);
patchResetChecksumOn = properties.get("migration.patchResetChecksumOn", patchResetChecksumOn);
@@ -532,44 +445,6 @@ public class DbMigrationConfig {
ddlHeader = properties.get("ddl.header", ddlHeader);
}
/**
* Return the migration version (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
public String getVersion() {
String envVersion = readEnvironment("ddl.migration.version");
if (!isEmpty(envVersion)) {
return envVersion.trim();
}
return version;
}
/**
* Return the migration name which is short description text that can be appended to
* the migration version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* </p>
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
* </p>
*/
public String getName() {
String envName = readEnvironment("ddl.migration.name");
if (!isEmpty(envName)) {
return envName.trim();
}
return name;
}
/**
* Return the system or environment property.
*/
@@ -30,7 +30,7 @@ import io.ebean.event.readaudit.ReadAuditPrepare;
import io.ebean.meta.MetaInfoManager;
import io.ebean.migration.MigrationRunner;
import io.ebean.util.StringHelper;
import org.avaje.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceConfig;
import javax.sql.DataSource;
import java.time.Clock;
@@ -176,9 +176,17 @@ public class DatabasePlatform {
* findIterate() and findVisit().
*/
protected boolean forwardOnlyHintOnFindIterate;
/**
* If set then use the CONCUR_UPDATABLE hint when creating ResultSets.
*
* This is {@code false} for HANA
*/
protected boolean supportsResultSetConcurrencyModeUpdatable = true;
/**
* By default we use JDBC batch when cascading (except for SQL Server).
* By default we use JDBC batch when cascading (except for SQL Server and HANA).
*/
protected PersistBatch persistBatchOnCascade = PersistBatch.ALL;
@@ -516,6 +524,24 @@ public class DatabasePlatform {
public void setForwardOnlyHintOnFindIterate(boolean forwardOnlyHintOnFindIterate) {
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
}
/**
* Return true if the ResultSet CONCUR_UPDATABLE Hint should be used on
* createNativeSqlTree() PreparedStatements.
* <p>
* This specifically is required for Hana which doesn't support CONCUR_UPDATABLE
* </p>
*/
public boolean isSupportsResultSetConcurrencyModeUpdatable() {
return supportsResultSetConcurrencyModeUpdatable;
}
/**
* Set to true if the ResultSet CONCUR_UPDATABLE Hint should be used by default on createNativeSqlTree() PreparedStatements.
*/
public void setSupportsResultSetConcurrencyModeUpdatable(boolean supportsResultSetConcurrencyModeUpdatable) {
this.supportsResultSetConcurrencyModeUpdatable = supportsResultSetConcurrencyModeUpdatable;
}
/**
* Normally not needed - overridden in CockroachPlatform.
@@ -8,7 +8,9 @@ import java.util.regex.Pattern;
*/
public class DbIdentity {
private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL);
private static final String TABLE_PLACEHOLDER = "{table}";
private static final Pattern TABLE_REPLACE = Pattern.compile(TABLE_PLACEHOLDER, Pattern.LITERAL);
/**
* Set if this DB supports sequences. Note some DB's support both Sequences
@@ -53,7 +55,9 @@ public class DbIdentity {
if (selectLastInsertedIdTemplate == null) {
return null;
}
if (!selectLastInsertedIdTemplate.contains(TABLE_PLACEHOLDER)) {
return selectLastInsertedIdTemplate;
}
return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table));
}
@@ -0,0 +1,24 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.config.dbplatform.BasicSqlLimiter;
public class HanaBasicSqlLimiter implements BasicSqlLimiter {
@Override
public String limit(String dbSql, int firstRow, int maxRows) {
StringBuilder sb = new StringBuilder(50 + dbSql.length());
sb.append(dbSql);
if (maxRows > 0) {
sb.append(" ").append("limit");
sb.append(" ").append(maxRows);
if (firstRow > 0) {
sb.append(" ").append("offset").append(" ");
sb.append(firstRow);
}
}
return sb.toString();
}
}
@@ -0,0 +1,27 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.config.dbplatform.DbStandardHistorySupport;
public class HanaHistorySupport extends DbStandardHistorySupport {
@Override
public String getAsOfViewSuffix(String asOfViewSuffix) {
return " for system_time as of ?";
}
@Override
public String getVersionsBetweenSuffix(String asOfViewSuffix) {
return " for system_time between ? and ?";
}
@Override
public String getSysPeriodLower(String tableAlias, String sysPeriod) {
return tableAlias + "." + sysPeriod + "_start";
}
@Override
public String getSysPeriodUpper(String tableAlias, String sysPeriod) {
return tableAlias + "." + sysPeriod + "_end";
}
}
@@ -0,0 +1,96 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.Query.ForUpdate;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.config.PlatformConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.config.dbplatform.DbType;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.SqlErrorCodes;
public class HanaPlatform extends DatabasePlatform {
public HanaPlatform() {
this.platform = Platform.HANA;
this.sqlLimiter = new HanaSqlLimiter();
this.persistBatchOnCascade = PersistBatch.NONE;
this.supportsResultSetConcurrencyModeUpdatable = false;
this.columnAliasPrefix = null;
this.historySupport = new HanaHistorySupport();
this.basicSqlLimiter = new HanaBasicSqlLimiter();
this.likeClauseRaw = "like ?";
this.maxConstraintNameLength = 127;
this.maxTableNameLength = 127;
this.dbDefaultValue.setNow("current_timestamp");
this.exceptionTranslator = new SqlErrorCodes().addAcquireLock("131", "133", "146")
.addDataIntegrity("130", "429", "461", "462").addDuplicateKey("144", "301", "349").build();
this.dbIdentity.setIdType(IdType.IDENTITY);
this.dbIdentity.setSelectLastInsertedIdTemplate("select current_identity_value() from sys.dummy");
this.dbIdentity.setSupportsGetGeneratedKeys(false);
this.dbIdentity.setSupportsIdentity(true);
this.dbTypeMap.put(DbType.BIGINT, new DbPlatformType("bigint", false));
this.dbTypeMap.put(DbType.BINARY, new DbPlatformType("varbinary", 255));
this.dbTypeMap.put(DbType.BIT, new DbPlatformType("smallint", false));
this.dbTypeMap.put(DbType.BLOB, new DbPlatformType("blob", false));
this.dbTypeMap.put(DbType.CHAR, new DbPlatformType("nvarchar", 255));
this.dbTypeMap.put(DbType.CLOB, new DbPlatformType("nclob", false));
this.dbTypeMap.put(DbType.INTEGER, new DbPlatformType("integer", false));
this.dbTypeMap.put(DbType.JSONVARCHAR, new DbPlatformType("nvarchar", 255));
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.LONGVARBINARY, new DbPlatformType("blob", false));
this.dbTypeMap.put(DbType.LONGVARCHAR, new DbPlatformType("nclob", false));
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point"));
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry"));
this.dbTypeMap.put(DbType.SMALLINT, new DbPlatformType("smallint", false));
this.dbTypeMap.put(DbType.TINYINT, new DbPlatformType("smallint", false));
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
this.dbTypeMap.put(DbType.VARBINARY, new DbPlatformType("varbinary", 255));
this.dbTypeMap.put(DbType.VARCHAR, new DbPlatformType("nvarchar", 255));
}
@Override
protected void addGeoTypes(int srid) {
this.dbTypeMap.put(DbType.LINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.MULTILINESTRING, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.MULTIPOINT, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.MULTIPOLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
this.dbTypeMap.put(DbType.POINT, new DbPlatformType("st_point(" + srid + ")", false));
this.dbTypeMap.put(DbType.POLYGON, new DbPlatformType("st_geometry(" + srid + ")", false));
}
@Override
protected String withForUpdate(String sql, ForUpdate forUpdateMode) {
switch (forUpdateMode) {
case BASE:
return sql + " for update";
case NOWAIT:
return sql + " for update nowait";
case SKIPLOCKED:
return sql + " for update ignore locked";
default:
throw new IllegalArgumentException("Unknown update mode: " + forUpdateMode.name());
}
}
@Override
protected void configure(PlatformConfig config, boolean allQuotedIdentifiers) {
super.configure(config, allQuotedIdentifiers);
if (config.getDbUuid().useBinary()) {
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varbinary", 16));
} else {
this.dbTypeMap.put(DbType.UUID, new DbPlatformType("varchar", 40));
}
}
}
@@ -0,0 +1,35 @@
package io.ebean.config.dbplatform.hana;
import io.ebean.config.dbplatform.SqlLimitRequest;
import io.ebean.config.dbplatform.SqlLimitResponse;
import io.ebean.config.dbplatform.SqlLimiter;
public class HanaSqlLimiter implements SqlLimiter {
@Override
public SqlLimitResponse limit(SqlLimitRequest request) {
String dbSql = request.getDbSql();
StringBuilder sb = new StringBuilder(50 + dbSql.length());
sb.append("select ");
if (request.isDistinct()) {
sb.append("distinct ");
}
sb.append(dbSql);
int firstRow = request.getFirstRow();
int maxRows = request.getMaxRows();
if (maxRows > 0) {
sb.append(" ").append("limit ").append(maxRows);
if (firstRow > 0) {
sb.append(" ").append("offset ");
sb.append(firstRow);
}
}
String sql = request.getDbPlatform().completeSql(sb.toString(), request.getOrmQuery());
return new SqlLimitResponse(sql, false);
}
}
@@ -0,0 +1,4 @@
/**
* SAP HANA specific support.
*/
package io.ebean.config.dbplatform.hana;
@@ -7,6 +7,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
/**
@@ -147,6 +148,11 @@ public interface DbMigration {
*/
void addDatabasePlatform(DatabasePlatform databasePlatform, String prefix);
/**
* Return the list of versions that contain pending drops.
*/
List<String> getPendingDrops();
/**
* Generate the next migration xml file and associated apply and rollback sql scripts.
* <p>
@@ -9,6 +9,7 @@ import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeanservice.docstore.api.DocStoreUpdates;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
@@ -29,16 +30,21 @@ public class TransactionEvent implements Serializable {
*/
private final transient boolean local;
private final long startMillis;
private TransactionEventTable eventTables;
private transient TransactionEventBeans eventBeans;
private transient List<PersistRequestBean<?>> listenerNotify;
private transient DeleteByIdMap deleteByIdMap;
private transient CacheChangeSet changeSet;
/**
* Create the TransactionEvent, one per Transaction.
*/
public TransactionEvent() {
public TransactionEvent(long startMillis) {
this.startMillis = startMillis;
this.local = true;
}
@@ -71,8 +77,8 @@ public class TransactionEvent implements Serializable {
/**
* Return the list of PersistRequestBean's for this transaction.
*/
public List<PersistRequestBean<?>> getPersistRequestBeans() {
return (eventBeans == null) ? null : eventBeans.getRequests();
public List<PersistRequestBean<?>> getListenerNotify() {
return (listenerNotify == null) ? null : listenerNotify;
}
public TransactionEventTable getEventTables() {
@@ -94,17 +100,13 @@ public class TransactionEvent implements Serializable {
}
/**
* Add a inserted updated or deleted bean to the event.
* Add post commit listeners. Watch this for large transactions.
*/
public void add(PersistRequestBean<?> request) {
if (request.isNotify()) {
// either a BeanListener or Cache is interested
if (eventBeans == null) {
eventBeans = new TransactionEventBeans();
}
eventBeans.add(request);
public void addListenerNotify(PersistRequestBean<?> request) {
if (listenerNotify == null) {
listenerNotify = new ArrayList<>();
}
listenerNotify.add(request);
}
/**
@@ -112,11 +114,13 @@ public class TransactionEvent implements Serializable {
*/
public CacheChangeSet buildCacheChanges(TransactionManager manager) {
if (eventBeans == null && deleteByIdMap == null && eventTables == null) {
if (changeSet == null && deleteByIdMap == null && eventTables == null) {
return null;
}
CacheChangeSet changeSet = new CacheChangeSet(manager.clockNowMillis());
if (changeSet == null) {
changeSet = new CacheChangeSet(manager.clockNowMillis());
}
if (eventTables != null && !eventTables.isEmpty()) {
// notify cache with table based changes
BeanDescriptorManager dm = manager.getBeanDescriptorManager();
@@ -124,9 +128,6 @@ public class TransactionEvent implements Serializable {
dm.cacheNotify(tableIUD, changeSet);
}
}
if (eventBeans != null) {
eventBeans.notifyCache(changeSet);
}
if (deleteByIdMap != null) {
deleteByIdMap.notifyCache(changeSet);
}
@@ -137,12 +138,25 @@ public class TransactionEvent implements Serializable {
* Add any relevant PersistRequestBean's to DocStoreUpdates for later processing.
*/
public void addDocStoreUpdates(DocStoreUpdates docStoreUpdates) {
List<PersistRequestBean<?>> persistRequestBeans = getPersistRequestBeans();
if (persistRequestBeans != null) {
for (PersistRequestBean<?> persistRequestBean : persistRequestBeans) {
List<PersistRequestBean<?>> requests = getListenerNotify();
if (requests != null) {
for (PersistRequestBean<?> persistRequestBean : requests) {
persistRequestBean.addDocStoreUpdates(docStoreUpdates);
}
}
}
/**
* Return the CacheChangeSet that we add cache notification messages to.
*
* We want to add to this change set as we process requests allowing the
* PersistRequestBean to be garbage collected for large transactions.
*/
public CacheChangeSet obtainCacheChangeSet() {
if (changeSet == null) {
changeSet = new CacheChangeSet(startMillis);
}
return changeSet;
}
}
@@ -1,44 +0,0 @@
package io.ebeaninternal.api;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.core.PersistRequestBean;
import java.util.ArrayList;
import java.util.List;
/**
* Lists of inserted updated and deleted beans that have a BeanPersistListener.
* <p>
* These beans will be sent to the appropriate BeanListeners after a successful
* commit of the transaction.
* </p>
*/
public class TransactionEventBeans {
final ArrayList<PersistRequestBean<?>> requests = new ArrayList<>();
/**
* Return the list of PersistRequests that BeanListeners are interested in.
*/
public List<PersistRequestBean<?>> getRequests() {
return requests;
}
/**
* Add a bean for BeanListener notification.
*/
public void add(PersistRequestBean<?> request) {
requests.add(request);
}
/**
* Collect the cache changes.
*/
public void notifyCache(CacheChangeSet changeSet) {
for (PersistRequestBean<?> request : requests) {
request.notifyCache(changeSet);
}
}
}
@@ -10,6 +10,7 @@ import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
@@ -57,7 +58,7 @@ import java.util.List;
*
* DbMigration migration = DbMigration.create();
* migration.setPathToResources("src/main/resources");
* migration.setPlatform(DbPlatformName.ORACLE);
* migration.setPlatform(Platform.POSTGRES);
*
* migration.generateMigration();
*
@@ -265,6 +266,7 @@ public class DefaultDbMigration implements DbMigration {
* migration.generateMigration();
*
* }</pre>
*
* @return the generated migration or null
*/
@Override
@@ -303,6 +305,23 @@ public class DefaultDbMigration implements DbMigration {
}
}
/**
* Return the versions containing pending drops.
*/
public List<String> getPendingDrops() {
if (!online) {
DbOffline.setGenerateMigration();
}
setDefaults();
try {
return createRequest().getPendingDrops();
} finally {
if (!online) {
DbOffline.reset();
}
}
}
/**
* Load the configuration for each of the target platforms.
*/
@@ -494,7 +513,7 @@ public class DefaultDbMigration implements DbMigration {
if (nextDrop != null) {
return nextDrop;
}
return migrationConfig.getGeneratePendingDrop();
return generatePendingDrop;
}
/**
@@ -504,14 +523,15 @@ public class DefaultDbMigration implements DbMigration {
*/
private String getFullVersion(MigrationModel migrationModel, String dropsFor) {
String version = migrationConfig.getVersion();
String version = getVersion();
if (version == null) {
version = migrationModel.getNextVersion(initialVersion);
}
String fullVersion = migrationConfig.getApplyPrefix() + version;
if (migrationConfig.getName() != null) {
fullVersion += "__" + toUnderScore(migrationConfig.getName());
String name = getName();
if (name != null) {
fullVersion += "__" + toUnderScore(name);
} else if (dropsFor != null) {
fullVersion += "__" + toUnderScore("dropsFor_" + MigrationVersion.trim(dropsFor));
@@ -558,7 +578,7 @@ public class DefaultDbMigration implements DbMigration {
if (file.exists()) {
return false;
}
String comment = migrationConfig.isIncludeGeneratedFileComment() ? GENERATED_COMMENT : null;
String comment = Boolean.TRUE.equals(includeGeneratedFileComment) ? GENERATED_COMMENT : null;
MigrationXmlWriter xmlWriter = new MigrationXmlWriter(comment);
xmlWriter.write(dbMigration, file);
return true;
@@ -586,21 +606,66 @@ public class DefaultDbMigration implements DbMigration {
if (header != null) {
migrationConfig.setDdlHeader(header);
}
if (includeGeneratedFileComment != null) {
migrationConfig.setIncludeGeneratedFileComment(includeGeneratedFileComment);
}
if (version != null) {
migrationConfig.setVersion(version);
}
if (name != null) {
migrationConfig.setName(name);
}
if (generatePendingDrop != null) {
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
}
}
}
/**
* Return the migration version (typically FlywayDb compatible).
* <p>
* Example: 1.1.1_2
* <p>
* The version is expected to be the combination of the current pom version plus
* a 'feature' id. The combined version must be unique and ordered to work with
* FlywayDb so each developer sets a unique version so that the migration script
* generated is unique (typically just prior to being submitted as a merge request).
*/
private String getVersion() {
String envVersion = readEnvironment("ddl.migration.version");
if (!isEmpty(envVersion)) {
return envVersion.trim();
}
return version;
}
/**
* Return the migration name which is short description text that can be appended to
* the migration version to become the ddl script file name.
* <p>
* So if the name is "a foo table" then the ddl script file could be:
* "1.1.1_2__a-foo-table.sql"
* </p>
* <p>
* When the DB migration relates to a git feature (merge request) then this description text
* is a short description of the feature.
* </p>
*/
private String getName() {
String envName = readEnvironment("ddl.migration.name");
if (!isEmpty(envName)) {
return envName.trim();
}
return name;
}
/**
* Return true if the string is null or empty.
*/
private boolean isEmpty(String val) {
return val == null || val.trim().isEmpty();
}
/**
* Return the system or environment property.
*/
private String readEnvironment(String key) {
String val = System.getProperty(key);
if (val == null) {
val = System.getenv(key);
}
return val;
}
/**
* Return the file path to write the xml and sql to.
*/
@@ -662,6 +727,8 @@ public class DefaultDbMigration implements DbMigration {
return new DB2Platform();
case SQLITE:
return new SQLitePlatform();
case HANA:
return new HanaPlatform();
case GENERIC:
return new DatabasePlatform();
@@ -28,7 +28,11 @@ public class BaseDdlHandler implements DdlHandler {
protected final TableDdl tableDdl;
public BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
this.tableDdl = new BaseTableDdl(serverConfig, platformDdl);
this(serverConfig, platformDdl, new BaseTableDdl(serverConfig, platformDdl));
}
protected BaseDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl, TableDdl tableDdl) {
this.tableDdl = tableDdl;
}
@Override
@@ -0,0 +1,225 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlHandler;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import java.io.IOException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AbstractHanaDdl extends PlatformDdl {
private static final Pattern ARRAY_PATTERN = Pattern.compile("(\\w+)\\s*\\[\\s*\\]\\s*(\\(\\d+\\))?", Pattern.CASE_INSENSITIVE);
public AbstractHanaDdl(DatabasePlatform platform) {
super(platform);
this.addColumn = "add (";
this.addColumnSuffix = ")";
this.alterColumn = "alter (";
this.alterColumnSuffix = ")";
this.columnDropDefault = " default null";
this.columnSetDefault = " default";
this.columnSetNotnull = " not null";
this.columnSetNull = " null";
this.dropColumn = "drop (";
this.dropColumnSuffix = ")";
this.dropConstraintIfExists = "drop constraint ";
this.dropIndexIfExists = "drop index ";
this.dropSequenceIfExists = "drop sequence ";
this.dropTableCascade = " cascade";
this.dropTableIfExists = "drop table ";
this.fallbackArrayType = "nvarchar(1000)";
this.historyDdl = new HanaHistoryDdl();
this.identitySuffix = " generated by default as identity";
}
@Override
public String alterColumnBaseAttributes(AlterColumn alter) {
String tableName = alter.getTableName();
String columnName = alter.getColumnName();
String currentType = alter.getCurrentType();
String type = alter.getType() != null ? alter.getType() : currentType;
type = convert(type, false);
currentType = convert(currentType, false);
boolean notnull = (alter.isNotnull() != null) ? alter.isNotnull() : Boolean.TRUE.equals(alter.isCurrentNotnull());
String notnullClause = notnull ? " not null" : "";
String defaultValue = DdlHelp.isDropDefault(alter.getDefaultValue()) ? "null"
: (alter.getDefaultValue() != null ? alter.getDefaultValue() : alter.getCurrentDefaultValue());
String defaultValueClause = (defaultValue == null || defaultValue.isEmpty()) ? "" : " default " + defaultValue;
try {
DdlBuffer buffer = new BaseDdlBuffer(null);
if (!isConvertible(currentType, type)) {
// add an intermediate conversion if possible
if (isNumberType(currentType)) {
// numbers can always be converted to decimal
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
.append(" decimal ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
.endOfStatement();
} else if (isStringType(currentType)) {
// strings can always be converted to nclob
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
.append(" nclob ").append(defaultValueClause).append(notnullClause).append(alterColumnSuffix)
.endOfStatement();
}
}
buffer.append("alter table ").append(tableName).append(" ").append(alterColumn).append(" ").append(columnName)
.append(" ").append(type).append(defaultValueClause).append(notnullClause).append(alterColumnSuffix);
return buffer.getBuffer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
throw new UnsupportedOperationException();
}
@Override
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
return null;
}
@Override
public DdlHandler createDdlHandler(ServerConfig serverConfig) {
return new HanaDdlHandler(serverConfig, this);
}
@Override
public String alterColumnType(String tableName, String columnName, String type) {
return null;
}
@Override
protected String convertArrayType(String logicalArrayType) {
Matcher matcher = ARRAY_PATTERN.matcher(logicalArrayType);
if (matcher.matches()) {
return convert(matcher.group(1), false) + " array" + (matcher.group(2) == null ? "" : matcher.group(2));
} else {
return fallbackArrayType;
}
}
@Override
public String alterTableAddUniqueConstraint(String tableName, String uqName, String[] columns, String[] nullableColumns) {
if (nullableColumns == null || nullableColumns.length == 0) {
return super.alterTableAddUniqueConstraint(tableName, uqName, columns, nullableColumns);
} else {
return "-- cannot create unique index \"" + uqName + "\" on table \"" + tableName + "\" with nullable columns";
}
}
@Override
public String alterTableDropUniqueConstraint(String tableName, String uniqueConstraintName) {
DdlBuffer buffer = new BaseDdlBuffer(null);
try {
buffer.append("delimiter $$").newLine();
buffer.append("do").newLine();
buffer.append("begin").newLine();
buffer.append("declare exit handler for sql_error_code 397 begin end").endOfStatement();
buffer.append("exec 'alter table ").append(tableName).append(" ").append(dropUniqueConstraint).append(" ")
.append(maxConstraintName(uniqueConstraintName)).append("'").endOfStatement();
buffer.append("end").endOfStatement();
buffer.append("$$");
return buffer.getBuffer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public String alterTableDropConstraint(String tableName, String constraintName) {
return alterTableDropUniqueConstraint(tableName, constraintName);
}
/**
* It is rather complex to delete a column on HANA as there must not exist any
* foreign keys. That's why we call a user stored procedure here
*/
@Override
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("CALL usp_ebean_drop_column('").append(tableName).append("', '").append(columnName).append("')")
.endOfStatement();
}
/**
* Check if a data type can be converted to another data type. Data types can't
* be converted if the target type has a lower precision than the source type.
*
* @param sourceType The source data type
* @param targetType the target data type
* @return {@code true} if the type can be converted, {@code false} otherwise
*/
private boolean isConvertible(String sourceType, String targetType) {
if (Objects.equals(sourceType, targetType)) {
return true;
}
if (sourceType == null || targetType == null) {
return true;
}
if ("bigint".equals(sourceType)) {
if ("integer".equals(targetType) || "smallint".equals(targetType) || "tinyint".equals(targetType)) {
return false;
}
} else if ("integer".equals(sourceType)) {
if ("smallint".equals(targetType) || "tinyint".equals(targetType)) {
return false;
}
} else if ("smallint".equals(sourceType)) {
if ("tinyint".equals(targetType)) {
return false;
}
} else if ("double".equals(sourceType)) {
if ("real".equals(targetType)) {
return false;
}
}
DbPlatformType dbPlatformSourceType = DbPlatformType.parse(sourceType);
if ("float".equals(dbPlatformSourceType.getName())) {
if ("real".equals(targetType)) {
return false;
}
} else if ("varchar".equals(dbPlatformSourceType.getName()) || "nvarchar".equals(dbPlatformSourceType.getName())) {
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
if ("varchar".equals(dbPlatformTargetType.getName()) || "nvarchar".equals(dbPlatformTargetType.getName())) {
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()) {
return false;
}
}
} else if ("decimal".equals(dbPlatformSourceType.getName())) {
DbPlatformType dbPlatformTargetType = DbPlatformType.parse(targetType);
if ("decimal".equals(dbPlatformTargetType.getName())) {
if (dbPlatformSourceType.getDefaultLength() > dbPlatformTargetType.getDefaultLength()
|| dbPlatformSourceType.getDefaultScale() > dbPlatformTargetType.getDefaultScale()) {
return false;
}
}
}
return true;
}
private boolean isNumberType(String type) {
return type != null
&& ("bigint".equals(type) || "integer".equals(type) || "smallint".equals(type) || "tinyint".equals(type)
|| type.startsWith("float") || "real".equals(type) || "double".equals(type) || type.startsWith("decimal"));
}
private boolean isStringType(String type) {
return type != null
&& (type.startsWith("varchar") || type.startsWith("nvarchar") || "clob".equals(type) || "nclob".equals(type));
}
}
@@ -260,7 +260,7 @@ public class BaseTableDdl implements TableDdl {
String partitionMode = createTable.getPartitionMode();
DdlBuffer apply = writer.apply();
apply.append("create table ").append(tableName).append(" (");
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(tableName).append(" (");
writeTableColumns(apply, columns, useIdentity);
writeCheckConstraints(apply, createTable);
writeUniqueConstraints(apply, createTable);
@@ -175,7 +175,7 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
protected void createHistoryTable(DdlBuffer apply, MTable table) throws IOException {
apply.append("create table ").append(table.getName()).append(historySuffix).append("(").newLine();
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(table.getName()).append(historySuffix).append("(").newLine();
Collection<MColumn> cols = table.allColumns();
for (MColumn column : cols) {
@@ -238,8 +238,8 @@ public abstract class DbTriggerBasedHistoryDdl implements PlatformHistoryDdl {
}
protected void dropSysPeriodColumns(DdlBuffer buffer, String baseTableName) throws IOException {
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodStart).endOfStatement();
buffer.append("alter table ").append(baseTableName).append(" drop column ").append(sysPeriodEnd).endOfStatement();
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodStart);
platformDdl.alterTableDropColumn(buffer, baseTableName, sysPeriodEnd);
}
protected void appendInsertIntoHistory(DdlBuffer buffer, String historyTable, List<String> columns) throws IOException {
@@ -0,0 +1,51 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import java.io.IOException;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
public class HanaColumnStoreDdl extends AbstractHanaDdl {
public HanaColumnStoreDdl(DatabasePlatform platform) {
super(platform);
this.createTable = "create column table";
}
@Override
public String createIndex(String indexName, String tableName, String[] columns) {
if (columns == null || columns.length == 0) {
return "-- cannot create index: no columns given";
}
if (columns.length == 1) {
return "-- explicit index \"" + indexName + "\" for single column \"" + columns[0] + "\" of table \"" + tableName
+ "\" is not necessary";
}
StringBuilder buffer = new StringBuilder();
buffer.append("create inverted hash index ").append(maxConstraintName(indexName)).append(" on ").append(tableName);
appendColumns(columns, buffer);
return buffer.toString();
}
@Override
public String dropIndex(String indexName, String tableName) {
DdlBuffer buffer = new BaseDdlBuffer(null);
try {
buffer.append("delimiter $$").newLine();
buffer.append("do").newLine();
buffer.append("begin").newLine();
buffer.append("declare exit handler for sql_error_code 261 begin end").endOfStatement();
buffer.append("exec '").append(dropIndexIfExists).append(maxConstraintName(indexName)).append("'")
.endOfStatement();
buffer.append("end").endOfStatement();
buffer.append("$$");
return buffer.getBuffer();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,11 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.BaseDdlHandler;
public class HanaDdlHandler extends BaseDdlHandler {
public HanaDdlHandler(ServerConfig serverConfig, PlatformDdl platformDdl) {
super(serverConfig, platformDdl, new HanaTableDdl(serverConfig, platformDdl));
}
}
@@ -0,0 +1,157 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlBuffer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AddHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.model.MColumn;
import io.ebeaninternal.dbmigration.model.MTable;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public class HanaHistoryDdl implements PlatformHistoryDdl {
private String systemPeriodStart;
private String systemPeriodEnd;
private PlatformDdl platformDdl;
private String historySuffix;
private final AtomicInteger counter = new AtomicInteger(0);
private Map<String, String> createdHistoryTables = new ConcurrentHashMap<>();
@Override
public void configure(ServerConfig serverConfig, PlatformDdl platformDdl) {
this.systemPeriodStart = serverConfig.getAsOfSysPeriod() + "_start";
this.systemPeriodEnd = serverConfig.getAsOfSysPeriod() + "_end";
this.platformDdl = platformDdl;
this.historySuffix = serverConfig.getHistoryTableSuffix();
}
@Override
public void createWithHistory(DdlWrite writer, MTable table) throws IOException {
String tableName = table.getName();
String historyTableName = tableName + historySuffix;
DdlBuffer apply = writer.applyHistoryView();
if (apply.isEmpty()) {
createdHistoryTables.clear();
}
apply.append(platformDdl.getCreateTableCommandPrefix()).append(" ").append(historyTableName).append(" (").newLine();
// create history table
Collection<MColumn> cols = table.allColumns();
for (MColumn column : cols) {
if (!column.isDraftOnly()) {
writeColumnDefinition(apply, column.getName(), column.getType(), column.getDefaultValue(), column.isNotnull(),
column.isIdentity() ? platformDdl.identitySuffix : null);
apply.append(",").newLine();
}
}
writeColumnDefinition(apply, systemPeriodStart, "TIMESTAMP", null, false, null);
apply.append(",").newLine();
writeColumnDefinition(apply, systemPeriodEnd, "TIMESTAMP", null, false, null);
apply.newLine().append(")").endOfStatement();
// enable system versioning
apply.append("alter table ").append(tableName).append(" add (").newLine();
apply.append(" ").append(systemPeriodStart).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW START, ").newLine();
apply.append(" ").append(systemPeriodEnd).append(" TIMESTAMP NOT NULL GENERATED ALWAYS AS ROW END").newLine();
apply.append(")").endOfStatement();
apply.append("alter table ").append(tableName).append(" add period for system_time(").append(systemPeriodStart)
.append(",").append(systemPeriodEnd).append(")").endOfStatement();
enableSystemVersioning(apply, tableName, historyTableName, true, false);
createdHistoryTables.put(tableName, historyTableName);
dropHistoryTable(writer.dropAll(), tableName, historyTableName);
}
@Override
public void dropHistoryTable(DdlWrite writer, DropHistoryTable dropHistoryTable) throws IOException {
dropHistoryTable(writer.applyDropDependencies(), dropHistoryTable.getBaseTable(),
dropHistoryTable.getBaseTable() + historySuffix);
}
protected void dropHistoryTable(DdlBuffer apply, String baseTable, String historyTable) throws IOException {
// disable system versioning
disableSystemVersioning(apply, baseTable);
apply.append("alter table ").append(baseTable).append(" drop period for system_time").endOfStatement();
// drop the period columns
apply.append("alter table ").append(baseTable).append(" drop (").append(systemPeriodStart).append(",")
.append(systemPeriodEnd).append(")").endOfStatement();
// drop the history table
apply.append("drop table ").append(historyTable).append(" cascade").endOfStatement();
}
@Override
public void addHistoryTable(DdlWrite writer, AddHistoryTable addHistoryTable) throws IOException {
MTable table = writer.getTable(addHistoryTable.getBaseTable());
if (table == null) {
throw new IllegalStateException("MTable " + addHistoryTable.getBaseTable() + " not found in writer? (required for history DDL)");
}
createWithHistory(writer, table);
}
@Override
public void updateTriggers(DdlWrite write, HistoryTableUpdate baseTable) {
// nothing to do
}
protected void writeColumnDefinition(DdlBuffer buffer, String columnName, String type, String defaultValue,
boolean isNotNull, String generated) throws IOException {
String platformType = platformDdl.convert(type, false);
buffer.append(" ").append(platformDdl.lowerColumnName(columnName));
buffer.append(" ").append(platformType);
if (defaultValue != null) {
buffer.append(" default ").append(defaultValue);
}
if (isNotNull) {
buffer.append(" not null");
}
if (generated != null) {
buffer.append(" ").append(generated);
}
}
public void disableSystemVersioning(DdlBuffer apply, String tableName) throws IOException {
disableSystemVersioning(apply, tableName, false);
}
public void disableSystemVersioning(DdlBuffer apply, String tableName, boolean uniqueStatement) throws IOException {
apply.append("alter table ").append(tableName).append(" drop system versioning");
if (uniqueStatement) {
// needed for the DB migration test to prevent the statement from being filtered
// out as a duplicate
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
}
apply.endOfStatement();
}
public void enableSystemVersioning(DdlBuffer apply, String tableName, String historyTableName, boolean validated,
boolean uniqueStatement) throws IOException {
apply.append("alter table ").append(tableName).append(" add system versioning history table ").append(historyTableName);
if (!validated) {
apply.append(" not validated");
}
if (uniqueStatement) {
// needed for the DB migration test to prevent the statement from being filtered
// out as a duplicate
apply.append(" /* ").append(String.valueOf(counter.getAndIncrement())).append(" */");
}
apply.endOfStatement();
}
public boolean isSystemVersioningEnabled(String tableName) {
return !createdHistoryTables.containsKey(tableName);
}
}
@@ -0,0 +1,11 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.dbplatform.DatabasePlatform;
public class HanaRowStoreDdl extends AbstractHanaDdl {
public HanaRowStoreDdl(DatabasePlatform platform) {
super(platform);
this.createTable = "create row table";
}
}
@@ -0,0 +1,134 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import java.io.IOException;
import java.util.List;
import io.ebean.config.PropertiesWrapper;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.AddColumn;
import io.ebeaninternal.dbmigration.migration.AlterColumn;
import io.ebeaninternal.dbmigration.migration.Column;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.model.MTable;
public class HanaTableDdl extends BaseTableDdl {
private final HanaHistoryDdl historyDdl;
private final boolean generateUniqueDdl;
public HanaTableDdl(ServerConfig serverConfig, PlatformDdl platformDdl) {
super(serverConfig, platformDdl);
this.historyDdl = (HanaHistoryDdl) platformDdl.historyDdl;
if (serverConfig.getProperties() != null) {
PropertiesWrapper wrapper = new PropertiesWrapper("ebean", "hana", serverConfig.getProperties(), serverConfig.getClassLoadConfig());
this.generateUniqueDdl = wrapper.getBoolean("generateUniqueDdl", false);
} else {
this.generateUniqueDdl = false;
}
}
@Override
protected void alterColumnDefaultValue(DdlWrite writer, AlterColumn alter) throws IOException {
String ddl = platformDdl.alterColumnBaseAttributes(alter);
if (hasValue(ddl)) {
writer.apply().append(ddl).endOfStatement();
}
}
@Override
public void generate(DdlWrite writer, AddColumn addColumn) throws IOException {
String tableName = addColumn.getTableName();
MTable table = writer.getTable(tableName);
if (table == null) {
super.generate(writer, addColumn);
return;
}
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
if (manageSystemVersioning) {
historyDdl.disableSystemVersioning(writer.apply(), table.getName(), this.generateUniqueDdl);
}
super.generate(writer, addColumn);
if (manageSystemVersioning) {
// make same changes to the history table
String historyTable = historyTable(tableName);
List<Column> columns = addColumn.getColumn();
for (Column column : columns) {
alterTableAddColumn(writer.apply(), historyTable, column, true, true);
}
historyDdl.enableSystemVersioning(writer.apply(), table.getName(), historyTable, false, this.generateUniqueDdl);
}
}
@Override
public void generate(DdlWrite writer, AlterColumn alterColumn) throws IOException {
String tableName = alterColumn.getTableName();
MTable table = writer.getTable(tableName);
if (table == null) {
super.generate(writer, alterColumn);
return;
}
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
if (manageSystemVersioning) {
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
}
super.generate(writer, alterColumn);
if (manageSystemVersioning) {
// make same changes to the history table
String historyTable = historyTable(tableName);
if (hasValue(alterColumn.getType()) || hasValue(alterColumn.getDefaultValue()) || alterColumn.isNotnull() != null) {
AlterColumn alterHistoryColumn = new AlterColumn();
alterHistoryColumn.setTableName(historyTable);
alterHistoryColumn.setColumnName(alterColumn.getColumnName());
alterHistoryColumn.setType(alterColumn.getType());
alterHistoryColumn.setDefaultValue(alterColumn.getDefaultValue());
alterHistoryColumn.setNotnull(alterColumn.isNotnull());
alterHistoryColumn.setCurrentType(alterColumn.getCurrentType());
alterHistoryColumn.setCurrentDefaultValue(alterColumn.getCurrentDefaultValue());
alterHistoryColumn.setCurrentNotnull(alterColumn.isCurrentNotnull());
String histColumnDdl = platformDdl.alterColumnBaseAttributes(alterHistoryColumn);
// write the apply to history table
writer.apply().append(histColumnDdl).endOfStatement();
}
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
}
}
@Override
public void generate(DdlWrite writer, DropColumn dropColumn) throws IOException {
String tableName = dropColumn.getTableName();
MTable table = writer.getTable(tableName);
if (table == null) {
super.generate(writer, dropColumn);
return;
}
boolean manageSystemVersioning = isTrue(table.isWithHistory()) && historyDdl.isSystemVersioningEnabled(tableName);
if (manageSystemVersioning) {
historyDdl.disableSystemVersioning(writer.apply(), tableName, this.generateUniqueDdl);
}
super.generate(writer, dropColumn);
if (manageSystemVersioning) {
// also drop from the history table
String historyTable = historyTable(tableName);
alterTableDropColumn(writer.apply(), historyTable, dropColumn.getColumnName());
historyDdl.enableSystemVersioning(writer.apply(), tableName, historyTable, false, this.generateUniqueDdl);
}
}
}
@@ -78,12 +78,16 @@ public class PlatformDdl {
protected String dropIndexIfExists = "drop index if exists ";
protected String alterColumn = "alter column";
protected String alterColumnSuffix = "";
protected String dropUniqueConstraint = "drop constraint";
protected String addConstraint = "add constraint";
protected String addColumn = "add column";
protected String addColumnSuffix = "";
protected String columnSetType = "";
@@ -96,6 +100,12 @@ public class PlatformDdl {
protected String columnSetNull = "set null";
protected String updateNullWithDefault = "update ${table} set ${column} = ${default} where ${column} is null";
protected String createTable = "create table";
protected String dropColumn = "drop column";
protected String dropColumnSuffix = "";
/**
* Set false for MsSqlServer to allow multiple nulls for OneToOne mapping.
@@ -458,6 +468,7 @@ public class PlatformDdl {
if (isTrue(column.isNotnull())) {
buffer.append(" not null");
}
buffer.append(addColumnSuffix);
buffer.endOfStatement();
// check constraints cannot be added in one statement for h2
@@ -467,14 +478,15 @@ public class PlatformDdl {
buffer.append(ddl).endOfStatement();
}
} else {
buffer.append(addColumnSuffix);
buffer.endOfStatement();
}
}
public void alterTableDropColumn(DdlBuffer buffer, String tableName, String columnName) throws IOException {
buffer.append("alter table ").append(tableName).append(" drop column ").append(columnName)
.endOfStatement();
buffer.append("alter table ").append(tableName).append(" ").append(dropColumn).append(" ").append(columnName)
.append(dropColumnSuffix).endOfStatement();
}
/**
@@ -494,19 +506,19 @@ public class PlatformDdl {
*/
public String alterColumnType(String tableName, String columnName, String type) {
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false);
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + columnSetType + convert(type, false) + alterColumnSuffix;
}
/**
* Alter a column adding or removing the not null constraint.
* <p>
* Note that that MySql and SQL Server instead use alterColumnBaseAttributes()
* Note that that MySql, SQL Server, and HANA instead use alterColumnBaseAttributes()
* </p>
*/
public String alterColumnNotnull(String tableName, String columnName, boolean notnull) {
String suffix = notnull ? columnSetNotnull : columnSetNull;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
}
/**
@@ -522,17 +534,17 @@ public class PlatformDdl {
*/
public String alterColumnDefaultValue(String tableName, String columnName, String defaultValue) {
String suffix = DdlHelp.isDropDefault(defaultValue) ? columnDropDefault : columnSetDefault + " " + convertDefaultValue(defaultValue);
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix;
return "alter table " + tableName + " " + alterColumn + " " + columnName + " " + suffix + alterColumnSuffix;
}
/**
* Alter column setting both the type and not null constraint.
* <p>
* Used by MySql and SQL Server as these require both column attributes to be set together.
* Used by MySql, SQL Server, and HANA as these require both column attributes to be set together.
* </p>
*/
public String alterColumnBaseAttributes(AlterColumn alter) {
// by default do nothing, only used by mysql and sql server as they can only
// by default do nothing, only used by mysql, sql server, and HANA as they can only
// modify the column with the full column definition
return null;
}
@@ -662,6 +674,17 @@ public class PlatformDdl {
public void unlockTables(DdlBuffer buffer, Collection<String> tables) throws IOException {
}
/**
* Returns the database-specific "create table" command prefix. For HANA this is
* either "create column table" or "create row table", for all other databases
* it is "create table".
*
* @return The "create table" command prefix
*/
public String getCreateTableCommandPrefix() {
return createTable;
}
public boolean suppressPrimaryKeyOnPartition() {
return false;
@@ -29,6 +29,8 @@ public class CurrentModel {
private final boolean platformTypes;
private final boolean jaxbPresent;
private ModelContainer model;
private ChangeSet changeSet;
@@ -58,6 +60,7 @@ public class CurrentModel {
this.constraintNaming = constraintNaming;
this.maxLength = maxLength(server, constraintNaming);
this.platformTypes = platformTypes;
this.jaxbPresent = server.getServerConfig().getClassLoadConfig().isJavaxJAXBPresent();
}
/**
@@ -130,7 +133,9 @@ public class CurrentModel {
ddl.append(header).append('\n');
}
addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
if (jaxbPresent) {
addExtraDdl(ddl, ExtraDdlXmlReader.readBuiltin(), "-- init script ");
}
ddl.append(write.apply().getBuffer());
ddl.append(write.applyForeignKeys().getBuffer());
@@ -5,6 +5,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.cockroach.CockroachPlatform;
import io.ebean.config.dbplatform.db2.DB2Platform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.hsqldb.HsqldbPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
@@ -106,6 +107,9 @@ public class DatabasePlatformFactory {
if (dbName.equals("sqlite")) {
return new SQLitePlatform();
}
if (dbName.equals("hana")) {
return new HanaPlatform();
}
throw new RuntimeException("database platform " + dbName + " is not known?");
}
@@ -156,6 +160,8 @@ public class DatabasePlatformFactory {
return new DB2Platform();
} else if (dbProductName.contains("sql anywhere")) {
return new SqlAnywherePlatform();
} else if (dbProductName.contains("hdb")) {
return new HanaPlatform();
}
// use the standard one
@@ -15,10 +15,10 @@ import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.bootup.BootupClassPathSearch;
import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.lib.ShutdownManager;
import org.avaje.datasource.DataSourceAlertFactory;
import org.avaje.datasource.DataSourceConfig;
import org.avaje.datasource.DataSourceFactory;
import org.avaje.datasource.DataSourcePoolListener;
import io.ebean.datasource.DataSourceAlertFactory;
import io.ebean.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceFactory;
import io.ebean.datasource.DataSourcePoolListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -87,7 +87,7 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
import io.ebeanservice.docstore.api.DocStoreIntegration;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.TenantCatalogProvider;
import io.ebeaninternal.server.transaction.DataSourceSupplier;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import javax.sql.DataSource;
import java.io.PrintWriter;
@@ -3,7 +3,7 @@ package io.ebeaninternal.server.core;
import io.ebean.config.CurrentTenantProvider;
import io.ebean.config.TenantSchemaProvider;
import io.ebeaninternal.server.transaction.DataSourceSupplier;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import javax.sql.DataSource;
import java.io.PrintWriter;
@@ -173,6 +173,16 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
private int pendingPostUpdateNotify;
/**
* Set to true when post execute has occured (so includes batch flush).
*/
private boolean postExecute;
/**
* Set to true after many properties have been persisted (so includes element collections).
*/
private boolean complete;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
@@ -441,10 +451,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Return true if this change should notify cache, listener or doc store.
* Return true if this change should notify persist listener or doc store (and keep the request).
*/
public boolean isNotify() {
return notifyCache || isNotifyPersistListener() || isDocStoreNotify();
private boolean isNotifyListeners() {
return isNotifyPersistListener() || isDocStoreNotify();
}
/**
@@ -918,7 +928,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
*/
@Override
public void postExecute() {
postExecute = true;
if (controller != null) {
controllerPost();
}
@@ -939,7 +949,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
postInsert();
}
addEvent();
addPostCommitListeners();
notifyCacheOnPostExecute();
if (isLogSummary()) {
logSummary();
@@ -1017,14 +1028,12 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Add the bean to the TransactionEvent. This will be used by TransactionManager to sync Cache,
* Cluster and text indexes.
* Add the request to TransactionEvent if there are post commit listeners.
*/
private void addEvent() {
private void addPostCommitListeners() {
TransactionEvent event = transaction.getEvent();
if (event != null) {
event.add(this);
if (event != null && isNotifyListeners()) {
event.addListenerNotify(this);
}
}
@@ -1087,23 +1096,62 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
/**
* Check if any of its many properties where cascade saved and hence we need to update related
* many property caches.
* Completed insert or delete request. Do cache notify in non-batched case.
*/
public void checkUpdatedManysOnly() {
public void complete() {
notifyCacheOnComplete();
}
/**
* Completed update request handling cases for element collection and where ONLY
* many properties were updated.
*/
public void completeUpdate() {
if (!dirty && updatedManys != null) {
// set the flag and register for post commit processing if there
// is caching or registered listeners
if (idValue == null) {
this.idValue = beanDescriptor.getId(entityBean);
}
// not dirty so postExecute() will never be called
// set true to trigger cache notify if needed
postExecute = true;
updatedManysOnly = true;
setNotifyCache();
addEvent();
addPostCommitListeners();
}
notifyCacheOnComplete();
postUpdateNotify();
}
/**
* Notify cache when using batched persist.
*/
private void notifyCacheOnPostExecute() {
postExecute = true;
if (notifyCache && complete) {
// add cache notification (on batch persist)
TransactionEvent event = transaction.getEvent();
if (event != null) {
notifyCache(event.obtainCacheChangeSet());
}
}
}
/**
* Notify cache when not use batched persist.
*/
private void notifyCacheOnComplete() {
complete = true;
if (notifyCache && postExecute) {
// add cache notification (on non-batch persist)
TransactionEvent event = transaction.getEvent();
if (event != null) {
notifyCache(event.obtainCacheChangeSet());
}
}
}
/**
* For requests that update document store add this event to either the list
* of queue events or list of update events.
@@ -1390,7 +1438,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
public void setImportedOrphanForRemoval(BeanPropertyAssocOne<?> prop) {
Object orphan = getOrigValue(prop);
if (orphan instanceof EntityBean) {
orphanBean = (EntityBean)orphan;
orphanBean = (EntityBean) orphan;
}
}
@@ -1398,4 +1446,10 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return orphanBean;
}
/**
* Return the SQL used to fetch the last inserted id value.
*/
public String getSelectLastInsertedId() {
return beanDescriptor.getSelectLastInsertedId(publish);
}
}
@@ -4,6 +4,7 @@ import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.CockroachDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.DB2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.H2Ddl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HanaColumnStoreDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.HsqldbDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.MySqlDdl;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.Oracle10Ddl;
@@ -47,6 +48,8 @@ public class PlatformDdlBuilder {
case SQLSERVER17:
case SQLSERVER:
return new SqlServerDdl(platform);
case HANA:
return new HanaColumnStoreDdl(platform);
default:
return new PlatformDdl(platform);
}
@@ -1,7 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebeaninternal.server.transaction.DataSourceSupplier;
import org.avaje.datasource.DataSourcePool;
import io.ebean.datasource.DataSourcePool;
import javax.sql.DataSource;
import java.sql.Connection;
@@ -22,7 +22,7 @@ public class BeanCollectionUtil {
}
/**
* Return the details of the collection or map taking care to avoid
* Return the details (map entry set) of the collection or map taking care to avoid
* unnecessary fetching of the data.
*/
public static Collection<?> getActualEntries(Object o) {
@@ -48,4 +48,32 @@ public class BeanCollectionUtil {
}
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
}
/**
* Return the details (map values) of the collection or map taking care to avoid
* unnecessary fetching of the data.
*/
public static Collection<?> getActualDetails(Object o) {
if (o == null) {
return null;
}
if (o instanceof BeanCollection<?>) {
BeanCollection<?> bc = (BeanCollection<?>) o;
if (!bc.isPopulated()) {
return null;
}
// For maps this is a collection of Map.Entry, otherwise it
// returns a collection of beans
return bc.getActualDetails();
}
if (o instanceof Map<?, ?>) {
// yes, we want the entrySet (to set the keys)
return ((Map<?, ?>) o).values();
} else if (o instanceof Collection<?>) {
return ((Collection<?>) o);
}
throw new PersistenceException("expecting a Map or Collection but got [" + o.getClass().getName() + "]");
}
}
@@ -7,7 +7,6 @@ import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PartitionMode;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
@@ -182,6 +181,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* getGeneratedKeys is not supported.
*/
private final String selectLastInsertedId;
private final String selectLastInsertedIdDraft;
private final boolean autoTunable;
@@ -473,6 +473,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.sequenceInitialValue = deploy.getSequenceInitialValue();
this.sequenceAllocationSize = deploy.getSequenceAllocationSize();
this.selectLastInsertedId = deploy.getSelectLastInsertedId();
this.selectLastInsertedIdDraft = deploy.getSelectLastInsertedIdDraft();
this.concurrencyMode = deploy.getConcurrencyMode();
this.updateChangesOnly = deploy.isUpdateChangesOnly();
this.indexDefinitions = deploy.getIndexDefinitions();
@@ -3086,8 +3087,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
* supported.
* </p>
*/
public String getSelectLastInsertedId() {
return selectLastInsertedId;
public String getSelectLastInsertedId(boolean publish) {
return publish ? selectLastInsertedId : selectLastInsertedIdDraft;
}
/**
* Return true if this bean uses a SQL select to fetch the last inserted id value.
*/
public boolean supportsSelectLastInsertedId() {
return selectLastInsertedId != null;
}
@Override
@@ -348,13 +348,12 @@ final class BeanDescriptorCacheHelp<T> {
private CachedManyIds createManyIds(BeanPropertyAssocMany<?> many, Object details) {
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
Collection<?> actualDetails = BeanCollectionUtil.getActualEntries(details);
Collection<?> actualDetails = BeanCollectionUtil.getActualDetails(details);
if (actualDetails == null) {
return null;
}
BeanDescriptor<?> targetDescriptor = many.getTargetDescriptor();
List<Object> idList = new ArrayList<>(actualDetails.size());
for (Object bean : actualDetails) {
idList.add(targetDescriptor.getId((EntityBean) bean));
@@ -794,7 +793,6 @@ final class BeanDescriptorCacheHelp<T> {
// query caching only
return;
}
List<BeanPropertyAssocMany<?>> manyCollections = updateRequest.getUpdatedManyCollections();
if (manyCollections != null) {
for (BeanPropertyAssocMany<?> many : manyCollections) {
@@ -6,6 +6,7 @@ import io.ebean.RawSqlBuilder;
import io.ebean.annotation.ConstraintMode;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.config.BeanNotEnhancedException;
import io.ebean.config.EncryptKey;
import io.ebean.config.EncryptKeyManager;
import io.ebean.config.NamingConvention;
@@ -388,6 +389,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return asOfTableMap;
} catch (BeanNotEnhancedException e) {
throw e;
} catch (RuntimeException e) {
logger.error("Error in deployment", e);
throw e;
@@ -1387,9 +1391,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
}
if (IdType.IDENTITY == desc.getIdType()) {
// used when getGeneratedKeys is not supported (SQL Server 2000)
// used when getGeneratedKeys is not supported (SQL Server 2000, SAP Hana)
String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable());
desc.setSelectLastInsertedId(selectLastInsertedId);
String selectLastInsertedIdDraft = (!desc.isDraftable()) ? selectLastInsertedId : dbIdentity.getSelectLastInsertedId(desc.getDraftTable());
desc.setSelectLastInsertedId(selectLastInsertedId, selectLastInsertedIdDraft);
return;
}
@@ -1470,7 +1475,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
if (isPersistentField(prop)) {
throw new IllegalStateException(
"If you are running in an IDE with enhancement plugin try a Build -> Rebuild Project to recompile and enhance all entity beans. " +
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
"Error - property " + propName + " not found in " + reflectProps + " for type " + desc.getBeanType());
}
} else {
@@ -1553,7 +1558,9 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
Class<?> beanClass = desc.getBeanType();
if (!hasEntityBeanInterface(beanClass)) {
throw new IllegalStateException("Bean " + beanClass + " is not enhanced?");
String msg = "Bean " + beanClass + " is not enhanced? If you are running in IDEA or Eclipse check" +
" that the enhancement plugin is installed. See https://ebean-orm.github.io/docs/trouble-shooting#not-enhanced";
throw new BeanNotEnhancedException(msg);
}
// the bean already implements EntityBean
@@ -1582,7 +1589,7 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
// ok to stop and treat just the same as Object.class
return;
}
throw new IllegalStateException("Super type " + superclass + " is not enhanced?");
throw new BeanNotEnhancedException("Super type " + superclass + " is not enhanced? See https://ebean-orm.github.io/docs/trouble-shooting#not-enhanced");
}
// recursively continue up the inheritance hierarchy
@@ -1665,11 +1672,14 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
private ElementHelp elementHelper(ManyType manyType) {
switch (manyType) {
case LIST: return new ElementHelpList();
case SET: return new ElementHelpSet();
case MAP: return new ElementHelpMap();
case LIST:
return new ElementHelpList();
case SET:
return new ElementHelpSet();
case MAP:
return new ElementHelpMap();
default:
throw new IllegalStateException("manyType unexpected "+manyType);
throw new IllegalStateException("manyType unexpected " + manyType);
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.deploy;
import io.ebean.config.BeanNotRegisteredException;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocOne;
import javax.persistence.PersistenceException;
@@ -22,8 +23,8 @@ public class BeanEmbeddedMetaFactory {
BeanDescriptor<?> targetDesc = owner.getBeanDescriptor(prop.getTargetType());
if (targetDesc == null) {
String msg = "Could not find BeanDescriptor for " + prop.getTargetType()
+ ". Perhaps the EmbeddedId class is not registered?";
throw new PersistenceException(msg);
+ ". Perhaps the EmbeddedId class is not registered? See https://ebean-orm.github.io/docs/trouble-shooting#not-registered";
throw new BeanNotRegisteredException(msg);
}
// deployment override information (column names)
@@ -1285,7 +1285,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* Return true if this is a ManyToMany with history support (on the intersection table).
*/
public boolean isManyToManyWithHistory() {
return !excludedFromHistory && descriptor.isHistorySupport();
return false;
}
/**
@@ -208,6 +208,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
}
@Override
public boolean isManyToManyWithHistory() {
return manyToMany && !excludedFromHistory && descriptor.isHistorySupport();
}
@Override
protected void docStoreIncludeByDefault(PathProperties pathProps) {
// by default not including "Many" properties in document store
@@ -3,7 +3,6 @@ package io.ebeaninternal.server.deploy.meta;
import io.ebean.annotation.Cache;
import io.ebean.annotation.DocStore;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PartitionMode;
import io.ebean.config.ServerConfig;
import io.ebean.config.TableName;
import io.ebean.config.dbplatform.IdType;
@@ -126,6 +125,7 @@ public class DeployBeanDescriptor<T> {
* Used with Identity columns but no getGeneratedKeys support.
*/
private String selectLastInsertedId;
private String selectLastInsertedIdDraft;
/**
* The concurrency mode for beans of this type.
@@ -222,6 +222,8 @@ public class DeployBeanDescriptor<T> {
private short profileId;
private Object jacksonAnnotatedClass;
/**
* Construct the BeanDescriptor.
*/
@@ -839,11 +841,16 @@ public class DeployBeanDescriptor<T> {
return selectLastInsertedId;
}
public String getSelectLastInsertedIdDraft() {
return selectLastInsertedIdDraft;
}
/**
* Set the SQL used to return the last inserted Id.
*/
public void setSelectLastInsertedId(String selectLastInsertedId) {
public void setSelectLastInsertedId(String selectLastInsertedId, String selectLastInsertedIdDraft) {
this.selectLastInsertedId = selectLastInsertedId;
this.selectLastInsertedIdDraft = selectLastInsertedIdDraft;
}
/**
@@ -1232,6 +1239,7 @@ public class DeployBeanDescriptor<T> {
this.descriptor = descriptor;
}
@Override
public String getDeployWord(String expression) {
return descriptor.getDeployWord(expression);
}
@@ -1246,4 +1254,13 @@ public class DeployBeanDescriptor<T> {
return (property == null) ? null : "${ta}." + property.getDbColumn();
}
/**
* Returns the jackson annotated class, if jackson is present.
*/
public Object /*AnnotatedClass*/ getJacksonAnnotatedClass() {
if (jacksonAnnotatedClass == null) {
jacksonAnnotatedClass = new DeployBeanObtainJackson(serverConfig, beanType).obtain();
}
return jacksonAnnotatedClass;
}
}
@@ -0,0 +1,30 @@
package io.ebeaninternal.server.deploy.meta;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedClassResolver;
import io.ebean.config.ServerConfig;
/**
* Used to obtain the Jackson AnnotatedClass for a given bean type utlimately to obtain field level Jackson annotations.
*/
class DeployBeanObtainJackson<T> {
private final ServerConfig serverConfig;
private final Class<T> beanType;
DeployBeanObtainJackson(ServerConfig serverConfig, Class<T> beanType) {
this.serverConfig = serverConfig;
this.beanType = beanType;
}
/**
* Return the Jackson AnnotatedClass for the given bean type.
*/
Object obtain() {
ObjectMapper objectMapper = (ObjectMapper) serverConfig.getObjectMapper();
JavaType javaType = objectMapper.getTypeFactory().constructType(beanType);
return AnnotatedClassResolver.resolve(objectMapper.getDeserializationConfig(), javaType, objectMapper.getDeserializationConfig());
}
}
@@ -1124,4 +1124,18 @@ public class DeployBeanProperty {
public void setElementProperty() {
this.elementProperty = true;
}
/**
* Returns the jackson annotated field, if jackson is present.
*/
public Object /*AnnotatedField*/ getJacksonField() {
com.fasterxml.jackson.databind.introspect.AnnotatedClass jac =
(com.fasterxml.jackson.databind.introspect.AnnotatedClass) getDesc().getJacksonAnnotatedClass();
for (com.fasterxml.jackson.databind.introspect.AnnotatedField candidate : jac.fields()) {
if (candidate.getName().equals(getName())) {
return candidate;
}
}
return null;
}
}
@@ -6,6 +6,7 @@ import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.PrivateOwned;
import io.ebean.annotation.Where;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebean.config.TableName;
import io.ebean.util.CamelCaseHelper;
@@ -430,7 +431,7 @@ class AnnotationAssocManys extends AnnotationParser {
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered?";
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? See https://ebean-orm.github.io/docs/trouble-shooting#not-registered";
}
private void readToMany(ManyToMany propAnn, DeployBeanPropertyAssocMany<?> manyProp) {
@@ -451,8 +452,7 @@ class AnnotationAssocManys extends AnnotationParser {
// find the other many table (not intersection)
BeanTable assoc = factory.getBeanTable(targetType);
if (assoc == null) {
String msg = errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName());
throw new RuntimeException(msg);
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName()));
}
manyProp.setManyToMany();
@@ -478,8 +478,7 @@ class AnnotationAssocManys extends AnnotationParser {
BeanTable assoc = factory.getBeanTable(targetType);
if (assoc == null) {
String msg = errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName());
throw new RuntimeException(msg);
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(targetType, manyProp.getFullBeanName()));
}
manyProp.setBeanTable(assoc);
@@ -4,6 +4,7 @@ import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.TenantId;
import io.ebean.annotation.Where;
import io.ebean.config.BeanNotRegisteredException;
import io.ebean.config.NamingConvention;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanTable;
@@ -194,13 +195,13 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation?";
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation? See https://ebean-orm.github.io/docs/trouble-shooting#not-registered";
}
private BeanTable beanTable(DeployBeanPropertyAssoc<?> prop) {
BeanTable assoc = factory.getBeanTable(prop.getPropertyType());
if (assoc == null) {
throw new RuntimeException(errorMsgMissingBeanTable(prop.getPropertyType(), prop.getFullBeanName()));
throw new BeanNotRegisteredException(errorMsgMissingBeanTable(prop.getPropertyType(), prop.getFullBeanName()));
}
return assoc;
}
@@ -239,10 +239,9 @@ public class DeployUtil {
private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) {
Class<?> type = prop.getPropertyType();
ScalarType<?> scalarType = typeManager.getJsonScalarType(type, dbType, dbLength, prop.getGenericType());
ScalarType<?> scalarType = typeManager.getJsonScalarType(prop, dbType, dbLength);
if (scalarType == null) {
throw new RuntimeException("No ScalarType for JSON type [" + type + "] [" + dbType + "]");
throw new RuntimeException("No ScalarType for JSON property [" + prop + "] [" + dbType + "]");
}
prop.setDbType(dbType);
prop.setScalarType(scalarType);
@@ -17,6 +17,7 @@ import io.ebean.PagedList;
import io.ebean.Pairs;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Transaction;
import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebean.search.Match;
@@ -356,11 +357,21 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.delete();
}
@Override
public int delete(Transaction transaction) {
return query.delete(transaction);
}
@Override
public int update() {
return query.update();
}
@Override
public int update(Transaction transaction) {
return query.update(transaction);
}
@Override
public FutureIds<T> findFutureIds() {
return query.findFutureIds();
@@ -16,6 +16,7 @@ import io.ebean.PagedList;
import io.ebean.Pairs;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Transaction;
import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebean.search.Match;
@@ -318,11 +319,21 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.delete();
}
@Override
public int delete(Transaction transaction) {
return exprList.delete(transaction);
}
@Override
public int update() {
return exprList.update();
}
@Override
public int update(Transaction transaction) {
return exprList.update(transaction);
}
@Override
public Query<T> asOf(Timestamp asOf) {
return exprList.asOf(asOf);
@@ -25,6 +25,8 @@ public class DbExpressionHandlerFactory {
case SQLSERVER17:
case SQLSERVER:
return new SqlServerDbExpression();
case HANA:
return new HanaDbExpression();
default:
return new BasicDbExpression();
}
@@ -0,0 +1,59 @@
package io.ebeaninternal.server.expression.platform;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.expression.BitwiseOp;
import io.ebeaninternal.server.expression.Op;
/**
* HANA handling of platform specific expressions.
*/
public class HanaDbExpression extends BaseDbExpression {
@Override
public void bitwise(SpiExpressionRequest request, String propName, BitwiseOp operator, long flags, String compare, long match) {
bitwiseFunction(request, propName, operator, compare);
}
@Override
public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
request.append("json_value(").append(propName).append(", '$.").append(path).append("')");
request.append(operator.bind());
}
@Override
public void arrayIsEmpty(SpiExpressionRequest request, String propName, boolean empty) {
request.append("cardinality(").append(propName).append(")");
if (empty) {
request.append(" = 0");
} else {
request.append(" <> 0");
}
}
@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();
}
@Override
public void arrayContains(SpiExpressionRequest request, String propName, boolean contains, Object... values) {
for (int i = 0; i < values.length; i++) {
if (i > 0) {
request.append(" and ");
}
request.append("(?");
if (!contains) {
request.append(" not ");
}
request.append(" member of ").append(propName).append(")");
}
}
}
@@ -412,7 +412,7 @@ public final class DefaultPersister implements Persister {
if (req.isPersistCascade()) {
saveAssocMany(req);
}
req.checkUpdatedManysOnly();
req.completeUpdate();
} else {
update(req);
}
@@ -472,7 +472,7 @@ public final class DefaultPersister implements Persister {
request.flagUpdate();
saveAssocMany(request);
}
request.checkUpdatedManysOnly();
request.completeUpdate();
} else {
if (request.isInsert()) {
insert(request);
@@ -506,6 +506,8 @@ public final class DefaultPersister implements Persister {
// save any associated List held beans
saveAssocMany(request);
}
request.complete();
} finally {
request.unRegisterBean();
}
@@ -539,7 +541,7 @@ public final class DefaultPersister implements Persister {
saveAssocMany(request);
}
request.checkUpdatedManysOnly();
request.completeUpdate();
} finally {
request.unRegisterBean();
@@ -883,6 +885,7 @@ public final class DefaultPersister implements Persister {
unloadedForeignKeys.deleteCascade();
}
}
request.complete();
// return true if using JDBC batch (as we can't tell until the batch is flushed)
return count;
@@ -39,7 +39,7 @@ public class InsertHandler extends DmlHandler {
* A SQL Select used to fetch back the Id where generatedKeys is not
* supported.
*/
private String selectLastInsertedId;
private boolean useSelectLastInsertedId;
/**
* Create to handle the insert execution.
@@ -79,7 +79,7 @@ public class InsertHandler extends DmlHandler {
useGeneratedKeys = true;
} else {
// use a query to get the last inserted id
selectLastInsertedId = meta.getSelectLastInsertedId();
useSelectLastInsertedId = meta.supportsSelectLastInsertedId();
}
}
@@ -117,8 +117,7 @@ public class InsertHandler extends DmlHandler {
}
/**
* Execute the insert in a normal non batch fashion. Additionally using
* getGeneratedKeys if required.
* Execute non batched insert additionally using getGeneratedKeys if required.
*/
@Override
public int execute() throws SQLException, OptimisticLockException {
@@ -127,7 +126,7 @@ public class InsertHandler extends DmlHandler {
// get the auto-increment value back and set into the bean
getGeneratedKeys();
} else if (selectLastInsertedId != null) {
} else if (useSelectLastInsertedId) {
// fetch back the Id using a query
fetchGeneratedKeyUsingSelect();
}
@@ -167,12 +166,10 @@ public class InsertHandler extends DmlHandler {
*/
private void fetchGeneratedKeyUsingSelect() throws SQLException {
Connection conn = transaction.getConnection();
PreparedStatement stmt = null;
ResultSet rset = null;
try {
stmt = conn.prepareStatement(selectLastInsertedId);
stmt = transaction.getConnection().prepareStatement(persistRequest.getSelectLastInsertedId());
rset = stmt.executeQuery();
setGeneratedKey(rset);
} finally {
@@ -38,7 +38,7 @@ public final class InsertMeta {
/**
* Used for DB that do not support getGeneratedKeys.
*/
private final String selectLastInsertedId;
private final boolean supportsSelectLastInsertedId;
private final Bindable shadowFKey;
@@ -69,7 +69,7 @@ public final class InsertMeta {
this.sqlNullId = null;
this.sqlDraftNullId = null;
this.supportsGetGeneratedKeys = false;
this.selectLastInsertedId = null;
this.supportsSelectLastInsertedId = false;
} else {
// insert sql for db identity or sequence insert
@@ -77,11 +77,11 @@ public final class InsertMeta {
if (id.getIdentityColumn() == null) {
this.identityDbColumns = new String[]{};
this.supportsGetGeneratedKeys = false;
this.selectLastInsertedId = null;
this.supportsSelectLastInsertedId = false;
} else {
this.identityDbColumns = new String[]{id.getIdentityColumn()};
this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys();
this.selectLastInsertedId = desc.getSelectLastInsertedId();
this.supportsSelectLastInsertedId = desc.supportsSelectLastInsertedId();
}
this.sqlNullId = genSql(true, tableName, false);
this.sqlDraftNullId = desc.isDraftable() ? genSql(true, draftTableName, true) : sqlNullId;
@@ -116,15 +116,11 @@ public final class InsertMeta {
}
/**
* Returns sql that is used to fetch back the last inserted id. This will
* return null if it should not be used.
* <p>
* This is only for DB's that do not support getGeneratedKeys. For MS
* SQLServer 2000 this could return "SELECT (at)(at)IDENTITY as id".
* </p>
* Return true if we should use a SQL query to return the generated key.
* This can not be used with JDBC batch mode.
*/
public String getSelectLastInsertedId() {
return selectLastInsertedId;
public boolean supportsSelectLastInsertedId() {
return supportsSelectLastInsertedId;
}
/**
@@ -426,7 +426,7 @@ class CQueryBuilder {
try {
// 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);
PreparedStatement statement = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, dbPlatform.isSupportsResultSetConcurrencyModeUpdatable() ? ResultSet.CONCUR_UPDATABLE : ResultSet.CONCUR_READ_ONLY);
predicates.bind(statement, connection);
ResultSet resultSet = statement.executeQuery();
@@ -4,7 +4,7 @@ import java.util.regex.Pattern;
class DbOrderByTrim {
private static final Pattern orderByTrim = Pattern.compile("(?i)\\b asc\\b|\\b desc\\b");
private static final Pattern orderByTrim = Pattern.compile("(?i)\\b asc\\b|\\b desc\\b|\\b nulls first\\b|\\b nulls last\\b");
/**
* Convert the dbOrderBy clause to be safe for adding to select or distinct on.
@@ -21,6 +21,7 @@ import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.QueryType;
import io.ebean.RawSql;
import io.ebean.Transaction;
import io.ebean.Version;
import io.ebean.bean.CallStack;
import io.ebean.bean.ObjectGraphNode;
@@ -1384,11 +1385,21 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
return server.delete(this, null);
}
@Override
public int delete(Transaction transaction) {
return server.delete(this, transaction);
}
@Override
public int update() {
return server.update(this, null);
}
@Override
public int update(Transaction transaction) {
return server.update(this, transaction);
}
@Override
public <A> List<A> findIds() {
// a copy of this query is made in the server
@@ -14,7 +14,6 @@ import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.TransactionEvent;
import io.ebeaninternal.api.TxnProfileEventCodes;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
import io.ebeaninternal.server.core.PersistRequest;
import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.BatchControl;
@@ -855,7 +854,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
public TransactionEvent getEvent() {
queryOnly = false;
if (event == null) {
event = new TransactionEvent();
event = new TransactionEvent(startMillis);
}
return event;
}
@@ -1052,7 +1051,7 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
// the event has been sent to the transaction manager
// for postCommit processing (l2 cache updates etc)
// start a new transaction event
event = new TransactionEvent();
event = new TransactionEvent(startMillis);
} catch (Exception e) {
doRollback(e);
@@ -33,9 +33,7 @@ final class PostCommitProcessing {
private final TransactionManager manager;
private final List<PersistRequestBean<?>> persistBeanRequests;
private final BeanPersistIdMap beanPersistIdMap;
private final List<PersistRequestBean<?>> listenerNotify;
private final RemoteTransactionEvent remoteTransactionEvent;
@@ -57,8 +55,7 @@ final class PostCommitProcessing {
this.txnDocStoreBatchSize = 0;
this.event = event;
this.deleteByIdMap = event.getDeleteByIdMap();
this.persistBeanRequests = event.getPersistRequestBeans();
this.beanPersistIdMap = createBeanPersistIdMap();
this.listenerNotify = event.getListenerNotify();
this.remoteTransactionEvent = createRemoteTransactionEvent();
}
@@ -74,8 +71,7 @@ final class PostCommitProcessing {
this.txnDocStoreBatchSize = transaction.getDocStoreBatchSize();
this.event = transaction.getEvent();
this.deleteByIdMap = event.getDeleteByIdMap();
this.persistBeanRequests = event.getPersistRequestBeans();
this.beanPersistIdMap = createBeanPersistIdMap();
this.listenerNotify = event.getListenerNotify();
this.remoteTransactionEvent = createRemoteTransactionEvent();
}
@@ -159,9 +155,9 @@ final class PostCommitProcessing {
}
private void localPersistListenersNotify() {
if (persistBeanRequests != null) {
for (PersistRequestBean<?> persistBeanRequest : persistBeanRequests) {
persistBeanRequest.notifyLocalPersistListener();
if (listenerNotify != null) {
for (PersistRequestBean<?> request : listenerNotify) {
request.notifyLocalPersistListener();
}
}
TransactionEventTable eventTables = event.getEventTables();
@@ -175,13 +171,13 @@ final class PostCommitProcessing {
private BeanPersistIdMap createBeanPersistIdMap() {
if (persistBeanRequests == null) {
if (listenerNotify == null) {
return null;
}
BeanPersistIdMap m = new BeanPersistIdMap();
for (PersistRequestBean<?> persistBeanRequest : persistBeanRequests) {
persistBeanRequest.addToPersistMap(m);
for (PersistRequestBean<?> request : listenerNotify) {
request.addToPersistMap(m);
}
return m;
}
@@ -193,6 +189,7 @@ final class PostCommitProcessing {
}
RemoteTransactionEvent remoteTransactionEvent = new RemoteTransactionEvent(serverName);
BeanPersistIdMap beanPersistIdMap = createBeanPersistIdMap();
if (beanPersistIdMap != null) {
for (BeanPersistIds beanPersist : beanPersistIdMap.values()) {
remoteTransactionEvent.addBeanPersistIds(beanPersist);
@@ -452,7 +452,7 @@ public class TransactionManager implements SpiTransactionManager {
private void externalModificationEvent(TransactionEventTable tableEvents) {
TransactionEvent event = new TransactionEvent();
TransactionEvent event = new TransactionEvent(clockNowMillis());
event.add(tableEvents);
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, event);
@@ -2,6 +2,8 @@ package io.ebeaninternal.server.type;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import io.ebean.annotation.DbArray;
import io.ebean.annotation.DbEnumType;
import io.ebean.annotation.DbEnumValue;
@@ -17,6 +19,7 @@ import io.ebean.util.AnnotationUtil;
import io.ebeaninternal.api.ExtraTypeFactory;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.core.bootup.BootupClasses;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
@@ -379,31 +382,36 @@ public final class DefaultTypeManager implements TypeManager {
}
@Override
public ScalarType<?> getJsonScalarType(Class<?> type, int dbType, int dbLength, Type genericType) {
public ScalarType<?> getJsonScalarType(DeployBeanProperty prop, int dbType, int dbLength) {
Class<?> type = prop.getPropertyType();
Type genericType = prop.getGenericType();
boolean hasJacksonAnnotations = objectMapperPresent && checkJacksonAnnotations(prop);
if (type.equals(List.class)) {
DocPropertyType docType = getDocType(genericType);
if (isValueTypeSimple(genericType)) {
if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) {
return ScalarTypeJsonList.typeFor(postgres, dbType, docType);
} else {
return createJsonObjectMapperType(type, genericType, dbType, docType);
return createJsonObjectMapperType(prop, dbType, docType);
}
}
if (type.equals(Set.class)) {
DocPropertyType docType = getDocType(genericType);
if (isValueTypeSimple(genericType)) {
if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) {
return ScalarTypeJsonSet.typeFor(postgres, dbType, docType);
} else {
return createJsonObjectMapperType(type, genericType, dbType, docType);
return createJsonObjectMapperType(prop, dbType, docType);
}
}
if (type.equals(Map.class)) {
if (isMapValueTypeObject(genericType)) {
if (!hasJacksonAnnotations && isMapValueTypeObject(genericType)) {
return ScalarTypeJsonMap.typeFor(postgres, dbType);
} else {
return createJsonObjectMapperType(type, genericType, dbType, DocPropertyType.OBJECT);
return createJsonObjectMapperType(prop, dbType, DocPropertyType.OBJECT);
}
}
@@ -426,7 +434,15 @@ public final class DefaultTypeManager implements TypeManager {
}
}
return createJsonObjectMapperType(type, type, dbType, DocPropertyType.OBJECT);
return createJsonObjectMapperType(prop, dbType, DocPropertyType.OBJECT);
}
/**
* Returns TRUE, if there is any jackson annotation on that property. All jackson annotations
* are annotated with the &#64;JacksonAnnotation meta annotation. So detection is easy.
*/
private boolean checkJacksonAnnotations(DeployBeanProperty prop) {
return AnnotationUtil.findAnnotation(prop.getField(), com.fasterxml.jackson.annotation.JacksonAnnotation.class) != null;
}
private DocPropertyType getDocType(Type genericType) {
@@ -459,11 +475,12 @@ public final class DefaultTypeManager implements TypeManager {
return Object.class.equals(typeArgs[1]) || "?".equals(typeArgs[1].toString());
}
private ScalarType<?> createJsonObjectMapperType(Class<?> type, Type genericType, int dbType, DocPropertyType docType) {
private ScalarType<?> createJsonObjectMapperType(DeployBeanProperty prop, int dbType, DocPropertyType docType) {
Class<?> type = prop.getPropertyType();
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
return ScalarTypeJsonObjectMapper.createTypeFor(postgres, type, (ObjectMapper) objectMapper, genericType, dbType, docType);
return ScalarTypeJsonObjectMapper.createTypeFor(postgres, (AnnotatedField) prop.getJacksonField(), (ObjectMapper) objectMapper, dbType, docType);
}
/**
@@ -9,14 +9,19 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationConfig;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import javax.persistence.PersistenceException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
@@ -31,20 +36,21 @@ public class ScalarTypeJsonObjectMapper {
/**
* Create and return the appropriate ScalarType.
*/
public static ScalarType<?> createTypeFor(boolean postgres, Class<?> type, ObjectMapper objectMapper,
Type genericType, int dbType, DocPropertyType docType) {
public static ScalarType<?> createTypeFor(boolean postgres, AnnotatedField field, ObjectMapper objectMapper,
int dbType, DocPropertyType docType) {
Class<?> type = field.getRawType();
String pgType = getPostgresType(postgres, dbType);
if (Set.class.equals(type)) {
return new OmSet(objectMapper, genericType, dbType, pgType, docType);
return new OmSet(objectMapper, field, dbType, pgType, docType);
}
if (List.class.equals(type)) {
return new OmList(objectMapper, genericType, dbType, pgType, docType);
return new OmList(objectMapper, field, dbType, pgType, docType);
}
if (Map.class.equals(type)) {
return new OmMap(objectMapper, genericType, dbType, pgType);
return new OmMap(objectMapper, field, dbType, pgType);
}
return new GenericObject(objectMapper, genericType, dbType, pgType);
return new GenericObject(objectMapper, field, dbType, pgType);
}
private static String getPostgresType(boolean postgres, int dbType) {
@@ -64,8 +70,8 @@ public class ScalarTypeJsonObjectMapper {
*/
private static class GenericObject extends Base<Object> {
public GenericObject(ObjectMapper objectMapper, Type type, int dbType, String pgType) {
super(Object.class, objectMapper, type, dbType, pgType, DocPropertyType.OBJECT);
public GenericObject(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) {
super(Object.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT);
}
}
@@ -75,8 +81,8 @@ public class ScalarTypeJsonObjectMapper {
@SuppressWarnings("rawtypes")
private static class OmSet extends Base<Set> {
public OmSet(ObjectMapper objectMapper, Type type, int dbType, String pgType, DocPropertyType docType) {
super(Set.class, objectMapper, type, dbType, pgType, docType);
public OmSet(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
super(Set.class, objectMapper, field, dbType, pgType, docType);
}
@Override
@@ -93,8 +99,8 @@ public class ScalarTypeJsonObjectMapper {
@SuppressWarnings("rawtypes")
private static class OmList extends Base<List> {
public OmList(ObjectMapper objectMapper, Type type, int dbType, String pgType, DocPropertyType docType) {
super(List.class, objectMapper, type, dbType, pgType, docType);
public OmList(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
super(List.class, objectMapper, field, dbType, pgType, docType);
}
@Override
@@ -111,8 +117,8 @@ public class ScalarTypeJsonObjectMapper {
@SuppressWarnings("rawtypes")
private static class OmMap extends Base<Map> {
public OmMap(ObjectMapper objectMapper, Type type, int dbType, String pgType) {
super(Map.class, objectMapper, type, dbType, pgType, DocPropertyType.OBJECT);
public OmMap(ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType) {
super(Map.class, objectMapper, field, dbType, pgType, DocPropertyType.OBJECT);
}
@Override
@@ -129,9 +135,11 @@ public class ScalarTypeJsonObjectMapper {
*/
private static abstract class Base<T> extends ScalarTypeBase<T> {
private final ObjectMapper objectMapper;
private final ObjectWriter objectWriter;
private final JavaType javaType;
private final ObjectMapper objectReader;
private JavaType deserType;
private final String pgType;
@@ -140,12 +148,39 @@ public class ScalarTypeJsonObjectMapper {
/**
* Construct given the object mapper, property type and DB type for storage.
*/
public Base(Class<T> cls, ObjectMapper objectMapper, Type type, int dbType, String pgType, DocPropertyType docType) {
public Base(Class<T> cls, ObjectMapper objectMapper, AnnotatedField field, int dbType, String pgType, DocPropertyType docType) {
super(cls, false, dbType);
this.pgType = pgType;
this.docType = docType;
this.objectMapper = objectMapper;
this.javaType = objectMapper.getTypeFactory().constructType(type);
this.objectReader = objectMapper;
JavaType javaType = field.getType();
DeserializationConfig deserConfig = objectMapper.getDeserializationConfig();
AnnotationIntrospector ai = deserConfig.getAnnotationIntrospector();
if (ai != null && javaType != null && !javaType.hasRawClass(Object.class)) {
try {
this.deserType = ai.refineDeserializationType(deserConfig, field, javaType);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
} else {
this.deserType = javaType;
}
SerializationConfig serConfig = objectMapper.getSerializationConfig();
ai = deserConfig.getAnnotationIntrospector();
if (ai != null && javaType != null && !javaType.hasRawClass(Object.class)) {
try {
JavaType serType = ai.refineSerializationType(serConfig, field, javaType);
this.objectWriter = objectMapper.writerFor(serType);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
}
} else {
this.objectWriter = objectMapper.writerFor(javaType);
}
}
/**
@@ -171,7 +206,7 @@ public class ScalarTypeJsonObjectMapper {
return null;
}
try {
return objectMapper.readValue(json, javaType);
return objectReader.readValue(json, deserType);
} catch (IOException e) {
throw new SQLException("Unable to convert JSON", e);
}
@@ -187,7 +222,7 @@ public class ScalarTypeJsonObjectMapper {
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);
String json = objectWriter.writeValueAsString(value);
bind.setString(json);
} catch (JsonProcessingException e) {
throw new SQLException("Unable to create JSON", e);
@@ -212,7 +247,7 @@ public class ScalarTypeJsonObjectMapper {
@Override
public String formatValue(T value) {
try {
return objectMapper.writeValueAsString(value);
return objectWriter.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new PersistenceException("Unable to create JSON", e);
}
@@ -221,7 +256,7 @@ public class ScalarTypeJsonObjectMapper {
@Override
public T parse(String value) {
try {
return objectMapper.readValue(value, javaType);
return objectReader.readValue(value, deserType);
} catch (IOException e) {
throw new PersistenceException("Unable to convert JSON", e);
}
@@ -244,12 +279,12 @@ public class ScalarTypeJsonObjectMapper {
@Override
public T jsonRead(JsonParser parser) throws IOException {
return objectMapper.readValue(parser, javaType);
return objectReader.readValue(parser, deserType);
}
@Override
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
objectMapper.writeValue(writer, value);
objectWriter.writeValue(writer, value);
}
@Override
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.type;
import io.ebean.annotation.DbArray;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import javax.persistence.EnumType;
import java.lang.reflect.Type;
@@ -57,7 +58,7 @@ public interface TypeManager {
* Note that type expected to be JsonNode or Map.
* </p>
*/
ScalarType<?> getJsonScalarType(Class<?> type, int dbType, int dbLength, Type genericType);
ScalarType<?> getJsonScalarType(DeployBeanProperty prop, int dbType, int dbLength);
/**
* Return the ScalarType used to handle DB ARRAY.
@@ -159,4 +159,39 @@ BEGIN
END
$$
</ddl-script>
<ddl-script name="create procs" platforms="hana" init="true">-- Inital script to create stored procedures etc for the hana platform
delimiter $$
--
-- PROCEDURE: usp_ebean_drop_foreign_keys TABLE, COLUMN
-- deletes all constraints and foreign keys referring to TABLE.COLUMN
--
CREATE OR REPLACE PROCEDURE usp_ebean_drop_foreign_keys(IN table_name NVARCHAR(256), IN column_name NVARCHAR(256))
AS
BEGIN
DECLARE foreign_key_names TABLE(CONSTRAINT_NAME NVARCHAR(256), TABLE_NAME NVARCHAR(256));
DECLARE i INT;
foreign_key_names = SELECT CONSTRAINT_NAME, TABLE_NAME FROM SYS.REFERENTIAL_CONSTRAINTS WHERE SCHEMA_NAME=CURRENT_SCHEMA AND TABLE_NAME=UPPER(:table_name) AND COLUMN_NAME=UPPER(:column_name);
FOR I IN 1 .. RECORD_COUNT(:foreign_key_names) DO
EXEC 'ALTER TABLE "' || ESCAPE_DOUBLE_QUOTES(:foreign_key_names.TABLE_NAME[i]) || '" DROP CONSTRAINT "' || ESCAPE_DOUBLE_QUOTES(:foreign_key_names.CONSTRAINT_NAME[i]) || '"';
END FOR;
END;
$$
delimiter $$
--
-- PROCEDURE: usp_ebean_drop_column TABLE, COLUMN
-- deletes the column and ensures that all indices and constraints are dropped first
--
CREATE OR REPLACE PROCEDURE usp_ebean_drop_column(IN table_name NVARCHAR(256), IN column_name NVARCHAR(256))
AS
BEGIN
CALL usp_ebean_drop_foreign_keys(table_name, column_name);
EXEC 'ALTER TABLE "' || UPPER(ESCAPE_DOUBLE_QUOTES(table_name)) || '" DROP ("' || UPPER(ESCAPE_DOUBLE_QUOTES(column_name)) || '")';
END;
$$
</ddl-script>
</extra-ddl>
+24
View File
@@ -1,5 +1,6 @@
package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaTimedMetric;
@@ -10,6 +11,9 @@ import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.server.core.HelpCreateQueryRequest;
import io.ebeaninternal.server.core.OrmQueryRequest;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
import org.avaje.agentloader.AgentLoader;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
@@ -144,6 +148,10 @@ public abstract class BaseTestCase {
public boolean isMySql() {
return Platform.MYSQL == platform();
}
public boolean isHana() {
return Platform.HANA == platform();
}
public boolean isPlatformBooleanNative() {
return Types.BOOLEAN == spiEbeanServer().getDatabasePlatform().getBooleanDbType();
@@ -152,6 +160,10 @@ public abstract class BaseTestCase {
public boolean isPlatformOrderNullsSupport() {
return isH2() || isPostgres();
}
public boolean isPersistBatchOnCascade() {
return spiEbeanServer().getDatabasePlatform().getPersistBatchOnCascade() != PersistBatch.NONE;
}
/**
* Wait for the L2 cache to propagate changes post-commit.
@@ -205,6 +217,18 @@ public abstract class BaseTestCase {
assertThat(sql).contains(containsIn+" not in ");
}
}
/**
* Platform specific CONCAT clause.
*/
protected String concat(String property0, String separator, String property1) {
return concat(property0, separator, property1, null);
}
protected String concat(String property0, String separator, String property1, String suffix) {
DbExpressionHandler dbExpressionHandler = DbExpressionHandlerFactory.from(spiEbeanServer().getDatabasePlatform());
return dbExpressionHandler.concat(property0, separator, property1, suffix);
}
protected <T> OrmQueryRequest<T> createQueryRequest(SpiQuery.Type type, Query<T> query, Transaction t) {
return HelpCreateQueryRequest.create(server(), type, query, t);
+42 -71
View File
@@ -3,6 +3,7 @@ package io.ebean;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaQueryMetric;
import io.ebean.meta.MetaTimedMetric;
import org.ebeantest.LoggedSqlCollector;
import org.junit.AfterClass;
import org.junit.BeforeClass;
@@ -43,17 +44,13 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
resetAllMetrics();
String[] prefix = {"Bl", "B", "Red", "jim"};
String[] prefix = { "Bl", "B", "Red", "jim" };
for (String val : prefix) {
List<ContactDto> list = Ebean.find(Contact.class)
.select("email, concat(lastName,', ',firstName) as fullName")
.where().istartsWith("concat(lastName,', ',firstName)", val)
.orderBy().asc("lastName")
.setMaxRows(10)
.asDto(ContactDto.class)
.setLabel("prefixLoop")
.findList();
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where()
.istartsWith(concat("lastName", ", ", "firstName"), val).orderBy().asc("lastName").setMaxRows(10)
.asDto(ContactDto.class).setLabel("prefixLoop").findList();
System.out.println("List:" + list);
}
@@ -77,14 +74,10 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
DtoQuery<ContactDto> query =
Ebean.find(Contact.class)
DtoQuery<ContactDto> query = Ebean.find(Contact.class)
// we must explicitly add the id property for DTO query (if we want it)
.select("id, email, concat(lastName,', ',firstName) as fullName")
.where().isNotNull("email").isNotNull("lastName")
.orderBy().asc("lastName")
.asDto(ContactDto.class)
.setLabel("explicitId")
.select("id, email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").asDto(ContactDto.class).setLabel("explicitId")
.setRelaxedMode();
List<ContactDto> dtos = query.findList();
@@ -98,7 +91,8 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select t0.id, t0.email, concat(t0.last_name,', ',t0.first_name) fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
assertThat(sql.get(0)).contains("select t0.id, t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
@Test
@@ -108,12 +102,9 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
DtoQuery<ContactDto> query =
Ebean.find(Contact.class)
.select("email, concat(lastName,', ',firstName) as fullName")
.where().isNotNull("email").isNotNull("lastName")
.orderBy().asc("lastName")
.asDto(ContactDto.class);
DtoQuery<ContactDto> query = Ebean.find(Contact.class)
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").asDto(ContactDto.class);
List<ContactDto> dtos = query.findList();
@@ -126,10 +117,10 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select t0.email, concat(t0.last_name,', ',t0.first_name) fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
assertThat(sql.get(0)).contains("select t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
@Test
public void example() {
@@ -137,15 +128,9 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactDto> contactDtos
= Ebean.find(Contact.class)
.setLabel("emailFullName")
.select("email, concat(lastName,', ',firstName) as fullName")
.where().isNotNull("email").isNotNull("lastName")
.orderBy().asc("lastName")
.setMaxRows(10)
.asDto(ContactDto.class)
.findList();
List<ContactDto> contactDtos = Ebean.find(Contact.class).setLabel("emailFullName")
.select("email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").setMaxRows(10).asDto(ContactDto.class).findList();
assertThat(contactDtos).isNotEmpty();
@@ -158,10 +143,12 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
if (isSqlServer()) {
assertThat(sql.get(0)).contains("select top 10 t0.email, concat(t0.last_name,', ',t0.first_name) fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
assertThat(sql.get(0)).contains("select top 10 t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
} else {
assertThat(sql.get(0)).contains("select t0.email, concat(t0.last_name,', ',t0.first_name) fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
assertThat(sql.get(0)).contains("select t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
}
@@ -172,14 +159,9 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactDto> contactDtos
= Ebean.find(Contact.class)
.select("id, email, concat(lastName,', ',firstName) as fullName")
.where().isNotNull("email").isNotNull("lastName")
.orderBy().asc("lastName")
.setMaxRows(10)
.asDto(ContactDto.class)
.findList();
List<ContactDto> contactDtos = Ebean.find(Contact.class)
.select("id, email, " + concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("email")
.isNotNull("lastName").orderBy().asc("lastName").setMaxRows(10).asDto(ContactDto.class).findList();
assertThat(contactDtos).isNotEmpty();
@@ -192,9 +174,12 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
List<String> sql = LoggedSqlCollector.stop();
if (isSqlServer()) {
assertThat(sql.get(0)).contains("select top 10 t0.id, t0.email, concat(t0.last_name,', ',t0.first_name) fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
assertThat(sql.get(0)).contains("select top 10 t0.id, t0.email, "
+ concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
} else {
assertThat(sql.get(0)).contains("select t0.id, t0.email, concat(t0.last_name,', ',t0.first_name) fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
assertThat(sql.get(0)).contains("select t0.id, t0.email, " + concat("t0.last_name", ", ", "t0.first_name")
+ " fullName from contact t0 where t0.email is not null and t0.last_name is not null order by t0.last_name");
}
}
@@ -205,15 +190,9 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactDto> contactDtos
= Ebean.find(Contact.class)
.select("concat(lastName,', ',firstName) as fullName")
.where().isNotNull("lastName")
.orderBy().asc("lastName")
.asDto(ContactDto.class)
.setFirstRow(2)
.setMaxRows(5)
.findList();
List<ContactDto> contactDtos = Ebean.find(Contact.class)
.select(concat("lastName", ", ", "firstName") + " as fullName").where().isNotNull("lastName").orderBy()
.asc("lastName").asDto(ContactDto.class).setFirstRow(2).setMaxRows(5).findList();
assertThat(contactDtos).isNotEmpty();
@@ -225,10 +204,10 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select concat(t0.last_name,', ',t0.first_name) fullName from contact t0 where");
assertThat(sql.get(0))
.contains("select " + concat("t0.last_name", ", ", "t0.first_name") + " fullName from contact t0 where");
}
@Test
public void example_aggregate() {
@@ -236,14 +215,9 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
LoggedSqlCollector.start();
List<ContactTotals> contactDtos
= Ebean.find(Contact.class)
.select("lastName, count(*) as totalCount")
.where().isNotNull("lastName")
.having().gt("count(*)", 1)
.orderBy().desc("count(*)")
.asDto(ContactTotals.class)
.findList();
List<ContactTotals> contactDtos = Ebean.find(Contact.class).select("lastName, count(*) as totalCount").where()
.isNotNull("lastName").having().gt("count(*)", 1).orderBy().desc("count(*)").asDto(ContactTotals.class)
.findList();
assertThat(contactDtos).isNotEmpty();
@@ -253,7 +227,8 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
}
List<String> sql = LoggedSqlCollector.stop();
assertThat(sql.get(0)).contains("select t0.last_name, count(*) totalCount from contact t0 where t0.last_name is not null group by t0.last_name having count(*) > ?");
assertThat(sql.get(0)).contains(
"select t0.last_name, count(*) totalCount from contact t0 where t0.last_name is not null group by t0.last_name having count(*) > ?");
}
@Test
@@ -261,12 +236,8 @@ public class DtoQueryFromOrmTest extends BaseTestCase {
ResetBasicData.reset();
List<ContactTotals> contactDtos
= Ebean.find(Contact.class)
.select("lastName, count(*) as totalCount")
.where().isNotNull("lastName")
.asDto(ContactTotals.class)
.findList();
List<ContactTotals> contactDtos = Ebean.find(Contact.class).select("lastName, count(*) as totalCount").where()
.isNotNull("lastName").asDto(ContactTotals.class).findList();
assertThat(contactDtos).isNotEmpty();
}
@@ -16,6 +16,8 @@ public class SqlRowBooleanTest extends BaseTestCase {
sqlQuery = Ebean.createSqlQuery("SELECT 1 AS ISNT_NULL from dual");
} else if (isDb2()) {
sqlQuery = Ebean.createSqlQuery("SELECT 1 AS ISNT_NULL from SYSIBM.SYSDUMMY1");
} else if (isHana()) {
sqlQuery = Ebean.createSqlQuery("SELECT 1 AS ISNT_NULL from sys.dummy");
} else {
sqlQuery = Ebean.createSqlQuery("SELECT 1 IS NOT NULL AS ISNT_NULL");
}
@@ -215,6 +215,67 @@ public class UpdateQueryTest extends BaseTestCase {
assertThat(rows).isEqualTo(0);
}
@Test
public void updateQuery_withExplicitTransaction() {
EbeanServer server = server();
int rowsExprList;
int rowsQuery;
try (Transaction transaction = server.beginTransaction()) {
rowsExprList = server
.update(Customer.class)
.setRaw("status = coalesce(status, ?)", Customer.Status.ACTIVE)
.where()
.gt("id", 10000)
.update(transaction);
rowsQuery = server
.update(Customer.class)
.setRaw("status = coalesce(status, ?)", Customer.Status.ACTIVE)
.where()
.gt("id", 10001)
.query().update(transaction);
transaction.commit();
}
assertThat(rowsExprList).isEqualTo(0);
assertThat(rowsQuery).isEqualTo(0);
}
@Test
public void deleteQuery_withExplicitTransaction() {
EbeanServer server = server();
int rowsExprList;
int rowsQuery;
try (Transaction transaction = server.beginTransaction()) {
rowsExprList = server
.update(Customer.class)
.where()
.gt("id", 10000)
.delete(transaction);
rowsQuery = server
.update(Customer.class)
.where()
.gt("id", 10001)
.query().delete(transaction);
transaction.commit();
}
assertThat(rowsExprList).isEqualTo(0);
assertThat(rowsQuery).isEqualTo(0);
}
@Test
public void useViaEbean() {
@@ -0,0 +1,103 @@
package io.ebean.config;
import io.ebean.EbeanServer;
import io.ebean.EbeanServerFactory;
import io.ebean.Transaction;
import io.ebean.annotation.Platform;
import io.ebean.config.dbplatform.DbIdentity;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.h2.H2Platform;
import org.junit.Test;
import org.tests.model.basic.EBasicVer;
import org.tests.model.draftable.BasicDraftableBean;
import static org.assertj.core.api.StrictAssertions.assertThat;
public class PlatformNoGeneratedKeysTest {
static EbeanServer server = testH2Server();
@Test
public void insertBatch_expect_noIdValuesFetched() {
EBasicVer b0 = new EBasicVer("a");
EBasicVer b1 = new EBasicVer("b");
EBasicVer b2 = new EBasicVer("c");
try (Transaction transaction = server.beginTransaction()) {
transaction.setBatchMode(true);
server.save(b0);
server.save(b1);
server.save(b2);
transaction.commit();
}
assertThat(b0.getId()).isNull();
assertThat(b1.getId()).isNull();
assertThat(b2.getId()).isNull();
}
@Test
public void insertNoBatch_expect_selectIdentity() {
EBasicVer b0 = new EBasicVer("one");
server.save(b0);
assertThat(b0.getId()).isNotNull();
BasicDraftableBean d0 = new BasicDraftableBean("done");
server.save(d0);
assertThat(d0.getId()).isNotNull();
server.publish(BasicDraftableBean.class, d0.getId());
BasicDraftableBean one = server.find(BasicDraftableBean.class, d0.getId());
assertThat(one.getName()).isEqualTo("done");
assertThat(one.isDraft()).isFalse();
}
private static EbeanServer testH2Server() {
ServerConfig config = new ServerConfig();
config.setName("h2_noGeneratedKeys");
OtherH2Platform platform = new OtherH2Platform();
DbIdentity dbIdentity = platform.getDbIdentity();
dbIdentity.setIdType(IdType.IDENTITY);
dbIdentity.setSupportsIdentity(true);
dbIdentity.setSupportsGetGeneratedKeys(false);
dbIdentity.setSupportsSequence(false);
dbIdentity.setSelectLastInsertedIdTemplate("select identity() --{table}");
config.setDatabasePlatform(platform);
config.getDataSourceConfig().setUsername("sa");
config.getDataSourceConfig().setPassword("");
config.getDataSourceConfig().setUrl("jdbc:h2:mem:withPCQuery;");
config.getDataSourceConfig().setDriver("org.h2.Driver");
config.setDisableL2Cache(true);
config.setDefaultServer(false);
config.setRegister(false);
config.setDdlGenerate(true);
config.setDdlRun(true);
config.getClasses().add(EBasicVer.class);
config.getClasses().add(BasicDraftableBean.class);
return EbeanServerFactory.create(config);
}
public static class OtherH2Platform extends H2Platform {
public OtherH2Platform() {
super();
this.platform = Platform.GENERIC;
}
}
}
@@ -2,7 +2,7 @@ package io.ebean.config;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.dbplatform.IdType;
import org.avaje.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceConfig;
import org.junit.Test;
import java.util.Properties;
@@ -0,0 +1,48 @@
package io.ebean.config.dbplatform;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import io.ebean.config.dbplatform.hana.HanaHistorySupport;
public class HanaHistorySupportTest {
private HanaHistorySupport support = new HanaHistorySupport();
@Test
public void getAsOfPredicate() {
String asOfPredicate = support.getAsOfPredicate("t0", "sys_period");
assertNull(asOfPredicate);
}
@Test
public void getAsOfViewSuffix() {
String asOfViewSuffix = support.getAsOfViewSuffix("_with_history");
assertEquals(asOfViewSuffix, " for system_time as of ?");
}
@Test
public void getVersionsBetweenSuffix() {
String asOfViewSuffix = support.getVersionsBetweenSuffix("_with_history");
assertEquals(asOfViewSuffix, " for system_time between ? and ?");
}
@Test
public void getLower() throws Exception {
String lower = support.getSysPeriodLower("t0", "sys_period");
assertEquals(lower, "t0.sys_period_start");
}
@Test
public void getUpper() throws Exception {
String upper = support.getSysPeriodUpper("t0", "sys_period");
assertEquals(upper, "t0.sys_period_end");
}
}
@@ -0,0 +1,67 @@
package io.ebean.config.dbplatform;
import io.ebean.config.PlatformConfig;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.platform.PlatformDdl;
import io.ebeaninternal.server.core.PlatformDdlBuilder;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class HanaPlatformTest {
HanaPlatform platform = new HanaPlatform();
@Test
public void testTypeConversion() {
PlatformDdl ddl = PlatformDdlBuilder.create(platform);
assertThat(ddl.convert("clob", false)).isEqualTo("nclob");
assertThat(ddl.convert("blob", false)).isEqualTo("blob");
assertThat(ddl.convert("json", false)).isEqualTo("nclob");
assertThat(ddl.convert("jsonb", false)).isEqualTo("nclob");
assertThat(ddl.convert("jsonvarchar", false)).isEqualTo("nvarchar(255)");
assertThat(ddl.convert("double", false)).isEqualTo("double");
assertThat(ddl.convert("varchar(20)", false)).isEqualTo("nvarchar(20)");
assertThat(ddl.convert("decimal(10)", false)).isEqualTo("decimal(10)");
assertThat(ddl.convert("decimal(8,4)", false)).isEqualTo("decimal(8,4)");
assertThat(ddl.convert("boolean", false)).isEqualTo("boolean");
assertThat(ddl.convert("bit", false)).isEqualTo("smallint");
assertThat(ddl.convert("tinyint", false)).isEqualTo("smallint");
assertThat(ddl.convert("binary", false)).isEqualTo("varbinary(255)");
assertThat(ddl.convert("binary(16)", false)).isEqualTo("varbinary(16)");
assertThat(ddl.convert("point", false)).isEqualTo("st_point");
assertThat(ddl.convert("multilinestring", false)).isEqualTo("st_geometry");
assertThat(ddl.convert("multipolygon", false)).isEqualTo("st_geometry");
assertThat(ddl.convert("multipoint", false)).isEqualTo("st_geometry");
assertThat(ddl.convert("linestring", false)).isEqualTo("st_geometry");
assertThat(ddl.convert("polygon", false)).isEqualTo("st_geometry");
}
@Test
public void uuid_default() {
HanaPlatform platform = new HanaPlatform();
platform.configure(new PlatformConfig());
DbPlatformType dbType = platform.getDbTypeMap().get(DbPlatformType.UUID);
assertThat(dbType.renderType(0, 0)).isEqualTo("varchar(40)");
}
@Test
public void uuid_as_binary() {
HanaPlatform platform = new HanaPlatform();
PlatformConfig config = new PlatformConfig();
config.setDbUuid(PlatformConfig.DbUuid.AUTO_BINARY);
platform.configure(config);
DbPlatformType dbType = platform.getDbTypeMap().get(DbPlatformType.UUID);
assertThat(dbType.renderType(0, 0)).isEqualTo("varbinary(16)");
}
}
@@ -2,7 +2,6 @@ package io.ebeaninternal.dbmigration;
import io.ebean.EbeanServer;
import io.ebean.EbeanServerFactory;
import io.ebean.annotation.Platform;
import io.ebean.config.ServerConfig;
import org.junit.Test;
import org.slf4j.Logger;
@@ -13,6 +12,7 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.StrictAssertions.assertThatThrownBy;
@@ -71,8 +71,11 @@ public class DbMigrationDropHistoryTest {
assertThat(migration.generateMigration()).isNull(); // subsequent call
List<String> pendingDrops = migration.getPendingDrops();
assertThat(pendingDrops).contains("1.1");
System.setProperty("ddl.migration.pendingDropsFor", "1.1");
//System.setProperty("ddl.migration.pendingDropsFor", "1.1");
migration.setGeneratePendingDrop("1.1");
assertThat(migration.generateMigration()).isEqualTo("1.2__dropsFor_1.1");
assertThatThrownBy(()->migration.generateMigration())
.isInstanceOf(IllegalArgumentException.class)
@@ -53,12 +53,14 @@ public class DbMigrationGenerateTest {
migration.addPlatform(Platform.ORACLE, "oracle");
migration.addPlatform(Platform.SQLITE, "sqlite");
migration.addPlatform(Platform.SQLSERVER17, "sqlserver17");
migration.addPlatform(Platform.HANA, "hana");
ServerConfig config = new ServerConfig();
config.setName("migrationtest");
config.loadFromProperties();
config.setRegister(false);
config.setDefaultServer(false);
config.getProperties().put("ebean.hana.generateUniqueDdl", "true"); // need to generate unique statements to prevent them from being filtered out as duplicates by the DdlRunner
config.setPackages(Arrays.asList("misc.migration.v1_0"));
@@ -7,7 +7,6 @@ import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebean.migration.MigrationConfig;
import io.ebean.migration.ddl.DdlRunner;
import io.ebeaninternal.dbmigration.ddlgeneration.Helper;
@@ -90,7 +89,7 @@ public class DbMigrationTest extends BaseTestCase {
runScript(false, "1.0__initial.sql");
if (isOracle()) {
if (isOracle() || isHana()) {
SqlUpdate update = server().createSqlUpdate("insert into migtest_e_basic (id, old_boolean, user_id) values (1, :false, 1)");
update.setParameter("false", false);
assertThat(server().execute(update)).isEqualTo(1);
@@ -199,6 +198,7 @@ public class DbMigrationTest extends BaseTestCase {
for (String table : tables) {
// simple and stupid try to execute all commands on all dialects.
sb.append("alter table ").append(table).append(" set ( system_versioning = OFF );\n");
sb.append("alter table ").append(table).append(" drop system versioning;\n");
sb.append("drop table ").append(table).append(";\n");
sb.append("drop table ").append(table).append(" cascade;\n");
sb.append("drop table ").append(table).append("_history;\n");
@@ -5,6 +5,7 @@ import io.ebean.Ebean;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -16,7 +17,6 @@ import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class BaseDdlHandlerTest extends BaseTestCase {
private ServerConfig serverConfig = new ServerConfig();
@@ -37,6 +37,10 @@ public class BaseDdlHandlerTest extends BaseTestCase {
return handler(new SqlServer17Platform());
}
private DdlHandler hanaHandler() {
return handler(new HanaPlatform());
}
@Test
public void addColumn_nullable_noConstraint() throws Exception {
@@ -47,6 +51,10 @@ public class BaseDdlHandlerTest extends BaseTestCase {
write = new DdlWrite();
sqlserverHandler().generate(write, Helper.getAddColumn());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add added_to_foo nvarchar(20);\n\n");
write = new DdlWrite();
hanaHandler().generate(write, Helper.getAddColumn());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add ( added_to_foo nvarchar(20));\n\n");
}
@Test
@@ -56,10 +64,16 @@ public class BaseDdlHandlerTest extends BaseTestCase {
h2Handler().generate(write, Helper.getAlterTableAddColumnWithCheckConstraint());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add column status integer;\n"
+ "alter table foo add constraint ck_ordering_status check ( status in (0,1));\n\n");
write = new DdlWrite();
hanaHandler().generate(write, Helper.getAlterTableAddColumnWithCheckConstraint());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add ( status integer);\n"
+ "alter table foo add constraint ck_ordering_status check ( status in (0,1));\n\n");
}
/**
* Test the functionality of the Ebean {@literal @}DbArray extension during DDL generation.
* Test the functionality of the Ebean {@literal @}DbArray extension during DDL
* generation.
*/
@Test
public void addColumn_dbarray() throws Exception {
@@ -76,6 +90,12 @@ public class BaseDdlHandlerTest extends BaseTestCase {
DdlHandler sqlserverHandler = sqlserverHandler();
sqlserverHandler.generate(write, Helper.getAlterTableAddDbArrayColumn());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add dbarray_added_to_foo varchar(1000);\n\n");
write = new DdlWrite();
DdlHandler hanaHandler = hanaHandler();
hanaHandler.generate(write, Helper.getAlterTableAddDbArrayColumn());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add ( dbarray_added_to_foo nvarchar(255) array);\n\n");
}
@Test
@@ -93,6 +113,10 @@ public class BaseDdlHandlerTest extends BaseTestCase {
write = new DdlWrite();
sqlserverHandler().generate(write, Helper.getAlterTableAddDbArrayColumnWithLength());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add dbarray_ninety varchar(90);\n\n");
write = new DdlWrite();
hanaHandler().generate(write, Helper.getAlterTableAddDbArrayColumnWithLength());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add ( dbarray_ninety nvarchar(255) array(90));\n\n");
}
@Test
@@ -113,6 +137,14 @@ public class BaseDdlHandlerTest extends BaseTestCase {
write = new DdlWrite();
sqlserverHandler().generate(write, Helper.getAlterTableAddDbArrayColumnInteger());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add dbarray_integer varchar(1000);\n\n");
write = new DdlWrite();
hanaHandler().generate(write, Helper.getAlterTableAddDbArrayColumnIntegerWithLength());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add ( dbarray_integer integer array(90));\n\n");
write = new DdlWrite();
hanaHandler().generate(write, Helper.getAlterTableAddDbArrayColumnInteger());
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo add ( dbarray_integer integer array);\n\n");
}
@Test
@@ -127,7 +159,8 @@ public class BaseDdlHandlerTest extends BaseTestCase {
assertThat(buffer).contains("alter table foo add column some_id integer;");
String fkBuffer = write.applyForeignKeys().getBuffer();
assertThat(fkBuffer).contains("alter table foo add constraint fk_foo_some_id foreign key (some_id) references bar (id) on delete restrict on update restrict;");
assertThat(fkBuffer).contains(
"alter table foo add constraint fk_foo_some_id foreign key (some_id) references bar (id) on delete restrict on update restrict;");
assertThat(fkBuffer).contains("create index idx_foo_some_id on foo (some_id);");
assertThat(write.dropAll().getBuffer()).isEqualTo("");
}
@@ -142,8 +175,15 @@ public class BaseDdlHandlerTest extends BaseTestCase {
assertThat(write.apply().getBuffer()).isEqualTo("alter table foo drop column col2;\n\n");
assertThat(write.dropAll().getBuffer()).isEqualTo("");
}
write = new DdlWrite();
DdlHandler hanaHandler = hanaHandler();
hanaHandler.generate(write, Helper.getDropColumn());
assertThat(write.apply().getBuffer()).isEqualTo("CALL usp_ebean_drop_column('foo', 'col2');\n\n");
assertThat(write.dropAll().getBuffer()).isEqualTo("");
}
@Test
public void createTable() throws Exception {
@@ -157,6 +197,16 @@ public class BaseDdlHandlerTest extends BaseTestCase {
assertThat(write.apply().getBuffer()).isEqualTo(createTableDDL);
assertThat(write.dropAll().getBuffer().trim()).isEqualTo("drop table if exists foo;");
write = new DdlWrite();
DdlHandler hanaHandler = hanaHandler();
hanaHandler.generate(write, Helper.getCreateTable());
String createColumnTableDDL = Helper.asText(this, "/assert/create-column-table.txt");
assertThat(write.apply().getBuffer()).isEqualTo(createColumnTableDDL);
assertThat(write.dropAll().getBuffer().trim()).isEqualTo("drop table foo cascade;");
}
@Test
@@ -174,7 +224,6 @@ public class BaseDdlHandlerTest extends BaseTestCase {
assertThat(write.dropAll().getBuffer()).isEqualTo(rollbackLast);
}
@Ignore
@Test
public void generateChangeSetFromModel() throws Exception {
@@ -0,0 +1,42 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import org.junit.Test;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlWrite;
import io.ebeaninternal.dbmigration.migration.Column;
public class HanaDdlTest {
@Test
public void alterTableDropColumn() throws IOException {
HanaColumnStoreDdl ddl = new HanaColumnStoreDdl(new HanaPlatform());
DdlWrite write = new DdlWrite();
ddl.alterTableDropColumn(write.apply(), "my_table", "my_column");
assertEquals("CALL usp_ebean_drop_column('my_table', 'my_column');\n", write.apply().getBuffer());
}
@Test
public void alterTableAddColumn() throws IOException {
HanaColumnStoreDdl ddl = new HanaColumnStoreDdl(new HanaPlatform());
DdlWrite write = new DdlWrite();
Column column = new Column();
column.setName("my_column");
column.setComment("comment");
column.setDefaultValue("1");
column.setNotnull(Boolean.TRUE);
column.setType("int");
column.setUnique("unique");
column.setPrimaryKey(Boolean.TRUE);
column.setCheckConstraint("CHECK(my_column > 0)");
column.setCheckConstraintName("check_constraint");
column.setHistoryExclude(Boolean.TRUE);
column.setIdentity(Boolean.TRUE);
ddl.alterTableAddColumn(write.apply(), "my_table", column, false, "1");
assertEquals("alter table my_table add ( my_column int default 1 not null);\nalter table my_table add constraint check_constraint CHECK(my_column > 0);\n", write.apply().getBuffer());
}
}
@@ -4,6 +4,7 @@ import io.ebean.Ebean;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.IdType;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
@@ -16,6 +17,8 @@ import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class PlatformDdl_AlterColumnTest {
@@ -24,6 +27,7 @@ public class PlatformDdl_AlterColumnTest {
private PlatformDdl mysqlDdl = PlatformDdlBuilder.create(new MySqlPlatform());
private PlatformDdl oraDdl = PlatformDdlBuilder.create(new OraclePlatform());
private PlatformDdl sqlServerDdl = PlatformDdlBuilder.create(new SqlServer17Platform());
private PlatformDdl hanaDdl = PlatformDdlBuilder.create(new HanaPlatform());
{
ServerConfig serverConfig = Ebean.getDefaultServer().getPluginApi().getServerConfig();
@@ -63,6 +67,14 @@ public class PlatformDdl_AlterColumnTest {
assertThat(pgDdl.convertArrayType("varchar[]")).isEqualTo("varchar[]");
assertThat(pgDdl.convertArrayType("integer[]")).isEqualTo("integer[]");
}
@Test
public void convertArrayType_hana() {
assertThat(hanaDdl.convertArrayType("varchar[](90)")).isEqualTo("nvarchar(255) array(90)");
assertThat(hanaDdl.convertArrayType("integer[](60)")).isEqualTo("integer array(60)");
assertThat(hanaDdl.convertArrayType("varchar[]")).isEqualTo("nvarchar(255) array");
assertThat(hanaDdl.convertArrayType("integer[]")).isEqualTo("integer array");
}
@Test
public void testAlterColumnBaseAttributes() throws Exception {
@@ -77,20 +89,32 @@ public class PlatformDdl_AlterColumnTest {
sql = sqlServerDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab alter column acol nvarchar(5) not null", sql);
sql = hanaDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab alter ( acol nvarchar(5) not null)", sql);
alterColumn.setNotnull(Boolean.FALSE);
sql = mysqlDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab modify acol varchar(5)", sql);
sql = hanaDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab alter ( acol nvarchar(5))", sql);
alterColumn.setNotnull(null);
alterColumn.setType("varchar(100)");
sql = mysqlDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab modify acol varchar(100)", sql);
sql = hanaDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab alter ( acol nvarchar(100))", sql);
alterColumn.setCurrentNotnull(Boolean.TRUE);
sql = mysqlDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab modify acol varchar(100) not null", sql);
sql = hanaDdl.alterColumnBaseAttributes(alterColumn);
assertEquals("alter table mytab alter ( acol nvarchar(100) not null)", sql);
}
@Test
@@ -110,6 +134,9 @@ public class PlatformDdl_AlterColumnTest {
sql = sqlServerDdl.alterColumnType("mytab", "acol", "varchar(20)");
assertNull(sql);
sql = hanaDdl.alterColumnType("mytab", "acol", "varchar(20)");
assertNull(sql);
}
@Test
@@ -129,6 +156,9 @@ public class PlatformDdl_AlterColumnTest {
sql = sqlServerDdl.alterColumnNotnull("mytab", "acol", true);
assertNull(sql);
sql = hanaDdl.alterColumnNotnull("mytab", "acol", true);
assertNull(sql);
}
@Test
@@ -148,6 +178,9 @@ public class PlatformDdl_AlterColumnTest {
sql = sqlServerDdl.alterColumnNotnull("mytab", "acol", false);
assertNull(sql);
sql = hanaDdl.alterColumnNotnull("mytab", "acol", false);
assertNull(sql);
}
@Test
@@ -167,6 +200,15 @@ public class PlatformDdl_AlterColumnTest {
sql = sqlServerDdl.alterColumnDefaultValue("mytab", "acol", "'hi'");
assertEquals("alter table mytab add default 'hi' for acol", sql);
boolean exceptionCaught = false;
try {
hanaDdl.alterColumnDefaultValue("mytab", "acol", "'hi'");
}
catch (UnsupportedOperationException e) {
exceptionCaught = true;
}
assertTrue(exceptionCaught);
}
@Test
@@ -186,6 +228,15 @@ public class PlatformDdl_AlterColumnTest {
sql = sqlServerDdl.alterColumnDefaultValue("mytab", "acol", "DROP DEFAULT");
assertEquals("EXEC usp_ebean_drop_default_constraint mytab, acol", sql);
boolean exceptionCaught = false;
try {
hanaDdl.alterColumnDefaultValue("mytab", "acol", "DROP DEFAULT");
}
catch (UnsupportedOperationException e) {
exceptionCaught = true;
}
assertTrue(exceptionCaught);
}
@Test
@@ -226,4 +277,14 @@ public class PlatformDdl_AlterColumnTest {
assertEquals(oraDdl.useIdentityType(IdentityType.GENERATOR), IdType.GENERATOR);
assertEquals(oraDdl.useIdentityType(IdentityType.EXTERNAL), IdType.EXTERNAL);
}
@Test
public void useIdentityType_hana() {
assertEquals(hanaDdl.useIdentityType(null), IdType.IDENTITY);
assertEquals(hanaDdl.useIdentityType(IdentityType.SEQUENCE), IdType.IDENTITY);
assertEquals(hanaDdl.useIdentityType(IdentityType.IDENTITY), IdType.IDENTITY);
assertEquals(hanaDdl.useIdentityType(IdentityType.GENERATOR), IdType.GENERATOR);
assertEquals(hanaDdl.useIdentityType(IdentityType.EXTERNAL), IdType.EXTERNAL);
}
}
@@ -1,6 +1,8 @@
package io.ebeaninternal.dbmigration.ddlgeneration.platform;
import io.ebean.config.ServerConfig;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.dbplatform.hana.HanaPlatform;
import io.ebean.config.dbplatform.mysql.MySqlPlatform;
import io.ebean.config.dbplatform.oracle.OraclePlatform;
import io.ebean.config.dbplatform.postgres.PostgresPlatform;
@@ -12,12 +14,12 @@ import static org.junit.Assert.assertEquals;
public class PlatformDdl_dropUniqueConstraintTest {
private PlatformDdl h2Ddl = PlatformDdlBuilder.create(new H2Platform());
private PlatformDdl pgDdl = PlatformDdlBuilder.create(new PostgresPlatform());
private PlatformDdl mysqlDdl = PlatformDdlBuilder.create(new MySqlPlatform());
private PlatformDdl oraDdl = PlatformDdlBuilder.create(new OraclePlatform());
private PlatformDdl sqlServerDdl = PlatformDdlBuilder.create(new SqlServer17Platform());
private PlatformDdl hanaDdl = PlatformDdlBuilder.create(new HanaPlatform());
@Test
public void test() throws Exception {
@@ -30,11 +32,22 @@ public class PlatformDdl_dropUniqueConstraintTest {
assertEquals("alter table mytab drop constraint uq_name", sql);
sql = sqlServerDdl.alterTableDropUniqueConstraint("mytab", "uq_name");
assertEquals("IF (OBJECT_ID('uq_name', 'UQ') IS NOT NULL) alter table mytab drop constraint uq_name;\n"
+ "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('mytab','U') AND name = 'uq_name') drop index uq_name ON mytab", sql);
+ "IF EXISTS (SELECT name FROM sys.indexes WHERE object_id = OBJECT_ID('mytab','U') AND name = 'uq_name') drop index uq_name ON mytab",
sql);
sql = mysqlDdl.alterTableDropUniqueConstraint("mytab", "uq_name");
assertEquals("alter table mytab drop index uq_name", sql);
ServerConfig serverConfig = new ServerConfig();
hanaDdl.configure(serverConfig);
sql = hanaDdl.alterTableDropUniqueConstraint("mytab", "uq_name");
assertEquals("delimiter $$\n" +
"do\n" +
"begin\n" +
"declare exit handler for sql_error_code 397 begin end;\n" +
"exec 'alter table mytab drop constraint uq_name';\n" +
"end;\n" +
"$$", sql);
}
}
@@ -0,0 +1,70 @@
package io.ebeaninternal.server.expression.platform;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.expression.Op;
public class HanaDbExpressionTest {
private HanaDbExpression expression = new HanaDbExpression();
@Test
public void testArrayContains() {
SpiExpressionRequest request = new DefaultExpressionRequest(null);
expression.arrayContains(request, "arrayproperty", true, "v1", "v2", "v3");
assertEquals("(? member of arrayproperty) and (? member of arrayproperty) and (? member of arrayproperty)",
request.getSql());
}
@Test
public void testArrayNotContains() {
SpiExpressionRequest request = new DefaultExpressionRequest(null);
expression.arrayContains(request, "arrayproperty", false, "v1", "v2", "v3");
assertEquals(
"(? not member of arrayproperty) and (? not member of arrayproperty) and (? not member of arrayproperty)",
request.getSql());
}
@Test
public void testArrayContainsEmpty() {
SpiExpressionRequest request = new DefaultExpressionRequest(null);
expression.arrayContains(request, "arrayproperty", true);
assertEquals("", request.getSql());
}
@Test
public void testArrayIsEmpty() {
SpiExpressionRequest request = new DefaultExpressionRequest(null);
expression.arrayIsEmpty(request, "arrayproperty", true);
assertEquals("cardinality(arrayproperty) = 0", request.getSql());
}
@Test
public void testArrayIsNotEmpty() {
SpiExpressionRequest request = new DefaultExpressionRequest(null);
expression.arrayIsEmpty(request, "arrayproperty", false);
assertEquals("cardinality(arrayproperty) <> 0", request.getSql());
}
@Test
public void testConcat() {
String concat = expression.concat("property0", "separator", "property1", "suffix");
assertEquals("concat(property0, 'separator'||property1||'suffix')", concat);
}
@Test
public void testConcatNullSuffix() {
String concat = expression.concat("property0", "separator", "property1", null);
assertEquals("concat(property0, 'separator'||property1)", concat);
}
@Test
public void testJson() {
SpiExpressionRequest request = new DefaultExpressionRequest(null);
expression.json(request, "jsonproperty", "path", Op.EQ, "val");
assertEquals("json_value(jsonproperty, '$.path') = ? ", request.getSql());
}
}
@@ -3,6 +3,9 @@ package io.ebeaninternal.server.grammer;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.Query;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebeaninternal.api.SpiQuery;
import org.junit.Test;
import org.tests.model.basic.Customer;
@@ -11,6 +14,8 @@ import org.tests.model.basic.ResetBasicData;
import java.util.Arrays;
import java.util.List;
import javax.xml.ws.RequestWrapper;
import static org.assertj.core.api.Assertions.assertThat;
public class EqlParserTest extends BaseTestCase {
@@ -127,6 +132,7 @@ public class EqlParserTest extends BaseTestCase {
}
@Test
@IgnorePlatform(Platform.HANA) // The HANA JDBC driver checks the field length on binding and rejects 'NEW'
public void where_or1() {
Query<Customer> query = parse("where name = 'Rob' or (status = 'NEW' and smallnote is null)");
@@ -134,8 +140,19 @@ public class EqlParserTest extends BaseTestCase {
assertThat(query.getGeneratedSql()).contains("where (t0.name = ? or (t0.status = ? and t0.smallnote is null ) )");
}
@Test
@ForPlatform(Platform.HANA)
public void where_or1_hana() {
Query<Customer> query = parse("where name = 'Rob' or (status = 'N' and smallnote is null)");
query.findList();
assertThat(query.getGeneratedSql()).contains("where (t0.name = ? or (t0.status = ? and t0.smallnote is null ) )");
}
@Test
@IgnorePlatform(Platform.HANA) // The HANA JDBC driver checks the field length on binding and rejects 'NEW'
public void where_or2() {
Query<Customer> query = parse("where (name = 'Rob' or status = 'NEW') and smallnote is null");
@@ -143,8 +160,19 @@ public class EqlParserTest extends BaseTestCase {
assertThat(query.getGeneratedSql()).contains("where ((t0.name = ? or t0.status = ? ) and t0.smallnote is null )");
}
@Test
@ForPlatform(Platform.HANA)
public void where_or2_hana() {
Query<Customer> query = parse("where (name = 'Rob' or status = 'N') and smallnote is null");
query.findList();
assertThat(query.getGeneratedSql()).contains("where ((t0.name = ? or t0.status = ? ) and t0.smallnote is null )");
}
@Test
@IgnorePlatform(Platform.HANA) // The HANA JDBC driver checks the field length on binding and rejects 'NEW'
public void test_simplifyExpressions() {
Query<Customer> query = parse("where not (name = 'Rob' and status = 'NEW')");
@@ -159,6 +187,23 @@ public class EqlParserTest extends BaseTestCase {
query.findList();
assertThat(query.getGeneratedSql()).contains("where not (t0.name = ? and t0.status = ? )");
}
@Test
@ForPlatform(Platform.HANA)
public void test_simplifyExpressions_hana() {
Query<Customer> query = parse("where not (name = 'Rob' and status = 'N')");
query.findList();
assertThat(query.getGeneratedSql()).contains("where not (t0.name = ? and t0.status = ? )");
query = parse("where not ((name = 'Rob' and status = 'N'))");
query.findList();
assertThat(query.getGeneratedSql()).contains("where not (t0.name = ? and t0.status = ? )");
query = parse("where not (((name = 'Rob') and (status = 'N')))");
query.findList();
assertThat(query.getGeneratedSql()).contains("where not (t0.name = ? and t0.status = ? )");
}
@Test
@@ -7,57 +7,58 @@ import static org.junit.Assert.*;
public class SqlTreeBuilderTest {
@Test
public void mergeOnDistinct_equal() throws Exception {
public void mergeOnDistinct_equal() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.id"), "t0.id");
}
@Test
public void mergeOnDistinct_add() throws Exception {
public void mergeOnDistinct_add() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre"), "t0.cre, t0.id");
}
@Test
public void mergeOnDistinct_contained() throws Exception {
public void mergeOnDistinct_contained() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre, t0.id"), "t0.cre, t0.id");
}
@Test
public void mergeOnDistinct_overlap() throws Exception {
public void mergeOnDistinct_overlap() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id, t1.id", "t0.cre, t0.id"), "t0.cre, t0.id, t1.id");
}
@Test
public void mergeOnDistinct_overlapBoth() throws Exception {
public void mergeOnDistinct_overlapBoth() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id, t1.id", "t0.cre, t1.id, t0.id"), "t0.cre, t1.id, t0.id");
}
@Test
public void mergeOnDistinct_inlineAscDesc() throws Exception {
public void mergeOnDistinct_inlineAscDesc() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre asc, t1.bb desc, t3.b"), "t0.cre, t1.bb, t3.b, t0.id");
}
@Test
public void mergeOnDistinct_inlineAscDesc2() throws Exception {
public void mergeOnDistinct_inlineAscDesc2() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre desc, t1.bb asc, t3.b"), "t0.cre, t1.bb, t3.b, t0.id");
}
@Test
public void mergeOnDistinct_trailingAsc() throws Exception {
public void mergeOnDistinct_trailingAsc() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre asc"), "t0.cre, t0.id");
}
@Test
public void mergeOnDistinct_trailingDesc() throws Exception {
public void mergeOnDistinct_trailingDesc() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre desc"), "t0.cre, t0.id");
}
@Test
public void mergeOnDistinct_trailingDescNullsFirst() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre desc nulls first"), "t0.cre, t0.id");
}
@Test
public void mergeOnDistinct_trailingDescNullsLast() {
assertEquals(SqlTreeBuilder.mergeOnDistinct("t0.id", "t0.cre desc nulls last"), "t0.cre, t0.id");
}
}
+76
View File
@@ -0,0 +1,76 @@
package org;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.EbeanServerFactory;
import io.ebean.Transaction;
import io.ebean.annotation.Cache;
import io.ebean.config.PlatformNoGeneratedKeysTest;
import io.ebean.config.ServerConfig;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import java.util.Arrays;
/**
*
* Test preparation:
*
* - Enable file based H2 in ebean.properties (line `datasource.h2.databaseUrl=jdbc:h2:file:~/tests`)
* - disable logging of io.ebean.SUM/TXN/SQL in logback-test.xml
* - remove existing database in ~/tests (do that before each run)
* - run this class with `-Xmx1g` jvm argument
* - an OOM in iteration ~45000 will occur (45k * 10k chars = 45k * 20k bytes = 900MB)
*
* remove the line `@Cache(enableQueryCache=true)` - and it will run forever (until disk is full)
*/
public class MainMemoryLeak {
@Cache(enableQueryCache=true)
@Entity
public static class ECachedBean {
@Id
private Long id;
@Lob
private String description;
}
public static void main(String[] args) {
PlatformNoGeneratedKeysTest.OtherH2Platform platform = new PlatformNoGeneratedKeysTest.OtherH2Platform();
ServerConfig config = new ServerConfig();
config.setDatabasePlatform(platform);
config.addClass(ECachedBean.class);
config.loadFromProperties();
EbeanServer server = EbeanServerFactory.create(config);
// create a string with 10k chars
char[] c = new char[10_000];
Arrays.fill(c, 'x');
try (Transaction txn = server.beginTransaction()) {
for (int i = 0; i < 500000; i++) {
if (i % 1000 == 0) {
long mem = Runtime.getRuntime().freeMemory() / 1024 / 1024;
if (mem > 1024) {
throw new IllegalStateException("-Xmx1g JVM argument expected");
}
System.out.println("Iteration: " + i + " Free mem: " + mem + " MB");
}
ECachedBean a = new ECachedBean();
a.description = new String(c);
server.save(a);
}
System.out.println("Success, no mem limit detected");
txn.commit();
}
}
}
@@ -9,7 +9,7 @@ import io.ebean.config.dbplatform.postgres.PostgresPlatform;
import org.tests.model.basic.TOne;
import org.tests.model.basic.TSDetail;
import org.tests.model.basic.TSMaster;
import org.avaje.datasource.DataSourceConfig;
import io.ebean.datasource.DataSourceConfig;
import org.junit.Assert;
import java.util.List;
@@ -1,6 +1,6 @@
package org.tests.basic;
import org.avaje.datasource.DataSourcePoolListener;
import io.ebean.datasource.DataSourcePoolListener;
import java.sql.Connection;
@@ -18,7 +18,12 @@ public class TestErrorBindLog extends BaseTestCase {
} catch (PersistenceException e) {
String msg = e.getMessage();
Assert.assertTrue(msg.contains("Bind values:"));
if (isHana()) {
Assert.assertTrue(msg.contains("Error with property[1] dt[12]data[JUNK]"));
}
else {
Assert.assertTrue(msg.contains("Bind values:"));
}
}
}
}
@@ -54,7 +54,7 @@ public class TestTransient extends BaseTestCase {
selected = c.getSelected();
Assert.assertNotNull(selected);
String updateStmt = "update customer set name = 'Rob' where id = :id";
String updateStmt = "update customer set name = 'testTrans2' where id = :id";
int rows = Ebean.createUpdate(Customer.class, updateStmt).set("id", custId).execute();
Assert.assertTrue("changed name back", 1 == rows);
@@ -4,7 +4,9 @@ import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.Transaction;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Transactional;
import io.ebean.meta.BasicMetricVisitor;
import io.ebean.meta.MetaTimedMetric;
@@ -119,6 +121,7 @@ public class TestBatchInsertFlush extends BaseTestCase {
@Test
@Transactional(batch = PersistBatch.ALL)
@IgnorePlatform(Platform.HANA)
public void transactional_flushOnGetId() {
EbeanServer server = Ebean.getDefaultServer();
@@ -141,6 +144,7 @@ public class TestBatchInsertFlush extends BaseTestCase {
}
@Test
@IgnorePlatform(Platform.HANA)
public void testFlushOnGetId() {
EbeanServer server = Ebean.getDefaultServer();
@@ -3,7 +3,9 @@ package org.tests.batchinsert;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.Transaction;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Transactional;
import org.junit.Test;
import org.tests.model.basic.UTDetail;
@@ -46,6 +48,7 @@ public class TestBatchInsertSimple extends BaseTestCase {
}
@Test
@IgnorePlatform(Platform.HANA)
public void testTransactional() {
saveWithFullBatchMode();
@@ -131,7 +134,7 @@ public class TestBatchInsertSimple extends BaseTestCase {
Transaction transaction = Ebean.beginTransaction();
try {
transaction.setBatch(PersistBatch.NONE);
transaction.setBatchOnCascade(PersistBatch.ALL);
transaction.setBatchOnCascade(spiEbeanServer().getDatabasePlatform().getPersistBatchOnCascade());
transaction.setBatchSize(20);
// escalate based on batchOnCascade value
@@ -3,7 +3,9 @@ package org.tests.batchinsert;
import io.ebean.BaseTestCase;
import io.ebean.EbeanServer;
import io.ebean.Transaction;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.Platform;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.persist.BatchControl;
import org.tests.model.basic.EBasicWithUniqueCon;
@@ -20,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat;
public class TestBatchOnCascadeExceptionHandling extends BaseTestCase {
@IgnorePlatform(Platform.HANA)
@Test
public void testBatchScenarioWithSavepoint() throws SQLException {
server().save(createEntityWithName("conflict", "before"));
@@ -84,7 +87,7 @@ public class TestBatchOnCascadeExceptionHandling extends BaseTestCase {
Transaction txn = server.beginTransaction();
try {
assertThat(txn.getBatch()).isSameAs(PersistBatch.NONE);
assertThat(txn.getBatchOnCascade()).isSameAs(PersistBatch.ALL);
assertThat(txn.getBatchOnCascade()).isSameAs(spiEbeanServer().getDatabasePlatform().getPersistBatchOnCascade());
failingOperation.run();
Assertions.fail("PersistenceException expected");
@@ -3,6 +3,8 @@ package org.tests.batchinsert;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
import io.ebean.annotation.Transactional;
import org.tests.model.basic.Customer;
import org.junit.Test;
@@ -18,6 +20,7 @@ public class TestBatchSaveWithGetBeanId extends BaseTestCase {
*/
@Transactional(batchSize = 10)
@Test
@IgnorePlatform(Platform.HANA) // HANA doesn't support insert batching
public void test() {
EbeanServer server = Ebean.getDefaultServer();
@@ -6,10 +6,12 @@ import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Version;
import java.util.List;
@Entity
@Table(name="`type`") // needs to be quoted because it's a keyword on HANA
public class Type {
@Id
private TypeKey key;
@@ -0,0 +1,113 @@
package org.tests.json;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.StrictAssertions.assertThat;
import java.io.IOException;
import org.junit.Test;
import org.tests.model.json.EBasicJsonJackson;
import org.tests.model.json.EBasicJsonJackson2;
import org.tests.model.json.LongJacksonType;
import org.tests.model.json.StringJacksonType;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
public class TestDbJson_Jackson extends BaseTestCase {
/**
* This testcase verifies if the &#64;JsonDeserialize Annotation will work properly in ebean.
*/
@Test
public void testJsonDeserializeAnnotation() throws IOException {
EBasicJsonJackson bean = new EBasicJsonJackson();
bean.setName("stuff");
bean.setPlainValue("plain");
bean.getValueSet().add("A");
bean.getValueSet().add("B");
bean.getValueList().add("A");
bean.getValueList().add("B");
bean.getValueMap().put(1, "one");
bean.getValueMap().put(2, "two");
ObjectMapper mapper = (ObjectMapper) Ebean.getDefaultServer().getPluginApi().getServerConfig().getObjectMapper();
String json = mapper.writeValueAsString(bean);
EBasicJsonJackson found = mapper.readValue(json, EBasicJsonJackson.class);
// here we assert, that we can serialize/deserialize that bean with jackson
assertThat(found.getPlainValue()).isEqualTo("plain");
assertThat(found.getValueList()).containsExactly("A", "B");
assertThat(found.getValueSet()).containsExactly("A", "B");
assertThat(found.getValueMap()).containsEntry(1L, "one").containsEntry(2L, "two");
Ebean.save(bean);
// here we do the same and expect, that we can deserialize the bean also
found = Ebean.find(EBasicJsonJackson.class, bean.getId());
assertThat(found.getPlainValue()).isEqualTo("plain");
assertThat(found.getValueList()).containsExactly("A", "B");
assertThat(found.getValueSet()).containsExactly("A", "B");
assertThat(found.getValueMap()).containsEntry(1L, "one").containsEntry(2L, "two");
}
/**
* This testcase verifies if polymorph objects will work in ebean.
*
* for BasicJacksonType there exists two types and has a &#64;JsonTypeInfo
* annotation. It is expected that this information is also honored by ebean.
*/
@Test
public void testPolymorph() throws IOException {
EBasicJsonJackson2 bean = new EBasicJsonJackson2();
bean.setName("stuff");
bean.setPlainValue(new LongJacksonType(42L));
bean.getValueSet().add(new StringJacksonType("A"));
bean.getValueSet().add(new LongJacksonType(7l));
bean.getValueList().add(new StringJacksonType("A"));
bean.getValueList().add(new LongJacksonType(7l));
bean.getValueMap().put(1, new StringJacksonType("A"));
bean.getValueMap().put(2, new LongJacksonType(7l));
ObjectMapper mapper = (ObjectMapper) Ebean.getDefaultServer().getPluginApi().getServerConfig().getObjectMapper();
String json = mapper.writeValueAsString(bean);
assertThat(json)
.contains("\"type\":\"string\"")
.contains("\"type\":\"long\"");
EBasicJsonJackson2 found = mapper.readValue(json, EBasicJsonJackson2.class);
assertThat(found.getPlainValue()).isInstanceOf(LongJacksonType.class);
assertThat(found.getValueList()).hasSize(2);
assertThat(found.getValueSet()).hasSize(2);
assertThat(found.getValueMap()).hasSize(2);;
Ebean.save(bean);
found = Ebean.find(EBasicJsonJackson2.class, bean.getId());
assertThat(found.getPlainValue()).isInstanceOf(LongJacksonType.class);
assertThat(found.getValueList()).hasSize(2);
assertThat(found.getValueSet()).hasSize(2);
assertThat(found.getValueMap()).hasSize(2);;
}
}

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