From 14868f7d840ab65ee9ec855225a24197218a1e80 Mon Sep 17 00:00:00 2001 From: Noemi Szemenyei Date: Fri, 21 Jan 2022 14:30:06 +0100 Subject: [PATCH] Fix select distinct lob fields with db2 --- .../server/deploy/BeanProperty.java | 6 +- .../DynamicPropertyAggregationFormula.java | 5 ++ .../server/query/CQueryBuilder.java | 4 ++ .../server/query/STreeProperty.java | 5 ++ .../server/query/SqlTreeBuilder.java | 6 ++ .../server/query/SqlTreeNode.java | 5 ++ .../server/query/SqlTreeNodeBean.java | 35 +++++++++- .../src/test/resources/dbinit/db2/1.4.sql | 3 - .../migrationtest/db2/1.0__initial.sql | 3 - .../dbmigration/migrationtest/db2/1.1.sql | 5 -- .../dbmigration/migrationtest/db2/1.3.sql | 3 - .../migrationtest/db2/idx_db2.migrations | 6 +- .../java/org/tests/json/TestJsonMapBasic.java | 64 +++++++++++++------ 13 files changed, 112 insertions(+), 38 deletions(-) diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java index c0283097c..5eac01c20 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/BeanProperty.java @@ -1160,7 +1160,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty { public boolean isLob() { return lob; } - + + public boolean isDbLob() { + return lob || dbType == DbPlatformType.JSON && dbLength == 0; + } + public static boolean isLobType(int type) { switch (type) { case Types.CLOB: diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java index 970145632..ec1c7035b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/deploy/DynamicPropertyAggregationFormula.java @@ -62,4 +62,9 @@ class DynamicPropertyAggregationFormula extends DynamicPropertyBase { ctx.appendParseSelect(parsedFormula, alias); } + @Override + public boolean isDbLob() { + return false; + } + } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java index 278603f5b..6cf669f16 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/CQueryBuilder.java @@ -752,6 +752,10 @@ final class CQueryBuilder { return dbPlatform.isPlatform(Platform.POSTGRES); } + boolean isPlatformDistinctNoLobs() { + return dbPlatform.isPlatform(Platform.DB2); // CHECKME: Also oracle? + } + /** * Return the 'for update' FROM hint (sql server). */ diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java index 0981d5ac4..3bc4194b2 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/STreeProperty.java @@ -28,6 +28,11 @@ public interface STreeProperty extends ScalarDataReader { * Return true if the property is the Id. */ boolean isId(); + + /** + * Returns true, if this is a lob property. + */ + boolean isDbLob(); /** * Return true if the property is an embedded type. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 0ee056430..09d8959ee 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -35,6 +35,7 @@ public final class SqlTreeBuilder { private final CQueryPredicates predicates; private final boolean subQuery; private final boolean distinctOnPlatform; + private final boolean distinctNoLobs; /** * Property if resultSet contains master and detail rows. */ @@ -65,6 +66,7 @@ public final class SqlTreeBuilder { this.query = null; this.subQuery = false; this.distinctOnPlatform = false; + this.distinctNoLobs = false; this.queryDetail = queryDetail; this.predicates = predicates; this.temporalMode = SpiQuery.TemporalMode.CURRENT; @@ -97,6 +99,7 @@ public final class SqlTreeBuilder { this.predicates = predicates; this.alias = new SqlTreeAlias(request.baseTableAlias(), temporalMode); this.distinctOnPlatform = builder.isPlatformDistinctOn(); + this.distinctNoLobs = builder.isPlatformDistinctNoLobs(); String fromForUpdate = builder.fromForUpdate(query); CQueryHistorySupport historySupport = builder.getHistorySupport(query); CQueryDraftSupport draftSupport = builder.getDraftSupport(query); @@ -266,6 +269,9 @@ public final class SqlTreeBuilder { if (joinList != null) { joinList.add(selectNode); } + if (sqlDistinct && distinctNoLobs) { + selectNode.unselectLobs(); + } return selectNode; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java index 39844cb59..7930e968a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNode.java @@ -92,4 +92,9 @@ interface SqlTreeNode { * Add dependent tables to the given set. */ void dependentTables(Set tables); + + /** + * Unselect lobs (for distinct queries on DB2 and Oracle). + */ + default void unselectLobs() {}; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java index 547eededc..8198ae816 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeNodeBean.java @@ -4,7 +4,9 @@ import io.ebean.bean.BeanCollection; import io.ebean.bean.EntityBean; import io.ebean.bean.EntityBeanIntercept; import io.ebean.bean.PersistenceContext; +import io.ebean.core.type.DataReader; import io.ebean.core.type.ScalarDataReader; +import io.ebean.core.type.ScalarType; import io.ebean.util.SplitName; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuery.Mode; @@ -15,6 +17,7 @@ import io.ebeaninternal.server.deploy.TableJoin; import io.ebeaninternal.server.deploy.id.IdBinder; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -36,8 +39,8 @@ class SqlTreeNodeBean implements SqlTreeNode { /** * Set to true if this is a partial object fetch. */ - private final boolean partialObject; - private final STreeProperty[] properties; + private boolean partialObject; + private STreeProperty[] properties; /** * Extra where clause added by Where annotation on associated many. */ @@ -700,4 +703,32 @@ class SqlTreeNodeBean implements SqlTreeNode { } return false; } + + @Override + public void unselectLobs() { + if (children != null) { + for (SqlTreeNode child : children) { + child.unselectLobs(); + } + } + if (hasLob()) { + List lst = new ArrayList<>(); + for (STreeProperty prop : properties) { + if (!prop.isDbLob()) { + lst.add(prop); + } + } + properties = lst.toArray(new STreeProperty[0]); + partialObject = true; + } + } + + private boolean hasLob() { + for (STreeProperty prop : properties) { + if (prop.isDbLob()) { + return true; + } + } + return false; + } } diff --git a/ebean-ddl-generator/src/test/resources/dbinit/db2/1.4.sql b/ebean-ddl-generator/src/test/resources/dbinit/db2/1.4.sql index 5efada4cc..340fd3334 100644 --- a/ebean-ddl-generator/src/test/resources/dbinit/db2/1.4.sql +++ b/ebean-ddl-generator/src/test/resources/dbinit/db2/1.4.sql @@ -75,9 +75,7 @@ create table migtest_e_basic ( constraint ck_migtest_e_basic_status2 check ( status2 in ('N','A','I')), constraint pk_migtest_e_basic primary key (id) ); --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest2 on migtest_e_basic(indextest2) exclude null keys; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest6 on migtest_e_basic(indextest6) exclude null keys; create table migtest_e_enum ( @@ -131,7 +129,6 @@ create table migtest_e_ref ( name varchar(127) not null, constraint pk_migtest_e_ref primary key (id) ); --- alterTableAddUniqueConstraint alter table migtest_e_ref add constraint uq_migtest_e_ref_name unique (name); create table migtest_e_softdelete ( diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.0__initial.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.0__initial.sql index 6460708c9..5d4113a24 100644 --- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.0__initial.sql +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.0__initial.sql @@ -75,9 +75,7 @@ create table migtest_e_basic ( constraint ck_migtest_e_basic_status2 check ( status2 in ('N','A','I')), constraint pk_migtest_e_basic primary key (id) ); --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest2 on migtest_e_basic(indextest2) exclude null keys; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest6 on migtest_e_basic(indextest6) exclude null keys; create table migtest_e_enum ( @@ -131,7 +129,6 @@ create table migtest_e_ref ( name varchar(127) not null, constraint pk_migtest_e_ref primary key (id) ); --- alterTableAddUniqueConstraint alter table migtest_e_ref add constraint uq_migtest_e_ref_name unique (name); create table migtest_e_softdelete ( diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql index 143edc578..b68a9e881 100644 --- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.1.sql @@ -67,7 +67,6 @@ alter table migtest_e_basic alter column status2 drop not null; call sysproc.admin_cmd('reorg table migtest_e_basic') /* reorg #1 */; -- db2 does not support parial null indices :( - so we have to clean; update migtest_e_basic set status = 'N' where id = 1; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_description on migtest_e_basic(description) exclude null keys; insert into migtest_e_user (id) select distinct user_id from migtest_e_basic; @@ -111,13 +110,9 @@ if exists (select indname from syscat.indexes where indschema = current_schema a execute stmt; end if; end$$; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_status_indextest1 on migtest_e_basic(status,indextest1) exclude null keys; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_name on migtest_e_basic(name) exclude null keys; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest4 on migtest_e_basic(indextest4) exclude null keys; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest5 on migtest_e_basic(indextest5) exclude null keys; delimiter $$ begin diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql index 87fea5f81..4b5fdcf13 100644 --- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/1.3.sql @@ -5,7 +5,6 @@ create table migtest_e_ref ( name varchar(127) not null, constraint pk_migtest_e_ref primary key (id) ); --- alterTableAddUniqueConstraint alter table migtest_e_ref add constraint uq_migtest_e_ref_name unique (name); delimiter $$ @@ -156,9 +155,7 @@ if exists (select indname from syscat.indexes where indschema = current_schema a end if; end$$; call sysproc.admin_cmd('reorg table migtest_e_basic') /* reorg #3 */; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest2 on migtest_e_basic(indextest2) exclude null keys; --- alterTableAddUniqueConstraint create unique index uq_migtest_e_basic_indextest6 on migtest_e_basic(indextest6) exclude null keys; delimiter $$ begin diff --git a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations index 86c945d59..b743c8e07 100644 --- a/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations +++ b/ebean-ddl-generator/src/test/resources/dbmigration/migrationtest/db2/idx_db2.migrations @@ -1,6 +1,6 @@ -997100585, 1.0__initial.sql -855187652, 1.1.sql +1580812656, 1.0__initial.sql +312838996, 1.1.sql 1091886546, 1.2__dropsFor_1.1.sql --364921922, 1.3.sql +-1041850370, 1.3.sql 2020621716, 1.4__dropsFor_1.3.sql diff --git a/ebean-test/src/test/java/org/tests/json/TestJsonMapBasic.java b/ebean-test/src/test/java/org/tests/json/TestJsonMapBasic.java index e186de792..6e7fc481b 100644 --- a/ebean-test/src/test/java/org/tests/json/TestJsonMapBasic.java +++ b/ebean-test/src/test/java/org/tests/json/TestJsonMapBasic.java @@ -3,6 +3,8 @@ package org.tests.json; import io.ebean.BaseTestCase; import io.ebean.DB; import io.ebean.Query; +import io.ebean.annotation.ForPlatform; +import io.ebean.annotation.Platform; import io.ebean.test.LoggedSql; import io.ebean.text.json.EJson; import org.junit.jupiter.api.Test; @@ -10,6 +12,7 @@ import org.tests.model.json.EBasicJsonMap; import org.tests.model.json.EBasicJsonMapDetail; import java.io.IOException; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -19,12 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class TestJsonMapBasic extends BaseTestCase { @Test - public void whereManyPredicate() { - - if (!isPostgres()) { - // testing postgres specific select distinct on clause - return; - } + @ForPlatform(Platform.POSTGRES) + public void whereManyPredicatePg() { EBasicJsonMap bean = new EBasicJsonMap(); bean.setName("own1"); @@ -33,28 +32,60 @@ public class TestJsonMapBasic extends BaseTestCase { DB.save(bean); - Query query1 = DB.find(EBasicJsonMap.class) - .fetch("details") - .where().startsWith("details.name", "detail") - .query(); + Query query1 = DB.find(EBasicJsonMap.class).fetch("details").where() + .startsWith("details.name", "detail").query(); query1.findList(); assertThat(query1.getGeneratedSql()).contains("select distinct on (t0.id, t1.id) "); - Query query2 = DB.find(EBasicJsonMap.class) - .where().startsWith("details.name", "detail") - .query(); + Query query2 = DB.find(EBasicJsonMap.class).where().startsWith("details.name", "detail").query(); query2.findList(); assertThat(query2.getGeneratedSql()).contains("select distinct on (t0.id) "); } + @Test + @ForPlatform(Platform.DB2) + public void whereManyPredicateDb2() { + + EBasicJsonMap bean = new EBasicJsonMap(); + Map m1 = new HashMap<>(); + m1.put("foo", "bar"); + bean.setContent(m1); + bean.setName("own1"); + bean.getDetails().add(new EBasicJsonMapDetail("db2-detail1")); + bean.getDetails().add(new EBasicJsonMapDetail("db2-detail2")); + + DB.save(bean); + try { + Query query1 = DB.find(EBasicJsonMap.class).select("*").fetch("details").where() + .startsWith("details.name", "db2-detail").query(); + + List lst = query1.findList(); + + assertThat(query1.getGeneratedSql()).contains("select distinct t0.id, t0.name, t0.version,") + .doesNotContain("content"); + assertThat(lst).hasSize(1); + assertThat(lst.get(0).getContent()).containsEntry("foo", "bar"); + + Query query2 = DB.find(EBasicJsonMap.class).where().startsWith("details.name", "db2-detail") + .query(); + query2.findList(); + + assertThat(query2.getGeneratedSql()).contains("select distinct t0.id, t0.name, t0.version from"); + } finally { + // temporär hier, sollte die ganze Testklasse aufräumen + DB.delete(bean); + } + } + @Test public void testInsertUpdateDelete() throws IOException { String s0 = "{\"docId\":18,\"contentId\":\"asd\",\"active\":true,\"contentType\":\"pg-hello\",\"content\":{\"name\":\"rob\",\"age\":45}}"; - //String s1 = "{\"docId\":19,\"contentId\":\"asd\",\"active\":true,\"contentType\":\"pg-hello\",\"content\":{\"name\":\"rob\",\"age\":45}}"; + // String s1 = + // "{\"docId\":19,\"contentId\":\"asd\",\"active\":true,\"contentType\":\"pg-hello\",\"content\":{\"name\":\"rob\",\"age\":45}}"; Map content = EJson.parseObject(s0); @@ -117,10 +148,7 @@ public class TestJsonMapBasic extends BaseTestCase { LoggedSql.start(); - final int rows = DB.update(EBasicJsonMap.class) - .set("content", content1) - .where().eq("id", bean.getId()) - .update(); + final int rows = DB.update(EBasicJsonMap.class).set("content", content1).where().eq("id", bean.getId()).update(); final List sql = LoggedSql.stop();