summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Newson <rnewson@apache.org>2023-05-01 10:50:56 +0100
committerRobert Newson <rnewson@apache.org>2023-05-01 10:51:00 +0100
commit30096d3b9192d7c89b72b483f7b1b4d0e71df1fa (patch)
tree0b04ecffd6cbb636c83fb2fadbe83b97acf989fe
parentfe445bb95c8fc6c0836f31017a98b41736200a71 (diff)
downloadcouchdb-30096d3b9192d7c89b72b483f7b1b4d0e71df1fa.tar.gz
initial geo thoughts
think I'll fork StandardSyntaxParser.jj to make it tidier though
-rw-r--r--nouveau/src/main/java/org/apache/couchdb/nouveau/api/Field.java2
-rw-r--r--nouveau/src/main/java/org/apache/couchdb/nouveau/api/LatLonField.java43
-rw-r--r--nouveau/src/main/java/org/apache/couchdb/nouveau/api/XYField.java43
-rw-r--r--nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java10
-rw-r--r--nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java183
-rw-r--r--nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/Lucene9IndexTest.java12
-rw-r--r--nouveau/src/test/resources/fixtures/DocumentUpdateRequest.json12
-rw-r--r--share/server/nouveau.js24
-rw-r--r--test/elixir/test/config/nouveau.elixir3
-rw-r--r--test/elixir/test/nouveau_test.exs40
10 files changed, 366 insertions, 6 deletions
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/Field.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/Field.java
index 52d5b815f..98439d22d 100644
--- a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/Field.java
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/Field.java
@@ -28,9 +28,11 @@ import jakarta.validation.constraints.Pattern;
property = "@type")
@JsonSubTypes({
@JsonSubTypes.Type(value = DoubleField.class, name = "double"),
+ @JsonSubTypes.Type(value = LatLonField.class, name = "latlon"),
@JsonSubTypes.Type(value = StoredField.class, name = "stored"),
@JsonSubTypes.Type(value = StringField.class, name = "string"),
@JsonSubTypes.Type(value = TextField.class, name = "text"),
+ @JsonSubTypes.Type(value = XYField.class, name = "xy"),
})
public abstract class Field {
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/LatLonField.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/LatLonField.java
new file mode 100644
index 000000000..e40f360aa
--- /dev/null
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/LatLonField.java
@@ -0,0 +1,43 @@
+//
+// 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 org.apache.couchdb.nouveau.api;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public final class LatLonField extends Field {
+
+ private final double lon;
+
+ private final double lat;
+
+ public LatLonField(@JsonProperty("name") final String name, @JsonProperty("lat") final double lat,
+ @JsonProperty("lon") final double lon) {
+ super(name);
+ this.lat = lat;
+ this.lon = lon;
+ }
+
+ public double getLat() {
+ return lat;
+ }
+
+ public double getLon() {
+ return lon;
+ }
+
+ @Override
+ public String toString() {
+ return "LatLonField [name=" + name + ", lon=" + lon + ", lat=" + lat + "]";
+ }
+
+}
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/api/XYField.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/XYField.java
new file mode 100644
index 000000000..c4cacf033
--- /dev/null
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/api/XYField.java
@@ -0,0 +1,43 @@
+//
+// 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 org.apache.couchdb.nouveau.api;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public final class XYField extends Field {
+
+ private final float x;
+
+ private final float y;
+
+ public XYField(@JsonProperty("name") final String name, @JsonProperty("x") final float x,
+ @JsonProperty("y") final float y) {
+ super(name);
+ this.x = x;
+ this.y = y;
+ }
+
+ public float getX() {
+ return x;
+ }
+
+ public float getY() {
+ return y;
+ }
+
+ @Override
+ public String toString() {
+ return "XYField [name=" + name + ", x=" + x + ", y=" + y + "]";
+ }
+
+}
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java
index 02818f41f..13ab650ca 100644
--- a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/Lucene9Index.java
@@ -37,12 +37,14 @@ import org.apache.couchdb.nouveau.api.DocumentUpdateRequest;
import org.apache.couchdb.nouveau.api.DoubleField;
import org.apache.couchdb.nouveau.api.DoubleRange;
import org.apache.couchdb.nouveau.api.Field;
+import org.apache.couchdb.nouveau.api.LatLonField;
import org.apache.couchdb.nouveau.api.SearchHit;
import org.apache.couchdb.nouveau.api.SearchRequest;
import org.apache.couchdb.nouveau.api.SearchResults;
import org.apache.couchdb.nouveau.api.StoredField;
import org.apache.couchdb.nouveau.api.StringField;
import org.apache.couchdb.nouveau.api.TextField;
+import org.apache.couchdb.nouveau.api.XYField;
import org.apache.couchdb.nouveau.core.IOUtils;
import org.apache.couchdb.nouveau.core.Index;
import org.apache.couchdb.nouveau.core.ser.ByteArrayWrapper;
@@ -416,6 +418,14 @@ public class Lucene9Index extends Index {
} else {
throw new WebApplicationException(field + " is not valid", Status.BAD_REQUEST);
}
+ } else if (field instanceof XYField) {
+ var f = (XYField) field;
+ result.add(new org.apache.lucene.document.XYPointField(f.getName(), f.getX(), f.getY()));
+ result.add(new org.apache.lucene.document.XYDocValuesField(f.getName(), f.getX(), f.getY()));
+ } else if (field instanceof LatLonField) {
+ var f = (LatLonField) field;
+ result.add(new org.apache.lucene.document.LatLonPoint(f.getName(), f.getLat(), f.getLon()));
+ result.add(new org.apache.lucene.document.LatLonDocValuesField(f.getName(), f.getLat(), f.getLon()));
} else {
throw new WebApplicationException(field + " is not valid", Status.BAD_REQUEST);
}
diff --git a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java
index 6aad65cd4..e6b159f0f 100644
--- a/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java
+++ b/nouveau/src/main/java/org/apache/couchdb/nouveau/lucene9/NouveauQueryParser.java
@@ -16,24 +16,67 @@ package org.apache.couchdb.nouveau.lucene9;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.document.XYPointField;
import org.apache.lucene.queryparser.flexible.core.QueryNodeException;
import org.apache.lucene.queryparser.flexible.core.QueryParserHelper;
+import org.apache.lucene.queryparser.flexible.core.builders.QueryTreeBuilder;
+import org.apache.lucene.queryparser.flexible.core.nodes.BooleanQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.BoostQueryNode;
import org.apache.lucene.queryparser.flexible.core.nodes.FieldQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.FieldableNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.FuzzyQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.GroupQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.MatchAllDocsQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.MatchNoDocsQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.ModifierQueryNode;
import org.apache.lucene.queryparser.flexible.core.nodes.QueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.QueryNodeImpl;
import org.apache.lucene.queryparser.flexible.core.nodes.RangeQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.SlopQueryNode;
+import org.apache.lucene.queryparser.flexible.core.nodes.TokenizedPhraseQueryNode;
+import org.apache.lucene.queryparser.flexible.core.parser.EscapeQuerySyntax;
import org.apache.lucene.queryparser.flexible.core.processors.NoChildOptimizationQueryNodeProcessor;
import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessorImpl;
import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessorPipeline;
import org.apache.lucene.queryparser.flexible.core.processors.RemoveDeletedQueryNodesProcessor;
-import org.apache.lucene.queryparser.flexible.standard.builders.StandardQueryTreeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.BooleanQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.BoostQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.DummyQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.FieldQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.FuzzyQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.GroupQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.IntervalQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.MatchAllDocsQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.MatchNoDocsQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.MinShouldMatchNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.ModifierQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.MultiPhraseQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.PhraseQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.PointRangeQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.PrefixWildcardQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.RegexpQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.SlopQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.StandardQueryBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.SynonymQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.TermRangeQueryNodeBuilder;
+import org.apache.lucene.queryparser.flexible.standard.builders.WildcardQueryNodeBuilder;
import org.apache.lucene.queryparser.flexible.standard.config.PointsConfig;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler.ConfigurationKeys;
+import org.apache.lucene.queryparser.flexible.standard.nodes.IntervalQueryNode;
+import org.apache.lucene.queryparser.flexible.standard.nodes.MinShouldMatchNode;
+import org.apache.lucene.queryparser.flexible.standard.nodes.MultiPhraseQueryNode;
import org.apache.lucene.queryparser.flexible.standard.nodes.PointQueryNode;
import org.apache.lucene.queryparser.flexible.standard.nodes.PointRangeQueryNode;
+import org.apache.lucene.queryparser.flexible.standard.nodes.PrefixWildcardQueryNode;
+import org.apache.lucene.queryparser.flexible.standard.nodes.RegexpQueryNode;
+import org.apache.lucene.queryparser.flexible.standard.nodes.SynonymQueryNode;
import org.apache.lucene.queryparser.flexible.standard.nodes.TermRangeQueryNode;
+import org.apache.lucene.queryparser.flexible.standard.nodes.WildcardQueryNode;
import org.apache.lucene.queryparser.flexible.standard.parser.StandardSyntaxParser;
import org.apache.lucene.queryparser.flexible.standard.processors.AllowLeadingWildcardProcessor;
import org.apache.lucene.queryparser.flexible.standard.processors.AnalyzerQueryNodeProcessor;
@@ -61,7 +104,7 @@ public final class NouveauQueryParser extends QueryParserHelper {
new StandardQueryConfigHandler(),
new StandardSyntaxParser(),
new NouveauQueryNodeProcessorPipeline(),
- new StandardQueryTreeBuilder());
+ new NouveauQueryTreeBuilder());
getQueryConfigHandler().set(ConfigurationKeys.ENABLE_POSITION_INCREMENTS, true);
getQueryConfigHandler().set(ConfigurationKeys.ANALYZER, analyzer);
}
@@ -76,7 +119,7 @@ public final class NouveauQueryParser extends QueryParserHelper {
* PointQueryNodeProcessor and PointRangeQueryNodeProcessor for
* NouveauPointProcessor below.
*/
- public static class NouveauQueryNodeProcessorPipeline extends QueryNodeProcessorPipeline {
+ private static class NouveauQueryNodeProcessorPipeline extends QueryNodeProcessorPipeline {
public NouveauQueryNodeProcessorPipeline() {
super(null);
@@ -87,6 +130,7 @@ public final class NouveauQueryParser extends QueryParserHelper {
add(new MatchAllDocsQueryNodeProcessor());
add(new OpenRangeQueryNodeProcessor());
add(new NouveauPointProcessor());
+ add(new NouveauXYProcessor());
add(new TermRangeQueryNodeProcessor());
add(new AllowLeadingWildcardProcessor());
add(new AnalyzerQueryNodeProcessor());
@@ -103,10 +147,42 @@ public final class NouveauQueryParser extends QueryParserHelper {
}
}
+ private static class NouveauQueryTreeBuilder extends QueryTreeBuilder implements StandardQueryBuilder {
+
+ public NouveauQueryTreeBuilder() {
+ setBuilder(GroupQueryNode.class, new GroupQueryNodeBuilder());
+ setBuilder(FieldQueryNode.class, new FieldQueryNodeBuilder());
+ setBuilder(BooleanQueryNode.class, new BooleanQueryNodeBuilder());
+ setBuilder(FuzzyQueryNode.class, new FuzzyQueryNodeBuilder());
+ setBuilder(PointQueryNode.class, new DummyQueryNodeBuilder());
+ setBuilder(PointRangeQueryNode.class, new PointRangeQueryNodeBuilder());
+ setBuilder(BoostQueryNode.class, new BoostQueryNodeBuilder());
+ setBuilder(ModifierQueryNode.class, new ModifierQueryNodeBuilder());
+ setBuilder(WildcardQueryNode.class, new WildcardQueryNodeBuilder());
+ setBuilder(TokenizedPhraseQueryNode.class, new PhraseQueryNodeBuilder());
+ setBuilder(MatchNoDocsQueryNode.class, new MatchNoDocsQueryNodeBuilder());
+ setBuilder(PrefixWildcardQueryNode.class, new PrefixWildcardQueryNodeBuilder());
+ setBuilder(TermRangeQueryNode.class, new TermRangeQueryNodeBuilder());
+ setBuilder(RegexpQueryNode.class, new RegexpQueryNodeBuilder());
+ setBuilder(SlopQueryNode.class, new SlopQueryNodeBuilder());
+ setBuilder(SynonymQueryNode.class, new SynonymQueryNodeBuilder());
+ setBuilder(MultiPhraseQueryNode.class, new MultiPhraseQueryNodeBuilder());
+ setBuilder(MatchAllDocsQueryNode.class, new MatchAllDocsQueryNodeBuilder());
+ setBuilder(MinShouldMatchNode.class, new MinShouldMatchNodeBuilder());
+ setBuilder(IntervalQueryNode.class, new IntervalQueryNodeBuilder());
+ setBuilder(XYBoxQueryNode.class, new XYBoxQueryNodeBuilder());
+ }
+
+ @Override
+ public Query build(QueryNode queryNode) throws QueryNodeException {
+ return (Query) super.build(queryNode);
+ }
+ }
+
/**
* If it looks like a number, treat it as a number.
*/
- public static class NouveauPointProcessor extends QueryNodeProcessorImpl {
+ private static class NouveauPointProcessor extends QueryNodeProcessorImpl {
@Override
protected QueryNode postProcessNode(final QueryNode node) throws QueryNodeException {
@@ -178,4 +254,103 @@ public final class NouveauQueryParser extends QueryParserHelper {
}
+ private static class NouveauXYProcessor extends QueryNodeProcessorImpl {
+
+ private final Pattern p = Pattern
+ .compile("%(\\d+(?:\\.\\d+)?),(\\d+(?:\\.\\d+)?),(\\d+(?:\\.\\d+)?),(\\d+(?:\\.\\d+)?)");
+
+ @Override
+ protected QueryNode postProcessNode(final QueryNode node) throws QueryNodeException {
+ if (node instanceof FieldQueryNode && !(node.getParent() instanceof RangeQueryNode)) {
+ final var fieldNode = (FieldQueryNode) node;
+ String text = fieldNode.getTextAsString();
+ if (text.length() == 0) {
+ return node;
+ }
+ final Matcher m = p.matcher(text);
+ if (m.matches()) {
+ System.err.println("YAY");
+ final float minX = Float.parseFloat(m.group(1));
+ final float maxX = Float.parseFloat(m.group(2));
+ final float minY = Float.parseFloat(m.group(3));
+ final float maxY = Float.parseFloat(m.group(4));
+ return new XYBoxQueryNode(fieldNode.getFieldAsString(), minX, maxX, minY, maxY);
+ }
+ }
+ return node;
+ }
+
+ @Override
+ protected QueryNode preProcessNode(final QueryNode node) throws QueryNodeException {
+ return node;
+ }
+
+ @Override
+ protected List<QueryNode> setChildrenOrder(final List<QueryNode> children) throws QueryNodeException {
+ return children;
+ }
+
+ }
+
+ private static class XYBoxQueryNode extends QueryNodeImpl implements FieldableNode {
+
+ private CharSequence field;
+
+ private final float minX, maxX, minY, maxY;
+
+ public XYBoxQueryNode(String field, float minX, float maxX, float minY, float maxY) {
+ this.field = field;
+ this.minX = minX;
+ this.maxX = maxX;
+ this.minY = minY;
+ this.maxY = maxY;
+ }
+
+ @Override
+ public CharSequence toQueryString(EscapeQuerySyntax escapeSyntaxParser) {
+ final String value = String.format("<%f,%f,%f,%f>", minX, maxX, minY, maxY);
+ if (isDefaultField(this.field)) {
+ return value;
+ } else {
+ return this.field + ":" + value;
+ }
+ }
+
+ @Override
+ public CharSequence getField() {
+ return field;
+ }
+
+ public float getMinX() {
+ return minX;
+ }
+
+ public float getMinY() {
+ return minY;
+ }
+
+ public float getMaxX() {
+ return maxX;
+ }
+
+ public float getMaxY() {
+ return maxY;
+ }
+
+ @Override
+ public void setField(CharSequence field) {
+ this.field = field;
+ }
+ }
+
+ private static class XYBoxQueryNodeBuilder implements StandardQueryBuilder {
+
+ @Override
+ public Query build(QueryNode queryNode) throws QueryNodeException {
+ var n = (XYBoxQueryNode) queryNode;
+ return XYPointField.newBoxQuery(n.getField().toString(), n.getMinX(), n.getMaxX(), n.getMinY(),
+ n.getMaxY());
+ }
+ }
+
} \ No newline at end of file
diff --git a/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/Lucene9IndexTest.java b/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/Lucene9IndexTest.java
index f6d47e61a..88792ba79 100644
--- a/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/Lucene9IndexTest.java
+++ b/nouveau/src/test/java/org/apache/couchdb/nouveau/lucene9/Lucene9IndexTest.java
@@ -30,9 +30,12 @@ import org.apache.couchdb.nouveau.api.DoubleRange;
import org.apache.couchdb.nouveau.api.Field;
import org.apache.couchdb.nouveau.api.IndexDefinition;
import org.apache.couchdb.nouveau.api.IndexInfo;
+import org.apache.couchdb.nouveau.api.LatLonField;
import org.apache.couchdb.nouveau.api.SearchRequest;
import org.apache.couchdb.nouveau.api.SearchResults;
+import org.apache.couchdb.nouveau.api.StoredField;
import org.apache.couchdb.nouveau.api.StringField;
+import org.apache.couchdb.nouveau.api.XYField;
import org.apache.couchdb.nouveau.core.Index;
import org.apache.couchdb.nouveau.core.IndexLoader;
import org.apache.couchdb.nouveau.core.UpdatesOutOfOrderException;
@@ -72,7 +75,14 @@ public class Lucene9IndexTest {
try {
final int count = 100;
for (int i = 1; i <= count; i++) {
- final Collection<Field> fields = List.of(new StringField("foo", "bar", false));
+ final Collection<Field> fields = List.of(
+ new StringField("foo", "bar", true),
+ new DoubleField("bar", 12.0, true),
+ new StoredField("baz", "bar"),
+ new StoredField("foobar", 12.0),
+ new XYField("foobaz", 12.f, 12.0f),
+ new LatLonField("bazbar", 12.0, 12.0)
+ );
final DocumentUpdateRequest request = new DocumentUpdateRequest(i, null, fields);
index.update("doc" + i, request);
}
diff --git a/nouveau/src/test/resources/fixtures/DocumentUpdateRequest.json b/nouveau/src/test/resources/fixtures/DocumentUpdateRequest.json
index a22e322d4..3b0aafd20 100644
--- a/nouveau/src/test/resources/fixtures/DocumentUpdateRequest.json
+++ b/nouveau/src/test/resources/fixtures/DocumentUpdateRequest.json
@@ -17,6 +17,18 @@
"@type": "double",
"name": "doublefoo",
"value": 12
+ },
+ {
+ "@type": "xy",
+ "name": "xy",
+ "x": 12.0,
+ "y": 12.0
+ },
+ {
+ "@type": "latlon",
+ "name": "latlon",
+ "lajt": 12.0,
+ "lon": 12.0
}
]
}
diff --git a/share/server/nouveau.js b/share/server/nouveau.js
index 8c75d4a25..f107d59e4 100644
--- a/share/server/nouveau.js
+++ b/share/server/nouveau.js
@@ -83,6 +83,30 @@ var Nouveau = (function () {
'value': value
});
break;
+ case 'xy':
+ var x = arguments[2];
+ var y = arguments[3];
+ assertType('x', 'number', x);
+ assertType('y', 'number', y);
+ index_results.push({
+ '@type': type,
+ 'name': name,
+ 'x': x,
+ 'y': y
+ });
+ break;
+ case 'latlon':
+ var lat = arguments[2];
+ assertType('lat', 'number', lat);
+ var lon = arguments[3];
+ assertType('lon', 'number', lon);
+ index_results.push({
+ '@type': type,
+ 'name': name,
+ 'lat': lat,
+ 'lon': lon
+ });
+ break;
default:
throw ({ name: 'TypeError', message: type + ' not supported' });
}
diff --git a/test/elixir/test/config/nouveau.elixir b/test/elixir/test/config/nouveau.elixir
index 5c13aac2b..3dc22aafa 100644
--- a/test/elixir/test/config/nouveau.elixir
+++ b/test/elixir/test/config/nouveau.elixir
@@ -17,6 +17,7 @@
"mango search by string",
"search GET (partitioned)",
"search POST (partitioned)",
- "mango (partitioned)"
+ "mango (partitioned)",
+ "geo search xy"
]
}
diff --git a/test/elixir/test/nouveau_test.exs b/test/elixir/test/nouveau_test.exs
index 3bea874d9..93059194b 100644
--- a/test/elixir/test/nouveau_test.exs
+++ b/test/elixir/test/nouveau_test.exs
@@ -360,4 +360,44 @@ defmodule NouveauTest do
assert ids == ["bar:doc3"]
end
+ @tag :with_db
+ test "geo search xy", context do
+ db_name = context[:db_name]
+
+ # create docs
+ resp = Couch.post("/#{db_name}/_bulk_docs",
+ headers: ["Content-Type": "application/json"],
+ body: %{:docs => [
+ %{"_id" => "doc1", "x" => 12, "y" => 100},
+ %{"_id" => "doc2", "x" => 100, "y" => 12},
+ ]}
+ )
+ assert resp.status_code in [201]
+
+ # create geo ddoc
+ ddoc = %{
+ nouveau: %{
+ bar: %{
+ default_analyzer: "standard",
+ index: """
+ function (doc) {
+ index("xy", "position", doc.x, doc.y);
+ }
+ """
+ }
+ }
+ }
+ resp = Couch.put("/#{db_name}/_design/foo", body: ddoc)
+ assert resp.status_code in [201]
+ assert Map.has_key?(resp.body, "ok") == true
+
+ # search for it
+ url = "/#{db_name}/_design/foo/_nouveau/bar"
+ resp = Couch.get(url, query: %{q: "position: %11,13,99,101", include_docs: true})
+ assert_status_code(resp, 200)
+ ids = get_ids(resp)
+ # nouveau sorts by _id as tie-breaker
+ assert ids == ["doc1"]
+ end
+
end