From 7a8d1b85a9b941ae1fbe468caeefa914bed6c8a0 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Mon, 7 Nov 2016 21:57:03 +1300 Subject: [PATCH] #866 - Fix RawSql mapping such that it automatically maps foreign key columns --- .../server/query/CQueryBuilder.java | 16 ++++- .../server/query/CQueryPlanRawSql.java | 16 ++++- .../tests/rawsql/TestRawSqlMasterDetail.java | 69 ++++++++++++++++++- 3 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java index e83847952..fa039444b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java @@ -363,8 +363,15 @@ class CQueryBuilder { RawSql.ColumnMapping.Column column = it.next(); String propertyName = column.getPropertyName(); if (!RawSqlBuilder.IGNORE_COLUMN.equals(propertyName)) { - ElPropertyValue el = descriptor.getElGetValue(propertyName); + if (el == null && propertyName.endsWith("Id")) { + // try default naming convention for foreign key columns + String foreignIdPath = assocOneIdPath(propertyName); + el = descriptor.getElGetValue(foreignIdPath); + if (el != null) { + propertyName = foreignIdPath; + } + } if (el == null) { throw new PersistenceException("Property [" + propertyName + "] not found on " + descriptor.getFullName()); } else { @@ -404,6 +411,13 @@ class CQueryBuilder { return new SqlTreeBuilder(request, predicates, detail, rawNoId).build(); } + /** + * Return a path for a foreign key property using the default naming convention. + */ + private String assocOneIdPath(String propertyName) { + return propertyName.substring(0, propertyName.length() - 2) + ".id"; + } + /** * Return the SQL response with row limiting (when not an update statement). */ diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java index 11ce4a076..2f0ec4a51 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlanRawSql.java @@ -35,7 +35,13 @@ class CQueryPlanRawSql extends CQueryPlan { // set the resultSet index positions for the property expressions for (int i = 0; i < chain.size(); i++) { - indexPositions[i] = 1 + columnMapping.getIndexPosition(chain.get(i)); + String logicalPropertyPath = chain.get(i); + int mappedPosition = columnMapping.getIndexPosition(logicalPropertyPath); + if (mappedPosition == -1 && logicalPropertyPath.endsWith(".id")) { + // try a automatically mapped foreign key + mappedPosition = columnMapping.getIndexPosition(foreignKeyPath(logicalPropertyPath)); + } + indexPositions[i] = 1 + mappedPosition; } // check and handle the case where a discriminator column for @@ -51,4 +57,12 @@ class CQueryPlanRawSql extends CQueryPlan { return indexPositions; } + + /** + * Return the path for a foreign key column that was automatically mapped. + */ + private String foreignKeyPath(String logicalPropertyPath) { + // trim the .id and replace with Id ... to reverse the auto fk mapping earlier + return logicalPropertyPath.substring(0, logicalPropertyPath.length() - 3) + "Id"; + } } diff --git a/src/test/java/com/avaje/tests/rawsql/TestRawSqlMasterDetail.java b/src/test/java/com/avaje/tests/rawsql/TestRawSqlMasterDetail.java index 801fa3f80..1a1e7639f 100644 --- a/src/test/java/com/avaje/tests/rawsql/TestRawSqlMasterDetail.java +++ b/src/test/java/com/avaje/tests/rawsql/TestRawSqlMasterDetail.java @@ -4,6 +4,7 @@ import com.avaje.ebean.BaseTestCase; import com.avaje.ebean.Ebean; import com.avaje.ebean.RawSql; import com.avaje.ebean.RawSqlBuilder; +import com.avaje.tests.model.basic.Customer; import com.avaje.tests.model.basic.EBasic; import com.avaje.tests.model.basic.Order; import com.avaje.tests.model.basic.OrderDetail; @@ -13,7 +14,7 @@ import org.junit.Test; import java.util.List; import java.util.Random; -import static org.assertj.core.api.StrictAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -54,6 +55,72 @@ public class TestRawSqlMasterDetail extends BaseTestCase { } + @Test + public void testForeignKeyColumn() { + + ResetBasicData.reset(); + + // billing_address_id fk column is automatically + // mapped to the logical path: billingAddress.id + + String rs = "select c.id, c.name, c.billing_address_id, c.updtime "+ + "from o_customer c " + + "order by c.id"; + + RawSql rawSql = RawSqlBuilder.parse(rs).create(); + + List customers = Ebean.find(Customer.class) + .setRawSql(rawSql) + .findList(); + + assertThat(customers).isNotEmpty(); + } + + @Test + public void testSimpleNestedColumn() { + + ResetBasicData.reset(); + + + String rs = "select c.id, c.name, c.billing_address_id, ba.line_1, ba.city, c.updtime "+ + "from o_customer c " + + " left join o_address ba on ba.id = c.billing_address_id "+ + "order by c.id"; + + RawSql rawSql = RawSqlBuilder.parse(rs) + .tableAliasMapping("ba", "billingAddress") + .create(); + + List customers = Ebean.find(Customer.class) + .setRawSql(rawSql) + .findList(); + + assertThat(customers).isNotEmpty(); + } + + @Test + public void testDoubleJoinColumn() { + + ResetBasicData.reset(); + + String rs = "select c.id, c.name, c.billing_address_id, ba.line_1, ba.city, c.updtime, sa.id, sa.line_1, sa.city "+ + "from o_customer c " + + " left join o_address ba on ba.id = c.billing_address_id "+ + " left join o_address sa on sa.id = c.shipping_address_id "+ + "order by c.id"; + + RawSql rawSql = RawSqlBuilder.parse(rs) + .tableAliasMapping("ba", "billingAddress") + .tableAliasMapping("sa", "shippingAddress") + .create(); + + List customers = Ebean.find(Customer.class) + .setRawSql(rawSql) + .findList(); + + assertThat(customers).isNotEmpty(); + } + @Test public void testWithTableAliasMapping() {