diff --git a/docs/index.md b/docs/index.md
index 4962ab8f9..a762b583d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -39,6 +39,7 @@ fluent API.
| [SQL (JDBC)]({{ site.baseurl }}/tutorials/sql) | `{{ site.group_id }}:querydsl-sql` |
| [R2DBC (Reactive SQL)]({{ site.baseurl }}/tutorials/r2dbc) | `{{ site.group_id }}:querydsl-r2dbc` |
| [MongoDB]({{ site.baseurl }}/tutorials/mongodb) | `{{ site.group_id }}:querydsl-mongodb` |
+| [Lucene]({{ site.baseurl }}/tutorials/lucene) | `{{ site.group_id }}:querydsl-lucene9` / `querydsl-lucene10` |
| [Collections]({{ site.baseurl }}/tutorials/collections) | `{{ site.group_id }}:querydsl-collections` |
| [Spatial]({{ site.baseurl }}/tutorials/spatial) | `{{ site.group_id }}:querydsl-sql-spatial` |
| [Kotlin Extensions]({{ site.baseurl }}/tutorials/kotlin) | `{{ site.group_id }}:querydsl-kotlin` |
diff --git a/docs/introduction.md b/docs/introduction.md
index ece1714cf..2cae7c053 100644
--- a/docs/introduction.md
+++ b/docs/introduction.md
@@ -19,7 +19,7 @@ construction faster and safer.
HQL for Hibernate was the first target language for Querydsl. Today the
framework supports **JPA**, **SQL (JDBC)**, **R2DBC**, **MongoDB**,
-**Collections**, **Spatial**, **Kotlin**, and **Scala** as backends.
+**Lucene**, **Collections**, **Spatial**, **Kotlin**, and **Scala** as backends.
If you are new to database access in Java,
[this guide](https://www.marcobehler.com/guides/a-guide-to-accessing-databases-in-java)
diff --git a/docs/migration.md b/docs/migration.md
index 4226447aa..1ea3e8624 100644
--- a/docs/migration.md
+++ b/docs/migration.md
@@ -158,9 +158,9 @@ The following modules have been removed from this fork:
| Module | Reason | Alternative |
|:-------|:-------|:------------|
| `querydsl-jdo` | JDO usage has declined significantly | Use JPA instead |
-| `querydsl-lucene3` | Lucene 3 is EOL | Use Lucene/Elasticsearch client directly |
-| `querydsl-lucene4` | Lucene 4 is EOL | Use Lucene/Elasticsearch client directly |
-| `querydsl-lucene5` | Lucene integration is rarely used | Use Lucene/Elasticsearch client directly |
+| `querydsl-lucene3` | Lucene 3 is EOL | Use `querydsl-lucene9` or `querydsl-lucene10` |
+| `querydsl-lucene4` | Lucene 4 is EOL | Use `querydsl-lucene9` or `querydsl-lucene10` |
+| `querydsl-lucene5` | Lucene 5 is EOL | Use `querydsl-lucene9` or `querydsl-lucene10` |
| `querydsl-hibernate-search` | Hibernate Search has its own query DSL | Use Hibernate Search API directly |
If you depend on any of these modules, you have two options:
@@ -174,6 +174,8 @@ If you depend on any of these modules, you have two options:
| Module | Description |
|:-------|:------------|
| [`querydsl-r2dbc`]({{ site.baseurl }}/tutorials/r2dbc) | Reactive, non-blocking database access via R2DBC and Project Reactor |
+| [`querydsl-lucene9`]({{ site.baseurl }}/tutorials/lucene) | Lucene 9 integration (Java 17+), replacing the old lucene3/4/5 modules |
+| [`querydsl-lucene10`]({{ site.baseurl }}/tutorials/lucene) | Lucene 10 integration (Java 21+) |
| [`querydsl-kotlin`]({{ site.baseurl }}/tutorials/kotlin) | Kotlin extension functions — use `+`, `-`, `*`, `/`, `%` operators on expressions |
## Step-by-Step Migration
diff --git a/docs/tutorials/lucene.md b/docs/tutorials/lucene.md
new file mode 100644
index 000000000..b26fafa70
--- /dev/null
+++ b/docs/tutorials/lucene.md
@@ -0,0 +1,212 @@
+---
+layout: default
+title: Querying Lucene
+parent: Tutorials
+nav_order: 6
+---
+
+# Querying Lucene
+
+This chapter describes the querying functionality of the Lucene modules.
+
+## Maven Integration
+
+Two modules are available depending on your Lucene version:
+
+**Lucene 9** (Java 17+):
+
+```xml
+
+ {{ site.group_id }}
+ querydsl-lucene9
+ {{ site.querydsl_version }}
+
+```
+
+**Lucene 10** (Java 21+):
+
+```xml
+
+ {{ site.group_id }}
+ querydsl-lucene10
+ {{ site.querydsl_version }}
+
+```
+
+Both modules provide the same API. The examples below use the `com.querydsl.lucene9`
+package — replace with `com.querydsl.lucene10` if you are on Lucene 10.
+
+## Creating the Query Types
+
+Since Lucene has no schema, query types are created manually. For a document
+with `year` and `title` fields the query type looks like this:
+
+```java
+public class QDocument extends EntityPathBase {
+ private static final long serialVersionUID = -4872833626508344081L;
+
+ public QDocument(String var) {
+ super(Document.class, PathMetadataFactory.forVariable(var));
+ }
+
+ public final StringPath year = createString("year");
+
+ public final StringPath title = createString("title");
+}
+```
+
+`QDocument` represents a Lucene document with the fields `year` and `title`.
+
+Code generation is not available for Lucene since no schema data is available.
+
+## Querying
+
+Querying with Querydsl Lucene is straightforward:
+
+```java
+QDocument doc = new QDocument("doc");
+
+IndexSearcher searcher = new IndexSearcher(index);
+LuceneQuery query = new LuceneQuery(searcher);
+List documents = query
+ .where(doc.year.between("1800", "2000").and(doc.title.startsWith("Huckle")))
+ .fetch();
+```
+
+This is transformed into the following Lucene query:
+
+```
++year:[1800 TO 2000] +title:huckle*
+```
+
+### Custom Serializer
+
+The default `LuceneSerializer` does not lowercase terms and splits on
+whitespace. To customize this behavior, pass a serializer to the query:
+
+```java
+LuceneSerializer serializer = new LuceneSerializer(true, true);
+LuceneQuery query = new LuceneQuery(serializer, searcher);
+```
+
+The constructor parameters are:
+
+| Parameter | Description |
+|:----------|:------------|
+| `lowerCase` | Convert search terms to lowercase |
+| `splitTerms` | Split terms by whitespace into multi-term queries |
+
+## Typed Queries
+
+Use `TypedQuery` to transform Lucene documents into custom types:
+
+```java
+TypedQuery query = new TypedQuery<>(searcher, doc -> {
+ Person person = new Person();
+ person.setName(doc.get("name"));
+ person.setAge(Integer.parseInt(doc.get("age")));
+ return person;
+});
+List results = query.where(doc.title.eq("Engineer")).fetch();
+```
+
+## General Usage
+
+Use the cascading methods of the `LuceneQuery` class:
+
+**where:** Add query filters, either in varargs form separated via commas or
+cascaded via the and-operator. Supported operations include equality, inequality,
+range queries, string matching (`like`, `startsWith`, `endsWith`, `contains`),
+and collection operations (`in`, `notIn`).
+
+**orderBy:** Add ordering of the result as a varargs array of order
+expressions. Use `asc()` and `desc()` on numeric, string, and other comparable
+expressions to access the `OrderSpecifier` instances.
+
+**limit, offset, restrict:** Set the paging of the result. `limit` for max
+results, `offset` for skipping rows, and `restrict` for defining both in one
+call.
+
+**load:** Select specific fields to load from the index instead of loading
+the entire document.
+
+## Ordering
+
+```java
+query
+ .where(doc.title.like("*"))
+ .orderBy(doc.title.asc(), doc.year.desc())
+ .fetch();
+```
+
+This is equivalent to the Lucene query `title:*` with results sorted ascending
+by title and descending by year.
+
+Alternatively, use a `Sort` instance directly:
+
+```java
+Sort sort = ...;
+query
+ .where(doc.title.like("*"))
+ .sort(sort)
+ .fetch();
+```
+
+## Limit
+
+```java
+query
+ .where(doc.title.like("*"))
+ .limit(10)
+ .fetch();
+```
+
+## Offset
+
+```java
+query
+ .where(doc.title.like("*"))
+ .offset(3)
+ .fetch();
+```
+
+## Field Selection
+
+Load only specific fields from the index:
+
+```java
+query
+ .where(doc.title.ne(""))
+ .load(doc.title)
+ .fetch();
+```
+
+## Fuzzy Searches
+
+Fuzzy searches can be expressed via `fuzzyLike` methods in the
+`LuceneExpressions` class:
+
+```java
+query
+ .where(LuceneExpressions.fuzzyLike(doc.title, "Hello"))
+ .fetch();
+```
+
+You can also control the maximum edit distance and prefix length:
+
+```java
+query
+ .where(LuceneExpressions.fuzzyLike(doc.title, "Hello", 2, 0))
+ .fetch();
+```
+
+## Applying Lucene Filters
+
+Apply a native Lucene `Query` as a filter:
+
+```java
+query
+ .where(doc.title.like("*"))
+ .filter(IntPoint.newExactQuery("year", 1990))
+ .fetch();
+```
diff --git a/pom.xml b/pom.xml
index 098883cb2..b74d4f3d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -878,6 +878,14 @@
Lucene 5com.querydsl.lucene5*
+
+ Lucene 9
+ com.querydsl.lucene9*
+
+
+ Lucene 10
+ com.querydsl.lucene10*
+ Hibernate Searchcom.querydsl.hibernate.search*
diff --git a/querydsl-libraries/pom.xml b/querydsl-libraries/pom.xml
index 73b166c66..b8f927bcb 100644
--- a/querydsl-libraries/pom.xml
+++ b/querydsl-libraries/pom.xml
@@ -36,6 +36,10 @@
querydsl-scalaquerydsl-kotlin
+
+
+ querydsl-lucene9
+ querydsl-lucene10
diff --git a/querydsl-libraries/querydsl-lucene10/README.md b/querydsl-libraries/querydsl-lucene10/README.md
new file mode 100644
index 000000000..bdeb11981
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/README.md
@@ -0,0 +1,57 @@
+## Querydsl Lucene 10
+
+The Lucene module provides integration with the Lucene 10 indexing library.
+
+**Maven integration**
+
+ Add the following dependencies to your Maven project :
+```XML
+
+ io.github.openfeign.querydsl
+ querydsl-lucene10
+ ${querydsl.version}
+
+```
+
+**Creating the query types**
+
+With fields year and title a manually created query type could look something like this:
+
+```JAVA
+public class QDocument extends EntityPathBase{
+ private static final long serialVersionUID = -4872833626508344081L;
+
+ public QDocument(String var) {
+ super(Document.class, PathMetadataFactory.forVariable(var));
+ }
+
+ public final StringPath year = createString("year");
+
+ public final StringPath title = createString("title");
+}
+```
+
+QDocument represents a Lucene document with the fields year and title.
+
+Code generation is not available for Lucene, since no schema data is available.
+
+**Querying**
+
+Querying with Querydsl Lucene is as simple as this:
+
+```JAVA
+QDocument doc = new QDocument("doc");
+
+IndexSearcher searcher = new IndexSearcher(index);
+LuceneQuery query = new LuceneQuery(true, searcher);
+List documents = query
+ .where(doc.year.between("1800", "2000").and(doc.title.startsWith("Huckle"))
+ .fetch();
+```
+
+which is transformed into the following Lucene query :
+```
++year:[1800 TO 2000] +title:huckle*
+```
+
+For more information on the Querydsl Lucene module visit the reference documentation http://www.querydsl.com/static/querydsl/latest/reference/html/ch02s05.html
diff --git a/querydsl-libraries/querydsl-lucene10/pom.xml b/querydsl-libraries/querydsl-lucene10/pom.xml
new file mode 100644
index 000000000..2db58c233
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/pom.xml
@@ -0,0 +1,99 @@
+
+
+ 4.0.0
+
+
+ io.github.openfeign.querydsl
+ querydsl-libraries
+ 7.2-SNAPSHOT
+
+
+ querydsl-lucene10
+ Querydsl - Lucene 10 support
+ Lucene 10 support for Querydsl
+
+
+ 10.3.2
+ 21
+ org.apache.lucene.*;version="[10.0.0,11)",
+ ${osgi.import.package.root}
+
+
+
+
+ org.jetbrains
+ annotations
+ provided
+
+
+ org.apache.lucene
+ lucene-core
+ ${lucene.version}
+ provided
+
+
+ org.apache.lucene
+ lucene-analysis-common
+ ${lucene.version}
+ provided
+
+
+ org.apache.lucene
+ lucene-queryparser
+ ${lucene.version}
+ provided
+
+
+ io.github.openfeign.querydsl
+ querydsl-core
+ ${project.version}
+
+
+
+
+ io.github.openfeign.querydsl
+ querydsl-core
+ ${project.version}
+ test-jar
+ test
+
+
+
+ io.github.openfeign.querydsl
+ querydsl-apt
+ ${project.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.3.0
+
+
+
+ com.querydsl.lucene10
+
+
+
+
+
+
+ org.apache.felix
+ maven-bundle-plugin
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ com.querydsl.apt.QuerydslAnnotationProcessor
+
+
+
+
+
+
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/AbstractLuceneQuery.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/AbstractLuceneQuery.java
new file mode 100644
index 000000000..c321326eb
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/AbstractLuceneQuery.java
@@ -0,0 +1,352 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.CloseableIterator;
+import com.querydsl.core.DefaultQueryMetadata;
+import com.querydsl.core.Fetchable;
+import com.querydsl.core.NonUniqueResultException;
+import com.querydsl.core.QueryException;
+import com.querydsl.core.QueryMetadata;
+import com.querydsl.core.QueryModifiers;
+import com.querydsl.core.QueryResults;
+import com.querydsl.core.SimpleQuery;
+import com.querydsl.core.support.QueryMixin;
+import com.querydsl.core.types.OrderSpecifier;
+import com.querydsl.core.types.ParamExpression;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.Predicate;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.TotalHitCountCollectorManager;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * AbstractLuceneQuery is an abstract super class for Lucene query implementations
+ *
+ * @author tiwe
+ * @param projection type
+ * @param concrete subtype of querydsl
+ */
+public abstract class AbstractLuceneQuery>
+ implements SimpleQuery, Fetchable {
+
+ private static final String JAVA_ISO_CONTROL = "[\\p{Cntrl}&&[^\r\n\t]]";
+
+ private final QueryMixin queryMixin;
+
+ private final IndexSearcher searcher;
+
+ private final LuceneSerializer serializer;
+
+ private final Function transformer;
+
+ @Nullable private Set fieldsToLoad;
+
+ private List filters = Collections.emptyList();
+
+ @Nullable private Query filter;
+
+ @Nullable private Sort querySort;
+
+ @SuppressWarnings("unchecked")
+ public AbstractLuceneQuery(
+ LuceneSerializer serializer, IndexSearcher searcher, Function transformer) {
+ queryMixin = new QueryMixin((Q) this, new DefaultQueryMetadata());
+ this.serializer = serializer;
+ this.searcher = searcher;
+ this.transformer = transformer;
+ }
+
+ public AbstractLuceneQuery(IndexSearcher searcher, Function transformer) {
+ this(LuceneSerializer.DEFAULT, searcher, transformer);
+ }
+
+ private long innerCount() {
+ try {
+ final int maxDoc = searcher.getIndexReader().maxDoc();
+ if (maxDoc == 0) {
+ return 0;
+ }
+ return searcher.search(
+ createQuery(), new TotalHitCountCollectorManager(searcher.getSlices()));
+ } catch (IOException | IllegalArgumentException e) {
+ throw new QueryException(e);
+ }
+ }
+
+ @Override
+ public long fetchCount() {
+ return innerCount();
+ }
+
+ protected Query createQuery() {
+ Query originalQuery;
+ if (queryMixin.getMetadata().getWhere() == null) {
+ originalQuery = new MatchAllDocsQuery();
+ } else {
+ originalQuery =
+ serializer.toQuery(queryMixin.getMetadata().getWhere(), queryMixin.getMetadata());
+ }
+ Query filter = getFilter();
+ if (filter != null) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ builder.add(originalQuery, Occur.MUST);
+ builder.add(filter, Occur.FILTER);
+ return builder.build();
+ }
+ return originalQuery;
+ }
+
+ @Override
+ public Q distinct() {
+ throw new UnsupportedOperationException("use distinct(path) instead");
+ }
+
+ /**
+ * Apply the given Lucene Query as a filter to the search results
+ *
+ * @param filter filter query
+ * @return the current object
+ */
+ @SuppressWarnings("unchecked")
+ public Q filter(Query filter) {
+ if (filters.isEmpty()) {
+ this.filter = filter;
+ filters = Collections.singletonList(filter);
+ } else {
+ this.filter = null;
+ if (filters.size() == 1) {
+ filters = new ArrayList<>();
+ }
+ filters.add(filter);
+ }
+ return (Q) this;
+ }
+
+ private Query getFilter() {
+ if (filter == null && !filters.isEmpty()) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ for (Query f : filters) {
+ builder.add(f, Occur.SHOULD);
+ }
+ filter = builder.build();
+ }
+ return filter;
+ }
+
+ @Override
+ public Q limit(long limit) {
+ return queryMixin.limit(limit);
+ }
+
+ @Override
+ public CloseableIterator iterate() {
+ final QueryMetadata metadata = queryMixin.getMetadata();
+ final List> orderBys = metadata.getOrderBy();
+ final Integer queryLimit = metadata.getModifiers().getLimitAsInteger();
+ final Integer queryOffset = metadata.getModifiers().getOffsetAsInteger();
+ Sort sort = querySort;
+ int limit;
+ final int offset = queryOffset != null ? queryOffset : 0;
+ try {
+ limit = maxDoc();
+ if (limit == 0) {
+ return CloseableIterator.of(Collections.emptyIterator());
+ }
+ } catch (IOException | IllegalArgumentException e) {
+ throw new QueryException(e);
+ }
+ if (queryLimit != null && queryLimit < limit) {
+ limit = queryLimit;
+ }
+ if (sort == null && !orderBys.isEmpty()) {
+ sort = serializer.toSort(orderBys);
+ }
+
+ try {
+ ScoreDoc[] scoreDocs;
+ int sumOfLimitAndOffset = limit + offset;
+ if (sumOfLimitAndOffset < 1) {
+ throw new QueryException(
+ "The given limit ("
+ + limit
+ + ") and offset ("
+ + offset
+ + ") cause an integer overflow.");
+ }
+ if (sort != null) {
+ scoreDocs = searcher.search(createQuery(), sumOfLimitAndOffset, sort).scoreDocs;
+ } else {
+ scoreDocs = searcher.search(createQuery(), sumOfLimitAndOffset, Sort.INDEXORDER).scoreDocs;
+ }
+ if (offset < scoreDocs.length) {
+ return new ResultIterator(scoreDocs, offset, searcher, fieldsToLoad, transformer);
+ }
+ return CloseableIterator.of(Collections.emptyIterator());
+ } catch (final IOException e) {
+ throw new QueryException(e);
+ }
+ }
+
+ private List innerList() {
+ return CloseableIterator.asList(iterate());
+ }
+
+ @Override
+ public List fetch() {
+ return innerList();
+ }
+
+ /**
+ * Set the given fields to load
+ *
+ * @param fieldsToLoad fields to load
+ * @return the current object
+ */
+ @SuppressWarnings("unchecked")
+ public Q load(Set fieldsToLoad) {
+ this.fieldsToLoad = fieldsToLoad;
+ return (Q) this;
+ }
+
+ /**
+ * Load only the fields of the given paths
+ *
+ * @param paths fields to load
+ * @return the current object
+ */
+ @SuppressWarnings("unchecked")
+ public Q load(Path>... paths) {
+ Set fields = new HashSet();
+ for (Path> path : paths) {
+ fields.add(serializer.toField(path));
+ }
+ this.fieldsToLoad = fields;
+ return (Q) this;
+ }
+
+ @Override
+ public QueryResults fetchResults() {
+ List documents = innerList();
+ return new QueryResults(documents, queryMixin.getMetadata().getModifiers(), innerCount());
+ }
+
+ @Override
+ public Q offset(long offset) {
+ return queryMixin.offset(offset);
+ }
+
+ public Q orderBy(OrderSpecifier> o) {
+ return queryMixin.orderBy(o);
+ }
+
+ @Override
+ public Q orderBy(OrderSpecifier>... o) {
+ return queryMixin.orderBy(o);
+ }
+
+ @Override
+ public Q restrict(QueryModifiers modifiers) {
+ return queryMixin.restrict(modifiers);
+ }
+
+ @Override
+ public
Q set(ParamExpression
param, P value) {
+ return queryMixin.set(param, value);
+ }
+
+ @SuppressWarnings("unchecked")
+ public Q sort(Sort sort) {
+ this.querySort = sort;
+ return (Q) this;
+ }
+
+ @Nullable
+ private T oneResult(boolean unique) {
+ try {
+ int maxDoc = maxDoc();
+ if (maxDoc == 0) {
+ return null;
+ }
+ final ScoreDoc[] scoreDocs =
+ searcher.search(createQuery(), maxDoc, Sort.INDEXORDER).scoreDocs;
+ int index = 0;
+ QueryModifiers modifiers = queryMixin.getMetadata().getModifiers();
+ Long offset = modifiers.getOffset();
+ if (offset != null) {
+ index = offset.intValue();
+ }
+ Long limit = modifiers.getLimit();
+ if (unique
+ && (limit == null ? scoreDocs.length - index > 1 : limit > 1 && scoreDocs.length > 1)) {
+ throw new NonUniqueResultException(
+ "Unique result requested, but " + scoreDocs.length + " found.");
+ } else if (scoreDocs.length > index) {
+ Document document;
+ if (fieldsToLoad != null) {
+ document = searcher.storedFields().document(scoreDocs[index].doc, fieldsToLoad);
+ } else {
+ document = searcher.storedFields().document(scoreDocs[index].doc);
+ }
+ return transformer.apply(document);
+ } else {
+ return null;
+ }
+ } catch (IOException | IllegalArgumentException e) {
+ throw new QueryException(e);
+ }
+ }
+
+ @Override
+ public T fetchFirst() {
+ return oneResult(false);
+ }
+
+ @Override
+ public T fetchOne() throws NonUniqueResultException {
+ return oneResult(true);
+ }
+
+ public Q where(Predicate e) {
+ return queryMixin.where(e);
+ }
+
+ @Override
+ public Q where(Predicate... e) {
+ return queryMixin.where(e);
+ }
+
+ @Override
+ public String toString() {
+ return createQuery().toString().replaceAll(JAVA_ISO_CONTROL, "_");
+ }
+
+ private int maxDoc() throws IOException {
+ return searcher.getIndexReader().maxDoc();
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/IgnoreCaseUnsupportedException.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/IgnoreCaseUnsupportedException.java
new file mode 100644
index 000000000..7d6fd71d5
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/IgnoreCaseUnsupportedException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+/**
+ * Thrown for case ignore usage
+ *
+ * @author tiwe
+ */
+public class IgnoreCaseUnsupportedException extends UnsupportedOperationException {
+
+ private static final long serialVersionUID = 412913389929530788L;
+
+ public IgnoreCaseUnsupportedException() {
+ super("Ignore case queries are not supported with Lucene");
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneExpressions.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneExpressions.java
new file mode 100644
index 000000000..27ad3d5bd
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneExpressions.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.FuzzyQuery;
+import org.apache.lucene.util.automaton.LevenshteinAutomata;
+
+/**
+ * Utility methods to create filter expressions for Lucene queries that are not covered by the
+ * Querydsl standard expression model
+ *
+ * @author tiwe
+ */
+public final class LuceneExpressions {
+
+ /**
+ * Create a fuzzy query
+ *
+ * @param path path
+ * @param value value to match
+ * @return condition
+ */
+ public static BooleanExpression fuzzyLike(Path path, String value) {
+ Term term = new Term(path.getMetadata().getName(), value);
+ return new QueryElement(new FuzzyQuery(term));
+ }
+
+ /**
+ * Create a fuzzy query
+ *
+ * @param path path
+ * @param value value to match
+ * @param maxEdits must be >= 0 and <= {@link
+ * LevenshteinAutomata#MAXIMUM_SUPPORTED_DISTANCE}.
+ * @return condition
+ */
+ public static BooleanExpression fuzzyLike(Path path, String value, int maxEdits) {
+ Term term = new Term(path.getMetadata().getName(), value);
+ return new QueryElement(new FuzzyQuery(term, maxEdits));
+ }
+
+ /**
+ * Create a fuzzy query
+ *
+ * @param path path
+ * @param value value to match
+ * @param maxEdits must be >= 0 and <= {@link
+ * LevenshteinAutomata#MAXIMUM_SUPPORTED_DISTANCE}.
+ * @param prefixLength length of common (non-fuzzy) prefix
+ * @return condition
+ */
+ public static BooleanExpression fuzzyLike(
+ Path path, String value, int maxEdits, int prefixLength) {
+ Term term = new Term(path.getMetadata().getName(), value);
+ return new QueryElement(new FuzzyQuery(term, maxEdits, prefixLength));
+ }
+
+ private LuceneExpressions() {}
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneOps.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneOps.java
new file mode 100644
index 000000000..936e299c2
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneOps.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.types.Operator;
+
+/**
+ * Lucene specific operators
+ *
+ * @author tiwe
+ */
+public enum LuceneOps implements Operator {
+ LUCENE_QUERY(Object.class),
+ PHRASE(String.class),
+ TERM(String.class);
+
+ private final Class> type;
+
+ LuceneOps(Class> type) {
+ this.type = type;
+ }
+
+ @Override
+ public Class> getType() {
+ return type;
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneQuery.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneQuery.java
new file mode 100644
index 000000000..8617228bc
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneQuery.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import java.util.function.Function;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.search.IndexSearcher;
+
+/**
+ * {@code LuceneQuery} is a Querydsl query implementation for Lucene queries.
+ *
+ *
Example:
+ *
+ *
{@code
+ * QDocument doc = new QDocument("doc");
+ * IndexSearcher searcher = new IndexSearcher(index);
+ * LuceneQuery query = new LuceneQuery(true, searcher);
+ * List documents = query
+ * .where(doc.year.between("1800", "2000").and(doc.title.startsWith("Huckle"))
+ * .fetch();
+ * }
+ *
+ * @author vema
+ */
+public class LuceneQuery extends AbstractLuceneQuery {
+
+ private static final Function TRANSFORMER =
+ new Function() {
+ @Override
+ public Document apply(Document input) {
+ return input;
+ }
+ };
+
+ public LuceneQuery(IndexSearcher searcher) {
+ super(searcher, TRANSFORMER);
+ }
+
+ public LuceneQuery(LuceneSerializer luceneSerializer, IndexSearcher searcher) {
+ super(luceneSerializer, searcher, TRANSFORMER);
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneSerializer.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneSerializer.java
new file mode 100644
index 000000000..a8a69ce60
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/LuceneSerializer.java
@@ -0,0 +1,572 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.QueryMetadata;
+import com.querydsl.core.types.Constant;
+import com.querydsl.core.types.Expression;
+import com.querydsl.core.types.ExpressionUtils;
+import com.querydsl.core.types.Operation;
+import com.querydsl.core.types.Operator;
+import com.querydsl.core.types.Ops;
+import com.querydsl.core.types.OrderSpecifier;
+import com.querydsl.core.types.ParamExpression;
+import com.querydsl.core.types.ParamNotSetException;
+import com.querydsl.core.types.Path;
+import com.querydsl.core.types.PathMetadata;
+import com.querydsl.core.types.PathType;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.lucene.document.DoublePoint;
+import org.apache.lucene.document.FloatPoint;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.queryparser.classic.QueryParser;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.MatchAllDocsQuery;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.SortedNumericSortField;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TermRangeQuery;
+import org.apache.lucene.search.WildcardQuery;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Serializes Querydsl queries to Lucene queries.
+ *
+ * @author vema
+ */
+public class LuceneSerializer {
+ private static final Map, SortField.Type> sortFields =
+ new HashMap, SortField.Type>();
+
+ static {
+ sortFields.put(Integer.class, SortField.Type.INT);
+ sortFields.put(Float.class, SortField.Type.FLOAT);
+ sortFields.put(Long.class, SortField.Type.LONG);
+ sortFields.put(Double.class, SortField.Type.DOUBLE);
+ sortFields.put(BigDecimal.class, SortField.Type.DOUBLE);
+ sortFields.put(BigInteger.class, SortField.Type.LONG);
+ }
+
+ public static final LuceneSerializer DEFAULT = new LuceneSerializer(false, true);
+
+ private final boolean lowerCase;
+
+ private final boolean splitTerms;
+
+ private final Locale sortLocale;
+
+ public LuceneSerializer(boolean lowerCase, boolean splitTerms) {
+ this(lowerCase, splitTerms, Locale.getDefault());
+ }
+
+ public LuceneSerializer(boolean lowerCase, boolean splitTerms, Locale sortLocale) {
+ this.lowerCase = lowerCase;
+ this.splitTerms = splitTerms;
+ this.sortLocale = sortLocale;
+ }
+
+ private Query toQuery(Operation> operation, QueryMetadata metadata) {
+ Operator op = operation.getOperator();
+ if (op == Ops.OR) {
+ return toTwoHandSidedQuery(operation, Occur.SHOULD, metadata);
+ } else if (op == Ops.AND) {
+ return toTwoHandSidedQuery(operation, Occur.MUST, metadata);
+ } else if (op == Ops.NOT) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ builder.add(new BooleanClause(toQuery(operation.getArg(0), metadata), Occur.MUST_NOT));
+ builder.add(new BooleanClause(new MatchAllDocsQuery(), Occur.MUST));
+ return builder.build();
+ } else if (op == Ops.LIKE) {
+ return like(operation, metadata);
+ } else if (op == Ops.LIKE_IC) {
+ throw new IgnoreCaseUnsupportedException();
+ } else if (op == Ops.EQ) {
+ return eq(operation, metadata, false);
+ } else if (op == Ops.EQ_IGNORE_CASE) {
+ throw new IgnoreCaseUnsupportedException();
+ } else if (op == Ops.NE) {
+ return ne(operation, metadata, false);
+ } else if (op == Ops.STARTS_WITH) {
+ return startsWith(metadata, operation, false);
+ } else if (op == Ops.STARTS_WITH_IC) {
+ throw new IgnoreCaseUnsupportedException();
+ } else if (op == Ops.ENDS_WITH) {
+ return endsWith(operation, metadata, false);
+ } else if (op == Ops.ENDS_WITH_IC) {
+ throw new IgnoreCaseUnsupportedException();
+ } else if (op == Ops.STRING_CONTAINS) {
+ return stringContains(operation, metadata, false);
+ } else if (op == Ops.STRING_CONTAINS_IC) {
+ throw new IgnoreCaseUnsupportedException();
+ } else if (op == Ops.BETWEEN) {
+ return between(operation, metadata);
+ } else if (op == Ops.IN) {
+ return in(operation, metadata, false);
+ } else if (op == Ops.NOT_IN) {
+ return notIn(operation, metadata, false);
+ } else if (op == Ops.LT) {
+ return lt(operation, metadata);
+ } else if (op == Ops.GT) {
+ return gt(operation, metadata);
+ } else if (op == Ops.LOE) {
+ return le(operation, metadata);
+ } else if (op == Ops.GOE) {
+ return ge(operation, metadata);
+ } else if (op == LuceneOps.LUCENE_QUERY) {
+ @SuppressWarnings("unchecked")
+ Constant expectedConstant = (Constant) operation.getArg(0);
+ return expectedConstant.getConstant();
+ }
+ throw new UnsupportedOperationException("Illegal operation " + operation);
+ }
+
+ private Query toTwoHandSidedQuery(Operation> operation, Occur occur, QueryMetadata metadata) {
+ Query lhs = toQuery(operation.getArg(0), metadata);
+ Query rhs = toQuery(operation.getArg(1), metadata);
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ builder.add(createBooleanClause(lhs, occur));
+ builder.add(createBooleanClause(rhs, occur));
+ return builder.build();
+ }
+
+ /**
+ * If the query is a BooleanQuery and it contains a single Occur.MUST_NOT clause it will be
+ * returned as is. Otherwise it will be wrapped in a BooleanClause with the given Occur.
+ */
+ private BooleanClause createBooleanClause(Query query, Occur occur) {
+ if (query instanceof BooleanQuery booleanQuery) {
+ List clauses = booleanQuery.clauses();
+ if (clauses.size() == 1 && clauses.get(0).occur().equals(Occur.MUST_NOT)) {
+ return clauses.get(0);
+ }
+ }
+ return new BooleanClause(query, occur);
+ }
+
+ protected Query like(Operation> operation, QueryMetadata metadata) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ String field = toField(path);
+ String[] terms = convert(path, operation.getArg(1));
+ if (terms.length > 1) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ for (String s : terms) {
+ builder.add(new WildcardQuery(new Term(field, "*" + s + "*")), Occur.MUST);
+ }
+ return builder.build();
+ }
+ return new WildcardQuery(new Term(field, terms[0]));
+ }
+
+ protected Query eq(Operation> operation, QueryMetadata metadata, boolean ignoreCase) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ String field = toField(path);
+
+ if (Number.class.isAssignableFrom(operation.getArg(1).getType())) {
+ @SuppressWarnings("unchecked")
+ Constant extends Number> rightArg = (Constant extends Number>) operation.getArg(1);
+ return exactNumberQuery(field, rightArg.getConstant());
+ }
+
+ return eq(field, convert(path, operation.getArg(1), metadata), ignoreCase);
+ }
+
+ private Query exactNumberQuery(String field, Number number) {
+ if (Integer.class.isInstance(number)
+ || Byte.class.isInstance(number)
+ || Short.class.isInstance(number)) {
+ return IntPoint.newExactQuery(field, number.intValue());
+ } else if (Long.class.isInstance(number) || BigInteger.class.isInstance(number)) {
+ return LongPoint.newExactQuery(field, number.longValue());
+ } else if (Double.class.isInstance(number) || BigDecimal.class.isInstance(number)) {
+ return DoublePoint.newExactQuery(field, number.doubleValue());
+ } else if (Float.class.isInstance(number)) {
+ return FloatPoint.newExactQuery(field, number.floatValue());
+ } else {
+ throw new IllegalArgumentException("Unsupported numeric type " + number.getClass().getName());
+ }
+ }
+
+ protected Query eq(String field, String[] terms, boolean ignoreCase) {
+ if (terms.length > 1) {
+ PhraseQuery.Builder builder = new PhraseQuery.Builder();
+ for (String s : terms) {
+ builder.add(new Term(field, s));
+ }
+ return builder.build();
+ }
+ return new TermQuery(new Term(field, terms[0]));
+ }
+
+ protected Query in(Operation> operation, QueryMetadata metadata, boolean ignoreCase) {
+ Path> path = getPath(operation.getArg(0));
+ String field = toField(path);
+ @SuppressWarnings("unchecked")
+ Constant> expectedConstant = (Constant>) operation.getArg(1);
+ Collection> values = expectedConstant.getConstant();
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ if (Number.class.isAssignableFrom(path.getType())) {
+ for (Object value : values) {
+ builder.add(exactNumberQuery(field, (Number) value), Occur.SHOULD);
+ }
+ } else {
+ for (Object value : values) {
+ String[] str = convert(path, value);
+ builder.add(eq(field, str, ignoreCase), Occur.SHOULD);
+ }
+ }
+ return builder.build();
+ }
+
+ protected Query notIn(Operation> operation, QueryMetadata metadata, boolean ignoreCase) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ builder.add(new BooleanClause(in(operation, metadata, false), Occur.MUST_NOT));
+ builder.add(new BooleanClause(new MatchAllDocsQuery(), Occur.MUST));
+ return builder.build();
+ }
+
+ protected Query ne(Operation> operation, QueryMetadata metadata, boolean ignoreCase) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ builder.add(new BooleanClause(eq(operation, metadata, ignoreCase), Occur.MUST_NOT));
+ builder.add(new BooleanClause(new MatchAllDocsQuery(), Occur.MUST));
+ return builder.build();
+ }
+
+ protected Query startsWith(QueryMetadata metadata, Operation> operation, boolean ignoreCase) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ String field = toField(path);
+ String[] terms = convertEscaped(path, operation.getArg(1), metadata);
+ if (terms.length > 1) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ for (int i = 0; i < terms.length; ++i) {
+ String s = i == 0 ? terms[i] + "*" : "*" + terms[i] + "*";
+ builder.add(new WildcardQuery(new Term(field, s)), Occur.MUST);
+ }
+ return builder.build();
+ }
+ return new PrefixQuery(new Term(field, terms[0]));
+ }
+
+ protected Query stringContains(
+ Operation> operation, QueryMetadata metadata, boolean ignoreCase) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ String field = toField(path);
+ String[] terms = convertEscaped(path, operation.getArg(1), metadata);
+ if (terms.length > 1) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ for (String s : terms) {
+ builder.add(new WildcardQuery(new Term(field, "*" + s + "*")), Occur.MUST);
+ }
+ return builder.build();
+ }
+ return new WildcardQuery(new Term(field, "*" + terms[0] + "*"));
+ }
+
+ protected Query endsWith(Operation> operation, QueryMetadata metadata, boolean ignoreCase) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ String field = toField(path);
+ String[] terms = convertEscaped(path, operation.getArg(1), metadata);
+ if (terms.length > 1) {
+ BooleanQuery.Builder builder = new BooleanQuery.Builder();
+ for (int i = 0; i < terms.length; ++i) {
+ String s = i == terms.length - 1 ? "*" + terms[i] : "*" + terms[i] + "*";
+ builder.add(new WildcardQuery(new Term(field, s)), Occur.MUST);
+ }
+ return builder.build();
+ }
+ return new WildcardQuery(new Term(field, "*" + terms[0]));
+ }
+
+ protected Query between(Operation> operation, QueryMetadata metadata) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ return range(
+ path, toField(path), operation.getArg(1), operation.getArg(2), true, true, metadata);
+ }
+
+ protected Query lt(Operation> operation, QueryMetadata metadata) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ return range(path, toField(path), null, operation.getArg(1), false, false, metadata);
+ }
+
+ protected Query gt(Operation> operation, QueryMetadata metadata) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ return range(path, toField(path), operation.getArg(1), null, false, false, metadata);
+ }
+
+ protected Query le(Operation> operation, QueryMetadata metadata) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ return range(path, toField(path), null, operation.getArg(1), true, true, metadata);
+ }
+
+ protected Query ge(Operation> operation, QueryMetadata metadata) {
+ verifyArguments(operation);
+ Path> path = getPath(operation.getArg(0));
+ return range(path, toField(path), operation.getArg(1), null, true, true, metadata);
+ }
+
+ protected Query range(
+ Path> leftHandSide,
+ String field,
+ @Nullable Expression> min,
+ @Nullable Expression> max,
+ boolean minInc,
+ boolean maxInc,
+ QueryMetadata metadata) {
+ if (min != null && Number.class.isAssignableFrom(min.getType())
+ || max != null && Number.class.isAssignableFrom(max.getType())) {
+ @SuppressWarnings("unchecked")
+ Constant extends Number> minConstant = (Constant extends Number>) min;
+ @SuppressWarnings("unchecked")
+ Constant extends Number> maxConstant = (Constant extends Number>) max;
+
+ Class extends Number> numType =
+ minConstant != null ? minConstant.getType() : maxConstant.getType();
+ @SuppressWarnings("unchecked")
+ Class unboundedNumType = (Class) numType;
+ return numericRange(
+ unboundedNumType,
+ field,
+ minConstant == null ? null : minConstant.getConstant(),
+ maxConstant == null ? null : maxConstant.getConstant(),
+ minInc,
+ maxInc);
+ }
+ return stringRange(leftHandSide, field, min, max, minInc, maxInc, metadata);
+ }
+
+ protected Query numericRange(
+ Class clazz,
+ String field,
+ @Nullable N min,
+ @Nullable N max,
+ boolean minInc,
+ boolean maxInc) {
+ if (clazz.equals(Integer.class) || clazz.equals(Byte.class) || clazz.equals(Short.class)) {
+ int lower = min != null ? adjustBound(min.intValue(), minInc, true) : Integer.MIN_VALUE;
+ int upper = max != null ? adjustBound(max.intValue(), maxInc, false) : Integer.MAX_VALUE;
+ return IntPoint.newRangeQuery(field, lower, upper);
+ } else if (clazz.equals(Long.class)) {
+ long lower = min != null ? adjustBound(min.longValue(), minInc, true) : Long.MIN_VALUE;
+ long upper = max != null ? adjustBound(max.longValue(), maxInc, false) : Long.MAX_VALUE;
+ return LongPoint.newRangeQuery(field, lower, upper);
+ } else if (clazz.equals(Double.class)) {
+ double lower =
+ min != null
+ ? (minInc ? min.doubleValue() : Math.nextUp(min.doubleValue()))
+ : Double.NEGATIVE_INFINITY;
+ double upper =
+ max != null
+ ? (maxInc ? max.doubleValue() : Math.nextDown(max.doubleValue()))
+ : Double.POSITIVE_INFINITY;
+ return DoublePoint.newRangeQuery(field, lower, upper);
+ } else if (clazz.equals(Float.class)) {
+ float lower =
+ min != null
+ ? (minInc ? min.floatValue() : Math.nextUp(min.floatValue()))
+ : Float.NEGATIVE_INFINITY;
+ float upper =
+ max != null
+ ? (maxInc ? max.floatValue() : Math.nextDown(max.floatValue()))
+ : Float.POSITIVE_INFINITY;
+ return FloatPoint.newRangeQuery(field, lower, upper);
+ } else {
+ throw new IllegalArgumentException("Unsupported numeric type " + clazz.getName());
+ }
+ }
+
+ private int adjustBound(int value, boolean inclusive, boolean isLower) {
+ if (inclusive) {
+ return value;
+ }
+ return isLower ? Math.addExact(value, 1) : Math.addExact(value, -1);
+ }
+
+ private long adjustBound(long value, boolean inclusive, boolean isLower) {
+ if (inclusive) {
+ return value;
+ }
+ return isLower ? Math.addExact(value, 1L) : Math.addExact(value, -1L);
+ }
+
+ protected Query stringRange(
+ Path> leftHandSide,
+ String field,
+ @Nullable Expression> min,
+ @Nullable Expression> max,
+ boolean minInc,
+ boolean maxInc,
+ QueryMetadata metadata) {
+
+ if (min == null) {
+ return TermRangeQuery.newStringRange(
+ field, null, convert(leftHandSide, max, metadata)[0], minInc, maxInc);
+ } else if (max == null) {
+ return TermRangeQuery.newStringRange(
+ field, convert(leftHandSide, min, metadata)[0], null, minInc, maxInc);
+ } else {
+ return TermRangeQuery.newStringRange(
+ field,
+ convert(leftHandSide, min, metadata)[0],
+ convert(leftHandSide, max, metadata)[0],
+ minInc,
+ maxInc);
+ }
+ }
+
+ private Path> getPath(Expression> leftHandSide) {
+ if (leftHandSide instanceof Path>) {
+ return (Path>) leftHandSide;
+ } else if (leftHandSide instanceof Operation>) {
+ Operation> operation = (Operation>) leftHandSide;
+ if (operation.getOperator() == Ops.LOWER || operation.getOperator() == Ops.UPPER) {
+ return (Path>) operation.getArg(0);
+ }
+ }
+ throw new IllegalArgumentException("Unable to transform " + leftHandSide + " to path");
+ }
+
+ /**
+ * template method, override to customize
+ *
+ * @param path path
+ * @return field name
+ */
+ protected String toField(Path> path) {
+ PathMetadata md = path.getMetadata();
+ if (md.getPathType() == PathType.COLLECTION_ANY) {
+ return toField(md.getParent());
+ } else {
+ String rv = md.getName();
+ if (md.getParent() != null) {
+ Path> parent = md.getParent();
+ if (parent.getMetadata().getPathType() != PathType.VARIABLE) {
+ rv = toField(parent) + "." + rv;
+ }
+ }
+ return rv;
+ }
+ }
+
+ private void verifyArguments(Operation> operation) {
+ List> arguments = operation.getArgs();
+ for (int i = 1; i < arguments.size(); ++i) {
+ if (!(arguments.get(i) instanceof Constant>)
+ && !(arguments.get(i) instanceof ParamExpression>)
+ && !(arguments.get(i) instanceof PhraseElement)
+ && !(arguments.get(i) instanceof TermElement)) {
+ throw new IllegalArgumentException(
+ "operand was of unsupported type " + arguments.get(i).getClass().getName());
+ }
+ }
+ }
+
+ protected String[] convert(
+ Path> leftHandSide, Expression> rightHandSide, QueryMetadata metadata) {
+ if (rightHandSide instanceof Operation) {
+ Operation> operation = (Operation>) rightHandSide;
+ if (operation.getOperator() == LuceneOps.PHRASE) {
+ return operation.getArg(0).toString().split("\\s+");
+ } else if (operation.getOperator() == LuceneOps.TERM) {
+ return new String[] {operation.getArg(0).toString()};
+ } else {
+ throw new IllegalArgumentException(rightHandSide.toString());
+ }
+ } else if (rightHandSide instanceof ParamExpression>) {
+ Object value = metadata.getParams().get(rightHandSide);
+ if (value == null) {
+ throw new ParamNotSetException((ParamExpression>) rightHandSide);
+ }
+ return convert(leftHandSide, value);
+
+ } else if (rightHandSide instanceof Constant>) {
+ return convert(leftHandSide, ((Constant>) rightHandSide).getConstant());
+ } else {
+ throw new IllegalArgumentException(rightHandSide.toString());
+ }
+ }
+
+ protected String[] convert(Path> leftHandSide, Object rightHandSide) {
+ String str = rightHandSide.toString();
+ if (lowerCase) {
+ str = str.toLowerCase();
+ }
+ if (splitTerms) {
+ if (str.equals("")) {
+ return new String[] {str};
+ } else {
+ return str.split("\\s+");
+ }
+ } else {
+ return new String[] {str};
+ }
+ }
+
+ private String[] convertEscaped(
+ Path> leftHandSide, Expression> rightHandSide, QueryMetadata metadata) {
+ String[] str = convert(leftHandSide, rightHandSide, metadata);
+ for (int i = 0; i < str.length; i++) {
+ str[i] = QueryParser.escape(str[i]);
+ }
+ return str;
+ }
+
+ public Query toQuery(Expression> expr, QueryMetadata metadata) {
+ if (expr instanceof Operation>) {
+ return toQuery((Operation>) expr, metadata);
+ } else {
+ return toQuery(ExpressionUtils.extract(expr), metadata);
+ }
+ }
+
+ public Sort toSort(List extends OrderSpecifier>> orderBys) {
+ List sorts = new ArrayList(orderBys.size());
+ for (OrderSpecifier> order : orderBys) {
+ if (!(order.getTarget() instanceof Path>)) {
+ throw new IllegalArgumentException("argument was not of type Path.");
+ }
+ Class> type = order.getTarget().getType();
+ boolean reverse = !order.isAscending();
+ Path> path = getPath(order.getTarget());
+ if (Number.class.isAssignableFrom(type)) {
+ sorts.add(new SortedNumericSortField(toField(path), sortFields.get(type), reverse));
+ } else {
+ sorts.add(new SortField(toField(path), SortField.Type.STRING, reverse));
+ }
+ }
+ return new Sort(sorts.toArray(new SortField[0]));
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/PhraseElement.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/PhraseElement.java
new file mode 100644
index 000000000..f1dab7193
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/PhraseElement.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.types.ConstantImpl;
+import com.querydsl.core.types.dsl.StringOperation;
+
+/**
+ * {@code PhraseElement} represents the embedded String as a phrase
+ *
+ * @author tiwe
+ */
+public class PhraseElement extends StringOperation {
+
+ private static final long serialVersionUID = 2350215644019186076L;
+
+ public PhraseElement(String str) {
+ super(LuceneOps.PHRASE, ConstantImpl.create(str));
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/QueryElement.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/QueryElement.java
new file mode 100644
index 000000000..1983c39a7
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/QueryElement.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.types.ConstantImpl;
+import com.querydsl.core.types.dsl.BooleanOperation;
+import org.apache.lucene.search.Query;
+
+/**
+ * {@code QueryElement} wraps a Lucene Query
+ *
+ * @author tiwe
+ */
+public class QueryElement extends BooleanOperation {
+
+ private static final long serialVersionUID = 470868107363840155L;
+
+ public QueryElement(Query query) {
+ super(LuceneOps.LUCENE_QUERY, ConstantImpl.create(query));
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/ResultIterator.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/ResultIterator.java
new file mode 100644
index 000000000..00ba4ede4
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/ResultIterator.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.CloseableIterator;
+import com.querydsl.core.QueryException;
+import java.io.IOException;
+import java.util.Set;
+import java.util.function.Function;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.ScoreDoc;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@code ResultIterator} is a {@link CloseableIterator} implementation for Lucene query results
+ *
+ * @author tiwe
+ * @param
+ */
+public final class ResultIterator implements CloseableIterator {
+
+ private final ScoreDoc[] scoreDocs;
+
+ private int cursor;
+
+ private final IndexSearcher searcher;
+
+ @Nullable private final Set fieldsToLoad;
+
+ private final Function transformer;
+
+ public ResultIterator(
+ ScoreDoc[] scoreDocs,
+ int offset,
+ IndexSearcher searcher,
+ @Nullable Set fieldsToLoad,
+ Function transformer) {
+ this.scoreDocs = scoreDocs.clone();
+ this.cursor = offset;
+ this.searcher = searcher;
+ this.fieldsToLoad = fieldsToLoad;
+ this.transformer = transformer;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return cursor != scoreDocs.length;
+ }
+
+ @Override
+ public T next() {
+ try {
+ Document document;
+ if (fieldsToLoad != null) {
+ document = searcher.storedFields().document(scoreDocs[cursor++].doc, fieldsToLoad);
+ } else {
+ document = searcher.storedFields().document(scoreDocs[cursor++].doc);
+ }
+ return transformer.apply(document);
+ } catch (IOException e) {
+ throw new QueryException(e);
+ }
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {}
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/TermElement.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/TermElement.java
new file mode 100644
index 000000000..8fa5a599d
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/TermElement.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import com.querydsl.core.types.ConstantImpl;
+import com.querydsl.core.types.dsl.StringOperation;
+
+/**
+ * {@code TermElement} represents the embedded String as a term
+ *
+ * @author tiwe
+ */
+public class TermElement extends StringOperation {
+
+ private static final long serialVersionUID = 2350215644019186076L;
+
+ public TermElement(String str) {
+ super(LuceneOps.TERM, ConstantImpl.create(str));
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/TypedQuery.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/TypedQuery.java
new file mode 100644
index 000000000..da26bf825
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/TypedQuery.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import java.util.function.Function;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.search.IndexSearcher;
+
+/**
+ * {@code TypedQuery} is a typed query implementation for Lucene queries.
+ *
+ *
Converts Lucene documents to typed results via a constructor supplied transformer
+ *
+ * @param result type
+ * @author laim
+ * @author tiwe
+ */
+public class TypedQuery extends AbstractLuceneQuery> {
+
+ /**
+ * Create a new TypedQuery instance
+ *
+ * @param searcher index searcher
+ * @param transformer transformer to transform Lucene documents to result objects
+ */
+ public TypedQuery(IndexSearcher searcher, Function transformer) {
+ super(searcher, transformer);
+ }
+
+ /**
+ * Create a new TypedQuery instance
+ *
+ * @param serializer serializer
+ * @param searcher index searcher
+ * @param transformer transformer to transform Lucene documents to result objects
+ */
+ public TypedQuery(
+ LuceneSerializer serializer, IndexSearcher searcher, Function transformer) {
+ super(serializer, searcher, transformer);
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/package-info.java b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/package-info.java
new file mode 100644
index 000000000..cf99c7637
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/main/java/com/querydsl/lucene10/package-info.java
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Lucene 10 support */
+package com.querydsl.lucene10;
diff --git a/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneQueryTest.java b/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneQueryTest.java
new file mode 100644
index 000000000..aa9fb833a
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneQueryTest.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.querydsl.core.NonUniqueResultException;
+import com.querydsl.core.QueryException;
+import com.querydsl.core.QueryModifiers;
+import com.querydsl.core.QueryResults;
+import com.querydsl.core.types.ParamNotSetException;
+import com.querydsl.core.types.dsl.Expressions;
+import com.querydsl.core.types.dsl.NumberPath;
+import com.querydsl.core.types.dsl.Param;
+import com.querydsl.core.types.dsl.StringPath;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.DoubleDocValuesField;
+import org.apache.lucene.document.DoublePoint;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.NumericDocValuesField;
+import org.apache.lucene.document.SortedDocValuesField;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.store.ByteBuffersDirectory;
+import org.apache.lucene.util.BytesRef;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for LuceneQuery
+ *
+ * @author vema
+ */
+public class LuceneQueryTest {
+
+ private LuceneQuery query;
+ private StringPath title;
+ private NumberPath year;
+ private NumberPath gross;
+
+ private final StringPath sort = Expressions.stringPath("sort");
+
+ private ByteBuffersDirectory idx;
+ private IndexWriter writer;
+ private IndexSearcher searcher;
+
+ private Document createDocument(
+ final String docTitle,
+ final String docAuthor,
+ final String docText,
+ final int docYear,
+ final double docGross) {
+ Document doc = new Document();
+
+ doc.add(new TextField("title", docTitle, Store.YES));
+ doc.add(new SortedDocValuesField("title", new BytesRef(docTitle)));
+
+ doc.add(new TextField("author", docAuthor, Store.YES));
+ doc.add(new SortedDocValuesField("author", new BytesRef(docAuthor)));
+
+ doc.add(new TextField("text", docText, Store.YES));
+ doc.add(new SortedDocValuesField("text", new BytesRef(docText)));
+
+ doc.add(new IntPoint("year", docYear));
+ doc.add(new StoredField("year", docYear));
+ doc.add(new NumericDocValuesField("year", docYear));
+
+ doc.add(new DoublePoint("gross", docGross));
+ doc.add(new StoredField("gross", docGross));
+ doc.add(new DoubleDocValuesField("gross", docGross));
+
+ return doc;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final QDocument entityPath = new QDocument("doc");
+ title = entityPath.title;
+ year = entityPath.year;
+ gross = entityPath.gross;
+
+ idx = new ByteBuffersDirectory();
+ writer = createWriter(idx);
+
+ writer.addDocument(
+ createDocument(
+ "Jurassic Park", "Michael Crichton", "It's a UNIX system! I know this!", 1990, 90.00));
+ writer.addDocument(
+ createDocument(
+ "Nummisuutarit", "Aleksis Kivi", "ESKO. Ja iloitset ja riemuitset?", 1864, 10.00));
+ writer.addDocument(
+ createDocument(
+ "The Lord of the Rings",
+ "John R. R. Tolkien",
+ "One Ring to rule them all, One Ring to find them, One Ring to bring them all and in"
+ + " the darkness bind them",
+ 1954,
+ 89.00));
+ writer.addDocument(
+ createDocument(
+ "Introduction to Algorithms",
+ "Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein",
+ "Bubble sort",
+ 1990,
+ 30.50));
+
+ writer.close();
+
+ IndexReader reader = DirectoryReader.open(idx);
+ searcher = new IndexSearcher(reader);
+ query = new LuceneQuery(new LuceneSerializer(true, true), searcher);
+ }
+
+ private IndexWriter createWriter(ByteBuffersDirectory idx) throws Exception {
+ IndexWriterConfig config =
+ new IndexWriterConfig(new StandardAnalyzer())
+ .setOpenMode(IndexWriterConfig.OpenMode.CREATE);
+ return new IndexWriter(idx, config);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ searcher.getIndexReader().close();
+ }
+
+ @Test
+ public void between() {
+ assertThat(query.where(year.between(1950, 1990)).fetchCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void count_empty_where_clause() {
+ assertThat(query.fetchCount()).isEqualTo(4);
+ }
+
+ @Test
+ public void exists() {
+ assertThat(query.where(title.eq("Jurassic Park")).fetchCount() > 0).isTrue();
+ assertThat(query.where(title.eq("Jurassic Park X")).fetchCount() > 0).isFalse();
+ }
+
+ @Test
+ public void notExists() {
+ assertThat(query.where(title.eq("Jurassic Park")).fetchCount() == 0).isFalse();
+ assertThat(query.where(title.eq("Jurassic Park X")).fetchCount() == 0).isTrue();
+ }
+
+ @Test
+ public void count() {
+ query.where(title.eq("Jurassic Park"));
+ assertThat(query.fetchCount()).isEqualTo(1);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void countDistinct() {
+ query.where(year.between(1900, 3000));
+ assertThat(query.distinct().fetchCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void in() {
+ assertThat(query.where(title.in("Jurassic Park", "Nummisuutarit")).fetchCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void in2() {
+ assertThat(query.where(year.in(1990, 1864)).fetchCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void list_sorted_by_year_ascending() {
+ query.where(year.between(1800, 2000));
+ query.orderBy(year.asc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_not_sorted() {
+ query.where(year.between(1800, 2000));
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_not_sorted_limit_2() {
+ query.where(year.between(1800, 2000));
+ query.limit(2);
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(2);
+ }
+
+ @Test
+ public void list_sorted_by_year_limit_1() {
+ query.where(year.between(1800, 2000));
+ query.limit(1);
+ query.orderBy(year.asc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(1);
+ }
+
+ @Test
+ public void list_not_sorted_offset_2() {
+ query.where(year.between(1800, 2000));
+ query.offset(2);
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(2);
+ }
+
+ @Test
+ public void list_sorted_ascending_by_year_offset_2() {
+ query.where(year.between(1800, 2000));
+ query.offset(2);
+ query.orderBy(year.asc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(2);
+ }
+
+ @Test
+ public void list_sorted_ascending_by_year_restrict_limit_2_offset_1() {
+ query.where(year.between(1800, 2000));
+ query.restrict(new QueryModifiers(2L, 1L));
+ query.orderBy(year.asc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(2);
+ }
+
+ @Test
+ public void list_sorted_ascending_by_year() {
+ query.where(year.between(1800, 2000));
+ query.orderBy(year.asc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_sort() {
+ Sort sort = LuceneSerializer.DEFAULT.toSort(Collections.singletonList(year.asc()));
+
+ query.where(year.between(1800, 2000));
+ query.sort(sort);
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_with_filter() {
+ assertThat(query.fetch()).hasSize(4);
+ assertThat(query.filter(IntPoint.newExactQuery("year", 1990)).fetch()).hasSize(2);
+ }
+
+ @Test
+ public void list_sorted_descending_by_year() {
+ query.where(year.between(1800, 2000));
+ query.orderBy(year.desc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_sorted_descending_by_gross() {
+ query.where(gross.between(0.0, 1000.00));
+ query.orderBy(gross.desc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_sorted_descending_by_year_and_ascending_by_title() {
+ query.where(year.between(1800, 2000));
+ query.orderBy(year.desc());
+ query.orderBy(title.asc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_sorted_descending_by_year_and_descending_by_title() {
+ query.where(year.between(1800, 2000));
+ query.orderBy(year.desc());
+ query.orderBy(title.desc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void offset() {
+ assertThat(query.where(title.eq("Jurassic Park")).offset(30).fetch()).isEmpty();
+ }
+
+ @Test
+ public void load_list() {
+ Document document = query.where(title.ne("")).load(title).fetch().get(0);
+ assertThat(document.get("title")).isNotNull();
+ assertThat(document.get("year")).isNull();
+ }
+
+ @Test
+ public void load_list_fieldSelector() {
+ Document document =
+ query.where(title.ne("")).load(Collections.singleton("title")).fetch().get(0);
+ assertThat(document.get("title")).isNotNull();
+ assertThat(document.get("year")).isNull();
+ }
+
+ @Test
+ public void load_singleResult() {
+ Document document = query.where(title.ne("")).load(title).fetchFirst();
+ assertThat(document.get("title")).isNotNull();
+ assertThat(document.get("year")).isNull();
+ }
+
+ @Test
+ public void load_singleResult_fieldSelector() {
+ Document document = query.where(title.ne("")).load(Collections.singleton("title")).fetchFirst();
+ assertThat(document.get("title")).isNotNull();
+ assertThat(document.get("year")).isNull();
+ }
+
+ @Test
+ public void singleResult() {
+ assertThat(query.where(title.ne("")).fetchFirst()).isNotNull();
+ }
+
+ @Test
+ public void single_result_takes_limit() {
+ assertThat(query.where(title.ne("")).limit(1).fetchFirst().get("title"))
+ .isEqualTo("Jurassic Park");
+ }
+
+ @Test
+ public void single_result_considers_limit_and_actual_result_size() {
+ query.where(title.startsWith("Nummi"));
+ final Document document = query.limit(3).fetchFirst();
+ assertThat(document.get("title")).isEqualTo("Nummisuutarit");
+ }
+
+ @Test
+ public void single_result_returns_null_if_nothing_is_in_range() {
+ query.where(title.startsWith("Nummi"));
+ assertThat(query.offset(10).fetchFirst()).isNull();
+ }
+
+ @Test
+ public void single_result_considers_offset() {
+ assertThat(query.where(title.ne("")).offset(3).fetchFirst().get("title"))
+ .isEqualTo("Introduction to Algorithms");
+ }
+
+ @Test
+ public void single_result_considers_limit_and_offset() {
+ assertThat(query.where(title.ne("")).limit(1).offset(2).fetchFirst().get("title"))
+ .isEqualTo("The Lord of the Rings");
+ }
+
+ @Test(expected = NonUniqueResultException.class)
+ public void uniqueResult_contract() {
+ query.where(title.ne("")).fetchOne();
+ }
+
+ @Test
+ public void unique_result_takes_limit() {
+ assertThat(query.where(title.ne("")).limit(1).fetchOne().get("title"))
+ .isEqualTo("Jurassic Park");
+ }
+
+ @Test
+ public void unique_result_considers_limit_and_actual_result_size() {
+ query.where(title.startsWith("Nummi"));
+ final Document document = query.limit(3).fetchOne();
+ assertThat(document.get("title")).isEqualTo("Nummisuutarit");
+ }
+
+ @Test
+ public void unique_result_returns_null_if_nothing_is_in_range() {
+ query.where(title.startsWith("Nummi"));
+ assertThat(query.offset(10).fetchOne()).isNull();
+ }
+
+ @Test
+ public void unique_result_considers_offset() {
+ assertThat(query.where(title.ne("")).offset(3).fetchOne().get("title"))
+ .isEqualTo("Introduction to Algorithms");
+ }
+
+ @Test
+ public void unique_result_considers_limit_and_offset() {
+ assertThat(query.where(title.ne("")).limit(1).offset(2).fetchOne().get("title"))
+ .isEqualTo("The Lord of the Rings");
+ }
+
+ @Test
+ public void uniqueResult() {
+ query.where(title.startsWith("Nummi"));
+ final Document document = query.fetchOne();
+ assertThat(document.get("title")).isEqualTo("Nummisuutarit");
+ }
+
+ @Test
+ public void uniqueResult_with_param() {
+ final Param param = new Param(String.class, "title");
+ query.set(param, "Nummi");
+ query.where(title.startsWith(param));
+ final Document document = query.fetchOne();
+ assertThat(document.get("title")).isEqualTo("Nummisuutarit");
+ }
+
+ @Test(expected = ParamNotSetException.class)
+ public void uniqueResult_param_not_set() {
+ final Param param = new Param(String.class, "title");
+ query.where(title.startsWith(param));
+ query.fetchOne();
+ }
+
+ @Test(expected = QueryException.class)
+ public void uniqueResult_finds_more_than_one_result() {
+ query.where(year.eq(1990));
+ query.fetchOne();
+ }
+
+ @Test
+ public void uniqueResult_finds_no_results() {
+ query.where(year.eq(2200));
+ assertThat(query.fetchOne()).isNull();
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void listDistinct() {
+ query.where(year.between(1900, 2000).or(title.startsWith("Jura")));
+ query.orderBy(year.asc());
+ final List documents = query.distinct().fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(3);
+ }
+
+ @Test
+ public void listResults() {
+ query.where(year.between(1800, 2000));
+ query.restrict(new QueryModifiers(2L, 1L));
+ query.orderBy(year.asc());
+ final QueryResults results = query.fetchResults();
+ assertThat(results.isEmpty()).isFalse();
+ assertThat(results.getResults()).hasSize(2);
+ assertThat(results.getLimit()).isEqualTo(2);
+ assertThat(results.getOffset()).isEqualTo(1);
+ assertThat(results.getTotal()).isEqualTo(4);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void listDistinctResults() {
+ query.where(year.between(1800, 2000).or(title.eq("The Lord of the Rings")));
+ query.restrict(new QueryModifiers(1L, 1L));
+ query.orderBy(year.asc());
+ final QueryResults results = query.distinct().fetchResults();
+ assertThat(results.isEmpty()).isFalse();
+ assertThat(results.getResults().get(0).get("year")).isEqualTo("1954");
+ assertThat(results.getLimit()).isEqualTo(1);
+ assertThat(results.getOffset()).isEqualTo(1);
+ assertThat(results.getTotal()).isEqualTo(4);
+ }
+
+ @Test
+ public void list_all() {
+ final List results =
+ query.where(title.like("*")).orderBy(title.asc(), year.desc()).fetch();
+ assertThat(results).hasSize(4);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void list_sorted_ascending_limit_negative() {
+ query.where(year.between(1800, 2000));
+ query.limit(-1);
+ query.orderBy(year.asc());
+ query.fetch();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void list_not_sorted_limit_negative() {
+ query.where(year.between(1800, 2000));
+ query.limit(-1);
+ query.fetch();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void list_sorted_ascending_limit_0() {
+ query.where(year.between(1800, 2000));
+ query.limit(0);
+ query.orderBy(year.asc());
+ query.fetch();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void list_not_sorted_limit_0() {
+ query.where(year.between(1800, 2000));
+ query.limit(0);
+ query.fetch();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void list_sorted_ascending_offset_negative() {
+ query.where(year.between(1800, 2000));
+ query.offset(-1);
+ query.orderBy(year.asc());
+ query.fetch();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void list_not_sorted_offset_negative() {
+ query.where(year.between(1800, 2000));
+ query.offset(-1);
+ query.fetch();
+ }
+
+ @Test
+ public void list_sorted_ascending_offset_0() {
+ query.where(year.between(1800, 2000));
+ query.offset(0);
+ query.orderBy(year.asc());
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void list_not_sorted_offset_0() {
+ query.where(year.between(1800, 2000));
+ query.offset(0);
+ final List documents = query.fetch();
+ assertThat(documents).isNotEmpty();
+ assertThat(documents).hasSize(4);
+ }
+
+ @Test
+ public void iterate() {
+ query.where(year.between(1800, 2000));
+ final Iterator iterator = query.iterate();
+ int count = 0;
+ while (iterator.hasNext()) {
+ iterator.next();
+ ++count;
+ }
+ assertThat(count).isEqualTo(4);
+ }
+
+ @Test
+ public void all_by_excluding_where() {
+ assertThat(query.fetch()).hasSize(4);
+ }
+
+ @Test
+ public void empty_index_should_return_empty_list() throws Exception {
+ idx = new ByteBuffersDirectory();
+
+ writer = createWriter(idx);
+ writer.close();
+ IndexReader reader = DirectoryReader.open(idx);
+ searcher = new IndexSearcher(reader);
+ query = new LuceneQuery(new LuceneSerializer(true, true), searcher);
+ assertThat(query.fetch()).isEmpty();
+ }
+
+ @Test(expected = QueryException.class)
+ public void
+ list_results_throws_an_illegal_argument_exception_when_sum_of_limit_and_offset_is_negative() {
+ query.limit(1).offset(Integer.MAX_VALUE).fetchResults();
+ }
+
+ @Test
+ public void limit_max_value() {
+ assertThat(query.limit(Long.MAX_VALUE).fetch()).hasSize(4);
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneSerializerNotTokenizedTest.java b/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneSerializerNotTokenizedTest.java
new file mode 100644
index 000000000..8170af781
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneSerializerNotTokenizedTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import static com.querydsl.lucene10.QPerson.person;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import com.querydsl.core.DefaultQueryMetadata;
+import com.querydsl.core.QueryMetadata;
+import com.querydsl.core.types.Expression;
+import com.querydsl.core.types.dsl.Expressions;
+import com.querydsl.core.types.dsl.StringPath;
+import java.time.LocalDate;
+import java.util.Arrays;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.ByteBuffersDirectory;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LuceneSerializerNotTokenizedTest {
+ private ByteBuffersDirectory idx;
+ private IndexWriter writer;
+ private IndexSearcher searcher;
+ private LuceneSerializer serializer;
+
+ private final QueryMetadata metadata = new DefaultQueryMetadata();
+
+ private final Person clooney = new Person("actor_1", "George Clooney", LocalDate.of(1961, 4, 6));
+ private final Person pitt = new Person("actor_2", "Brad Pitt", LocalDate.of(1963, 12, 18));
+
+ private void testQuery(Expression> expr, String expectedQuery, int expectedHits)
+ throws Exception {
+ Query query = serializer.toQuery(expr, metadata);
+ TopDocs docs = searcher.search(query, 100);
+ assertThat(docs.totalHits.value()).isEqualTo(expectedHits);
+ assertThat(query.toString()).isEqualTo(expectedQuery);
+ }
+
+ private Document createDocument(Person person) {
+ Document doc = new Document();
+ doc.add(new StringField("id", person.getId(), Store.YES));
+ doc.add(new StringField("name", person.getName(), Store.YES));
+ doc.add(new StringField("birthDate", person.getBirthDate().toString(), Store.YES));
+ return doc;
+ }
+
+ @Before
+ public void before() throws Exception {
+ serializer = new LuceneSerializer(false, false);
+ idx = new ByteBuffersDirectory();
+ IndexWriterConfig config =
+ new IndexWriterConfig(new StandardAnalyzer())
+ .setOpenMode(IndexWriterConfig.OpenMode.CREATE);
+ writer = new IndexWriter(idx, config);
+
+ writer.addDocument(createDocument(clooney));
+ writer.addDocument(createDocument(pitt));
+
+ Document document = new Document();
+ for (String movie : Arrays.asList("Interview with the Vampire", "Up in the Air")) {
+ document.add(new StringField("movie", movie, Store.YES));
+ }
+ writer.addDocument(document);
+
+ writer.close();
+
+ IndexReader reader = DirectoryReader.open(idx);
+ searcher = new IndexSearcher(reader);
+ }
+
+ @Test
+ public void equals_by_id_matches() throws Exception {
+ testQuery(person.id.eq("actor_1"), "id:actor_1", 1);
+ }
+
+ @Test
+ public void equals_by_id_does_not_match() throws Exception {
+ testQuery(person.id.eq("actor_8"), "id:actor_8", 0);
+ }
+
+ @Test
+ public void equals_by_name_matches() throws Exception {
+ testQuery(person.name.eq("George Clooney"), "name:George Clooney", 1);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void equals_by_name_ignoring_case_does_not_match() throws Exception {
+ testQuery(person.name.equalsIgnoreCase("george clooney"), "name:george clooney", 0);
+ }
+
+ @Test
+ public void equals_by_name_does_not_match() throws Exception {
+ testQuery(person.name.eq("George Looney"), "name:George Looney", 0);
+ }
+
+ @Test
+ public void starts_with_name_should_match() throws Exception {
+ testQuery(person.name.startsWith("George C"), "name:George C*", 1);
+ }
+
+ @Test
+ public void starts_with_name_should_not_match() throws Exception {
+ testQuery(person.name.startsWith("George L"), "name:George L*", 0);
+ }
+
+ @Test
+ public void ends_with_name_should_match() throws Exception {
+ testQuery(person.name.endsWith("e Clooney"), "name:*e Clooney", 1);
+ }
+
+ @Test
+ public void ends_with_name_should_not_match() throws Exception {
+ testQuery(person.name.endsWith("e Looney"), "name:*e Looney", 0);
+ }
+
+ @Test
+ public void contains_name_should_match() throws Exception {
+ testQuery(person.name.contains("oney"), "name:*oney*", 1);
+ }
+
+ @Test
+ public void contains_name_should_not_match() throws Exception {
+ testQuery(person.name.contains("bloney"), "name:*bloney*", 0);
+ }
+
+ @Test
+ public void in_names_should_match_2() throws Exception {
+ testQuery(
+ person.name.in("Brad Pitt", "George Clooney"), "name:Brad Pitt name:George Clooney", 2);
+ }
+
+ @Test
+ public void or_by_name_should_match_2() throws Exception {
+ testQuery(
+ person.name.eq("Brad Pitt").or(person.name.eq("George Clooney")),
+ "name:Brad Pitt name:George Clooney",
+ 2);
+ }
+
+ @Test
+ public void equals_by_birth_date() throws Exception {
+ testQuery(person.birthDate.eq(clooney.getBirthDate()), "birthDate:1961-04-06", 1);
+ }
+
+ @Test
+ public void between_phrase() throws Exception {
+ testQuery(
+ person.name.between("Brad Pitt", "George Clooney"),
+ "name:[Brad Pitt TO George Clooney]",
+ 2);
+ }
+
+ @Test
+ public void not_equals_finds_the_actors_and_movies() throws Exception {
+ testQuery(person.name.ne("Michael Douglas"), "-name:Michael Douglas +*:*", 3);
+ }
+
+ @Test
+ public void not_equals_finds_only_clooney_and_movies() throws Exception {
+ testQuery(person.name.ne("Brad Pitt"), "-name:Brad Pitt +*:*", 2);
+ }
+
+ @Test
+ public void and_with_two_not_equals_doesnt_find_the_actors() throws Exception {
+ testQuery(
+ person.name.ne("Brad Pitt").and(person.name.ne("George Clooney")),
+ "+(-name:Brad Pitt +*:*) +(-name:George Clooney +*:*)",
+ 1);
+ }
+
+ @Test
+ public void or_with_two_not_equals_finds_movies_and_actors() throws Exception {
+ testQuery(
+ person.name.ne("Brad Pitt").or(person.name.ne("George Clooney")),
+ "(-name:Brad Pitt +*:*) (-name:George Clooney +*:*)",
+ 3);
+ }
+
+ @Test
+ public void negation_of_equals_finds_movies_and_actors() throws Exception {
+ testQuery(person.name.eq("Michael Douglas").not(), "-name:Michael Douglas +*:*", 3);
+ }
+
+ @Test
+ public void negation_of_equals_finds_pitt_and_movies() throws Exception {
+ testQuery(person.name.eq("Brad Pitt").not(), "-name:Brad Pitt +*:*", 2);
+ }
+
+ @Test
+ public void multiple_field_search_from_movies() throws Exception {
+ StringPath movie = Expressions.stringPath("movie");
+ testQuery(movie.in("Interview with the Vampire"), "movie:Interview with the Vampire", 1);
+ testQuery(movie.eq("Up in the Air"), "movie:Up in the Air", 1);
+ }
+}
diff --git a/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneSerializerTest.java b/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneSerializerTest.java
new file mode 100644
index 000000000..cc63870e2
--- /dev/null
+++ b/querydsl-libraries/querydsl-lucene10/src/test/java/com/querydsl/lucene10/LuceneSerializerTest.java
@@ -0,0 +1,715 @@
+/*
+ * Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.querydsl.lucene10;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+import com.querydsl.core.BooleanBuilder;
+import com.querydsl.core.DefaultQueryMetadata;
+import com.querydsl.core.MatchingFiltersFactory;
+import com.querydsl.core.QueryMetadata;
+import com.querydsl.core.QuerydslModule;
+import com.querydsl.core.StringConstant;
+import com.querydsl.core.Target;
+import com.querydsl.core.types.Expression;
+import com.querydsl.core.types.Operation;
+import com.querydsl.core.types.Operator;
+import com.querydsl.core.types.Ops;
+import com.querydsl.core.types.Predicate;
+import com.querydsl.core.types.dsl.BooleanExpression;
+import com.querydsl.core.types.dsl.CollectionPath;
+import com.querydsl.core.types.dsl.Expressions;
+import com.querydsl.core.types.dsl.NumberPath;
+import com.querydsl.core.types.dsl.PathBuilder;
+import com.querydsl.core.types.dsl.StringPath;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.Set;
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.DoublePoint;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.FloatPoint;
+import org.apache.lucene.document.IntPoint;
+import org.apache.lucene.document.LongPoint;
+import org.apache.lucene.document.StoredField;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.ByteBuffersDirectory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Tests for LuceneSerializer
+ *
+ * @author vema
+ */
+public class LuceneSerializerTest {
+ private LuceneSerializer serializer;
+ private PathBuilder