From a78f89d0c2b19c0c19ba2c856a1e54ab17468c98 Mon Sep 17 00:00:00 2001 From: Matthew Davis Date: Mon, 26 Dec 2022 16:36:52 -0500 Subject: [PATCH 1/3] first pass at a query building helper --- .../zulia/client/command/factory/Values.java | 162 ++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 zulia-client/src/main/java/io/zulia/client/command/factory/Values.java diff --git a/zulia-client/src/main/java/io/zulia/client/command/factory/Values.java b/zulia-client/src/main/java/io/zulia/client/command/factory/Values.java new file mode 100644 index 00000000..7a865b92 --- /dev/null +++ b/zulia-client/src/main/java/io/zulia/client/command/factory/Values.java @@ -0,0 +1,162 @@ +package io.zulia.client.command.factory; + +import com.google.common.base.Joiner; +import io.zulia.client.command.builder.FilterQuery; +import io.zulia.client.command.builder.ScoredQuery; +import io.zulia.client.command.builder.StandardQuery; +import io.zulia.message.ZuliaQuery.Query.Operator; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +public class Values { + public static final Joiner COMMA_JOINER = Joiner.on(","); + public static final Joiner SPACE_JOINER = Joiner.on(" "); + public static final Joiner OR_JOINER = Joiner.on(" OR "); + + public static final Joiner AND_JOINER = Joiner.on(" AND "); + + public static Function VALUE_QUOTER = s -> { + s = s.trim(); + if (s.startsWith("\"") && s.endsWith("\"")) { + return s; + } + if (s.contains(" ") || s.contains("-")) { + return "\"" + s + "\""; + } + return s; + }; + + + private final Type type; + + private final Integer minimumShouldMatch; + + private Collection values; + + private Collection fields; + private boolean exclude; + + private Function valueHandler = VALUE_QUOTER; + + + private enum Type { + ANY, ALL, AT_LEAST + } + + public static Values any() { + return new Values(Type.ANY, null); + } + + public static Values all() { + return new Values(Type.ALL, null); + } + + public static Values atLeast(int minimumShouldMatch) { + return new Values(Type.AT_LEAST, minimumShouldMatch); + } + + private Values(Type type, Integer minimumShouldMatch) { + this.type = type; + this.minimumShouldMatch = null; + } + + public Values of(String... values) { + this.values = Arrays.stream(values).toList(); + return this; + } + + public Values of(Collection values) { + this.values = values; + return this; + } + + + public Values exclude() { + this.exclude = true; + return this; + } + + public Values include() { + this.exclude = false; + return this; + } + + + public Values withFields(String... fields) { + this.fields = List.of(fields); + return this; + } + + public Values withFields(Collection fields) { + this.fields = fields; + return this; + } + + + public void setValueHandler(Function valueHandler) { + this.valueHandler = valueHandler; + } + + public String asString() { + + StringBuilder sb = new StringBuilder(); + + if (exclude) { + sb.append("-"); + } + + COMMA_JOINER.appendTo(sb, fields); + sb.append(":("); + + List valuesHandled = values.stream().map(valueHandler).toList(); + if (type.equals(Type.ALL)) { + AND_JOINER.appendTo(sb, valuesHandled); + } else { + OR_JOINER.appendTo(sb, valuesHandled); + } + sb.append(")"); + if (type.equals(Type.AT_LEAST)) { + sb.append("~"); + sb.append(minimumShouldMatch); + } + return null; + } + + + public FilterQuery asFilterQuery() { + return asQuery(FilterQuery::new); + } + + public ScoredQuery asScoredQuery() { + return asQuery(ScoredQuery::new); + } + + + public T asQuery(Function constructor) { + List valuesHandled = values.stream().map(valueHandler).toList(); + String query = SPACE_JOINER.join(valuesHandled); + T tQuery = constructor.apply(query); + tQuery.setDefaultOperator(type.equals(Type.ALL) ? Operator.AND : Operator.OR); + fields.forEach(tQuery::addQueryField); + if (minimumShouldMatch != null) { + tQuery.setMinShouldMatch(minimumShouldMatch); + } + return tQuery; + } + + + public static void main(String[] args) { + String query = Values.any().of("a", "b", "c").withFields("title", "abstract").exclude().asString(); + + String query2 = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asString(); + + + FilterQuery filterQuery = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asFilterQuery(); + + ScoredQuery scoredQuery = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asScoredQuery(); + } + +} From 1f0410822a4c43ff8e73ae7537edf250bec53ab1 Mon Sep 17 00:00:00 2001 From: Matthew Davis Date: Tue, 27 Dec 2022 13:12:26 -0500 Subject: [PATCH 2/3] dont require full mapper object for search all --- .../src/main/java/io/zulia/client/pool/ZuliaWorkPool.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/zulia-client/src/main/java/io/zulia/client/pool/ZuliaWorkPool.java b/zulia-client/src/main/java/io/zulia/client/pool/ZuliaWorkPool.java index 594c5f2b..ec89cffc 100644 --- a/zulia-client/src/main/java/io/zulia/client/pool/ZuliaWorkPool.java +++ b/zulia-client/src/main/java/io/zulia/client/pool/ZuliaWorkPool.java @@ -6,7 +6,7 @@ import io.zulia.client.config.ClientIndexConfig; import io.zulia.client.config.ZuliaPoolConfig; import io.zulia.client.result.*; -import io.zulia.fields.Mapper; +import io.zulia.fields.GsonDocumentMapper; import io.zulia.message.ZuliaIndex.IndexAlias; import io.zulia.message.ZuliaQuery.ScoredResult; import io.zulia.util.ResultHelper; @@ -215,12 +215,11 @@ public SearchResult search(Search search) throws Exception { return execute(search); } - public void searchAllAsMappedDocument(Search search, Mapper mapper, Consumer mappedDocumentHandler) throws Exception { + public void searchAllAsMappedDocument(Search search, GsonDocumentMapper mapper, Consumer mappedDocumentHandler) throws Exception { searchAllAsScoredResult(search, scoredResult -> { try { mappedDocumentHandler.accept(mapper.fromScoredResult(scoredResult)); - } - catch (Exception e) { + } catch (Exception e) { throw new RuntimeException(e); } }); From 1ed9254adbf0d68f6c1fa699d0ff9885a9df971a Mon Sep 17 00:00:00 2001 From: Matthew Davis Date: Mon, 2 Jan 2023 08:54:49 -0500 Subject: [PATCH 3/3] add test for values --- .../client/command/builder/FilterQuery.java | 59 +++++++------- .../client/command/builder/ScoredQuery.java | 10 ++- .../client/command/builder/StandardQuery.java | 36 +++++---- .../zulia/client/command/factory/Values.java | 30 +++---- .../test/java/io/zulia/client/HelperTest.java | 81 +++++++++++++++++++ .../test/java/io/zulia/client/MapperTest.java | 45 +++++------ 6 files changed, 177 insertions(+), 84 deletions(-) create mode 100644 zulia-client/src/test/java/io/zulia/client/HelperTest.java diff --git a/zulia-client/src/main/java/io/zulia/client/command/builder/FilterQuery.java b/zulia-client/src/main/java/io/zulia/client/command/builder/FilterQuery.java index 71a7dab9..2357ff67 100644 --- a/zulia-client/src/main/java/io/zulia/client/command/builder/FilterQuery.java +++ b/zulia-client/src/main/java/io/zulia/client/command/builder/FilterQuery.java @@ -2,32 +2,37 @@ import io.zulia.message.ZuliaQuery; -public class FilterQuery extends StandardQuery { - - private boolean exclude = false; - - public FilterQuery(String query) { - super(query); - } - - public FilterQuery include() { - exclude = false; - return this; - } - - public FilterQuery exclude() { - exclude = true; - return this; - } - - @Override - protected void completeQuery(ZuliaQuery.Query.Builder queryBuilder) { - if (exclude) { - queryBuilder.setQueryType(ZuliaQuery.Query.QueryType.FILTER_NOT); - } - else { - queryBuilder.setQueryType(ZuliaQuery.Query.QueryType.FILTER); - } - } +public class FilterQuery extends StandardQuery { + + private boolean exclude = false; + + public FilterQuery(String query) { + super(query); + } + + @Override + protected FilterQuery getSelf() { + return this; + } + + public FilterQuery include() { + exclude = false; + return this; + } + + public FilterQuery exclude() { + exclude = true; + return this; + } + + @Override + protected void completeQuery(ZuliaQuery.Query.Builder queryBuilder) { + if (exclude) { + queryBuilder.setQueryType(ZuliaQuery.Query.QueryType.FILTER_NOT); + } else { + queryBuilder.setQueryType(ZuliaQuery.Query.QueryType.FILTER); + } + } + } diff --git a/zulia-client/src/main/java/io/zulia/client/command/builder/ScoredQuery.java b/zulia-client/src/main/java/io/zulia/client/command/builder/ScoredQuery.java index bb7ee38d..b536c7c0 100644 --- a/zulia-client/src/main/java/io/zulia/client/command/builder/ScoredQuery.java +++ b/zulia-client/src/main/java/io/zulia/client/command/builder/ScoredQuery.java @@ -3,7 +3,7 @@ import io.zulia.message.ZuliaQuery; import io.zulia.message.ZuliaQuery.Query.QueryType; -public class ScoredQuery extends StandardQuery { +public class ScoredQuery extends StandardQuery { private boolean must; private String scoreFunction; @@ -11,10 +11,14 @@ public ScoredQuery(String query) { this(query, true); } + @Override + protected ScoredQuery getSelf() { + return this; + } + /** - * * @param query - * @param must - if must is true than query will be required, otherwise it will be used to as an optional (should clause) to help scoring + * @param must - if must is true then query will be required, otherwise it will be used to as an optional (should clause) to help scoring */ public ScoredQuery(String query, boolean must) { super(query); diff --git a/zulia-client/src/main/java/io/zulia/client/command/builder/StandardQuery.java b/zulia-client/src/main/java/io/zulia/client/command/builder/StandardQuery.java index 44d04a57..71e83125 100644 --- a/zulia-client/src/main/java/io/zulia/client/command/builder/StandardQuery.java +++ b/zulia-client/src/main/java/io/zulia/client/command/builder/StandardQuery.java @@ -5,7 +5,7 @@ import java.util.List; -public abstract class StandardQuery implements QueryBuilder { +public abstract class StandardQuery implements QueryBuilder { private final ZuliaQuery.Query.Builder queryBuilder; @@ -18,60 +18,62 @@ public StandardQuery(String query) { } } - public StandardQuery setQuery(String query) { + public T setQuery(String query) { queryBuilder.setQ(query); - return this; + return getSelf(); } + protected abstract T getSelf(); + public List getQueryFields() { return queryBuilder.getQfList(); } - public StandardQuery addQueryField(String queryField) { + public T addQueryField(String queryField) { queryBuilder.addQf(queryField); - return this; + return getSelf(); } - public StandardQuery addQueryFields(String... queryFields) { + public T addQueryFields(String... queryFields) { queryBuilder.addAllQf(List.of(queryFields)); - return this; + return getSelf(); } - public StandardQuery addQueryFields(Iterable queryFields) { + public T addQueryFields(Iterable queryFields) { queryBuilder.addAllQf(queryFields); - return this; + return getSelf(); } - public StandardQuery clearQueryField() { + public T clearQueryField() { queryBuilder.clearQf(); - return this; + return getSelf(); } - public StandardQuery setQueryFields(@NotNull List queryFields) { + public T setQueryFields(@NotNull List queryFields) { if (queryFields == null) { throw new IllegalArgumentException("Query Fields cannot be null"); } queryBuilder.clearQf(); queryBuilder.addAllQf(queryFields); - return this; + return getSelf(); } public ZuliaQuery.Query.Operator getDefaultOperator() { return queryBuilder.getDefaultOp(); } - public StandardQuery setDefaultOperator(ZuliaQuery.Query.Operator defaultOperator) { + public T setDefaultOperator(ZuliaQuery.Query.Operator defaultOperator) { queryBuilder.setDefaultOp(defaultOperator); - return this; + return getSelf(); } public int getMinShouldMatch() { return queryBuilder.getMm(); } - public StandardQuery setMinShouldMatch(int minShouldMatch) { + public T setMinShouldMatch(int minShouldMatch) { queryBuilder.setMm(minShouldMatch); - return this; + return getSelf(); } @Override diff --git a/zulia-client/src/main/java/io/zulia/client/command/factory/Values.java b/zulia-client/src/main/java/io/zulia/client/command/factory/Values.java index 7a865b92..9b402a18 100644 --- a/zulia-client/src/main/java/io/zulia/client/command/factory/Values.java +++ b/zulia-client/src/main/java/io/zulia/client/command/factory/Values.java @@ -60,7 +60,7 @@ public static Values atLeast(int minimumShouldMatch) { private Values(Type type, Integer minimumShouldMatch) { this.type = type; - this.minimumShouldMatch = null; + this.minimumShouldMatch = minimumShouldMatch; } public Values of(String... values) { @@ -108,8 +108,11 @@ public String asString() { sb.append("-"); } - COMMA_JOINER.appendTo(sb, fields); - sb.append(":("); + if (fields != null && !fields.isEmpty()) { + COMMA_JOINER.appendTo(sb, fields); + sb.append(":"); + } + sb.append("("); List valuesHandled = values.stream().map(valueHandler).toList(); if (type.equals(Type.ALL)) { @@ -122,7 +125,7 @@ public String asString() { sb.append("~"); sb.append(minimumShouldMatch); } - return null; + return sb.toString(); } @@ -135,12 +138,20 @@ public ScoredQuery asScoredQuery() { } - public T asQuery(Function constructor) { + public > T asQuery(Function constructor) { List valuesHandled = values.stream().map(valueHandler).toList(); String query = SPACE_JOINER.join(valuesHandled); T tQuery = constructor.apply(query); tQuery.setDefaultOperator(type.equals(Type.ALL) ? Operator.AND : Operator.OR); fields.forEach(tQuery::addQueryField); + + if (exclude) { + if (tQuery instanceof FilterQuery fq) { + fq.exclude(); + } else if (tQuery instanceof ScoredQuery sq) { + throw new IllegalStateException("Exclude cannot be used with ScoredQuery"); + } + } if (minimumShouldMatch != null) { tQuery.setMinShouldMatch(minimumShouldMatch); } @@ -148,15 +159,6 @@ public T asQuery(Function constructor) { } - public static void main(String[] args) { - String query = Values.any().of("a", "b", "c").withFields("title", "abstract").exclude().asString(); - String query2 = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asString(); - - - FilterQuery filterQuery = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asFilterQuery(); - - ScoredQuery scoredQuery = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asScoredQuery(); - } } diff --git a/zulia-client/src/test/java/io/zulia/client/HelperTest.java b/zulia-client/src/test/java/io/zulia/client/HelperTest.java new file mode 100644 index 00000000..13431698 --- /dev/null +++ b/zulia-client/src/test/java/io/zulia/client/HelperTest.java @@ -0,0 +1,81 @@ +package io.zulia.client; + +import io.zulia.client.command.builder.FilterQuery; +import io.zulia.client.command.builder.ScoredQuery; +import io.zulia.client.command.factory.Values; +import io.zulia.message.ZuliaQuery; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class HelperTest { + + @Test + public void valuesTest() { + + + { + String query = Values.any().of("a", "b").asString(); + Assertions.assertEquals("(a OR b)", query); + } + + { + String query = Values.any().of("slow cat", "Pink Shirt").asString(); + Assertions.assertEquals("(\"slow cat\" OR \"Pink Shirt\")", query); + } + + { + String query = Values.all().of("slow cat", "Pink Shirt").asString(); + Assertions.assertEquals("(\"slow cat\" AND \"Pink Shirt\")", query); + } + + + { + String query = Values.any().of("a", "b").withFields("title", "abstract").asString(); + Assertions.assertEquals("title,abstract:(a OR b)", query); + } + + + { + String query = Values.any().of("a", "b", "c").withFields("title", "abstract").exclude().asString(); + Assertions.assertEquals("-title,abstract:(a OR b OR c)", query); + } + + + { + String query = Values.atLeast(2).of("fast dog", "b", "c").withFields("title", "abstract").asString(); + Assertions.assertEquals("title,abstract:(\"fast dog\" OR b OR c)~2", query); + } + + { + String query = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asString(); + Assertions.assertEquals("-title,abstract:(a OR b OR c)~2", query); + } + + { + FilterQuery fq = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asFilterQuery(); + FilterQuery fq2 = new FilterQuery("a b c").setDefaultOperator(ZuliaQuery.Query.Operator.OR).exclude().addQueryFields("title", "abstract").setMinShouldMatch(2); + Assertions.assertEquals(fq.getQuery(), fq2.getQuery()); + } + + { + FilterQuery fq = Values.all().of("a", "b", "c").withFields("title", "abstract").exclude().asFilterQuery(); + FilterQuery fq2 = new FilterQuery("a b c").setDefaultOperator(ZuliaQuery.Query.Operator.AND).exclude().addQueryFields("title", "abstract"); + Assertions.assertEquals(fq.getQuery(), fq2.getQuery()); + } + + { + ScoredQuery sq = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").asScoredQuery(); + ScoredQuery sq2 = new ScoredQuery("a b c").setDefaultOperator(ZuliaQuery.Query.Operator.OR).addQueryFields("title", "abstract").setMinShouldMatch(2); + Assertions.assertEquals(sq.getQuery(), sq2.getQuery()); + } + + { + Assertions.assertThrows(IllegalStateException.class, () -> { + // exclude not supported in scored queries + ScoredQuery sq = Values.atLeast(2).of("a", "b", "c").withFields("title", "abstract").exclude().asScoredQuery(); + }); + } + + } + +} diff --git a/zulia-client/src/test/java/io/zulia/client/MapperTest.java b/zulia-client/src/test/java/io/zulia/client/MapperTest.java index 31d685ae..cb765f69 100644 --- a/zulia-client/src/test/java/io/zulia/client/MapperTest.java +++ b/zulia-client/src/test/java/io/zulia/client/MapperTest.java @@ -11,35 +11,34 @@ public class MapperTest { - @Test - public void testSimpleCase() throws Exception { + @Test + public void testSimpleCase() throws Exception { - Date d = new Date(); - TestObj1 testObj1 = new TestObj1("test", 14, Arrays.asList("1", "2"), Set.of(4, 6, 7), d, Long.MIN_VALUE, true); + Date d = new Date(); + TestObj1 testObj1 = new TestObj1("test", 14, Arrays.asList("1", "2"), Set.of(4, 6, 7), d, Long.MIN_VALUE, true); - Mapper mapper = new Mapper<>(TestObj1.class); + Mapper mapper = new Mapper<>(TestObj1.class); - Document doc = mapper.toDocument(testObj1); + Document doc = mapper.toDocument(testObj1); - Assertions.assertEquals("test", doc.getString("field1")); - Assertions.assertEquals(14, (int) doc.getInteger("field2")); - Assertions.assertEquals(2, doc.getList("field3", String.class).size()); - Assertions.assertEquals(3, doc.getList("field4", Integer.class).size()); - Assertions.assertEquals(doc.getDate("field5"), d); - Assertions.assertEquals(Long.MIN_VALUE, doc.getLong("field6")); - Assertions.assertEquals(true, doc.getBoolean("field7")); + Assertions.assertEquals("test", doc.getString("field1")); + Assertions.assertEquals(14, (int) doc.getInteger("field2")); + Assertions.assertEquals(2, doc.getList("field3", String.class).size()); + Assertions.assertEquals(3, doc.getList("field4", Integer.class).size()); + Assertions.assertEquals(doc.getDate("field5"), d); + Assertions.assertEquals(Long.MIN_VALUE, doc.getLong("field6")); + Assertions.assertEquals(true, doc.getBoolean("field7")); - TestObj1 testObj1a = mapper.fromDocument(doc); - Assertions.assertEquals(testObj1a.getField1(), testObj1.getField1()); - Assertions.assertEquals(testObj1a.getField2(), testObj1.getField2()); - Assertions.assertEquals(testObj1a.getField3(), testObj1.getField3()); - Assertions.assertEquals(testObj1a.getField4(), testObj1.getField4()); - Assertions.assertEquals(testObj1a.getField5(), testObj1.getField5()); - Assertions.assertEquals(testObj1a.getField6(), testObj1.getField6()); - Assertions.assertEquals(testObj1a.isField7(), testObj1.isField7()); + TestObj1 testObj1a = mapper.fromDocument(doc); + Assertions.assertEquals(testObj1a.getField1(), testObj1.getField1()); + Assertions.assertEquals(testObj1a.getField2(), testObj1.getField2()); + Assertions.assertEquals(testObj1a.getField3(), testObj1.getField3()); + Assertions.assertEquals(testObj1a.getField4(), testObj1.getField4()); + Assertions.assertEquals(testObj1a.getField5(), testObj1.getField5()); + Assertions.assertEquals(testObj1a.getField6(), testObj1.getField6()); + Assertions.assertEquals(testObj1a.isField7(), testObj1.isField7()); - System.out.println(doc); - } + } }