From 35f2510bab74ee3a2080c775f1f65383dc9f3181 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20BIDON?=
st_makebox2d(geometry_a: geometry, geometry_b: geometry) → box2d
Creates a box2d from two points. Errors if arguments are not two non-empty points.
st_makeenvelope(xmin: float, ymin: float, xmax: float, ymax: float) → geometry
Returns a box2d from bounds (the default srid value is 0).
+st_makeenvelope(xmin: float, ymin: float, xmax: float, ymax: float, srid: int) → geometry
Returns a box2d from bounds.
+st_makepoint(x: float, y: float) → geometry
Returns a new Point with the given X and Y coordinates.
st_makepoint(x: float, y: float, z: float) → geometry
Returns a new Point with the given X, Y, and Z coordinates.
diff --git a/pkg/sql/sem/builtins/BUILD.bazel b/pkg/sql/sem/builtins/BUILD.bazel index ea3b1fc070fe..f044a5095e03 100644 --- a/pkg/sql/sem/builtins/BUILD.bazel +++ b/pkg/sql/sem/builtins/BUILD.bazel @@ -130,6 +130,7 @@ go_test( embed = [":builtins"], deps = [ "//pkg/base", + "//pkg/geo/geopb", "//pkg/keys", "//pkg/kv", "//pkg/security", diff --git a/pkg/sql/sem/builtins/geo_builtins.go b/pkg/sql/sem/builtins/geo_builtins.go index 196a3bbb7806..f57b8cea60f6 100644 --- a/pkg/sql/sem/builtins/geo_builtins.go +++ b/pkg/sql/sem/builtins/geo_builtins.go @@ -5302,7 +5302,7 @@ The calculations are done on a sphere.`, }, Info: infoBuilder{ info: `Snaps the vertices and segments of input geometry the target geometry's vertices. -Tolerance is used to control where snapping is performed. The result geometry is the input geometry with the vertices snapped. +Tolerance is used to control where snapping is performed. The result geometry is the input geometry with the vertices snapped. If no snapping occurs then the input geometry is returned unchanged.`, }.String(), Volatility: tree.VolatilityImmutable, @@ -5558,6 +5558,42 @@ Bottom Left.`, Volatility: tree.VolatilityImmutable, }, ), + "st_makeenvelope": makeBuiltin( + defProps(), + tree.Overload{ + Types: tree.ArgTypes{ + {"xmin", types.Float}, + {"ymin", types.Float}, + {"xmax", types.Float}, + {"ymax", types.Float}, + {"srid", types.Int}, + }, + ReturnType: tree.FixedReturnType(types.Geometry), + Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + return makeGeometryFromBounds(args) + }, + Info: infoBuilder{ + info: "Returns a box2d from bounds.", + }.String(), + Volatility: tree.VolatilityImmutable, + }, + tree.Overload{ + Types: tree.ArgTypes{ + {"xmin", types.Float}, + {"ymin", types.Float}, + {"xmax", types.Float}, + {"ymax", types.Float}, + }, + ReturnType: tree.FixedReturnType(types.Geometry), + Fn: func(_ *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + return makeGeometryFromBounds(args) + }, + Info: infoBuilder{ + info: "Returns a box2d from bounds (the default srid value is 0).", + }.String(), + Volatility: tree.VolatilityImmutable, + }, + ), "st_flipcoordinates": makeBuiltin( defProps(), tree.Overload{ @@ -6768,7 +6804,7 @@ May return a Point or LineString in the case of degenerate inputs.`, return tree.NewDInt(tree.DInt(ret)), nil }, Info: infoBuilder{ - info: `Returns an interger value defining behavior of crossing of lines: + info: `Returns an interger value defining behavior of crossing of lines: 0: lines do not cross, -1: linestring_b crosses linestring_a from right to left, 1: linestring_b crosses linestring_a from left to right, @@ -7502,3 +7538,28 @@ func applyGeoindexConfigStorageParams( } return indexDesc.GeoConfig, nil } + +// makeGeometryFromBounds builds a rectangle geometry from types.Float bounds as datums +func makeGeometryFromBounds(args tree.Datums) (tree.Datum, error) { + xmin := float64(tree.MustBeDFloat(args[0])) + ymin := float64(tree.MustBeDFloat(args[1])) + xmax := float64(tree.MustBeDFloat(args[2])) + ymax := float64(tree.MustBeDFloat(args[3])) + + var srid int + if len(args) > 4 { + srid = int(tree.MustBeDInt(args[4])) + } + + extent, err := geo.MakeGeometryFromGeomT( + geom.NewBounds(geom.XY). + Set(xmin, ymin, xmax, ymax). + Polygon(). + SetSRID(srid), + ) + if err != nil { + return nil, err + } + + return tree.NewDGeometry(extent), nil +} diff --git a/pkg/sql/sem/builtins/geo_builtins_test.go b/pkg/sql/sem/builtins/geo_builtins_test.go index 677c510b7e5f..3f5f8364005f 100644 --- a/pkg/sql/sem/builtins/geo_builtins_test.go +++ b/pkg/sql/sem/builtins/geo_builtins_test.go @@ -17,6 +17,7 @@ import ( "testing" "unicode" + "github.com/cockroachdb/cockroach/pkg/geo/geopb" "github.com/cockroachdb/cockroach/pkg/sql/randgen" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/types" @@ -103,3 +104,88 @@ func TestGeoBuiltinsPointEmptyArgs(t *testing.T) { }) } } + +func mustParseDFloat(t testing.TB, in string) tree.Datum { + d, err := tree.ParseDFloat(in) + require.NoError(t, err) + + return d +} + +func mustParseDGeometry(t testing.TB, in string, srid int) tree.Datum { + d, err := tree.ParseDGeometry(in) + require.NoError(t, err) + + withSRID, err := d.CloneWithSRID(geopb.SRID(srid)) + require.NoError(t, err) + + return tree.NewDGeometry(withSRID) +} + +type multiArgsBuiltinFixture struct { + Title string + Builtin tree.Overload + Args tree.Datums + Expected tree.Datum + ExpectError bool +} + +func TestGeoBuiltinsSTMakeEnvelope(t *testing.T) { + defer leaktest.AfterTest(t)() + + // polygonGeometry, err := tree.ParseDGeometry("POLYGON((170 50,170 72,-130 72,-130 50,170 50))") + for _, toPin := range []multiArgsBuiltinFixture{ + { + Title: "st_makeenvelope happy path, srid", + Builtin: geoBuiltins["st_makeenvelope"].overloads[0], + Args: tree.Datums{ + mustParseDFloat(t, "30.01"), + mustParseDFloat(t, "50.01"), + mustParseDFloat(t, "72.01"), + mustParseDFloat(t, "52.01"), + tree.NewDInt(tree.DInt(4326)), + }, + Expected: mustParseDGeometry(t, "POLYGON((30.01 50.01,30.01 52.01,72.01 52.01,72.01 50.01,30.01 50.01))", 4326), + }, + { + Title: "st_makeenvelope happy path, unknown srid", + Builtin: geoBuiltins["st_makeenvelope"].overloads[1], + Args: tree.Datums{ + mustParseDFloat(t, "30.01"), + mustParseDFloat(t, "50.01"), + mustParseDFloat(t, "72.01"), + mustParseDFloat(t, "52.01"), + }, + Expected: mustParseDGeometry(t, "POLYGON((30.01 50.01,30.01 52.01,72.01 52.01,72.01 50.01,30.01 50.01))", 0), + }, + { + Title: "st_makeenvelope degenerate bounds, unknown srid", + Builtin: geoBuiltins["st_makeenvelope"].overloads[1], + Args: tree.Datums{ + mustParseDFloat(t, "30"), + mustParseDFloat(t, "50"), + mustParseDFloat(t, "30"), + mustParseDFloat(t, "50"), + }, + Expected: mustParseDGeometry(t, "POLYGON((30 50,30 50,30 50,30 50,30 50))", 0), + }, + } { + testcase := toPin + + t.Run(testcase.Title, func(t *testing.T) { + t.Parallel() // SAFE FOR TESTING + + overload := testcase.Builtin + datums := testcase.Args + res, err := overload.Fn(&tree.EvalContext{}, datums) + if testcase.ExpectError { + require.Error(t, err) + + return + } + + require.NoError(t, err) + require.EqualValues(t, testcase.Expected, res) + }) + } +}