beanType, String nativeSql) {
+ return serverMgr.getDefaultServer().findNative(beanType, nativeSql);
+ }
+
/**
* Create an Update query to perform a bulk update.
*
diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java
index 21f795437..8281d2813 100644
--- a/src/main/java/com/avaje/ebean/EbeanServer.java
+++ b/src/main/java/com/avaje/ebean/EbeanServer.java
@@ -298,6 +298,28 @@ public interface EbeanServer {
*/
Query find(Class beanType);
+ /**
+ * Create a query using native SQL.
+ *
+ * The native SQL can contain named parameters or positioned parameters.
+ *
+ * {@code
+ *
+ * String sql = "select c.id, c.name from customer c where c.name like ? order by c.name";
+ *
+ * Query query = ebeanServer.findNative(Customer.class, sql);
+ * query.setParameter(1, "Rob%");
+ *
+ * List customers = query.findList();
+ *
+ * }
+ *
+ * @param beanType The type of entity bean to fetch
+ * @param nativeSql The SQL that can contain named or positioned parameters
+ * @return The query to set parameters and execute
+ */
+ Query findNative(Class beanType, String nativeSql);
+
/**
* Return the next unique identity value for a given bean type.
*
diff --git a/src/main/java/com/avaje/ebean/Finder.java b/src/main/java/com/avaje/ebean/Finder.java
index 6cd7cf1bd..b904ec891 100644
--- a/src/main/java/com/avaje/ebean/Finder.java
+++ b/src/main/java/com/avaje/ebean/Finder.java
@@ -157,4 +157,10 @@ public class Finder {
return db().find(type);
}
+ /**
+ * Creates a native sql query.
+ */
+ public Query nativeSql(String nativeSql) {
+ return db().findNative(type, nativeSql);
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
index b4f9ca1af..0b0a57311 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiQuery.java
@@ -148,6 +148,16 @@ public interface SpiQuery extends Query {
*/
boolean isAutoTunable();
+ /**
+ * Return true if this is a native sql query.
+ */
+ boolean isNativeSql();
+
+ /**
+ * Return the unmodified native sql query (with named params etc).
+ */
+ String getNativeSql();
+
/**
* Return the bean descriptor for this query.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
index fbead895c..7c0a8d1ca 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultServer.java
@@ -897,6 +897,17 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return createQuery(beanType);
}
+ @Override
+ public Query findNative(Class beanType, String nativeSql) {
+ BeanDescriptor desc = getBeanDescriptor(beanType);
+ if (desc == null) {
+ throw new PersistenceException(beanType.getName() + " is NOT an Entity Bean registered with this server?");
+ }
+ DefaultOrmQuery query = new DefaultOrmQuery<>(desc, this, expressionFactory);
+ query.setNativeSql(nativeSql);
+ return query;
+ }
+
@Override
public Query createNamedQuery(Class beanType, String namedQuery) {
BeanDescriptor desc = getBeanDescriptor(beanType);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
index 8e9ac1e77..a17d0e5c3 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/OrmQueryRequest.java
@@ -53,8 +53,6 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
private final Boolean readOnly;
- private final RawSql rawSql;
-
private LoadContext loadContext;
private PersistenceContext persistenceContext;
@@ -73,7 +71,6 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery query, SpiTransaction t) {
super(server, t);
this.beanDescriptor = query.getBeanDescriptor();
- this.rawSql = query.getRawSql();
this.finder = beanDescriptor.getBeanFinder();
this.queryEngine = queryEngine;
this.query = query;
@@ -161,13 +158,17 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
this.queryPlanKey = query.prepare(this);
}
+ public boolean isNativeSql() {
+ return query.isNativeSql();
+ }
+
public boolean isRawSql() {
- return rawSql != null;
+ return query.isRawSql();
}
public DeployParser createDeployParser() {
- if (rawSql != null) {
- return new DeployPropertyParserMap(rawSql.getColumnMapping().getMapping());
+ if (query.isRawSql()) {
+ return new DeployPropertyParserMap(query.getRawSql().getColumnMapping().getMapping());
} else {
return beanDescriptor.createDeployPropertyParser();
}
@@ -531,4 +532,5 @@ public final class OrmQueryRequest extends BeanRequest implements BeanQueryRe
public void setDefaultFetchBuffer(int fetchSize) {
query.setDefaultFetchBuffer(fetchSize);
}
+
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
index 360a8e41a..de27672fe 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java
@@ -89,6 +89,7 @@ import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -204,6 +205,16 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
*/
protected final LinkedHashMap propMap;
+ /**
+ * Map of DB column to property path (for nativeSql mapping).
+ */
+ private final Map columnPath = new HashMap<>();
+
+ /**
+ * Map of related table to assoc property (for nativeSql mapping).
+ */
+ private final Map> tablePath = new HashMap<>();
+
/**
* The type of bean this describes.
*/
@@ -673,6 +684,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
if (!prop.isId()) {
prop.initialise();
}
+ prop.registerColumn(this, null);
}
}
@@ -699,6 +711,16 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
}
}
+ void registerColumn(String dbColumn, String path) {
+ columnPath.put(dbColumn.toLowerCase(), path);
+ }
+
+ void registerTable(String baseTable, BeanPropertyAssoc> assocProperty) {
+ if (baseTable != null) {
+ tablePath.put(baseTable.toLowerCase(), assocProperty);
+ }
+ }
+
/**
* Perform last initialisation for the descriptor.
*/
@@ -1081,7 +1103,7 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
* Prepare the query for multi-tenancy check for document store only use.
*/
public void prepareQuery(SpiQuery query) {
- if (tenant != null) {
+ if (tenant != null && !query.isNativeSql()) {
Object tenantId = ebeanServer.currentTenantId();
if (tenantId != null) {
query.where().eq(tenant.getName(), tenantId);
@@ -2163,6 +2185,23 @@ public class BeanDescriptor implements MetaBeanInfo, BeanType {
return chain.add(property).build();
}
+ /**
+ * Return the property path given the db table and column.
+ */
+ public String findBeanPath(String tableName, String columnName) {
+ if (tableName.length() == 0 || tableName.equalsIgnoreCase(baseTable)) {
+ return columnPath.get(columnName);
+ }
+ BeanPropertyAssoc> assocProperty = tablePath.get(tableName);
+ if (assocProperty != null) {
+ String relativePath = assocProperty.getTargetDescriptor().findBeanPath(tableName, columnName);
+ if (relativePath != null) {
+ return SplitName.add(assocProperty.getName(), relativePath);
+ }
+ }
+ return null;
+ }
+
/**
* Find a BeanProperty including searching the inheritance hierarchy.
*
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
index 00efd2115..a8eeb6889 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
@@ -19,6 +19,7 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter;
import com.avaje.ebeaninternal.server.properties.BeanPropertySetter;
+import com.avaje.ebeaninternal.server.query.SplitName;
import com.avaje.ebeaninternal.server.query.SqlBeanLoad;
import com.avaje.ebeaninternal.server.query.SqlJoinType;
import com.avaje.ebeaninternal.server.text.json.ReadJson;
@@ -1424,4 +1425,10 @@ public class BeanProperty implements ElPropertyValue, Property {
public void merge(EntityBean bean, EntityBean existing) {
// do nothing unless Many property
}
+
+ public void registerColumn(BeanDescriptor> desc, String prefix) {
+ if (dbColumn != null) {
+ desc.registerColumn(dbColumn, SplitName.add(prefix, name));
+ }
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java
index ef723bd65..fce69143b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java
@@ -275,6 +275,26 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc {
}
}
+ @Override
+ public void registerColumn(BeanDescriptor> desc, String prefix) {
+ if (embedded) {
+ for (BeanProperty prop : embeddedProps) {
+ prop.registerColumn(desc, SplitName.add(prefix, name));
+ }
+ } else {
+ if (targetIdProperty != null) {
+ BeanDescriptor target = getTargetDescriptor();
+ String basePath = SplitName.add(prefix, name);
+ if (dbColumn != null) {
+ BeanProperty idProperty = target.getIdProperty();
+ desc.registerColumn(dbColumn, SplitName.add(basePath, idProperty.getName()));
+ }
+
+ desc.registerTable(target.getBaseTable(), this);
+ }
+ }
+ }
+
/**
* Return meta data for the deployment of the embedded bean specific to this
* property.
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 39ac47505..22ecc40f8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryBuilder.java
@@ -24,6 +24,12 @@ import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryLimitRequest;
import javax.persistence.PersistenceException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -343,13 +349,58 @@ class CQueryBuilder {
*/
private SqlTree createSqlTree(OrmQueryRequest> request, CQueryPredicates predicates, CQueryHistorySupport historySupport, CQueryDraftSupport draftSupport) {
+ if (request.isNativeSql()) {
+ return createNativeSqlTree(request, predicates);
+ }
if (request.isRawSql()) {
return createRawSqlSqlTree(request, predicates);
}
-
return new SqlTreeBuilder(tableAliasPlaceHolder, columnAliasPrefix, request, predicates, historySupport, draftSupport).build();
}
+ /**
+ * Create the SqlTree by reading the ResultSetMetaData and mapping table/columns to bean property paths.
+ */
+ private SqlTree createNativeSqlTree(OrmQueryRequest> request, CQueryPredicates predicates) {
+
+ SpiQuery> query = request.getQuery();
+
+ // parse named parameters returning the final sql to execute
+ String sql = predicates.parseBindParams(query.getNativeSql());
+ query.setGeneratedSql(sql);
+
+ Connection connection = request.getTransaction().getConnection();
+
+ BeanDescriptor> desc = request.getBeanDescriptor();
+ try {
+ PreparedStatement statement = connection.prepareStatement(sql);
+ predicates.bind(statement, connection);
+
+ List propertyNames = new ArrayList<>();
+
+ ResultSet resultSet = statement.executeQuery();
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ int cols = 1 + metaData.getColumnCount();
+ for (int i = 1; i < cols; i++) {
+ String tableName = metaData.getTableName(i).toLowerCase();
+ String columnName = metaData.getColumnName(i).toLowerCase();
+ String path = desc.findBeanPath(tableName, columnName);
+ if (path != null) {
+ propertyNames.add(path);
+ } else {
+ propertyNames.add(RawSqlBuilder.IGNORE_COLUMN);
+ }
+ }
+
+ RawSql rawSql = RawSqlBuilder.resultSet(resultSet, propertyNames.toArray(new String[propertyNames.size()]));
+ query.setRawSql(rawSql);
+ return createRawSqlSqlTree(request, predicates);
+
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private SqlTree createRawSqlSqlTree(OrmQueryRequest> request, CQueryPredicates predicates) {
BeanDescriptor> descriptor = request.getBeanDescriptor();
@@ -376,8 +427,12 @@ class CQueryBuilder {
throw new PersistenceException("Property [" + propertyName + "] not found on " + descriptor.getFullName());
} else {
BeanProperty beanProperty = el.getBeanProperty();
- if (beanProperty.isId() || beanProperty.isDiscriminator()) {
- // For @Id properties we chop off the last part of the path
+ if (beanProperty.isId()) {
+ if (propertyName.contains(".")) {
+ // For @Id properties we chop off the last part of the path
+ propertyName = SplitName.parent(propertyName);
+ }
+ } else if (beanProperty.isDiscriminator()) {
propertyName = SplitName.parent(propertyName);
} else if (beanProperty instanceof BeanPropertyAssocOne>) {
String msg = "Column [" + column.getDbColumn() + "] mapped to complex Property[" + propertyName + "]";
@@ -436,9 +491,11 @@ class CQueryBuilder {
SpiQuery> query = request.getQuery();
- RawSql rawSql = query.getRawSql();
- if (rawSql != null) {
- return rawSqlHandler.buildSql(request, predicates, rawSql.getSql());
+ if (query.isNativeSql()) {
+ return new SqlLimitResponse(query.getGeneratedSql(), false);
+ }
+ if (query.isRawSql()) {
+ return rawSqlHandler.buildSql(request, predicates, query.getRawSql().getSql());
}
BeanPropertyAssocMany> manyProp = select.getManyProperty();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java
index dcd59eaa3..f56363322 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java
@@ -136,16 +136,19 @@ public class CQueryPredicates {
dataBind.append(", ");
}
- int asOfTableCount = request.getQueryPlan().getAsOfTableCount();
- if (asOfTableCount > 0) {
- // bind the asOf value for each table alias as part of the from/join clauses
- // there is one effective date predicate per table alias
- Timestamp asOf = query.getAsOf();
- dataBind.append("asOf ").append(asOf);
- for (int i = 0; i < asOfTableCount * binder.getAsOfBindCount(); i++) {
- binder.bindObject(dataBind, asOf);
+ CQueryPlan queryPlan = request.getQueryPlan();
+ if (queryPlan != null) {
+ int asOfTableCount = queryPlan.getAsOfTableCount();
+ if (asOfTableCount > 0) {
+ // bind the asOf value for each table alias as part of the from/join clauses
+ // there is one effective date predicate per table alias
+ Timestamp asOf = query.getAsOf();
+ dataBind.append("asOf ").append(asOf);
+ for (int i = 0; i < asOfTableCount * binder.getAsOfBindCount(); i++) {
+ binder.bindObject(dataBind, asOf);
+ }
+ dataBind.append(", ");
}
- dataBind.append(", ");
}
if (idValue != null) {
@@ -184,18 +187,33 @@ public class CQueryPredicates {
}
}
+ public String parseBindParams(String sql) {
+ if (bindParams != null && bindParams.requiresNamedParamsPrepare()) {
+ return BindParamsParser.parse(bindParams, sql);
+ } else {
+ return sql;
+ }
+ }
+
/**
* Convert named parameters into an OrderedList.
*/
private void buildBindWhereRawSql(boolean buildSql) {
- if (!buildSql && query.isRawSql() && bindParams != null && bindParams.requiresNamedParamsPrepare()) {
- // RawSql query hit cached query plan. Need to convert
- // named parameters into positioned parameters so that
- // the named parameters are bound
- RawSql.Sql sql = query.getRawSql().getSql();
- String s = sql.isParsed() ? sql.getPreWhere() : sql.getUnparsedSql();
- BindParamsParser.parse(bindParams, s);
+ if (!buildSql && bindParams != null && bindParams.requiresNamedParamsPrepare()) {
+ if (query.isNativeSql()) {
+ // convert named params into positioned params
+ String sql = query.getNativeSql();
+ BindParamsParser.parse(bindParams, sql);
+
+ } else if (query.isRawSql()) {
+ // RawSql query hit cached query plan. Need to convert
+ // named parameters into positioned parameters so that
+ // the named parameters are bound
+ RawSql.Sql sql = query.getRawSql().getSql();
+ String s = sql.isParsed() ? sql.getPreWhere() : sql.getUnparsedSql();
+ BindParamsParser.parse(bindParams, s);
+ }
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/NativeSqlQueryPlanKey.java b/src/main/java/com/avaje/ebeaninternal/server/query/NativeSqlQueryPlanKey.java
new file mode 100644
index 000000000..b43fae81d
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/NativeSqlQueryPlanKey.java
@@ -0,0 +1,42 @@
+package com.avaje.ebeaninternal.server.query;
+
+import com.avaje.ebeaninternal.api.CQueryPlanKey;
+
+/**
+ * QueryPlanKey for native sql queries.
+ */
+public class NativeSqlQueryPlanKey implements CQueryPlanKey {
+
+ private final String sql;
+
+ public NativeSqlQueryPlanKey(String sql) {
+ this.sql = sql;
+ }
+
+ public String toString() {
+ return getPartialKey();
+ }
+
+ /**
+ * Return as a partial key. For rawSql hash the sql is part of the key and as such
+ * needs to be included in order to have a complete key. Typically the MD5 of the sql
+ * can be used as a short form proxy for the actual sql.
+ */
+ public String getPartialKey() {
+ return hashCode() + "_n";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NativeSqlQueryPlanKey that = (NativeSqlQueryPlanKey) o;
+ return sql.equals(that.sql);
+ }
+
+ @Override
+ public int hashCode() {
+ return sql.hashCode();
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index c6cac7176..c8b0b4dad 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -26,6 +26,7 @@ import com.avaje.ebeaninternal.server.deploy.TableJoin;
import com.avaje.ebeaninternal.server.expression.DefaultExpressionList;
import com.avaje.ebeaninternal.server.expression.SimpleExpression;
import com.avaje.ebeaninternal.server.query.CancelableQuery;
+import com.avaje.ebeaninternal.server.query.NativeSqlQueryPlanKey;
import java.sql.Timestamp;
import java.util.ArrayList;
@@ -234,6 +235,8 @@ public class DefaultOrmQuery implements SpiQuery {
private OrmUpdateProperties updateProperties;
+ private String nativeSql;
+
public DefaultOrmQuery(BeanDescriptor desc, EbeanServer server, ExpressionFactory expressionFactory) {
this.beanDescriptor = desc;
this.beanType = desc.getBeanType();
@@ -242,6 +245,10 @@ public class DefaultOrmQuery implements SpiQuery {
this.detail = new OrmQueryDetail();
}
+ public void setNativeSql(String nativeSql) {
+ this.nativeSql = nativeSql;
+ }
+
@Override
public BeanDescriptor getBeanDescriptor() {
return beanDescriptor;
@@ -259,7 +266,7 @@ public class DefaultOrmQuery implements SpiQuery {
@Override
public boolean isAutoTunable() {
- return beanDescriptor.isAutoTunable();
+ return nativeSql == null && beanDescriptor.isAutoTunable();
}
@Override
@@ -901,14 +908,27 @@ public class DefaultOrmQuery implements SpiQuery {
*/
CQueryPlanKey createQueryPlanKey() {
- queryPlanKey = new OrmQueryPlanKey(m2mIncludeJoin, type, detail, maxRows, firstRow,
- disableLazyLoading, orderBy,
- distinct, sqlDistinct, mapKey, id, bindParams, whereExpressions, havingExpressions,
- temporalMode, forUpdate, rootTableAlias, rawSql, updateProperties);
-
+ if (isNativeSql()) {
+ queryPlanKey = new NativeSqlQueryPlanKey(nativeSql);
+ } else {
+ queryPlanKey = new OrmQueryPlanKey(m2mIncludeJoin, type, detail, maxRows, firstRow,
+ disableLazyLoading, orderBy,
+ distinct, sqlDistinct, mapKey, id, bindParams, whereExpressions, havingExpressions,
+ temporalMode, forUpdate, rootTableAlias, rawSql, updateProperties);
+ }
return queryPlanKey;
}
+ @Override
+ public boolean isNativeSql() {
+ return nativeSql != null;
+ }
+
+ @Override
+ public String getNativeSql() {
+ return nativeSql;
+ }
+
/**
* Prepare the query which prepares any expressions (sub-query expressions etc) and calculates the query plan key.
*/
diff --git a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
index 91a381297..af816ab28 100644
--- a/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
+++ b/src/test/java/com/avaje/ebeaninternal/api/TDSpiEbeanServer.java
@@ -314,6 +314,11 @@ public class TDSpiEbeanServer implements SpiEbeanServer {
return null;
}
+ @Override
+ public Query findNative(Class beanType, String nativeSql) {
+ return null;
+ }
+
@Override
public Set validateQuery(Query query) {
return null;
diff --git a/src/test/java/com/avaje/tests/model/basic/finder/CustomerFinder.java b/src/test/java/com/avaje/tests/model/basic/finder/CustomerFinder.java
index 9554b865d..41aeed436 100644
--- a/src/test/java/com/avaje/tests/model/basic/finder/CustomerFinder.java
+++ b/src/test/java/com/avaje/tests/model/basic/finder/CustomerFinder.java
@@ -4,6 +4,8 @@ import com.avaje.ebean.Finder;
import com.avaje.tests.model.basic.Customer;
import org.jetbrains.annotations.Nullable;
+import java.util.List;
+
/**
* Finder methods for Customer.
*/
@@ -25,4 +27,11 @@ public class CustomerFinder extends Finder {
return query().where().eq("name", name).findUnique();
}
+
+ public List namesStartingWith(String name) {
+
+ return nativeSql("select name from o_customer where name like ? order by name")
+ .setParameter(1, name+"%")
+ .findSingleAttributeList();
+ }
}
diff --git a/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java b/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java
index d956b9030..7170dc250 100644
--- a/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java
+++ b/src/test/java/com/avaje/tests/query/finder/TestCustomerFinder.java
@@ -8,7 +8,7 @@ import org.junit.Test;
import java.util.List;
-import static org.assertj.core.api.StrictAssertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
public class TestCustomerFinder extends BaseTestCase {
@@ -62,4 +62,12 @@ public class TestCustomerFinder extends BaseTestCase {
assertThat(notThere).isNull();
}
+ @Test
+ public void test_nativeSingleAttribute() {
+
+ ResetBasicData.reset();
+
+ List names = Customer.find.namesStartingWith("F");
+ assertThat(names).isNotEmpty();
+ }
}
diff --git a/src/test/java/com/avaje/tests/rawsql/nativesql/TestNativeSqlBasic.java b/src/test/java/com/avaje/tests/rawsql/nativesql/TestNativeSqlBasic.java
new file mode 100644
index 000000000..9e2f23ac8
--- /dev/null
+++ b/src/test/java/com/avaje/tests/rawsql/nativesql/TestNativeSqlBasic.java
@@ -0,0 +1,159 @@
+package com.avaje.tests.rawsql.nativesql;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.BeanState;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.EbeanServer;
+import com.avaje.ebean.Query;
+import com.avaje.tests.model.basic.Customer;
+import com.avaje.tests.model.basic.Order;
+import com.avaje.tests.model.basic.ResetBasicData;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class TestNativeSqlBasic extends BaseTestCase {
+
+ @Test
+ public void noBindParams() {
+
+ ResetBasicData.reset();
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ String nativeSql = "select id, name from o_customer";
+
+ Query query = server.findNative(Customer.class, nativeSql);
+
+ List customers = query.findList();
+ assertThat(customers).isNotEmpty();
+
+ BeanState beanState = server.getBeanState(customers.get(0));
+ assertThat(beanState.getLoadedProps()).contains("id", "name");
+ }
+
+ @Test
+ public void selectAll() {
+
+ ResetBasicData.reset();
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ String nativeSql = "select * from o_customer";
+ Query query = server.findNative(Customer.class, nativeSql);
+
+ List customers = query.findList();
+ assertThat(customers).isNotEmpty();
+
+ BeanState beanState = server.getBeanState(customers.get(0));
+ assertThat(beanState.getLoadedProps().size()).isGreaterThan(10);
+ }
+
+ @Test
+ public void selectWithAssocOne() {
+
+ ResetBasicData.reset();
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ String nativeSql = "select c.*, b.city from o_customer c join o_address b on b.id = c.billing_address_id";
+ Query query = server.findNative(Customer.class, nativeSql);
+
+ List customers = query.findList();
+ assertThat(customers).isNotEmpty();
+ }
+
+ @Test
+ public void namedParam() {
+
+ ResetBasicData.reset();
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ String nativeSql = "select id, name from o_customer where id > :some";
+
+ Query query = server.findNative(Customer.class, nativeSql);
+ query.setParameter("some", 1);
+
+ List customers = query.findList();
+ assertThat(customers).isNotEmpty();
+
+ Query query2 = server.findNative(Customer.class, nativeSql);
+ query2.setParameter("some", 2);
+
+ List customers2 = query2.findList();
+ assertThat(customers2).isNotEmpty();
+ }
+
+ @Test
+ public void partialAndLazyLoad() {
+
+ ResetBasicData.reset();
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ String nativeSql = "select id, name from o_customer where id > ?";
+
+ List customers = server.findNative(Customer.class, nativeSql)
+ .setParameter(1, 1)
+ .findList();
+
+ for (Customer customer : customers) {
+ customer.getStatus();
+ }
+ }
+
+ @Test
+ public void partialAssoc() {
+
+ ResetBasicData.reset();
+
+ EbeanServer server = Ebean.getDefaultServer();
+
+ String nativeSql = "select o.id, o.status, c.id, c.name, c.version from o_order o join o_customer c on c.id = o.kcustomer_id ";
+
+ List orders = server.findNative(Order.class, nativeSql)
+ .findList();
+
+ for (Order order : orders) {
+ order.getStatus();
+ order.getCustomer().getName();
+ }
+ }
+
+ @Test
+ public void fetchQuery() {
+
+ ResetBasicData.reset();
+
+ String nativeSql = "select * from o_customer where id > ?";
+ List customers =
+ Ebean.findNative(Customer.class, nativeSql)
+ .setParameter(1, 1)
+ .fetchQuery("contacts")
+ .findList();
+
+ assertThat(customers).isNotEmpty();
+
+ BeanState beanState = Ebean.getBeanState(customers.get(0));
+ assertThat(beanState.getLoadedProps().size()).isGreaterThan(10);
+ }
+
+ @Test
+ public void columnAlias() {
+
+ ResetBasicData.reset();
+
+ String nativeSql = "select c.id, 'foo' as name from o_customer c";
+ List customers =
+ Ebean.findNative(Customer.class, nativeSql)
+ .findList();
+
+ assertThat(customers).isNotEmpty();
+
+ BeanState beanState = Ebean.getBeanState(customers.get(0));
+ assertThat(beanState.getLoadedProps()).contains("id", "name");
+ }
+}