Skip to content

Commit 08da5bb

Browse files
authored
SQL: Add ST_WktToSQL function (#35416)
Adds support for ST_WktToSQL function which accepts a string and parses it as WKT representation of a geoshape. Relates to #29872
1 parent 1ac1177 commit 08da5bb

File tree

20 files changed

+372
-22
lines changed

20 files changed

+372
-22
lines changed

docs/reference/sql/functions/geo.asciidoc

+25
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,28 @@ Returns the WKT representation of the `geometry`. The return type is string.
3030
--------------------------------------------------
3131
include-tagged::{sql-specs}/geo/docs.csv-spec[aswkt]
3232
--------------------------------------------------
33+
34+
35+
[[sql-functions-geo-st-as-wkt]]
36+
===== `ST_AsWKT`
37+
38+
.Synopsis:
39+
[source, sql]
40+
--------------------------------------------------
41+
ST_WKTToSQL(string<1>)
42+
--------------------------------------------------
43+
44+
*Input*:
45+
46+
<1> string geometry
47+
48+
*Output*: WKT string
49+
50+
.Description:
51+
52+
Returns the geometry from WKT representation. The return type is geometry.
53+
54+
["source","sql",subs="attributes,macros"]
55+
--------------------------------------------------
56+
include-tagged::{sql-specs}/geo/docs.csv-spec[aswkt]
57+
--------------------------------------------------

x-pack/plugin/sql/qa/src/main/resources/command.csv-spec

+3
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ RTRIM |SCALAR
100100
SPACE |SCALAR
101101
SUBSTRING |SCALAR
102102
UCASE |SCALAR
103+
ST_ASTEXT |SCALAR
103104
ST_ASWKT |SCALAR
105+
ST_GEOMFROMTEXT |SCALAR
106+
ST_WKTTOSQL |SCALAR
104107
CAST |SCALAR
105108
CONVERT |SCALAR
106109
SCORE |SCORE

x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec

+3
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ RTRIM |SCALAR
277277
SPACE |SCALAR
278278
SUBSTRING |SCALAR
279279
UCASE |SCALAR
280+
ST_ASTEXT |SCALAR
280281
ST_ASWKT |SCALAR
282+
ST_GEOMFROMTEXT |SCALAR
283+
ST_WKTTOSQL |SCALAR
281284
CAST |SCALAR
282285
CONVERT |SCALAR
283286
SCORE |SCORE

x-pack/plugin/sql/qa/src/main/resources/geo/docs.csv-spec

+10-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,18 @@
1010

1111
selectAsWKT
1212
// tag::aswkt
13-
SELECT city, ST_ASWKT(location) location FROM "geo" WHERE city = 'Amsterdam';
13+
SELECT city, ST_AsWKT(location) location FROM "geo" WHERE city = 'Amsterdam';
1414

1515
city:s | location:s
1616
Amsterdam |point (4.850311987102032 52.347556999884546)
1717
// end::aswkt
1818
;
19+
20+
selectWKTToSQL
21+
// tag::wkttosql
22+
SELECT CAST(ST_WKTToSQL('POINT (10 20)') AS STRING) location;
23+
24+
location:s
25+
point (10.0 20.0)
26+
// end::wkttosql
27+
;
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
11
{"index":{"_id": "1"}}
2-
{"region": "Americas", "city": "Mountain View", "location": {"lat":"37.386483", "lon":"-122.083843"}, "shape": "POINT (-122.083843 37.386483)"}
2+
{"region": "Americas", "city": "Mountain View", "location": {"lat":"37.386483", "lon":"-122.083843"}, "shape": "POINT (-122.083843 37.386483)", "region_point": "POINT(-105.2551 54.5260)"}
33
{"index":{"_id": "2"}}
4-
{"region": "Americas", "city": "Chicago", "location": [-87.637874, 41.888783], "shape": {"type" : "point", "coordinates" : [-87.637874, 41.888783] }}
4+
{"region": "Americas", "city": "Chicago", "location": [-87.637874, 41.888783], "shape": {"type" : "point", "coordinates" : [-87.637874, 41.888783]}, "region_point": "POINT(-105.2551 54.5260)"}
55
{"index":{"_id": "3"}}
6-
{"region": "Americas", "city": "New York", "location": "40.745171,-73.990027", "shape": "POINT (-73.990027 40.745171)"}
6+
{"region": "Americas", "city": "New York", "location": "40.745171,-73.990027", "shape": "POINT (-73.990027 40.745171)", "region_point": "POINT(-105.2551 54.5260)"}
77
{"index":{"_id": "4"}}
8-
{"region": "Americas", "city": "San Francisco", "location": "37.789541,-122.394228", "shape": "POINT (-122.394228 37.789541)"}
8+
{"region": "Americas", "city": "San Francisco", "location": "37.789541,-122.394228", "shape": "POINT (-122.394228 37.789541)", "region_point": "POINT(-105.2551 54.5260)"}
99
{"index":{"_id": "5"}}
10-
{"region": "Americas", "city": "Phoenix", "location": "33.376242,-111.973505", "shape": "POINT (-111.973505 33.376242)"}
10+
{"region": "Americas", "city": "Phoenix", "location": "33.376242,-111.973505", "shape": "POINT (-111.973505 33.376242)", "region_point": "POINT(-105.2551 54.5260)"}
1111
{"index":{"_id": "6"}}
12-
{"region": "Europe", "city": "Amsterdam", "location": "52.347557,4.850312", "shape": "POINT (4.850312 52.347557)"}
12+
{"region": "Europe", "city": "Amsterdam", "location": "52.347557,4.850312", "shape": "POINT (4.850312 52.347557)", "region_point": "POINT(15.2551 54.5260)"}
1313
{"index":{"_id": "7"}}
14-
{"region": "Europe", "city": "Berlin", "location": "52.486701,13.390889", "shape": "POINT (13.390889 52.486701)"}
14+
{"region": "Europe", "city": "Berlin", "location": "52.486701,13.390889", "shape": "POINT (13.390889 52.486701)", "region_point": "POINT(15.2551 54.5260)"}
1515
{"index":{"_id": "8"}}
16-
{"region": "Europe", "city": "Munich", "location": "48.146321,11.537505", "shape": "POINT (11.537505 48.146321)"}
16+
{"region": "Europe", "city": "Munich", "location": "48.146321,11.537505", "shape": "POINT (11.537505 48.146321)", "region_point": "POINT(15.2551 54.5260)"}
1717
{"index":{"_id": "9"}}
18-
{"region": "Europe", "city": "London", "location": "51.510871,-0.121672", "shape": "POINT (-0.121672 51.510871)"}
18+
{"region": "Europe", "city": "London", "location": "51.510871,-0.121672", "shape": "POINT (-0.121672 51.510871)", "region_point": "POINT(15.2551 54.5260)"}
1919
{"index":{"_id": "10"}}
20-
{"region": "Europe", "city": "Paris", "location": "48.845538,2.351773", "shape": "POINT (2.351773 48.845538)"}
20+
{"region": "Europe", "city": "Paris", "location": "48.845538,2.351773", "shape": "POINT (2.351773 48.845538)", "region_point": "POINT(15.2551 54.5260)"}
2121
{"index":{"_id": "11"}}
22-
{"region": "Asia", "city": "Singapore", "location": "1.295868,103.855535", "shape": "POINT (103.855535 1.295868)"}
22+
{"region": "Asia", "city": "Singapore", "location": "1.295868,103.855535", "shape": "POINT (103.855535 1.295868)", "region_point": "POINT(100.6197 34.0479)"}
2323
{"index":{"_id": "12"}}
24-
{"region": "Asia", "city": "Hong Kong", "location": "22.281397,114.183925", "shape": "POINT (114.183925 22.281397)"}
24+
{"region": "Asia", "city": "Hong Kong", "location": "22.281397,114.183925", "shape": "POINT (114.183925 22.281397)", "region_point": "POINT(100.6197 34.0479)"}
2525
{"index":{"_id": "13"}}
26-
{"region": "Asia", "city": "Seoul", "location": "37.509132,127.060851", "shape": "POINT (127.060851 37.509132)"}
26+
{"region": "Asia", "city": "Seoul", "location": "37.509132,127.060851", "shape": "POINT (127.060851 37.509132)", "region_point": "POINT(100.6197 34.0479)"}
2727
{"index":{"_id": "14"}}
28-
{"region": "Asia", "city": "Tokyo", "location": "35.669616,139.76402225", "shape": "POINT (139.76402225 35.669616)"}
28+
{"region": "Asia", "city": "Tokyo", "location": "35.669616,139.76402225", "shape": "POINT (139.76402225 35.669616)", "region_point": "POINT(100.6197 34.0479)"}
2929
{"index":{"_id": "15"}}
30-
{"region": "Asia", "city": "Sydney", "location": "-33.863385,151.208629", "shape": "POINT (151.208629 -33.863385)"}
30+
{"region": "Asia", "city": "Sydney", "location": "-33.863385,151.208629", "shape": "POINT (151.208629 -33.863385)", "region_point": "POINT(100.6197 34.0479)"}
3131

3232

3333

x-pack/plugin/sql/qa/src/main/resources/geo/geosql.csv-spec

+45
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ DESCRIBE "geo";
1818
city | VARCHAR | KEYWORD
1919
location | OTHER | GEO_POINT
2020
region | VARCHAR | KEYWORD
21+
region_point | VARCHAR | KEYWORD
2122
shape | OTHER | GEO_SHAPE
2223
;
2324

@@ -102,3 +103,47 @@ SELECT COUNT(city) count, CAST(SUBSTRING(ST_ASWKT(location), 8, 1) = '-' AS STRI
102103
9 |false
103104
6 |true
104105
;
106+
107+
selectRegionUsingWktToSql
108+
SELECT region, city, ST_ASWKT(ST_WKTTOSQL(region_point)) region_wkt FROM geo ORDER BY region, city;
109+
110+
region:s | city:s | region_wkt:s
111+
Americas |Chicago |point (-105.2551 54.526)
112+
Americas |Mountain View |point (-105.2551 54.526)
113+
Americas |New York |point (-105.2551 54.526)
114+
Americas |Phoenix |point (-105.2551 54.526)
115+
Americas |San Francisco |point (-105.2551 54.526)
116+
Asia |Hong Kong |point (100.6197 34.0479)
117+
Asia |Seoul |point (100.6197 34.0479)
118+
Asia |Singapore |point (100.6197 34.0479)
119+
Asia |Sydney |point (100.6197 34.0479)
120+
Asia |Tokyo |point (100.6197 34.0479)
121+
Europe |Amsterdam |point (15.2551 54.526)
122+
Europe |Berlin |point (15.2551 54.526)
123+
Europe |London |point (15.2551 54.526)
124+
Europe |Munich |point (15.2551 54.526)
125+
Europe |Paris |point (15.2551 54.526)
126+
;
127+
128+
selectCitiesWithAGroupByWktToSql
129+
SELECT COUNT(city) city_by_region, CAST(ST_WKTTOSQL(region_point) AS STRING) region FROM geo WHERE city LIKE '%a%' GROUP BY ST_WKTTOSQL(region_point) ORDER BY ST_WKTTOSQL(region_point);
130+
131+
city_by_region:l | region:s
132+
3 |point (-105.2551 54.526)
133+
1 |point (100.6197 34.0479)
134+
2 |point (15.2551 54.526)
135+
;
136+
137+
selectCitiesWithEOrderByWktToSql
138+
SELECT region, city FROM geo WHERE city LIKE '%e%' ORDER BY ST_WKTTOSQL(region_point), city;
139+
140+
region:s | city:s
141+
Americas |Mountain View
142+
Americas |New York
143+
Americas |Phoenix
144+
Asia |Seoul
145+
Asia |Singapore
146+
Asia |Sydney
147+
Europe |Amsterdam
148+
Europe |Berlin
149+
;

x-pack/plugin/sql/qa/src/main/resources/geo/geosql.json

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616
},
1717
"shape": {
1818
"type": "geo_shape"
19+
},
20+
"region_point": {
21+
"type": "keyword"
1922
}
2023
}
2124
}

x-pack/plugin/sql/qa/src/main/resources/ogc/ogc.sql-spec

+12
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,15 @@ selectNamedPlaces
2222
SELECT fid, name, boundary FROM named_places ORDER BY fid;
2323
selectMapNeatLines
2424
SELECT fid, neatline FROM map_neatlines ORDER BY fid;
25+
26+
//
27+
// Type conversion functions
28+
//
29+
30+
// The string serialization is slightly different between ES and H2, so we need to tweak it a bit by uppercasing both
31+
// and removing floating point
32+
selectRoadSegmentsAsWkt
33+
SELECT fid, name, num_lanes, aliases, REPLACE(UCASE(ST_AsText(centerline)), '.0', '') centerline_wkt FROM road_segments ORDER BY fid;
34+
35+
selectSinglePoint
36+
SELECT ST_GeomFromText('point (10.0 12.0)') point;

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/FunctionRegistry.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.WeekOfYear;
3636
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.Year;
3737
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StAswkt;
38+
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StWkttosql;
3839
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ACos;
3940
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ASin;
4041
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ATan;
@@ -214,7 +215,8 @@ private void defineDefaultFunctions() {
214215
def(UCase.class, UCase::new));
215216

216217
// Geo Functions
217-
addToMap(def(StAswkt.class, StAswkt::new));
218+
addToMap(def(StAswkt.class, StAswkt::new, "ST_ASTEXT"));
219+
addToMap(def(StWkttosql.class, StWkttosql::new, "ST_GEOMFROMTEXT"));
218220
// DataType conversion
219221
addToMap(def(Cast.class, Cast::new, "CONVERT"));
220222
// Special

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/Processors.java

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NamedDateTimeProcessor;
1212
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor;
1313
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor;
14+
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StWkttosqlProcessor;
1415
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor;
1516
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor;
1617
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor;
@@ -90,6 +91,7 @@ public static List<NamedWriteableRegistry.Entry> getNamedWriteables() {
9091
entries.add(new Entry(Processor.class, ReplaceFunctionProcessor.NAME, ReplaceFunctionProcessor::new));
9192
entries.add(new Entry(Processor.class, SubstringFunctionProcessor.NAME, SubstringFunctionProcessor::new));
9293
entries.add(new Entry(Processor.class, GeoProcessor.NAME, GeoProcessor::new));
94+
entries.add(new Entry(Processor.class, StWkttosqlProcessor.NAME, StWkttosqlProcessor::new));
9395
return entries;
9496
}
9597

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.sql.expression.function.scalar.geo;
8+
9+
import org.elasticsearch.xpack.sql.expression.Expression;
10+
import org.elasticsearch.xpack.sql.expression.Expressions;
11+
import org.elasticsearch.xpack.sql.expression.function.scalar.UnaryScalarFunction;
12+
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
13+
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
14+
import org.elasticsearch.xpack.sql.tree.Location;
15+
import org.elasticsearch.xpack.sql.tree.NodeInfo;
16+
import org.elasticsearch.xpack.sql.type.DataType;
17+
18+
/**
19+
* Constructs geometric objects from their WTK representations
20+
*/
21+
public class StWkttosql extends UnaryScalarFunction {
22+
23+
public StWkttosql(Location location, Expression field) {
24+
super(location, field);
25+
}
26+
27+
@Override
28+
protected StWkttosql replaceChild(Expression newChild) {
29+
return new StWkttosql(location(), newChild);
30+
}
31+
32+
@Override
33+
protected TypeResolution resolveType() {
34+
if (field().dataType().isString()) {
35+
return TypeResolution.TYPE_RESOLVED;
36+
}
37+
return Expressions.typeMustBeString(field(), functionName(), Expressions.ParamOrdinal.DEFAULT);
38+
}
39+
40+
@Override
41+
protected Processor makeProcessor() {
42+
return StWkttosqlProcessor.INSTANCE;
43+
}
44+
45+
@Override
46+
public DataType dataType() {
47+
return DataType.GEO_SHAPE;
48+
}
49+
50+
@Override
51+
protected NodeInfo<StWkttosql> info() {
52+
return NodeInfo.create(this, StWkttosql::new, field());
53+
}
54+
55+
@Override
56+
public String processScript(String script) {
57+
return Scripts.formatTemplate(Scripts.SQL_SCRIPTS + ".wktToSql(" + script + ")");
58+
}
59+
60+
@Override
61+
public Object fold() {
62+
return StWkttosqlProcessor.INSTANCE.process(field().fold());
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.sql.expression.function.scalar.geo;
8+
9+
import org.elasticsearch.ElasticsearchParseException;
10+
import org.elasticsearch.common.io.stream.StreamInput;
11+
import org.elasticsearch.common.io.stream.StreamOutput;
12+
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
13+
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
14+
15+
import java.io.IOException;
16+
17+
public class StWkttosqlProcessor implements Processor {
18+
19+
static final StWkttosqlProcessor INSTANCE = new StWkttosqlProcessor();
20+
21+
public static final String NAME = "geo_wkttosql";
22+
23+
StWkttosqlProcessor() {
24+
}
25+
26+
public StWkttosqlProcessor(StreamInput in) throws IOException {
27+
}
28+
29+
@Override
30+
public Object process(Object input) {
31+
return StWkttosqlProcessor.apply(input);
32+
}
33+
34+
public static GeoShape apply(Object input) {
35+
if (input == null) {
36+
return null;
37+
}
38+
39+
if ((input instanceof String) == false) {
40+
throw new SqlIllegalArgumentException("A string is required; received [{}]", input);
41+
}
42+
try {
43+
return new GeoShape(input);
44+
} catch (IOException | IllegalArgumentException | ElasticsearchParseException ex) {
45+
throw new SqlIllegalArgumentException("Cannot parse [{}] as a geo_shape value", input);
46+
}
47+
}
48+
49+
@Override
50+
public String getWriteableName() {
51+
return NAME;
52+
}
53+
54+
@Override
55+
public void writeTo(StreamOutput out) throws IOException {
56+
57+
}
58+
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/expression/function/scalar/whitelist/InternalSqlScriptUtils.java

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NamedDateTimeProcessor.NameExtractor;
1313
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor;
1414
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor;
15+
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoShape;
16+
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StWkttosqlProcessor;
1517
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation;
1618
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation;
1719
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
@@ -407,4 +409,8 @@ public static String aswktPoint(Object v) {
407409
public static String aswktShape(Object v) {
408410
return GeoProcessor.GeoOperation.ASWKT_SHAPE.apply(v).toString();
409411
}
412+
413+
public static GeoShape wktToSql(String wktString) {
414+
return StWkttosqlProcessor.apply(wktString);
415+
}
410416
}

x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/type/DataTypes.java

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.elasticsearch.xpack.sql.type;
77

88
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
9+
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoShape;
910
import org.joda.time.DateTime;
1011

1112
public final class DataTypes {
@@ -51,6 +52,9 @@ public static DataType fromJava(Object value) {
5152
if (value instanceof String || value instanceof Character) {
5253
return DataType.KEYWORD;
5354
}
55+
if (value instanceof GeoShape) {
56+
return DataType.GEO_SHAPE;
57+
}
5458
throw new SqlIllegalArgumentException("No idea what's the DataType for {}", value.getClass());
5559
}
5660

0 commit comments

Comments
 (0)