mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c2e9ccacd | ||
|
|
29aa395cd4 | ||
|
|
8160d1f01c | ||
|
|
6fd76ff697 | ||
|
|
249cc5df49 | ||
|
|
2646e8c27f | ||
|
|
3eaf47fd70 | ||
|
|
9d64352771 | ||
|
|
aa7325b249 | ||
|
|
2839434773 | ||
|
|
de9823b69b | ||
|
|
15cab24848 | ||
|
|
b87e360ac7 | ||
|
|
82e68d7756 | ||
|
|
0371043914 | ||
|
|
54b5684628 | ||
|
|
da95ec0c05 | ||
|
|
b5eec93995 | ||
|
|
fc26765a7e | ||
|
|
9a6d339449 | ||
|
|
5b09443cdf | ||
|
|
31093babe3 | ||
|
|
f8b9ca2034 | ||
|
|
c27749ae2d | ||
|
|
2c04430185 | ||
|
|
dcde47daf6 | ||
|
|
57056d7abd | ||
|
|
3409f264ec |
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>11.22.6</version>
|
||||
<version>11.22.10</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.6</tag>
|
||||
<tag>ebean-11.22.10</tag>
|
||||
</scm>
|
||||
|
||||
<profiles>
|
||||
|
||||
@@ -98,6 +98,11 @@ public interface SqlUpdate {
|
||||
/**
|
||||
* Execute the update returning the number of rows modified.
|
||||
* <p>
|
||||
* Note that if the transaction has batch mode on then this update will use JDBC batch and may not execute until
|
||||
* later - at commit time or a transaction flush. In this case this method returns -1 indicating that the
|
||||
* update has been batched for later execution.
|
||||
* </p>
|
||||
* <p>
|
||||
* After you have executed the SqlUpdate you can bind new variables using
|
||||
* {@link #setParameter(String, Object)} etc and then execute the SqlUpdate
|
||||
* again.
|
||||
@@ -112,6 +117,11 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Execute the statement now regardless of the JDBC batch mode of the transaction.
|
||||
*/
|
||||
int executeNow();
|
||||
|
||||
/**
|
||||
* Execute when addBatch() has been used to batch multiple bind executions.
|
||||
*
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -312,4 +312,8 @@ public interface SpiEbeanServer extends ExtendedServer, EbeanServer, BeanLoader,
|
||||
*/
|
||||
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the sql update regardless of transaction batch mode.
|
||||
*/
|
||||
int executeNow(SpiSqlUpdate sqlUpdate);
|
||||
}
|
||||
|
||||
@@ -134,27 +134,40 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
|
||||
/**
|
||||
* Includes soft deletes rows in the result.
|
||||
*/
|
||||
SOFT_DELETED,
|
||||
SOFT_DELETED(false),
|
||||
|
||||
/**
|
||||
* Query runs against draft tables.
|
||||
*/
|
||||
DRAFT,
|
||||
DRAFT(false),
|
||||
|
||||
/**
|
||||
* Query runs against current data (normal).
|
||||
*/
|
||||
CURRENT,
|
||||
CURRENT(false),
|
||||
|
||||
/**
|
||||
* Query runs potentially returning many versions of the same bean.
|
||||
*/
|
||||
VERSIONS,
|
||||
VERSIONS(true),
|
||||
|
||||
/**
|
||||
* Query runs 'As Of' a given date time.
|
||||
*/
|
||||
AS_OF;
|
||||
AS_OF(true);
|
||||
|
||||
private final boolean history;
|
||||
|
||||
TemporalMode(boolean history) {
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a history query.
|
||||
*/
|
||||
public boolean isHistory() {
|
||||
return history;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the mode of the query of if null return CURRENT mode.
|
||||
|
||||
@@ -65,7 +65,7 @@ import java.util.List;
|
||||
*/
|
||||
public class DefaultDbMigration implements DbMigration {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(DefaultDbMigration.class);
|
||||
protected static final Logger logger = LoggerFactory.getLogger("io.ebean.GenerateMigration");
|
||||
|
||||
private static final String initialVersion = "1.0";
|
||||
|
||||
@@ -303,6 +303,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,6 +511,9 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (nextDrop != null) {
|
||||
return nextDrop;
|
||||
}
|
||||
if (generatePendingDrop != null) {
|
||||
return generatePendingDrop;
|
||||
}
|
||||
return migrationConfig.getGeneratePendingDrop();
|
||||
}
|
||||
|
||||
@@ -595,9 +615,6 @@ public class DefaultDbMigration implements DbMigration {
|
||||
if (name != null) {
|
||||
migrationConfig.setName(name);
|
||||
}
|
||||
if (generatePendingDrop != null) {
|
||||
migrationConfig.setGeneratePendingDrop(generatePendingDrop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,6 @@ public class ModelBuildElementTable {
|
||||
|
||||
VisitAllUsing.visitOne(targetDescriptor, new ModelBuildPropertyVisitor(ctx, table, targetDescriptor));
|
||||
|
||||
ctx.fkeyBuilder(table)
|
||||
.addForeignKey(manyProp.getBeanDescriptor(), manyProp.getTableJoin(), true);
|
||||
|
||||
ctx.addTable(table);
|
||||
}
|
||||
|
||||
|
||||
@@ -2054,6 +2054,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return persister.executeSqlUpdate(updSql, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow(SpiSqlUpdate sqlUpdate) {
|
||||
return persister.executeSqlUpdateNow(sqlUpdate, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
|
||||
persister.addBatch(sqlUpdate, transaction);
|
||||
|
||||
@@ -133,6 +133,15 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow() {
|
||||
if (server != null) {
|
||||
return server.executeNow(this);
|
||||
} else {
|
||||
throw new IllegalStateException("server is null?");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] executeBatch() {
|
||||
if (server == null) {
|
||||
|
||||
@@ -8,6 +8,9 @@ import io.ebeaninternal.server.persist.BatchPostExecute;
|
||||
import io.ebeaninternal.server.persist.BatchedSqlException;
|
||||
import io.ebeaninternal.server.persist.PersistExecute;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Wraps all the objects used to persist a bean.
|
||||
*/
|
||||
@@ -95,6 +98,13 @@ public abstract class PersistRequest extends BeanRequest implements BatchPostExe
|
||||
return transaction.isBatchThisRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate the SQLException into a specific exception given the platform.
|
||||
*/
|
||||
public PersistenceException translateSqlException(SQLException e) {
|
||||
return transaction.translate(e.getMessage(), e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the statement.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanManager;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
import io.ebeaninternal.server.deploy.id.ImportedId;
|
||||
import io.ebeaninternal.server.persist.BatchControl;
|
||||
@@ -131,6 +132,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
*/
|
||||
private boolean[] dirtyProperties;
|
||||
|
||||
/**
|
||||
* Imported OneToOne orphan that needs to be deleted.
|
||||
*/
|
||||
private EntityBean orphanBean;
|
||||
|
||||
/**
|
||||
* Flag set when request is added to JDBC batch.
|
||||
*/
|
||||
@@ -1377,4 +1383,25 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
changeSet.addBeanUpdate(beanDescriptor, idValue, changes, updateNaturalKey, getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an orphan bean that needs to be deleted AFTER the request has persisted.
|
||||
*/
|
||||
public void setImportedOrphanForRemoval(BeanPropertyAssocOne<?> prop) {
|
||||
Object orphan = getOrigValue(prop);
|
||||
if (orphan instanceof EntityBean) {
|
||||
orphanBean = (EntityBean)orphan;
|
||||
}
|
||||
}
|
||||
|
||||
public EntityBean getImportedOrphanForRemoval() {
|
||||
return orphanBean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL used to fetch the last inserted id value.
|
||||
*/
|
||||
public String getSelectLastInsertedId() {
|
||||
return beanDescriptor.getSelectLastInsertedId(publish);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,18 +28,23 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
|
||||
private boolean addBatch;
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
private boolean forceNoBatch;
|
||||
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
|
||||
SpiTransaction t, PersistExecute persistExecute) {
|
||||
SpiTransaction t, PersistExecute persistExecute, boolean forceNoBatch) {
|
||||
|
||||
super(server, t, persistExecute, sqlUpdate.getLabel());
|
||||
this.type = Type.UPDATESQL;
|
||||
this.updateSql = sqlUpdate;
|
||||
this.forceNoBatch = forceNoBatch;
|
||||
updateSql.reset();
|
||||
}
|
||||
|
||||
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
|
||||
SpiTransaction t, PersistExecute persistExecute) {
|
||||
this(server, sqlUpdate, t, persistExecute, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void profile(long offset, int flushCount) {
|
||||
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
|
||||
@@ -60,7 +65,7 @@ public final class PersistRequestUpdateSql extends PersistRequest {
|
||||
|
||||
@Override
|
||||
public boolean isBatchThisRequest() {
|
||||
return addBatch || super.isBatchThisRequest();
|
||||
return !forceNoBatch && (addBatch || super.isBatchThisRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -74,10 +74,15 @@ public interface Persister {
|
||||
int executeOrmUpdate(Update<?> update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the UpdateSql.
|
||||
* Execute the SqlUpdate (taking into account transaction batch mode).
|
||||
*/
|
||||
int executeSqlUpdate(SqlUpdate update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the SqlUpdate now regardless of transaction batch mode.
|
||||
*/
|
||||
int executeSqlUpdateNow(SpiSqlUpdate update, Transaction t);
|
||||
|
||||
/**
|
||||
* Execute the CallableSql.
|
||||
*/
|
||||
|
||||
@@ -182,6 +182,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 +474,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 +3088,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
|
||||
|
||||
@@ -1387,9 +1387,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -318,11 +318,11 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
/**
|
||||
* Find the Id's of detail beans given a parent Id or list of parent Id's.
|
||||
*/
|
||||
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds) {
|
||||
public List<Object> findIdsByParentId(Object parentId, List<Object> parentIdList, Transaction t, List<Object> excludeDetailIds, boolean hard) {
|
||||
if (parentId != null) {
|
||||
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds);
|
||||
return sqlHelp.findIdsByParentId(parentId, t, excludeDetailIds, hard);
|
||||
} else {
|
||||
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds);
|
||||
return sqlHelp.findIdsByParentIdList(parentIdList, t, excludeDetailIds, hard);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,14 +127,16 @@ class BeanPropertyAssocManySqlHelp<T> {
|
||||
many.bindParentIdsIn(expr, parentIds, query);
|
||||
}
|
||||
|
||||
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds) {
|
||||
List<Object> findIdsByParentId(Object parentId, Transaction t, List<Object> excludeDetailIds, boolean hard) {
|
||||
|
||||
String rawWhere = deriveWhereParentIdSql(false, "");
|
||||
|
||||
SpiEbeanServer server = descriptor.getEbeanServer();
|
||||
SpiQuery<?> q = many.newQuery(server);
|
||||
many.bindParentIdEq(rawWhere, parentId, q);
|
||||
|
||||
if (hard) {
|
||||
q.setIncludeSoftDeletes();
|
||||
}
|
||||
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
|
||||
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
|
||||
}
|
||||
@@ -142,7 +144,7 @@ class BeanPropertyAssocManySqlHelp<T> {
|
||||
return server.findIds(q, t);
|
||||
}
|
||||
|
||||
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds) {
|
||||
List<Object> findIdsByParentIdList(List<Object> parentIds, Transaction t, List<Object> excludeDetailIds, boolean hard) {
|
||||
|
||||
String rawWhere = deriveWhereParentIdSql(true, "");
|
||||
String inClause = buildInClauseBinding(parentIds.size(), exportedPropertyBindProto);
|
||||
@@ -153,7 +155,9 @@ class BeanPropertyAssocManySqlHelp<T> {
|
||||
SpiQuery<?> q = many.newQuery(server);
|
||||
//Query<?> q = server.find(propertyType);
|
||||
many.bindParentIdsIn(expr, parentIds, q);
|
||||
|
||||
if (hard) {
|
||||
q.setIncludeSoftDeletes();
|
||||
}
|
||||
if (excludeDetailIds != null && !excludeDetailIds.isEmpty()) {
|
||||
q.where().not(q.getExpressionFactory().idIn(excludeDetailIds));
|
||||
}
|
||||
|
||||
@@ -126,6 +126,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.
|
||||
@@ -839,11 +840,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -229,6 +229,8 @@ public class AnnotationAssocOnes extends AnnotationParser {
|
||||
if (!"".equals(propAnn.mappedBy())) {
|
||||
prop.setOneToOneExported();
|
||||
prop.setOrphanRemoval(propAnn.orphanRemoval());
|
||||
} else if (propAnn.orphanRemoval()) {
|
||||
prop.setOrphanRemoval(true);
|
||||
}
|
||||
|
||||
setCascadeTypes(propAnn.cascade(), prop.getCascadeInfo());
|
||||
|
||||
@@ -137,7 +137,7 @@ public final class DefaultPersister implements Persister {
|
||||
try {
|
||||
return batchControl.execute(sqlUpdate.getSql(), sqlUpdate.isGetGeneratedKeys());
|
||||
} catch (SQLException e) {
|
||||
throw new PersistenceException(e);
|
||||
throw transaction.translate(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,11 @@ public final class DefaultPersister implements Persister {
|
||||
return executeOrQueue(new PersistRequestUpdateSql(server, (SpiSqlUpdate) updSql, (SpiTransaction) t, persistExecute));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeSqlUpdateNow(SpiSqlUpdate updSql, Transaction t) {
|
||||
return executeOrQueue(new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore draft beans to match live beans given the query.
|
||||
*/
|
||||
@@ -754,7 +759,7 @@ public final class DefaultPersister implements Persister {
|
||||
executeSqlUpdate(sqlDelete, t);
|
||||
} else {
|
||||
// we need to fetch the Id's to delete (recurse or notify L2 cache)
|
||||
List<Object> childIds = many.findIdsByParentId(id, idList, t, null);
|
||||
List<Object> childIds = many.findIdsByParentId(id, idList, t, null, deleteMode.isHard());
|
||||
if (!childIds.isEmpty()) {
|
||||
delete(targetDesc, null, childIds, t, deleteMode);
|
||||
}
|
||||
@@ -896,6 +901,11 @@ public final class DefaultPersister implements Persister {
|
||||
BeanDescriptor<?> desc = request.getBeanDescriptor();
|
||||
SpiTransaction t = request.getTransaction();
|
||||
|
||||
EntityBean orphanForRemoval = request.getImportedOrphanForRemoval();
|
||||
if (orphanForRemoval != null) {
|
||||
delete(orphanForRemoval, request.getTransaction(), true);
|
||||
}
|
||||
|
||||
// exported ones with cascade save
|
||||
for (BeanPropertyAssocOne<?> prop : desc.propertiesOneExportedSave()) {
|
||||
// check for partial beans
|
||||
@@ -1052,7 +1062,7 @@ public final class DefaultPersister implements Persister {
|
||||
} else {
|
||||
// Delete recurse using the Id values of the children
|
||||
Object parentId = desc.getId(parentBean);
|
||||
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds);
|
||||
List<Object> idsByParentId = many.findIdsByParentId(parentId, null, t, excludeDetailIds, deleteMode.isHard());
|
||||
if (!idsByParentId.isEmpty()) {
|
||||
deleteChildrenById(t, targetDesc, idsByParentId, deleteMode);
|
||||
}
|
||||
@@ -1092,6 +1102,10 @@ public final class DefaultPersister implements Persister {
|
||||
// imported ones with save cascade
|
||||
for (BeanPropertyAssocOne<?> prop : desc.propertiesOneImportedSave()) {
|
||||
// check for partial objects
|
||||
if (prop.isOrphanRemoval() && request.isDirtyProperty(prop)) {
|
||||
request.setImportedOrphanForRemoval(prop);
|
||||
}
|
||||
|
||||
if (request.isLoadedProperty(prop)) {
|
||||
EntityBean detailBean = prop.getValueAsEntityBean(request.getEntityBean());
|
||||
if (detailBean != null
|
||||
@@ -1108,14 +1122,18 @@ public final class DefaultPersister implements Persister {
|
||||
|
||||
for (BeanPropertyAssocOne<?> prop : desc.propertiesOneExportedSave()) {
|
||||
if (prop.isOrphanRemoval() && request.isDirtyProperty(prop)) {
|
||||
Object origValue = request.getOrigValue(prop);
|
||||
if (origValue instanceof EntityBean) {
|
||||
delete((EntityBean) origValue, request.getTransaction(), true);
|
||||
}
|
||||
deleteOrphan(request, prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteOrphan(PersistRequestBean<?> request, BeanPropertyAssocOne<?> prop) {
|
||||
Object origValue = request.getOrigValue(prop);
|
||||
if (origValue instanceof EntityBean) {
|
||||
delete((EntityBean) origValue, request.getTransaction(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Support for loading any Imported Associated One properties that are not
|
||||
* loaded but required for Delete cascade.
|
||||
|
||||
@@ -48,7 +48,7 @@ class ExeCallableSql {
|
||||
}
|
||||
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
throw request.translateSqlException(ex);
|
||||
|
||||
} finally {
|
||||
if (!batchThisRequest) {
|
||||
|
||||
@@ -55,8 +55,8 @@ class ExeUpdateSql {
|
||||
request.postExecute();
|
||||
return rowCount;
|
||||
}
|
||||
} catch (SQLException ex) {
|
||||
throw new PersistenceException(ex);
|
||||
} catch (SQLException e) {
|
||||
throw request.translateSqlException(e);
|
||||
|
||||
} finally {
|
||||
if (!batchThisRequest) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -138,11 +138,14 @@ class CQueryBuilder {
|
||||
// wrap as - delete from table where id in (select id ...)
|
||||
String sql = buildSql(null, request, predicates, sqlTree).getSql();
|
||||
sql = request.getBeanDescriptor().getDeleteByIdInSql() + "in (" + sql + ")";
|
||||
String alias = (rootTableAlias == null) ? "t0" : rootTableAlias;
|
||||
sql = aliasReplace(sql, alias);
|
||||
sql = aliasReplace(sql, alias(rootTableAlias));
|
||||
return sql;
|
||||
}
|
||||
|
||||
private String alias(String rootTableAlias) {
|
||||
return (rootTableAlias == null) ? "t0" : rootTableAlias;
|
||||
}
|
||||
|
||||
private <T> String buildUpdateSql(OrmQueryRequest<T> request, String rootTableAlias, CQueryPredicates predicates, SqlTree sqlTree) {
|
||||
|
||||
StringBuilder sb = new StringBuilder(200);
|
||||
@@ -160,8 +163,7 @@ class CQueryBuilder {
|
||||
// wrap as - update table set ... where id in (select id ...)
|
||||
String sql = buildSqlUpdate(null, request, predicates, sqlTree).getSql();
|
||||
sql = updateClause + " " + request.getBeanDescriptor().getWhereIdInSql() + "in (" + sql + ")";
|
||||
String alias = (rootTableAlias == null) ? "t0" : rootTableAlias;
|
||||
sql = aliasReplace(sql, alias);
|
||||
sql = aliasReplace(sql, alias(rootTableAlias));
|
||||
return sql;
|
||||
}
|
||||
|
||||
@@ -209,7 +211,12 @@ class CQueryBuilder {
|
||||
*/
|
||||
<T> CQueryFetchSingleAttribute buildFetchIdsQuery(OrmQueryRequest<T> request) {
|
||||
|
||||
request.getQuery().setSelectId();
|
||||
SpiQuery<T> query = request.getQuery();
|
||||
query.setSelectId();
|
||||
BeanDescriptor<T> desc = request.getBeanDescriptor();
|
||||
if (!query.isIncludeSoftDeletes() && desc.isSoftDelete()) {
|
||||
query.addSoftDeletePredicate(desc.getSoftDeletePredicate(alias(query.getAlias())));
|
||||
}
|
||||
return buildFetchAttributeQuery(request);
|
||||
}
|
||||
|
||||
@@ -217,7 +224,7 @@ class CQueryBuilder {
|
||||
* Return the history support if this query needs it (is a 'as of' type query).
|
||||
*/
|
||||
<T> CQueryHistorySupport getHistorySupport(SpiQuery<T> query) {
|
||||
return query.getTemporalMode() != SpiQuery.TemporalMode.CURRENT ? historySupport : null;
|
||||
return query.getTemporalMode().isHistory() ? historySupport : null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -148,7 +148,10 @@ class SqlTreeNodeBean implements SqlTreeNode {
|
||||
public ScalarType<?> getSingleAttributeScalarType() {
|
||||
if (properties == null || properties.length == 0) {
|
||||
// if we have no property ask first children (in a distinct select with join)
|
||||
// if we have also no children, NPE happens anyway.
|
||||
if (children.length == 0) {
|
||||
// expected to be a findIds query
|
||||
return desc.getIdBinder().getBeanProperty().getScalarType();
|
||||
}
|
||||
return children[0].getSingleAttributeScalarType();
|
||||
}
|
||||
if (properties[0] instanceof STreePropertyAssocOne) {
|
||||
|
||||
@@ -768,6 +768,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
copy.timeout = timeout;
|
||||
copy.mapKey = mapKey;
|
||||
copy.id = id;
|
||||
copy.label = label;
|
||||
copy.nativeSql = nativeSql;
|
||||
copy.useBeanCache = useBeanCache;
|
||||
copy.useQueryCache = useQueryCache;
|
||||
copy.readOnly = readOnly;
|
||||
@@ -1049,7 +1051,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
|
||||
CQueryPlanKey createQueryPlanKey() {
|
||||
|
||||
if (isNativeSql()) {
|
||||
queryPlanKey = new NativeSqlQueryPlanKey(nativeSql + "-" + firstRow + "-" + maxRows);
|
||||
queryPlanKey = new NativeSqlQueryPlanKey(type.ordinal() + nativeSql + "-" + firstRow + "-" + maxRows);
|
||||
} else {
|
||||
queryPlanKey = new OrmQueryPlanKey(planDescription(), maxRows, firstRow, rawSql);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,9 @@ create or replace function _partition_create(meta partition_meta, extra text)
|
||||
language plpgsql
|
||||
set timezone to 'UTC'
|
||||
as $$
|
||||
declare
|
||||
idx_col text;
|
||||
idx_name text;
|
||||
begin
|
||||
|
||||
execute format('create table if not exists %I partition of %I for values from (''%s'') TO (''%s'')', meta.part_name, meta.base_name, meta.period_start, meta.period_end);
|
||||
@@ -45,7 +48,12 @@ begin
|
||||
end if;
|
||||
|
||||
if (length(meta.index_column) > 0) then
|
||||
execute format('create index if not exists ix_%I_%s ON %I (%I)', meta.part_name, meta.index_column, meta.part_name, meta.index_column);
|
||||
-- delimited for multiple indexes
|
||||
foreach idx_col in array regexp_split_to_array(meta.index_column,';')
|
||||
loop
|
||||
idx_name = replace(idx_col, ',', '_');
|
||||
execute format('create index if not exists ix_%I_%s ON %I (%s)', meta.part_name, idx_name, meta.part_name, idx_col);
|
||||
end loop;
|
||||
end if;
|
||||
|
||||
if (length(extra) > 0) then
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebean.meta.MetaOrmQueryMetric;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Country;
|
||||
import org.tests.model.basic.Customer;
|
||||
import org.tests.model.basic.EBasicWithUniqueCon;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
@@ -42,6 +43,32 @@ public class UpdateQueryTest extends BaseTestCase {
|
||||
assertThat(ormQueryMetrics.get(0).getLabel()).isEqualTo("updateActive");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void update_withTransactionBatch() {
|
||||
|
||||
EbeanServer server = server();
|
||||
|
||||
try (Transaction transaction = server.beginTransaction()) {
|
||||
transaction.setBatchMode(true);
|
||||
|
||||
UpdateQuery<Customer> update = server.update(Customer.class);
|
||||
|
||||
Query<Customer> query = update
|
||||
.set("status", Customer.Status.ACTIVE)
|
||||
.set("updtime", new Timestamp(System.currentTimeMillis()))
|
||||
.where()
|
||||
.eq("status", Customer.Status.NEW)
|
||||
.gt("id", 99999)
|
||||
.query();
|
||||
|
||||
// update executes now regardless of transaction batch mode
|
||||
int rows = query.update();
|
||||
assertThat(rows).isEqualTo(0);
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@IgnorePlatform(Platform.SQLSERVER)
|
||||
public void withTableAlias() {
|
||||
@@ -199,4 +226,27 @@ public class UpdateQueryTest extends BaseTestCase {
|
||||
|
||||
assertThat(rows).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test(expected = DuplicateKeyException.class)
|
||||
public void exceptionTranslation() {
|
||||
|
||||
newEbasicWithUnique("o1","other1_a");
|
||||
Integer id = newEbasicWithUnique("o2", "other1_b");
|
||||
|
||||
Ebean.update(EBasicWithUniqueCon.class)
|
||||
.set("other", "other1_a")
|
||||
.set("otherOne", "other1_a")
|
||||
.where().idEq(id)
|
||||
.update();
|
||||
}
|
||||
|
||||
private Integer newEbasicWithUnique(String name, String other) {
|
||||
EBasicWithUniqueCon b0 = new EBasicWithUniqueCon();
|
||||
b0.setName(name);
|
||||
b0.setOther(other);
|
||||
b0.setOtherOne(other);
|
||||
Ebean.save(b0);
|
||||
|
||||
return b0.getId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
static class OtherH2Platform extends H2Platform {
|
||||
|
||||
OtherH2Platform() {
|
||||
super();
|
||||
this.platform = Platform.GENERIC;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -854,6 +854,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow(SpiSqlUpdate sqlUpdate) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -72,9 +72,9 @@ public class BeanPropertyAssocManyTest extends BaseTestCase {
|
||||
customerIds.add(1L);
|
||||
customerIds.add(2L);
|
||||
|
||||
List<Object> contactIdsForOne = contacts().findIdsByParentId(1L, null, null, null);
|
||||
List<Object> contactIdsForOne = contacts().findIdsByParentId(1L, null, null, null, true);
|
||||
|
||||
List<Object> contactIdsForMultiple = contacts().findIdsByParentId(null, customerIds, null, null);
|
||||
List<Object> contactIdsForMultiple = contacts().findIdsByParentId(null, customerIds, null, null, true);
|
||||
|
||||
assertThat(contactIdsForOne).isNotEmpty();
|
||||
assertThat(contactIdsForMultiple).isNotEmpty();
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.tests.cache;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import org.tests.model.basic.Country;
|
||||
import org.tests.model.basic.OCachedBean;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
@@ -41,4 +42,15 @@ public class TestBeanCache extends BaseTestCase {
|
||||
assertThat(sql).isEmpty();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void find_whenNotExits() {
|
||||
|
||||
Country country = Ebean.find(Country.class)
|
||||
.where()
|
||||
.eq("name","NotValid")
|
||||
.findOne();
|
||||
|
||||
assertThat(country).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -85,8 +85,8 @@ public class TestOnCascadeDeleteChildrenWithCompositeKeys extends BaseTestCase {
|
||||
ids.add(1L);
|
||||
ids.add(2L);
|
||||
|
||||
beanProperty.findIdsByParentId(null, ids, null, null);
|
||||
beanProperty.findIdsByParentId(1L, null, null, null);
|
||||
beanProperty.findIdsByParentId(null, ids, null, null, true);
|
||||
beanProperty.findIdsByParentId(1L, null, null, null, true);
|
||||
}
|
||||
|
||||
@Entity
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package org.tests.model.draftable;
|
||||
|
||||
import io.ebean.annotation.Draft;
|
||||
import io.ebean.annotation.Draftable;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
|
||||
@Entity
|
||||
@Draftable
|
||||
public class BasicDraftableBean extends BaseDomain {
|
||||
|
||||
private String name;
|
||||
|
||||
@Draft
|
||||
boolean draft;
|
||||
|
||||
public BasicDraftableBean(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.tests.model.history;
|
||||
|
||||
import io.ebean.annotation.History;
|
||||
import io.ebean.annotation.SoftDelete;
|
||||
import org.tests.model.draftable.BaseDomain;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
@History
|
||||
@Entity
|
||||
public class HsdSetting extends BaseDomain {
|
||||
|
||||
String key;
|
||||
String val;
|
||||
|
||||
@SoftDelete
|
||||
boolean deleted;
|
||||
|
||||
@OneToOne
|
||||
private HsdUser user;
|
||||
|
||||
|
||||
public HsdSetting(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public HsdSetting() {
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getVal() {
|
||||
return val;
|
||||
}
|
||||
|
||||
public void setVal(String val) {
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void setDeleted(boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
public HsdUser getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public void setUser(HsdUser user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.tests.model.history;
|
||||
|
||||
import io.ebean.annotation.History;
|
||||
import io.ebean.annotation.SoftDelete;
|
||||
import org.tests.model.draftable.BaseDomain;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
@History
|
||||
@Entity
|
||||
public class HsdUser extends BaseDomain {
|
||||
|
||||
String name;
|
||||
|
||||
@SoftDelete
|
||||
boolean deleted;
|
||||
|
||||
@OneToOne(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
|
||||
HsdSetting setting;
|
||||
|
||||
public HsdUser(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public HsdUser() {
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean isDeleted() {
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public void setDeleted(boolean deleted) {
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
public HsdSetting getSetting() {
|
||||
return setting;
|
||||
}
|
||||
|
||||
public void setSetting(HsdSetting setting) {
|
||||
this.setting = setting;
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public class TestHistoryExclude extends BaseTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSoftDelete() {
|
||||
public void testSoftDelete_includeSoftDeletes_findList() {
|
||||
|
||||
HeLink l = new HeLink("two", "boo");
|
||||
Ebean.save(l);
|
||||
@@ -44,6 +44,23 @@ public class TestHistoryExclude extends BaseTestCase {
|
||||
assertThat(list).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSoftDelete_includeSoftDeletes_findOne() {
|
||||
|
||||
HeLink l = new HeLink("three", "boo2");
|
||||
Ebean.save(l);
|
||||
|
||||
Ebean.delete(l);
|
||||
|
||||
HeLink found = Ebean.find(HeLink.class)
|
||||
.setId(l.getId())
|
||||
.setIncludeSoftDeletes()
|
||||
.findOne();
|
||||
|
||||
assertThat(found).isNotNull();
|
||||
assertThat(found.getName()).isEqualTo("three");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLazyLoad() {
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.tests.model.history;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestHistorySoftDeleteOneToOne extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void findOne() {
|
||||
|
||||
HsdUser u1 = new HsdUser("u1");
|
||||
|
||||
Ebean.save(u1);
|
||||
Ebean.delete(u1);
|
||||
|
||||
HsdUser one = Ebean.find(HsdUser.class)
|
||||
.setId(u1.getId())
|
||||
.setIncludeSoftDeletes()
|
||||
.findOne();
|
||||
|
||||
assertThat(one).isNotNull();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,38 @@ public class TestOneToOneOrphanStringId extends BaseTestCase {
|
||||
assertThat(deletes.get(0)).contains("delete from oto_atwo");
|
||||
assertThat(deletes.get(1)).contains("delete from oto_aone");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_remove() {
|
||||
|
||||
OtoAtwo b = new OtoAtwo("b3", "b test");
|
||||
Ebean.save(b);
|
||||
|
||||
OtoAone a = new OtoAone("a3", "a test");
|
||||
b.setAone(a);
|
||||
|
||||
Ebean.save(b);
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
OtoAone a2 = new OtoAone("a4", "a test");
|
||||
b.setAone(a2);
|
||||
Ebean.save(b);
|
||||
|
||||
List<String> sql = LoggedSqlCollector.current();
|
||||
assertThat(sql).hasSize(3);
|
||||
assertThat(sql.get(0)).contains("insert into oto_aone");
|
||||
assertThat(sql.get(1)).contains("update oto_atwo set aone_id=? where id=?");
|
||||
assertThat(sql.get(2)).contains("delete from oto_aone where id=?");
|
||||
|
||||
Ebean.delete(b);
|
||||
|
||||
List<String> deletes = LoggedSqlCollector.stop();
|
||||
assertThat(deletes).hasSize(2);
|
||||
assertThat(deletes.get(0)).contains("delete from oto_atwo");
|
||||
assertThat(deletes.get(1)).contains("delete from oto_aone");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.tests.query;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.PagedList;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.basic.Contact;
|
||||
import org.tests.model.basic.Customer;
|
||||
@@ -13,6 +16,114 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
public class TestQueryFindNative extends BaseTestCase {
|
||||
|
||||
|
||||
@Test
|
||||
public void findCount() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
String sql = "select n.id from contact n where n.first_name like ?";
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
int rowCount = server()
|
||||
.findNative(Contact.class, sql)
|
||||
.setParameter(1, "J%")
|
||||
.findCount();
|
||||
|
||||
List<Integer> nativeIds =
|
||||
server()
|
||||
.findNative(Contact.class, sql)
|
||||
.setParameter(1, "J%")
|
||||
.findIds();
|
||||
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
|
||||
assertThat(nativeIds).hasSize(rowCount);
|
||||
|
||||
assertThat(loggedSql).hasSize(2);
|
||||
assertThat(loggedSql.get(0)).contains("select count(*) from ( select n.id from contact n where n.first_name like ?)");
|
||||
assertThat(loggedSql.get(1)).startsWith("select n.id from contact n where n.first_name like ?");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findPagedList() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
String sql = "select n.id, n.first_name from contact n where n.first_name like ?";
|
||||
|
||||
PagedList<Contact> pagedList = server()
|
||||
.findNative(Contact.class, sql)
|
||||
.setParameter(1, "J%")
|
||||
.setMaxRows(100)
|
||||
.findPagedList();
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
int listSize = pagedList.getList().size();
|
||||
int totalCount = pagedList.getTotalCount();
|
||||
|
||||
List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
|
||||
assertThat(listSize).isEqualTo(totalCount);
|
||||
|
||||
assertThat(loggedSql).hasSize(2);
|
||||
assertThat(loggedSql.get(0)).startsWith("select n.id, n.first_name from contact n where n.first_name like ?");
|
||||
assertThat(loggedSql.get(1)).contains("select count(*) from ( select n.id, n.first_name from contact n where n.first_name like ?)");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void findPagedList_withColumnAlias() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
String sql = "select n.id, 'SillyName' first_name from contact n where n.id < ? ";
|
||||
|
||||
PagedList<Contact> pagedList = server()
|
||||
.findNative(Contact.class, sql)
|
||||
.setParameter(1, 100)
|
||||
.setMaxRows(100)
|
||||
.findPagedList();
|
||||
|
||||
int listSize = pagedList.getList().size();
|
||||
int totalCount = pagedList.getTotalCount();
|
||||
|
||||
assertThat(listSize).isEqualTo(totalCount);
|
||||
|
||||
for (Contact contact : pagedList.getList()) {
|
||||
assertThat(contact.getFirstName()).isEqualTo("SillyName");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void findIds() {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
String sql = "select c.id from contact c where c.first_name like ? ";
|
||||
|
||||
List<Integer> ids = Ebean.createSqlQuery(sql)
|
||||
.setParameter(1, "J%")
|
||||
.findSingleAttributeList(Integer.class);
|
||||
|
||||
List<Integer> idsScalar =
|
||||
server()
|
||||
.findNative(Contact.class, sql)
|
||||
.setParameter(1, "J%")
|
||||
.findSingleAttributeList();
|
||||
|
||||
List<Integer> nativeIds =
|
||||
server()
|
||||
.findNative(Contact.class, sql)
|
||||
.setParameter(1, "J%")
|
||||
.findIds();
|
||||
|
||||
|
||||
assertThat(nativeIds).isNotEmpty();
|
||||
assertThat(nativeIds).containsAll(ids);
|
||||
assertThat(idsScalar).containsAll(ids);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void joinFromManyToOne() {
|
||||
|
||||
|
||||
@@ -6,18 +6,83 @@ import io.ebean.Query;
|
||||
import io.ebean.SqlQuery;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.Transaction;
|
||||
|
||||
import org.tests.model.softdelete.EBasicSDChild;
|
||||
import org.tests.model.softdelete.EBasicSoftDelete;
|
||||
import org.ebeantest.LoggedSqlCollector;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.softdelete.EBasicSDChild;
|
||||
import org.tests.model.softdelete.EBasicSoftDelete;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TestSoftDeleteBasic extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void testCascadeSaveDelete_other() {
|
||||
|
||||
EBasicSoftDelete bean = new EBasicSoftDelete();
|
||||
bean.setName("cascadeOne");
|
||||
bean.addChild("child1", 10);
|
||||
bean.addChild("child2", 20);
|
||||
bean.addChild("child3", 30);
|
||||
bean.addNoSoftDeleteChild("nsd1", 101);
|
||||
bean.addNoSoftDeleteChild("nsd2", 102);
|
||||
|
||||
Ebean.save(bean);
|
||||
|
||||
assertEquals(new Long(1), bean.getVersion());
|
||||
assertEquals(new Long(1), bean.getChildren().get(0).getVersion());
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
Ebean.delete(bean);
|
||||
|
||||
// List<String> loggedSql = LoggedSqlCollector.stop();
|
||||
//
|
||||
// assertEquals(new Long(2), bean.getVersion());
|
||||
// assertEquals(new Long(2), bean.getChildren().get(0).getVersion()); // Fails with 1
|
||||
//
|
||||
// // The children without SoftDelete are left as is (so no third statement)
|
||||
// assertThat(loggedSql).hasSize(2);
|
||||
//
|
||||
// // first statement is a single bulk update of the children with SoftDelete
|
||||
// assertThat(loggedSql.get(0)).contains("update ebasic_sdchild set deleted=");
|
||||
// assertThat(loggedSql.get(0)).contains("where owner_id = ?");
|
||||
//
|
||||
// // second statement is the top level bean
|
||||
// assertThat(loggedSql.get(1)).contains(
|
||||
// "update ebasic_soft_delete set version=?, deleted=? where id=? and version=?");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindIdsWhenIncludeSoftDeletedChlld() {
|
||||
|
||||
EBasicSoftDelete bean = new EBasicSoftDelete();
|
||||
bean.setName("softDelChildren");
|
||||
bean.addChild("child1", 10);
|
||||
bean.addChild("child2", 20);
|
||||
bean.addChild("child3", 30);
|
||||
|
||||
Ebean.save(bean);
|
||||
Ebean.delete(bean.getChildren().get(0));
|
||||
|
||||
LoggedSqlCollector.start();
|
||||
|
||||
List<Object> ids = Ebean.find(EBasicSDChild.class).where().eq("owner", bean).findIds();
|
||||
assertThat(ids).hasSize(2);
|
||||
|
||||
List<EBasicSDChild> beans = Ebean.find(EBasicSDChild.class).where().eq("owner", bean).findList();
|
||||
assertThat(beans).hasSize(2);
|
||||
|
||||
List<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
assertThat(sql).hasSize(2);
|
||||
assertThat(sql.get(0)).contains("from ebasic_sdchild t0 where t0.owner_id = ? and t0.deleted = false");
|
||||
assertThat(sql.get(1)).contains("from ebasic_sdchild t0 where t0.owner_id = ? and t0.deleted = false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.tests.update;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.DuplicateKeyException;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class TestSqlUpdateExceptions extends BaseTestCase {
|
||||
|
||||
private String sql = "insert into uuone (id, name, version) values (?,?,?)";
|
||||
|
||||
@Test(expected = DuplicateKeyException.class)
|
||||
public void duplicateKey() {
|
||||
|
||||
UUID id = UUID.randomUUID();
|
||||
|
||||
SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql);
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "hi");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.execute();
|
||||
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "fail");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.execute();
|
||||
}
|
||||
|
||||
@Test(expected = DuplicateKeyException.class)
|
||||
public void duplicateKey_executeNow() {
|
||||
|
||||
|
||||
UUID id = UUID.randomUUID();
|
||||
|
||||
SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql);
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "hi");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.executeNow();
|
||||
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "fail");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.executeNow();
|
||||
}
|
||||
|
||||
@Test(expected = DuplicateKeyException.class)
|
||||
public void duplicateKey_inBatch() {
|
||||
|
||||
UUID id = UUID.randomUUID();
|
||||
|
||||
try (Transaction transaction = Ebean.beginTransaction()) {
|
||||
|
||||
SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql);
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "hi in batch");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.addBatch();
|
||||
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "fail in batch");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.addBatch();
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = DuplicateKeyException.class)
|
||||
public void duplicateKey_executeBatch() {
|
||||
|
||||
UUID id = UUID.randomUUID();
|
||||
|
||||
try (Transaction transaction = Ebean.beginTransaction()) {
|
||||
|
||||
SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql);
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "hi in batch");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.addBatch();
|
||||
|
||||
sqlUpdate.setParameter(1, id);
|
||||
sqlUpdate.setParameter(2, "fail in batch");
|
||||
sqlUpdate.setParameter(3, 1);
|
||||
sqlUpdate.addBatch();
|
||||
int[] ints = sqlUpdate.executeBatch();
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.tests.update;
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.Ebean;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.meta.MetaTimedMetric;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@@ -38,6 +39,41 @@ public class TestSqlUpdateInTxn extends BaseTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecute_inTransaction_withBatch() {
|
||||
|
||||
try (Transaction transaction = Ebean.beginTransaction()) {
|
||||
transaction.setBatchMode(true);
|
||||
|
||||
int row = Ebean.createSqlUpdate("update audit_log set description = description where id = ?")
|
||||
.setParameter(1, 999999)
|
||||
.execute();
|
||||
|
||||
// update statement using JDBC batch so not executed yet
|
||||
assertThat(row).isEqualTo(-1);
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExecuteNow_inTransaction_withBatch() {
|
||||
|
||||
try (Transaction transaction = Ebean.beginTransaction()) {
|
||||
transaction.setBatchMode(true);
|
||||
|
||||
int row = Ebean.createSqlUpdate("update audit_log set description = description where id = ?")
|
||||
.setParameter(1, 999999)
|
||||
.executeNow();
|
||||
|
||||
// update statement executed even though JDBC batch mode is on
|
||||
assertThat(row).isEqualTo(0);
|
||||
|
||||
transaction.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSqlUpdateWithWhitespace() {
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<ddl-script name="order views" platforms="h2" drop="true">
|
||||
drop view order_agg_vw if exists;
|
||||
</ddl-script>
|
||||
<ddl-script name="order views" platforms="generic,db2,h2,postgres,oracle,mysql">
|
||||
<ddl-script name="order views" platforms="db2,h2,postgres,oracle,mysql">
|
||||
|
||||
create or replace view order_agg_vw as
|
||||
select d.order_id, sum(d.order_qty * d.unit_price) as order_total,
|
||||
|
||||
Reference in New Issue
Block a user