mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Add InTuples expression for multi-column IN expression
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
final class DInTuples implements InTuples {
|
||||
|
||||
private final String[] properties;
|
||||
private final List<Entry> entries = new ArrayList<>();
|
||||
|
||||
|
||||
DInTuples(String[] properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
@Override
|
||||
public InTuples add(Object... values) {
|
||||
entries.add(new DEntry(values));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first property name.
|
||||
*/
|
||||
@Override
|
||||
public String[] properties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the value pairs.
|
||||
*/
|
||||
@Override
|
||||
public List<Entry> entries() {
|
||||
return Collections.unmodifiableList(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* A pair of 2 value objects.
|
||||
* <p>
|
||||
* Used to support inPairs() expression.
|
||||
*/
|
||||
static final class DEntry implements InTuples.Entry {
|
||||
|
||||
private final Object[] vals;
|
||||
|
||||
DEntry(Object[] vals) {
|
||||
this.vals = vals;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] values() {
|
||||
return vals;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,6 +369,11 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression inPairs(Pairs pairs);
|
||||
|
||||
/**
|
||||
* In expression using multiple columns.
|
||||
*/
|
||||
Expression inTuples(InTuples pairs);
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*/
|
||||
|
||||
@@ -1131,6 +1131,11 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> inPairs(Pairs pairs);
|
||||
|
||||
/**
|
||||
* In expression using multiple columns.
|
||||
*/
|
||||
ExpressionList<T> inTuples(InTuples pairs);
|
||||
|
||||
/**
|
||||
* EXISTS a raw SQL SubQuery.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package io.ebean;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IN expression using multiple columns.
|
||||
* <p>
|
||||
* Produces SQL expression in the form of (A,B,C) IN ((a0,b0,c0), (a1,b1,c1), ... )
|
||||
* where A,B,C are the properties in the tuples.
|
||||
*/
|
||||
public interface InTuples {
|
||||
|
||||
/**
|
||||
* Create given the properties in the tuples.
|
||||
*/
|
||||
static InTuples of(String... properties) {
|
||||
return new DInTuples(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create given the properties in the tuples.
|
||||
*/
|
||||
static InTuples of(Query.Property<?>... properties) {
|
||||
String[] props = new String[properties.length];
|
||||
for (int i = 0; i < properties.length; i++) {
|
||||
props[i] = properties[i].toString();
|
||||
}
|
||||
return new DInTuples(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tuple entry.
|
||||
*/
|
||||
InTuples add(Object... values);
|
||||
|
||||
/**
|
||||
* Return the properties of the tuples.
|
||||
*/
|
||||
String[] properties();
|
||||
|
||||
/**
|
||||
* Return all the tuple entries.
|
||||
*/
|
||||
List<Entry> entries();
|
||||
|
||||
/**
|
||||
* A tuple entry.
|
||||
*/
|
||||
interface Entry {
|
||||
|
||||
/**
|
||||
* Return all the values for this entry.
|
||||
*/
|
||||
Object[] values();
|
||||
}
|
||||
}
|
||||
+5
@@ -471,6 +471,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
|
||||
return new InPairsExpression(pairs, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression inTuples(InTuples pairs) {
|
||||
return new InTuplesExpression(pairs, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* In - property has a value in the array of values.
|
||||
*/
|
||||
|
||||
@@ -902,6 +902,10 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return add(expr.inPairs(pairs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> inTuples(InTuples pairs) {
|
||||
return add(expr.inTuples(pairs));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> exists(String sqlSubQuery, Object... bindValues) {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package io.ebeaninternal.server.expression;
|
||||
|
||||
import io.ebean.InTuples;
|
||||
import io.ebeaninternal.api.BindValuesKey;
|
||||
import io.ebeaninternal.api.NaturalKeyQueryData;
|
||||
import io.ebeaninternal.api.SpiExpression;
|
||||
import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
final class InTuplesExpression extends AbstractExpression {
|
||||
|
||||
private final boolean not;
|
||||
private final String[] properties;
|
||||
private final List<InTuples.Entry> entries;
|
||||
|
||||
InTuplesExpression(InTuples pairs, boolean not) {
|
||||
super(pairs.properties()[0]);
|
||||
this.properties = pairs.properties();
|
||||
// the entries might be modified on cache hit.
|
||||
this.entries = pairs.entries();
|
||||
this.not = not;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean naturalKey(NaturalKeyQueryData<?> data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeDocQuery(DocQueryContext context) {
|
||||
throw new RuntimeException("Not supported with document query");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addBindValues(SpiExpressionRequest request) {
|
||||
for (InTuples.Entry entry : entries) {
|
||||
for (Object val : entry.values()) {
|
||||
request.addBindValue(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSql(SpiExpressionRequest request) {
|
||||
if (entries.isEmpty()) {
|
||||
request.append(not ? SQL_TRUE : SQL_FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
request.append("(");
|
||||
for (int i = 0; i < properties.length; i++) {
|
||||
if (i > 0) {
|
||||
request.append(",");
|
||||
}
|
||||
request.property(properties[i]);
|
||||
}
|
||||
request.append(") in (");
|
||||
final String eb = entryBinding();
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
if (i > 0) {
|
||||
request.append(",");
|
||||
}
|
||||
request.append(eb);
|
||||
}
|
||||
request.append(")");
|
||||
}
|
||||
|
||||
private String entryBinding() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('(');
|
||||
for (int i = 0; i < properties.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append('?');
|
||||
}
|
||||
return sb.append(')').toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the number of values in the in clause.
|
||||
*/
|
||||
@Override
|
||||
public void queryPlanHash(StringBuilder builder) {
|
||||
if (not) {
|
||||
builder.append("Not");
|
||||
}
|
||||
builder.append("InTuple[");
|
||||
for (String property : properties) {
|
||||
builder.append(property).append("-");
|
||||
}
|
||||
builder.append(entries.size()).append("]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryBindKey(BindValuesKey key) {
|
||||
key.add(entries.size());
|
||||
for (InTuples.Entry entry : entries) {
|
||||
for (Object val : entry.values()) {
|
||||
key.add(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameByBind(SpiExpression other) {
|
||||
InTuplesExpression that = (InTuplesExpression) other;
|
||||
return this.entries.size() == that.entries.size() && entries.equals(that.entries);
|
||||
}
|
||||
}
|
||||
@@ -710,6 +710,11 @@ final class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expr
|
||||
return exprList.inPairs(pairs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> inTuples(InTuples pairs) {
|
||||
return exprList.inTuples(pairs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> in(String propertyName, Collection<?> values) {
|
||||
return exprList.in(propertyName, values);
|
||||
|
||||
@@ -1219,6 +1219,14 @@ public abstract class TQRootBean<T, R> {
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* In expression using multiple columns.
|
||||
*/
|
||||
public R inTuples(InTuples inTuples) {
|
||||
peekExprList().inTuples(inTuples);
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker that can be used to indicate that the order by clause is defined after this.
|
||||
* <p>
|
||||
|
||||
@@ -1,12 +1,35 @@
|
||||
package org.querytest;
|
||||
|
||||
import io.ebean.InTuples;
|
||||
import org.example.domain.query.QContact;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class QContactTest {
|
||||
|
||||
@Test
|
||||
void findInTuples() {
|
||||
|
||||
var c = QContact.alias();
|
||||
|
||||
InTuples inTuples = InTuples.of(c.firstName, c.lastName)
|
||||
.add("Rob", "B")
|
||||
.add("Bob", "C")
|
||||
.add("Mob", "D");
|
||||
|
||||
var query = new QContact()
|
||||
.firstName.isNotNull()
|
||||
.email.isNotNull()
|
||||
.inTuples(inTuples)
|
||||
.query();
|
||||
|
||||
query.findList();
|
||||
assertThat(query.getGeneratedSql()).contains("(t0.first_name,t0.last_name) in ((?,?),(?,?),(?,?))");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_oneToManyMap() {
|
||||
findThem();
|
||||
|
||||
+26
@@ -1,5 +1,6 @@
|
||||
package org.tests.model.basic.cache;
|
||||
|
||||
import io.ebean.InTuples;
|
||||
import io.ebean.xtest.BaseTestCase;
|
||||
import io.ebean.CacheMode;
|
||||
import io.ebean.DB;
|
||||
@@ -419,6 +420,31 @@ public class TestCacheViaComplexNaturalKey3 extends BaseTestCase {
|
||||
} else {
|
||||
assertSql(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and concat(t0.sku,':',t0.code,'-foo')");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findList_inTuples_noCache() {
|
||||
setup();
|
||||
|
||||
InTuples tuples = InTuples.of("sku", "code")
|
||||
.add("2", 1000)
|
||||
.add("2", 1001)
|
||||
.add("3", 1000);
|
||||
|
||||
LoggedSql.start();
|
||||
|
||||
List<OCachedNatKeyBean3> list = DB.find(OCachedNatKeyBean3.class)
|
||||
.where()
|
||||
.eq("store", "def")
|
||||
.inTuples(tuples)
|
||||
.setUseCache(false)
|
||||
.orderBy("sku desc")
|
||||
.findList();
|
||||
|
||||
List<String> sql = LoggedSql.stop();
|
||||
|
||||
assertThat(tuples.entries()).hasSize(3);
|
||||
assertThat(list).hasSize(3);
|
||||
assertSql(sql.get(0)).contains("from o_cached_natkey3 t0 where t0.store = ? and (sku,code) in ((?,?),(?,?),(?,?)) order by t0.sku desc;");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user