You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by rn...@apache.org on 2023/05/01 09:51:35 UTC
[couchdb] branch nouveau-geo created (now 30096d3b9)
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a change to branch nouveau-geo
in repository https://gitbox.apache.org/repos/asf/couchdb.git
at 30096d3b9 initial geo thoughts
This branch includes the following new commits:
new 30096d3b9 initial geo thoughts
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[couchdb] 01/01: initial geo thoughts
Posted by rn...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
rnewson pushed a commit to branch nouveau-geo
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit 30096d3b9192d7c89b72b483f7b1b4d0e71df1fa
Author: Robert Newson <rn...@apache.org>
AuthorDate: Mon May 1 10:50:56 2023 +0100
initial geo thoughts
think I'll fork StandardSyntaxParser.jj to make it tidier though
---
.../java/org/apache/couchdb/nouveau/api/Field.java | 2 +
.../apache/couchdb/nouveau/api/LatLonField.java | 43 +++++
.../org/apache/couchdb/nouveau/api/XYField.java | 43 +++++
.../couchdb/nouveau/lucene9/Lucene9Index.java | 10 ++
.../nouveau/lucene9/NouveauQueryParser.java | 183 ++++++++++++++++++++-
.../couchdb/nouveau/lucene9/Lucene9IndexTest.java | 12 +-
.../resources/fixtures/DocumentUpdateRequest.json | 12 ++
share/server/nouveau.js | 24 +++
test/elixir/test/config/nouveau.elixir | 3 +-
test/elixir/test/nouveau_test.exs | 40 +++++
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