diff --git a/docs/RFCS/20200421_geospatial.md b/docs/RFCS/20200421_geospatial.md index e3839fe17a39..f06a56277df2 100644 --- a/docs/RFCS/20200421_geospatial.md +++ b/docs/RFCS/20200421_geospatial.md @@ -513,6 +513,7 @@ For 2D geometry and geography, these are: * ST_Covers * ST_CoveredBy * ST_Contains (geometry only) +* ST_ContainsProperly (geometry only) * ST_Crosses (geometry only) * ST_DFullyWithin (geometry only) * ST_DWithin @@ -1039,6 +1040,8 @@ Functions map to the index functions: contains(g, x) * ST_Contains(g, x), ST_Contains(x, g): use contains(g, x) or contained-by(g, x) +* ST_ContainsProperly(g, x), ST_ContainsProperly(x, g): use contains(g, x) or + contained-by(g, x) * ST_Crosses: use intersects * ST_DFullyWithin(g, x, d), ST_DFullyWithin(x, g, d): extend g by distance d to produce a shape g’, and then use contains(g', x). The @@ -1667,3 +1670,7 @@ good stretch option as well. ## Unresolved questions None beyond what is already mentioned in earlier text. + +# Updates +* 2020-05-07: + * added ST_ContainsProperly as an indexable function. diff --git a/docs/generated/sql/functions.md b/docs/generated/sql/functions.md index af6cb2862a85..19a2aa312103 100644 --- a/docs/generated/sql/functions.md +++ b/docs/generated/sql/functions.md @@ -703,6 +703,10 @@ has no relationship with the commit order of concurrent transactions.
This function uses the GEOS module.
This function will automatically use any available index.
+st_containsproperly(geometry_a: geometry, geometry_b: geometry) → bool
Returns true if geometry_b intersects the interior of geometry_a but not the boundary or exterior of geometry_a.
+This function uses the GEOS module.
+This function will automatically use any available index.
+st_coveredby(geometry_a: geometry, geometry_b: geometry) → bool
Returns true if no point in geometry_a is outside geometry_b.
This function uses the GEOS module.
This function will automatically use any available index.
@@ -890,6 +894,12 @@ has no relationship with the commit order of concurrent transactions.st_polygonfromwkb(wkb: bytes, srid: int) → geometry
Returns the Geometry from a WKB representation with an SRID. If the shape underneath is not Polygon, NULL is returned.
st_relate(geometry_a: geometry, geometry_b: geometry) → string
Returns the DE-9IM spatial relation between geometry_a and geometry_b.
+This function uses the GEOS module.
+st_relate(geometry_a: geometry, geometry_b: geometry, pattern: string) → bool
Returns whether the DE-9IM spatial relation between geometry_a and geometry_b matches the DE-9IM pattern.
+This function uses the GEOS module.
+st_touches(geometry_a: geometry, geometry_b: geometry) → bool
Returns true if the only points in common between geometry_a and geometry_b are on the boundary. Note points do not touch other points.
This function uses the GEOS module.
This function will automatically use any available index.
diff --git a/pkg/geo/geomfn/binary_predicates.go b/pkg/geo/geomfn/binary_predicates.go index 17a5ef415983..3f3b7c9e4cdb 100644 --- a/pkg/geo/geomfn/binary_predicates.go +++ b/pkg/geo/geomfn/binary_predicates.go @@ -39,6 +39,16 @@ func Contains(a *geo.Geometry, b *geo.Geometry) (bool, error) { return geos.Contains(a.EWKB(), b.EWKB()) } +// ContainsProperly returns whether geometry A properly contains geometry B. +func ContainsProperly(a *geo.Geometry, b *geo.Geometry) (bool, error) { + // No GEOS CAPI to call ContainsProperly; fallback to Relate. + relate, err := Relate(a, b) + if err != nil { + return false, err + } + return MatchesDE9IM(relate, "T**FF*FF*") +} + // Crosses returns whether geometry A crosses geometry B. func Crosses(a *geo.Geometry, b *geo.Geometry) (bool, error) { if a.SRID() != b.SRID() { diff --git a/pkg/geo/geomfn/binary_predicates_test.go b/pkg/geo/geomfn/binary_predicates_test.go index a2e70e2e13fa..f355c6ebcda3 100644 --- a/pkg/geo/geomfn/binary_predicates_test.go +++ b/pkg/geo/geomfn/binary_predicates_test.go @@ -85,6 +85,8 @@ func TestContains(t *testing.T) { }{ {rightRect, rightRectPoint, true}, {rightRectPoint, rightRect, false}, + {rightRectPoint, rightRectPoint, true}, + {rightRect, rightRect, true}, {leftRect, rightRect, false}, } @@ -102,6 +104,31 @@ func TestContains(t *testing.T) { }) } +func TestContainsProperly(t *testing.T) { + testCases := []struct { + a *geo.Geometry + b *geo.Geometry + expected bool + }{ + {rightRect, rightRect, false}, + {rightRect, rightRectPoint, true}, + {rightRectPoint, rightRectPoint, true}, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("tc:%d", i), func(t *testing.T) { + g, err := ContainsProperly(tc.a, tc.b) + require.NoError(t, err) + require.Equal(t, tc.expected, g) + }) + } + + t.Run("errors if SRIDs mismatch", func(t *testing.T) { + _, err := ContainsProperly(mismatchingSRIDGeometryA, mismatchingSRIDGeometryB) + requireMismatchingSRIDError(t, err) + }) +} + func TestCrosses(t *testing.T) { testCases := []struct { a *geo.Geometry diff --git a/pkg/geo/geomfn/de9im.go b/pkg/geo/geomfn/de9im.go new file mode 100644 index 000000000000..7799d2b26c90 --- /dev/null +++ b/pkg/geo/geomfn/de9im.go @@ -0,0 +1,74 @@ +// Copyright 2020 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package geomfn + +import ( + "github.com/cockroachdb/cockroach/pkg/geo" + "github.com/cockroachdb/cockroach/pkg/geo/geos" + "github.com/cockroachdb/cockroach/pkg/util" + "github.com/cockroachdb/errors" +) + +// Relate returns the DE-9IM relation between A and B. +func Relate(a *geo.Geometry, b *geo.Geometry) (string, error) { + if a.SRID() != b.SRID() { + return "", geo.NewMismatchingSRIDsError(a, b) + } + return geos.Relate(a.EWKB(), b.EWKB()) +} + +// MatchesDE9IM checks whether the given DE-9IM relation matches the DE-91M pattern. +// Assumes the relation has been computed, and such has no 'T' and '*' characters. +// See: https://en.wikipedia.org/wiki/DE-9IM. +func MatchesDE9IM(relation string, pattern string) (bool, error) { + if len(relation) != 9 { + return false, errors.Newf("relation %q should be of length 9", relation) + } + if len(pattern) != 9 { + return false, errors.Newf("pattern %q should be of length 9", pattern) + } + for i := 0; i < len(relation); i++ { + matches, err := relationByteMatchesPatternByte(relation[i], pattern[i]) + if err != nil { + return false, err + } + if !matches { + return false, nil + } + } + return true, nil +} + +// relationByteMatchesPatternByte matches a single byte of a DE-9IM relation +// against the DE-9IM pattern. +// Pattern matches are as follows: +// * '*': allow anything. +// * 't'/'T': allow only if the relation is true. This means the relation must be +// '0' (point), '1' (line) or '2' (area) - which is the dimensionality of the +// intersection. +// * 'f'/'F': allow only if relation is also false, which is of the form 'f'/'F'. +func relationByteMatchesPatternByte(r byte, p byte) (bool, error) { + switch util.ToLowerSingleByte(p) { + case '*': + return true, nil + case 't': + if r < '0' || r > '2' { + return false, nil + } + case 'f': + if util.ToLowerSingleByte(r) != 'f' { + return false, nil + } + default: + return false, errors.Newf("unrecognized pattern character: %s", string(p)) + } + return true, nil +} diff --git a/pkg/geo/geomfn/de9im_test.go b/pkg/geo/geomfn/de9im_test.go new file mode 100644 index 000000000000..66d79dd1787f --- /dev/null +++ b/pkg/geo/geomfn/de9im_test.go @@ -0,0 +1,72 @@ +// Copyright 2020 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package geomfn + +import ( + "fmt" + "testing" + + "github.com/cockroachdb/cockroach/pkg/geo" + "github.com/stretchr/testify/require" +) + +func TestRelate(t *testing.T) { + testCases := []struct { + a *geo.Geometry + b *geo.Geometry + expected string + }{ + {leftRect, rightRect, "FF2F11212"}, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("tc:%d", i), func(t *testing.T) { + ret, err := Relate(tc.a, tc.b) + require.NoError(t, err) + require.Equal(t, tc.expected, ret) + }) + } +} + +func TestMatchesDE9IM(t *testing.T) { + testCases := []struct { + str string + pattern string + expected bool + expectedError string + }{ + {"", "T**FF*FF*", false, `relation "" should be of length 9`}, + {"TTTTTTTTT", "T**FF*FF*T", false, `pattern "T**FF*FF*T" should be of length 9`}, + {"TTTTTTTTT", "T**FF*FF*T", false, `pattern "T**FF*FF*T" should be of length 9`}, + {"000FFF000", "cTTFfFTTT", false, `unrecognized pattern character: c`}, + {"120FFF021", "TTTFfFTTT", true, ""}, + {"02FFFF000", "T**FfFTTT", true, ""}, + {"020F1F010", "TTTFFFTtT", false, ""}, + {"020FFF0f0", "TTTFFFTtT", false, ""}, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("%s has pattern %s", tc.str, tc.pattern), func(t *testing.T) { + ret, err := MatchesDE9IM(tc.str, tc.pattern) + if tc.expectedError == "" { + require.NoError(t, err) + require.Equal(t, tc.expected, ret) + } else { + require.EqualError(t, err, tc.expectedError) + } + }) + } + + t.Run("errors if SRIDs mismatch", func(t *testing.T) { + _, err := Relate(mismatchingSRIDGeometryA, mismatchingSRIDGeometryB) + requireMismatchingSRIDError(t, err) + }) +} diff --git a/pkg/geo/geos/geos.cc b/pkg/geo/geos/geos.cc index 46197aadd42e..7ef93637aff1 100644 --- a/pkg/geo/geos/geos.cc +++ b/pkg/geo/geos/geos.cc @@ -48,7 +48,7 @@ typedef void (*CR_GEOS_finish_r)(CR_GEOS_Handle); typedef CR_GEOS_MessageHandler (*CR_GEOS_Context_setErrorMessageHandler_r)(CR_GEOS_Handle, CR_GEOS_MessageHandler, void*); - +typedef void (*CR_GEOS_Free_r)(CR_GEOS_Handle, void* buffer); typedef void (*CR_GEOS_SetSRID_r)(CR_GEOS_Handle, CR_GEOS_Geometry, int); typedef int (*CR_GEOS_GetSRID_r)(CR_GEOS_Handle, CR_GEOS_Geometry); typedef void (*CR_GEOS_GeomDestroy_r)(CR_GEOS_Handle, CR_GEOS_Geometry); @@ -78,6 +78,8 @@ typedef char (*CR_GEOS_Overlaps_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geo typedef char (*CR_GEOS_Touches_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry); typedef char (*CR_GEOS_Within_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry); +typedef char* (*CR_GEOS_Relate_r)(CR_GEOS_Handle, CR_GEOS_Geometry, CR_GEOS_Geometry); + typedef CR_GEOS_WKBWriter (*CR_GEOS_WKBWriter_create_r)(CR_GEOS_Handle); typedef char* (*CR_GEOS_WKBWriter_write_r)(CR_GEOS_Handle, CR_GEOS_WKBWriter, CR_GEOS_Geometry, size_t*); @@ -99,6 +101,7 @@ struct CR_GEOS { CR_GEOS_init_r GEOS_init_r; CR_GEOS_finish_r GEOS_finish_r; CR_GEOS_Context_setErrorMessageHandler_r GEOSContext_setErrorMessageHandler_r; + CR_GEOS_Free_r GEOSFree_r; CR_GEOS_SetSRID_r GEOSSetSRID_r; CR_GEOS_GetSRID_r GEOSGetSRID_r; @@ -127,6 +130,8 @@ struct CR_GEOS { CR_GEOS_Touches_r GEOSTouches_r; CR_GEOS_Within_r GEOSWithin_r; + CR_GEOS_Relate_r GEOSRelate_r; + CR_GEOS_WKBWriter_create_r GEOSWKBWriter_create_r; CR_GEOS_WKBWriter_destroy_r GEOSWKBWriter_destroy_r; CR_GEOS_WKBWriter_setByteOrder_r GEOSWKBWriter_setByteOrder_r; @@ -154,6 +159,7 @@ struct CR_GEOS { INIT(GEOS_init_r); INIT(GEOS_finish_r); + INIT(GEOSFree_r); INIT(GEOSContext_setErrorMessageHandler_r); INIT(GEOSGeom_destroy_r); INIT(GEOSSetSRID_r); @@ -170,6 +176,7 @@ struct CR_GEOS { INIT(GEOSOverlaps_r); INIT(GEOSTouches_r); INIT(GEOSWithin_r); + INIT(GEOSRelate_r); INIT(GEOSWKTReader_create_r); INIT(GEOSWKTReader_destroy_r); INIT(GEOSWKTReader_read_r); @@ -431,3 +438,34 @@ CR_GEOS_Status CR_GEOS_Touches(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, c CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret) { return CR_GEOS_BinaryPredicate(lib, lib->GEOSWithin_r, a, b, ret); } + +// +// DE-9IM related +// See: https://en.wikipedia.org/wiki/DE-9IM. +// + +CR_GEOS_Status CR_GEOS_Relate(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_String *ret) { + std::string error; + auto handle = initHandleWithErrorBuffer(lib, &error); + + auto wkbReader = lib->GEOSWKBReader_create_r(handle); + auto geomA = lib->GEOSWKBReader_read_r(handle, wkbReader, a.data, a.len); + auto geomB = lib->GEOSWKBReader_read_r(handle, wkbReader, b.data, b.len); + lib->GEOSWKBReader_destroy_r(handle, wkbReader); + + if (geomA != nullptr && geomB != nullptr) { + auto r = lib->GEOSRelate_r(handle, geomA, geomB); + if (r != NULL) { + *ret = toGEOSString(r, strlen(r)); + lib->GEOSFree_r(handle, r); + } + } + if (geomA != nullptr) { + lib->GEOSGeom_destroy_r(handle, geomA); + } + if (geomB != nullptr) { + lib->GEOSGeom_destroy_r(handle, geomB); + } + lib->GEOS_finish_r(handle); + return toGEOSString(error.data(), error.length()); +} diff --git a/pkg/geo/geos/geos.go b/pkg/geo/geos/geos.go index 3804384b138f..932e7e880049 100644 --- a/pkg/geo/geos/geos.go +++ b/pkg/geo/geos/geos.go @@ -399,3 +399,23 @@ func Within(a geopb.EWKB, b geopb.EWKB) (bool, error) { } return ret == 1, nil } + +// +// DE-9IM related +// + +// Relate returns the DE-9IM relation between A and B. +func Relate(a geopb.EWKB, b geopb.EWKB) (string, error) { + g, err := ensureInitInternal() + if err != nil { + return "", err + } + var ret C.CR_GEOS_String + if err := statusToError(C.CR_GEOS_Relate(g, goToCSlice(a), goToCSlice(b), &ret)); err != nil { + return "", err + } + if ret.data == nil { + return "", errors.Newf("expected DE-9IM string but found nothing") + } + return string(cStringToSafeGoBytes(ret)), nil +} diff --git a/pkg/geo/geos/geos.h b/pkg/geo/geos/geos.h index 916b5e11a51a..2123726d6378 100644 --- a/pkg/geo/geos/geos.h +++ b/pkg/geo/geos/geos.h @@ -84,6 +84,11 @@ CR_GEOS_Status CR_GEOS_Overlaps(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_Status CR_GEOS_Touches(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret); CR_GEOS_Status CR_GEOS_Within(CR_GEOS* lib, CR_GEOS_Slice a, CR_GEOS_Slice b, char *ret); +// +// DE-9IM related +// + +CR_GEOS_Status CR_GEOS_Relate(CR_GEOS *lib, CR_GEOS_Slice a, CR_GEOS_Slice b, CR_GEOS_String *ret); #ifdef __cplusplus } // extern "C" #endif diff --git a/pkg/sql/colexec/execplan.go b/pkg/sql/colexec/execplan.go index b4795b2440a4..37179579ee87 100644 --- a/pkg/sql/colexec/execplan.go +++ b/pkg/sql/colexec/execplan.go @@ -882,7 +882,12 @@ func NewColOperator( } result.ColumnTypes = make([]*types.T, len(leftTypes)+len(rightTypes)) copy(result.ColumnTypes, leftTypes) - copy(result.ColumnTypes[len(leftTypes):], rightTypes) + if core.HashJoiner.Type == sqlbase.JoinType_LEFT_SEMI || + core.HashJoiner.Type == sqlbase.JoinType_LEFT_ANTI { + result.ColumnTypes = result.ColumnTypes[:len(leftTypes):len(leftTypes)] + } else { + copy(result.ColumnTypes[len(leftTypes):], rightTypes) + } if !core.HashJoiner.OnExpr.Empty() && core.HashJoiner.Type == sqlbase.JoinType_INNER { if err = result.planAndMaybeWrapOnExprAsFilter(ctx, flowCtx, core.HashJoiner.OnExpr, streamingMemAccount, processorConstructor); err != nil { @@ -945,7 +950,12 @@ func NewColOperator( result.ToClose = append(result.ToClose, mj.(IdempotentCloser)) result.ColumnTypes = make([]*types.T, len(leftTypes)+len(rightTypes)) copy(result.ColumnTypes, leftTypes) - copy(result.ColumnTypes[len(leftTypes):], rightTypes) + if core.MergeJoiner.Type == sqlbase.JoinType_LEFT_SEMI || + core.MergeJoiner.Type == sqlbase.JoinType_LEFT_ANTI { + result.ColumnTypes = result.ColumnTypes[:len(leftTypes):len(leftTypes)] + } else { + copy(result.ColumnTypes[len(leftTypes):], rightTypes) + } if onExpr != nil { if err = result.planAndMaybeWrapOnExprAsFilter(ctx, flowCtx, *onExpr, streamingMemAccount, processorConstructor); err != nil { diff --git a/pkg/sql/logictest/testdata/logic_test/geospatial b/pkg/sql/logictest/testdata/logic_test/geospatial index a038857f8cea..714bcfb28253 100644 --- a/pkg/sql/logictest/testdata/logic_test/geospatial +++ b/pkg/sql/logictest/testdata/logic_test/geospatial @@ -289,13 +289,14 @@ Square overlapping left and right square Square (right) Square overlapping left and right square Square overlapping left and right square 0 # Binary predicates -query TTBBBBBBBBB +query TTBBBBBBBBBB SELECT a.dsc, b.dsc, ST_Covers(a.geom, b.geom), ST_CoveredBy(a.geom, b.geom), ST_Contains(a.geom, b.geom), + ST_ContainsProperly(a.geom, b.geom), ST_Crosses(a.geom, b.geom), ST_Equals(a.geom, b.geom), ST_Intersects(a.geom, b.geom), @@ -306,55 +307,116 @@ FROM geom_operators_test a JOIN geom_operators_test b ON (1=1) ORDER BY a.dsc, b.dsc ---- -Line going through left and right square Line going through left and right square true true true false true true false false true -Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Line going through left and right square Point middle of Left Square true false false false false true false true false -Line going through left and right square Point middle of Right Square true false false false false true false true false -Line going through left and right square Square (left) false false false true false true false false false -Line going through left and right square Square (right) false false false true false true false false false -Line going through left and right square Square overlapping left and right square false false false true false true false false false -NULL Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL -NULL Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL -Point middle of Left Square Line going through left and right square false true false false false true false true false -Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Point middle of Left Square Point middle of Left Square true true true false true true false false true -Point middle of Left Square Point middle of Right Square false false false false false false false false false -Point middle of Left Square Square (left) false true false false false true false false true -Point middle of Left Square Square (right) false false false false false false false false false -Point middle of Left Square Square overlapping left and right square false false false false false false false false false -Point middle of Right Square Line going through left and right square false true false false false true false true false -Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Point middle of Right Square Point middle of Left Square false false false false false false false false false -Point middle of Right Square Point middle of Right Square true true true false true true false false true -Point middle of Right Square Square (left) false false false false false false false false false -Point middle of Right Square Square (right) false true false false false true false false true -Point middle of Right Square Square overlapping left and right square false true false false false true false false true -Square (left) Line going through left and right square false false false true false true false false false -Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Square (left) Point middle of Left Square true false true false false true false false false -Square (left) Point middle of Right Square false false false false false false false false false -Square (left) Square (left) true true true false true true false false true -Square (left) Square (right) false false false false false true false true false -Square (left) Square overlapping left and right square false false false false false true true false false -Square (right) Line going through left and right square false false false true false true false false false -Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Square (right) Point middle of Left Square false false false false false false false false false -Square (right) Point middle of Right Square true false true false false true false false false -Square (right) Square (left) false false false false false true false true false -Square (right) Square (right) true true true false true true false false true -Square (right) Square overlapping left and right square false true false false false true false false true -Square overlapping left and right square Line going through left and right square false false false true false true false false false -Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL -Square overlapping left and right square Point middle of Left Square false false false false false false false false false -Square overlapping left and right square Point middle of Right Square true false true false false true false false false -Square overlapping left and right square Square (left) false false false false false true true false false -Square overlapping left and right square Square (right) true false true false false true false false false -Square overlapping left and right square Square overlapping left and right square true true true false true true false false true +Line going through left and right square Line going through left and right square true true true false false true true false false true +Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Line going through left and right square Point middle of Left Square true false false false false false true false true false +Line going through left and right square Point middle of Right Square true false false false false false true false true false +Line going through left and right square Square (left) false false false false true false true false false false +Line going through left and right square Square (right) false false false false true false true false false false +Line going through left and right square Square overlapping left and right square false false false false true false true false false false +NULL Line going through left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +NULL Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Point middle of Left Square Line going through left and right square false true false false false false true false true false +Point middle of Left Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Point middle of Left Square Point middle of Left Square true true true true false true true false false true +Point middle of Left Square Point middle of Right Square false false false false false false false false false false +Point middle of Left Square Square (left) false true false false false false true false false true +Point middle of Left Square Square (right) false false false false false false false false false false +Point middle of Left Square Square overlapping left and right square false false false false false false false false false false +Point middle of Right Square Line going through left and right square false true false false false false true false true false +Point middle of Right Square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Point middle of Right Square Point middle of Left Square false false false false false false false false false false +Point middle of Right Square Point middle of Right Square true true true true false true true false false true +Point middle of Right Square Square (left) false false false false false false false false false false +Point middle of Right Square Square (right) false true false false false false true false false true +Point middle of Right Square Square overlapping left and right square false true false false false false true false false true +Square (left) Line going through left and right square false false false false true false true false false false +Square (left) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Square (left) Point middle of Left Square true false true true false false true false false false +Square (left) Point middle of Right Square false false false false false false false false false false +Square (left) Square (left) true true true false false true true false false true +Square (left) Square (right) false false false false false false true false true false +Square (left) Square overlapping left and right square false false false false false false true true false false +Square (right) Line going through left and right square false false false false true false true false false false +Square (right) NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Square (right) Point middle of Left Square false false false false false false false false false false +Square (right) Point middle of Right Square true false true true false false true false false false +Square (right) Square (left) false false false false false false true false true false +Square (right) Square (right) true true true false false true true false false true +Square (right) Square overlapping left and right square false true false false false false true false false true +Square overlapping left and right square Line going through left and right square false false false false true false true false false false +Square overlapping left and right square NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL NULL +Square overlapping left and right square Point middle of Left Square false false false false false false false false false false +Square overlapping left and right square Point middle of Right Square true false true true false false true false false false +Square overlapping left and right square Square (left) false false false false false false true true false false +Square overlapping left and right square Square (right) true false true false false false true false false false +Square overlapping left and right square Square overlapping left and right square true true true false false true true false false true + +# DE-9IM relations +query TTTB +SELECT + a.dsc, + b.dsc, + ST_Relate(a.geom, b.geom), + ST_Relate(a.geom, b.geom, 'T**FF*FF*') +FROM geom_operators_test a +JOIN geom_operators_test b ON (1=1) +ORDER BY a.dsc, b.dsc +---- +Line going through left and right square Line going through left and right square 1FFF0FFF2 false +Line going through left and right square NULL NULL NULL +Line going through left and right square Point middle of Left Square FF10F0FF2 false +Line going through left and right square Point middle of Right Square FF10F0FF2 false +Line going through left and right square Square (left) 1010F0212 false +Line going through left and right square Square (right) 1010F0212 false +Line going through left and right square Square overlapping left and right square 1010F0212 false +NULL Line going through left and right square NULL NULL +NULL NULL NULL NULL +NULL Point middle of Left Square NULL NULL +NULL Point middle of Right Square NULL NULL +NULL Square (left) NULL NULL +NULL Square (right) NULL NULL +NULL Square overlapping left and right square NULL NULL +Point middle of Left Square Line going through left and right square F0FFFF102 false +Point middle of Left Square NULL NULL NULL +Point middle of Left Square Point middle of Left Square 0FFFFFFF2 true +Point middle of Left Square Point middle of Right Square FF0FFF0F2 false +Point middle of Left Square Square (left) 0FFFFF212 false +Point middle of Left Square Square (right) FF0FFF212 false +Point middle of Left Square Square overlapping left and right square FF0FFF212 false +Point middle of Right Square Line going through left and right square F0FFFF102 false +Point middle of Right Square NULL NULL NULL +Point middle of Right Square Point middle of Left Square FF0FFF0F2 false +Point middle of Right Square Point middle of Right Square 0FFFFFFF2 true +Point middle of Right Square Square (left) FF0FFF212 false +Point middle of Right Square Square (right) 0FFFFF212 false +Point middle of Right Square Square overlapping left and right square 0FFFFF212 false +Square (left) Line going through left and right square 1020F1102 false +Square (left) NULL NULL NULL +Square (left) Point middle of Left Square 0F2FF1FF2 true +Square (left) Point middle of Right Square FF2FF10F2 false +Square (left) Square (left) 2FFF1FFF2 false +Square (left) Square (right) FF2F11212 false +Square (left) Square overlapping left and right square 212111212 false +Square (right) Line going through left and right square 1020F1102 false +Square (right) NULL NULL NULL +Square (right) Point middle of Left Square FF2FF10F2 false +Square (right) Point middle of Right Square 0F2FF1FF2 true +Square (right) Square (left) FF2F11212 false +Square (right) Square (right) 2FFF1FFF2 false +Square (right) Square overlapping left and right square 2FF11F212 false +Square overlapping left and right square Line going through left and right square 1020F1102 false +Square overlapping left and right square NULL NULL NULL +Square overlapping left and right square Point middle of Left Square FF2FF10F2 false +Square overlapping left and right square Point middle of Right Square 0F2FF1FF2 true +Square overlapping left and right square Square (left) 212111212 false +Square overlapping left and right square Square (right) 212F11FF2 false +Square overlapping left and right square Square overlapping left and right square 2FFF1FFF2 false subtest regression_48093 diff --git a/pkg/sql/logictest/testdata/logic_test/merge_join b/pkg/sql/logictest/testdata/logic_test/merge_join index 660af64b60f7..d3ea189fc18f 100644 --- a/pkg/sql/logictest/testdata/logic_test/merge_join +++ b/pkg/sql/logictest/testdata/logic_test/merge_join @@ -58,3 +58,14 @@ SELECT * FROM t44798_0 NATURAL JOIN t44798_1 ---- 0 2 + +# Regression test for batch type schema prefix mismatch after LEFT ANTI join +# (48622). +statement ok +CREATE TABLE l (l INT PRIMARY KEY); INSERT INTO l VALUES (1), (2); +CREATE TABLE r (r INT PRIMARY KEY); INSERT INTO r VALUES (1) + +query IB +SELECT *, true FROM (SELECT l FROM l WHERE l NOT IN (SELECT r FROM r)) +---- +2 true diff --git a/pkg/sql/opt/norm/fold_constants.go b/pkg/sql/opt/norm/fold_constants.go index b1b854676071..49b2c0fe5e77 100644 --- a/pkg/sql/opt/norm/fold_constants.go +++ b/pkg/sql/opt/norm/fold_constants.go @@ -517,6 +517,7 @@ var FoldFunctionWhitelist = map[string]struct{}{ "st_covers": {}, "st_coveredby": {}, "st_contains": {}, + "st_containsproperly": {}, "st_crosses": {}, "st_equals": {}, "st_intersects": {}, diff --git a/pkg/sql/opt/xform/custom_funcs.go b/pkg/sql/opt/xform/custom_funcs.go index d9e0344ea1cb..893f1dd65e30 100644 --- a/pkg/sql/opt/xform/custom_funcs.go +++ b/pkg/sql/opt/xform/custom_funcs.go @@ -1634,15 +1634,16 @@ func (c *CustomFuncs) GenerateGeoLookupJoins( // TODO(rytaft): add ST_DFullyWithin (geoindex.Covers) and ST_DWithin // (geoindex.Intersects) once we add support for extending a geometry. var geoRelationshipMap = map[string]geoindex.RelationshipType{ - "st_covers": geoindex.Covers, - "st_coveredby": geoindex.CoveredBy, - "st_contains": geoindex.Covers, - "st_crosses": geoindex.Intersects, - "st_equals": geoindex.Intersects, - "st_intersects": geoindex.Intersects, - "st_overlaps": geoindex.Intersects, - "st_touches": geoindex.Intersects, - "st_within": geoindex.CoveredBy, + "st_covers": geoindex.Covers, + "st_coveredby": geoindex.CoveredBy, + "st_contains": geoindex.Covers, + "st_containsproperly": geoindex.Covers, + "st_crosses": geoindex.Intersects, + "st_equals": geoindex.Intersects, + "st_intersects": geoindex.Intersects, + "st_overlaps": geoindex.Intersects, + "st_touches": geoindex.Intersects, + "st_within": geoindex.CoveredBy, } // IsGeoIndexFunction returns true if the given function is a geospatial diff --git a/pkg/sql/sem/builtins/geo_builtins.go b/pkg/sql/sem/builtins/geo_builtins.go index 6ec91db7ee31..e8feb70705ad 100644 --- a/pkg/sql/sem/builtins/geo_builtins.go +++ b/pkg/sql/sem/builtins/geo_builtins.go @@ -740,6 +740,17 @@ Note ST_Perimeter is only valid for Polygon - use ST_Length for LineString.`, }, ), ), + "st_containsproperly": makeBuiltin( + defProps(), + geometryOverload2BinaryPredicate( + geomfn.ContainsProperly, + infoBuilder{ + info: "Returns true if geometry_b intersects the interior of geometry_a but not the boundary or exterior of geometry_a.", + usesGEOS: true, + canUseIndex: true, + }, + ), + ), "st_crosses": makeBuiltin( defProps(), geometryOverload2BinaryPredicate( @@ -809,6 +820,56 @@ Note ST_Perimeter is only valid for Polygon - use ST_Length for LineString.`, }, ), ), + + // + // DE-9IM related + // + + "st_relate": makeBuiltin( + defProps(), + geometryOverload2( + func(ctx *tree.EvalContext, a *tree.DGeometry, b *tree.DGeometry) (tree.Datum, error) { + ret, err := geomfn.Relate(a.Geometry, b.Geometry) + if err != nil { + return nil, err + } + return tree.NewDString(ret), nil + }, + types.String, + infoBuilder{ + info: `Returns the DE-9IM spatial relation between geometry_a and geometry_b.`, + usesGEOS: true, + }, + tree.VolatilityImmutable, + ), + tree.Overload{ + Types: tree.ArgTypes{ + {"geometry_a", types.Geometry}, + {"geometry_b", types.Geometry}, + {"pattern", types.String}, + }, + ReturnType: tree.FixedReturnType(types.Bool), + Fn: func(ctx *tree.EvalContext, args tree.Datums) (tree.Datum, error) { + a := args[0].(*tree.DGeometry) + b := args[1].(*tree.DGeometry) + pattern := args[2].(*tree.DString) + relation, err := geomfn.Relate(a.Geometry, b.Geometry) + if err != nil { + return nil, err + } + ret, err := geomfn.MatchesDE9IM(relation, string(*pattern)) + if err != nil { + return nil, err + } + return tree.MakeDBool(tree.DBool(ret)), nil + }, + Info: infoBuilder{ + info: `Returns whether the DE-9IM spatial relation between geometry_a and geometry_b matches the DE-9IM pattern.`, + usesGEOS: true, + }.String(), + Volatility: tree.VolatilityImmutable, + }, + ), } // geometryOverload1 hides the boilerplate for builtins operating on one geometry.