Skip to content

Commit

Permalink
SQL: Add ST_WktToSQL function (#35416)
Browse files Browse the repository at this point in the history
Adds support for ST_WktToSQL function which accepts a string and parses
it as WKT representation of a geoshape.

Relates to #29872
  • Loading branch information
imotov authored Nov 15, 2018
1 parent 1ac1177 commit 08da5bb
Show file tree
Hide file tree
Showing 20 changed files with 372 additions and 22 deletions.
25 changes: 25 additions & 0 deletions docs/reference/sql/functions/geo.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,28 @@ Returns the WKT representation of the `geometry`. The return type is string.
--------------------------------------------------
include-tagged::{sql-specs}/geo/docs.csv-spec[aswkt]
--------------------------------------------------


[[sql-functions-geo-st-as-wkt]]
===== `ST_AsWKT`

.Synopsis:
[source, sql]
--------------------------------------------------
ST_WKTToSQL(string<1>)
--------------------------------------------------

*Input*:

<1> string geometry

*Output*: WKT string

.Description:

Returns the geometry from WKT representation. The return type is geometry.

["source","sql",subs="attributes,macros"]
--------------------------------------------------
include-tagged::{sql-specs}/geo/docs.csv-spec[aswkt]
--------------------------------------------------
3 changes: 3 additions & 0 deletions x-pack/plugin/sql/qa/src/main/resources/command.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,10 @@ RTRIM |SCALAR
SPACE |SCALAR
SUBSTRING |SCALAR
UCASE |SCALAR
ST_ASTEXT |SCALAR
ST_ASWKT |SCALAR
ST_GEOMFROMTEXT |SCALAR
ST_WKTTOSQL |SCALAR
CAST |SCALAR
CONVERT |SCALAR
SCORE |SCORE
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugin/sql/qa/src/main/resources/docs.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,10 @@ RTRIM |SCALAR
SPACE |SCALAR
SUBSTRING |SCALAR
UCASE |SCALAR
ST_ASTEXT |SCALAR
ST_ASWKT |SCALAR
ST_GEOMFROMTEXT |SCALAR
ST_WKTTOSQL |SCALAR
CAST |SCALAR
CONVERT |SCALAR
SCORE |SCORE
Expand Down
11 changes: 10 additions & 1 deletion x-pack/plugin/sql/qa/src/main/resources/geo/docs.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,18 @@

selectAsWKT
// tag::aswkt
SELECT city, ST_ASWKT(location) location FROM "geo" WHERE city = 'Amsterdam';
SELECT city, ST_AsWKT(location) location FROM "geo" WHERE city = 'Amsterdam';

city:s | location:s
Amsterdam |point (4.850311987102032 52.347556999884546)
// end::aswkt
;

selectWKTToSQL
// tag::wkttosql
SELECT CAST(ST_WKTToSQL('POINT (10 20)') AS STRING) location;

location:s
point (10.0 20.0)
// end::wkttosql
;
30 changes: 15 additions & 15 deletions x-pack/plugin/sql/qa/src/main/resources/geo/geosql-bulk.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
{"index":{"_id": "1"}}
{"region": "Americas", "city": "Mountain View", "location": {"lat":"37.386483", "lon":"-122.083843"}, "shape": "POINT (-122.083843 37.386483)"}
{"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)"}
{"index":{"_id": "2"}}
{"region": "Americas", "city": "Chicago", "location": [-87.637874, 41.888783], "shape": {"type" : "point", "coordinates" : [-87.637874, 41.888783] }}
{"region": "Americas", "city": "Chicago", "location": [-87.637874, 41.888783], "shape": {"type" : "point", "coordinates" : [-87.637874, 41.888783]}, "region_point": "POINT(-105.2551 54.5260)"}
{"index":{"_id": "3"}}
{"region": "Americas", "city": "New York", "location": "40.745171,-73.990027", "shape": "POINT (-73.990027 40.745171)"}
{"region": "Americas", "city": "New York", "location": "40.745171,-73.990027", "shape": "POINT (-73.990027 40.745171)", "region_point": "POINT(-105.2551 54.5260)"}
{"index":{"_id": "4"}}
{"region": "Americas", "city": "San Francisco", "location": "37.789541,-122.394228", "shape": "POINT (-122.394228 37.789541)"}
{"region": "Americas", "city": "San Francisco", "location": "37.789541,-122.394228", "shape": "POINT (-122.394228 37.789541)", "region_point": "POINT(-105.2551 54.5260)"}
{"index":{"_id": "5"}}
{"region": "Americas", "city": "Phoenix", "location": "33.376242,-111.973505", "shape": "POINT (-111.973505 33.376242)"}
{"region": "Americas", "city": "Phoenix", "location": "33.376242,-111.973505", "shape": "POINT (-111.973505 33.376242)", "region_point": "POINT(-105.2551 54.5260)"}
{"index":{"_id": "6"}}
{"region": "Europe", "city": "Amsterdam", "location": "52.347557,4.850312", "shape": "POINT (4.850312 52.347557)"}
{"region": "Europe", "city": "Amsterdam", "location": "52.347557,4.850312", "shape": "POINT (4.850312 52.347557)", "region_point": "POINT(15.2551 54.5260)"}
{"index":{"_id": "7"}}
{"region": "Europe", "city": "Berlin", "location": "52.486701,13.390889", "shape": "POINT (13.390889 52.486701)"}
{"region": "Europe", "city": "Berlin", "location": "52.486701,13.390889", "shape": "POINT (13.390889 52.486701)", "region_point": "POINT(15.2551 54.5260)"}
{"index":{"_id": "8"}}
{"region": "Europe", "city": "Munich", "location": "48.146321,11.537505", "shape": "POINT (11.537505 48.146321)"}
{"region": "Europe", "city": "Munich", "location": "48.146321,11.537505", "shape": "POINT (11.537505 48.146321)", "region_point": "POINT(15.2551 54.5260)"}
{"index":{"_id": "9"}}
{"region": "Europe", "city": "London", "location": "51.510871,-0.121672", "shape": "POINT (-0.121672 51.510871)"}
{"region": "Europe", "city": "London", "location": "51.510871,-0.121672", "shape": "POINT (-0.121672 51.510871)", "region_point": "POINT(15.2551 54.5260)"}
{"index":{"_id": "10"}}
{"region": "Europe", "city": "Paris", "location": "48.845538,2.351773", "shape": "POINT (2.351773 48.845538)"}
{"region": "Europe", "city": "Paris", "location": "48.845538,2.351773", "shape": "POINT (2.351773 48.845538)", "region_point": "POINT(15.2551 54.5260)"}
{"index":{"_id": "11"}}
{"region": "Asia", "city": "Singapore", "location": "1.295868,103.855535", "shape": "POINT (103.855535 1.295868)"}
{"region": "Asia", "city": "Singapore", "location": "1.295868,103.855535", "shape": "POINT (103.855535 1.295868)", "region_point": "POINT(100.6197 34.0479)"}
{"index":{"_id": "12"}}
{"region": "Asia", "city": "Hong Kong", "location": "22.281397,114.183925", "shape": "POINT (114.183925 22.281397)"}
{"region": "Asia", "city": "Hong Kong", "location": "22.281397,114.183925", "shape": "POINT (114.183925 22.281397)", "region_point": "POINT(100.6197 34.0479)"}
{"index":{"_id": "13"}}
{"region": "Asia", "city": "Seoul", "location": "37.509132,127.060851", "shape": "POINT (127.060851 37.509132)"}
{"region": "Asia", "city": "Seoul", "location": "37.509132,127.060851", "shape": "POINT (127.060851 37.509132)", "region_point": "POINT(100.6197 34.0479)"}
{"index":{"_id": "14"}}
{"region": "Asia", "city": "Tokyo", "location": "35.669616,139.76402225", "shape": "POINT (139.76402225 35.669616)"}
{"region": "Asia", "city": "Tokyo", "location": "35.669616,139.76402225", "shape": "POINT (139.76402225 35.669616)", "region_point": "POINT(100.6197 34.0479)"}
{"index":{"_id": "15"}}
{"region": "Asia", "city": "Sydney", "location": "-33.863385,151.208629", "shape": "POINT (151.208629 -33.863385)"}
{"region": "Asia", "city": "Sydney", "location": "-33.863385,151.208629", "shape": "POINT (151.208629 -33.863385)", "region_point": "POINT(100.6197 34.0479)"}



45 changes: 45 additions & 0 deletions x-pack/plugin/sql/qa/src/main/resources/geo/geosql.csv-spec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ DESCRIBE "geo";
city | VARCHAR | KEYWORD
location | OTHER | GEO_POINT
region | VARCHAR | KEYWORD
region_point | VARCHAR | KEYWORD
shape | OTHER | GEO_SHAPE
;

Expand Down Expand Up @@ -102,3 +103,47 @@ SELECT COUNT(city) count, CAST(SUBSTRING(ST_ASWKT(location), 8, 1) = '-' AS STRI
9 |false
6 |true
;

selectRegionUsingWktToSql
SELECT region, city, ST_ASWKT(ST_WKTTOSQL(region_point)) region_wkt FROM geo ORDER BY region, city;

region:s | city:s | region_wkt:s
Americas |Chicago |point (-105.2551 54.526)
Americas |Mountain View |point (-105.2551 54.526)
Americas |New York |point (-105.2551 54.526)
Americas |Phoenix |point (-105.2551 54.526)
Americas |San Francisco |point (-105.2551 54.526)
Asia |Hong Kong |point (100.6197 34.0479)
Asia |Seoul |point (100.6197 34.0479)
Asia |Singapore |point (100.6197 34.0479)
Asia |Sydney |point (100.6197 34.0479)
Asia |Tokyo |point (100.6197 34.0479)
Europe |Amsterdam |point (15.2551 54.526)
Europe |Berlin |point (15.2551 54.526)
Europe |London |point (15.2551 54.526)
Europe |Munich |point (15.2551 54.526)
Europe |Paris |point (15.2551 54.526)
;

selectCitiesWithAGroupByWktToSql
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);

city_by_region:l | region:s
3 |point (-105.2551 54.526)
1 |point (100.6197 34.0479)
2 |point (15.2551 54.526)
;

selectCitiesWithEOrderByWktToSql
SELECT region, city FROM geo WHERE city LIKE '%e%' ORDER BY ST_WKTTOSQL(region_point), city;

region:s | city:s
Americas |Mountain View
Americas |New York
Americas |Phoenix
Asia |Seoul
Asia |Singapore
Asia |Sydney
Europe |Amsterdam
Europe |Berlin
;
3 changes: 3 additions & 0 deletions x-pack/plugin/sql/qa/src/main/resources/geo/geosql.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
},
"shape": {
"type": "geo_shape"
},
"region_point": {
"type": "keyword"
}
}
}
Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugin/sql/qa/src/main/resources/ogc/ogc.sql-spec
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ selectNamedPlaces
SELECT fid, name, boundary FROM named_places ORDER BY fid;
selectMapNeatLines
SELECT fid, neatline FROM map_neatlines ORDER BY fid;

//
// Type conversion functions
//

// The string serialization is slightly different between ES and H2, so we need to tweak it a bit by uppercasing both
// and removing floating point
selectRoadSegmentsAsWkt
SELECT fid, name, num_lanes, aliases, REPLACE(UCASE(ST_AsText(centerline)), '.0', '') centerline_wkt FROM road_segments ORDER BY fid;

selectSinglePoint
SELECT ST_GeomFromText('point (10.0 12.0)') point;
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.WeekOfYear;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.Year;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StAswkt;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StWkttosql;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ACos;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ASin;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.ATan;
Expand Down Expand Up @@ -214,7 +215,8 @@ private void defineDefaultFunctions() {
def(UCase.class, UCase::new));

// Geo Functions
addToMap(def(StAswkt.class, StAswkt::new));
addToMap(def(StAswkt.class, StAswkt::new, "ST_ASTEXT"));
addToMap(def(StWkttosql.class, StWkttosql::new, "ST_GEOMFROMTEXT"));
// DataType conversion
addToMap(def(Cast.class, Cast::new, "CONVERT"));
// Special
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NamedDateTimeProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StWkttosqlProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor;
Expand Down Expand Up @@ -90,6 +91,7 @@ public static List<NamedWriteableRegistry.Entry> getNamedWriteables() {
entries.add(new Entry(Processor.class, ReplaceFunctionProcessor.NAME, ReplaceFunctionProcessor::new));
entries.add(new Entry(Processor.class, SubstringFunctionProcessor.NAME, SubstringFunctionProcessor::new));
entries.add(new Entry(Processor.class, GeoProcessor.NAME, GeoProcessor::new));
entries.add(new Entry(Processor.class, StWkttosqlProcessor.NAME, StWkttosqlProcessor::new));
return entries;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.sql.expression.function.scalar.geo;

import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.Expressions;
import org.elasticsearch.xpack.sql.expression.function.scalar.UnaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;
import org.elasticsearch.xpack.sql.expression.gen.script.Scripts;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;

/**
* Constructs geometric objects from their WTK representations
*/
public class StWkttosql extends UnaryScalarFunction {

public StWkttosql(Location location, Expression field) {
super(location, field);
}

@Override
protected StWkttosql replaceChild(Expression newChild) {
return new StWkttosql(location(), newChild);
}

@Override
protected TypeResolution resolveType() {
if (field().dataType().isString()) {
return TypeResolution.TYPE_RESOLVED;
}
return Expressions.typeMustBeString(field(), functionName(), Expressions.ParamOrdinal.DEFAULT);
}

@Override
protected Processor makeProcessor() {
return StWkttosqlProcessor.INSTANCE;
}

@Override
public DataType dataType() {
return DataType.GEO_SHAPE;
}

@Override
protected NodeInfo<StWkttosql> info() {
return NodeInfo.create(this, StWkttosql::new, field());
}

@Override
public String processScript(String script) {
return Scripts.formatTemplate(Scripts.SQL_SCRIPTS + ".wktToSql(" + script + ")");
}

@Override
public Object fold() {
return StWkttosqlProcessor.INSTANCE.process(field().fold());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.sql.expression.function.scalar.geo;

import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.gen.processor.Processor;

import java.io.IOException;

public class StWkttosqlProcessor implements Processor {

static final StWkttosqlProcessor INSTANCE = new StWkttosqlProcessor();

public static final String NAME = "geo_wkttosql";

StWkttosqlProcessor() {
}

public StWkttosqlProcessor(StreamInput in) throws IOException {
}

@Override
public Object process(Object input) {
return StWkttosqlProcessor.apply(input);
}

public static GeoShape apply(Object input) {
if (input == null) {
return null;
}

if ((input instanceof String) == false) {
throw new SqlIllegalArgumentException("A string is required; received [{}]", input);
}
try {
return new GeoShape(input);
} catch (IOException | IllegalArgumentException | ElasticsearchParseException ex) {
throw new SqlIllegalArgumentException("Cannot parse [{}] as a geo_shape value", input);
}
}

@Override
public String getWriteableName() {
return NAME;
}

@Override
public void writeTo(StreamOutput out) throws IOException {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.NamedDateTimeProcessor.NameExtractor;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.QuarterProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoShape;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.StWkttosqlProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.BinaryMathProcessor.BinaryMathOperation;
import org.elasticsearch.xpack.sql.expression.function.scalar.math.MathProcessor.MathOperation;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
Expand Down Expand Up @@ -407,4 +409,8 @@ public static String aswktPoint(Object v) {
public static String aswktShape(Object v) {
return GeoProcessor.GeoOperation.ASWKT_SHAPE.apply(v).toString();
}

public static GeoShape wktToSql(String wktString) {
return StWkttosqlProcessor.apply(wktString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.type;

import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.geo.GeoShape;
import org.joda.time.DateTime;

public final class DataTypes {
Expand Down Expand Up @@ -51,6 +52,9 @@ public static DataType fromJava(Object value) {
if (value instanceof String || value instanceof Character) {
return DataType.KEYWORD;
}
if (value instanceof GeoShape) {
return DataType.GEO_SHAPE;
}
throw new SqlIllegalArgumentException("No idea what's the DataType for {}", value.getClass());
}

Expand Down
Loading

0 comments on commit 08da5bb

Please sign in to comment.