mirror of
https://github.com/dolthub/dolt.git
synced 2026-03-06 18:38:54 -06:00
Merge pull request #2611 from dolthub/james/spatial-types
Spatial types actually stored as spatial types
This commit is contained in:
@@ -214,6 +214,12 @@ func bitTypeConverter(ctx context.Context, src *bitType, destTi TypeInfo) (tc Ty
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapIsValid(dest.IsValid, src, dest)
|
||||
case *timeType:
|
||||
|
||||
@@ -228,6 +228,12 @@ func blobStringTypeConverter(ctx context.Context, src *blobStringType, destTi Ty
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -231,6 +231,12 @@ func boolTypeConverter(ctx context.Context, src *boolType, destTi TypeInfo) (tc
|
||||
}, true, nil
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *intType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
|
||||
@@ -225,6 +225,12 @@ func datetimeTypeConverter(ctx context.Context, src *datetimeType, destTi TypeIn
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -259,6 +259,12 @@ func decimalTypeConverter(ctx context.Context, src *decimalType, destTi TypeInfo
|
||||
}, true, nil
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return func(ctx context.Context, vrw types.ValueReadWriter, v types.Value) (types.Value, error) {
|
||||
s, err := src.ConvertNomsValueToValue(v)
|
||||
|
||||
@@ -247,6 +247,12 @@ func enumTypeConverter(ctx context.Context, src *enumType, destTi TypeInfo) (tc
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -255,6 +255,12 @@ func floatTypeConverter(ctx context.Context, src *floatType, destTi TypeInfo) (t
|
||||
return floatTypeConverterRoundToZero(ctx, src, destTi)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return func(ctx context.Context, vrw types.ValueReadWriter, v types.Value) (types.Value, error) {
|
||||
if v == nil || v == types.NullValue {
|
||||
|
||||
@@ -241,6 +241,12 @@ func inlineBlobTypeConverter(ctx context.Context, src *inlineBlobType, destTi Ty
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -283,6 +283,12 @@ func intTypeConverter(ctx context.Context, src *intType, destTi TypeInfo) (tc Ty
|
||||
return wrapIsValid(dest.IsValid, src, dest)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -173,6 +173,12 @@ func jsonTypeConverter(ctx context.Context, src *jsonType, destTi TypeInfo) (tc
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapIsValid(dest.IsValid, src, dest)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
213
go/libraries/doltcore/schema/typeinfo/linestring.go
Normal file
213
go/libraries/doltcore/schema/typeinfo/linestring.go
Normal file
@@ -0,0 +1,213 @@
|
||||
// Copyright 2020 Dolthub, Inc.
|
||||
//
|
||||
// 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 typeinfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
)
|
||||
|
||||
// This is a dolt implementation of the MySQL type Point, thus most of the functionality
|
||||
// within is directly reliant on the go-mysql-server implementation.
|
||||
type linestringType struct {
|
||||
sqlLinestringType sql.LinestringType
|
||||
}
|
||||
|
||||
var _ TypeInfo = (*linestringType)(nil)
|
||||
|
||||
var LinestringType = &linestringType{sql.LinestringType{}}
|
||||
|
||||
// ConvertTypesLinestringToSQLLinestring basically makes a deep copy of sql.Linestring
|
||||
func ConvertTypesLinestringToSQLLinestring(l types.Linestring) sql.Linestring {
|
||||
points := make([]sql.Point, len(l.Points))
|
||||
for i, p := range l.Points {
|
||||
points[i] = ConvertTypesPointToSQLPoint(p)
|
||||
}
|
||||
return sql.Linestring{SRID: l.SRID, Points: points}
|
||||
}
|
||||
|
||||
// ConvertNomsValueToValue implements TypeInfo interface.
|
||||
func (ti *linestringType) ConvertNomsValueToValue(v types.Value) (interface{}, error) {
|
||||
// Expect a types.Linestring, return a sql.Linestring
|
||||
if val, ok := v.(types.Linestring); ok {
|
||||
return ConvertTypesLinestringToSQLLinestring(val), nil
|
||||
}
|
||||
// Check for null
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`"%v" cannot convert NomsKind "%v" to a value`, ti.String(), v.Kind())
|
||||
}
|
||||
|
||||
// ReadFrom reads a go value from a noms types.CodecReader directly
|
||||
func (ti *linestringType) ReadFrom(nbf *types.NomsBinFormat, reader types.CodecReader) (interface{}, error) {
|
||||
k := reader.ReadKind()
|
||||
switch k {
|
||||
case types.LinestringKind:
|
||||
l, err := reader.ReadLinestring()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ti.ConvertNomsValueToValue(l)
|
||||
case types.NullKind:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`"%v" cannot convert NomsKind "%v" to a value`, ti.String(), k)
|
||||
}
|
||||
|
||||
func ConvertSQLLinestringToTypesLinestring(l sql.Linestring) types.Linestring {
|
||||
points := make([]types.Point, len(l.Points))
|
||||
for i, p := range l.Points {
|
||||
points[i] = ConvertSQLPointToTypesPoint(p)
|
||||
}
|
||||
return types.Linestring{SRID: l.SRID, Points: points}
|
||||
}
|
||||
|
||||
// ConvertValueToNomsValue implements TypeInfo interface.
|
||||
func (ti *linestringType) ConvertValueToNomsValue(ctx context.Context, vrw types.ValueReadWriter, v interface{}) (types.Value, error) {
|
||||
// Check for null
|
||||
if v == nil {
|
||||
return types.NullValue, nil
|
||||
}
|
||||
|
||||
// Convert to sql.LinestringType
|
||||
line, err := ti.sqlLinestringType.Convert(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ConvertSQLLinestringToTypesLinestring(line.(sql.Linestring)), nil
|
||||
}
|
||||
|
||||
// Equals implements TypeInfo interface.
|
||||
func (ti *linestringType) Equals(other TypeInfo) bool {
|
||||
if other == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := other.(*linestringType)
|
||||
return ok
|
||||
}
|
||||
|
||||
// FormatValue implements TypeInfo interface.
|
||||
func (ti *linestringType) FormatValue(v types.Value) (*string, error) {
|
||||
if val, ok := v.(types.Linestring); ok {
|
||||
buf := make([]byte, types.EWKBHeaderSize+types.LengthSize+types.PointDataSize*len(val.Points))
|
||||
types.WriteEWKBHeader(val, buf[:types.EWKBHeaderSize])
|
||||
types.WriteEWKBLineData(val, buf[types.EWKBHeaderSize:])
|
||||
resStr := string(buf)
|
||||
return &resStr, nil
|
||||
}
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`"%v" has unexpectedly encountered a value of type "%T" from embedded type`, ti.String(), v.Kind())
|
||||
}
|
||||
|
||||
// GetTypeIdentifier implements TypeInfo interface.
|
||||
func (ti *linestringType) GetTypeIdentifier() Identifier {
|
||||
return LinestringTypeIdentifier
|
||||
}
|
||||
|
||||
// GetTypeParams implements TypeInfo interface.
|
||||
func (ti *linestringType) GetTypeParams() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
// IsValid implements TypeInfo interface.
|
||||
func (ti *linestringType) IsValid(v types.Value) bool {
|
||||
if _, ok := v.(types.Linestring); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NomsKind implements TypeInfo interface.
|
||||
func (ti *linestringType) NomsKind() types.NomsKind {
|
||||
return types.LinestringKind
|
||||
}
|
||||
|
||||
// Promote implements TypeInfo interface.
|
||||
func (ti *linestringType) Promote() TypeInfo {
|
||||
return &linestringType{ti.sqlLinestringType.Promote().(sql.LinestringType)}
|
||||
}
|
||||
|
||||
// String implements TypeInfo interface.
|
||||
func (ti *linestringType) String() string {
|
||||
return "Linestring"
|
||||
}
|
||||
|
||||
// ToSqlType implements TypeInfo interface.
|
||||
func (ti *linestringType) ToSqlType() sql.Type {
|
||||
return ti.sqlLinestringType
|
||||
}
|
||||
|
||||
// linestringTypeConverter is an internal function for GetTypeConverter that handles the specific type as the source TypeInfo.
|
||||
func linestringTypeConverter(ctx context.Context, src *linestringType, destTi TypeInfo) (tc TypeConverter, needsConversion bool, err error) {
|
||||
switch dest := destTi.(type) {
|
||||
case *bitType:
|
||||
return func(ctx context.Context, vrw types.ValueReadWriter, v types.Value) (types.Value, error) {
|
||||
return types.Uint(0), nil
|
||||
}, true, nil
|
||||
case *blobStringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *boolType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *datetimeType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *decimalType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *enumType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *floatType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *inlineBlobType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *intType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return identityTypeConverter, false, nil
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *uintType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *uuidType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *varBinaryType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *varStringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *yearType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
default:
|
||||
return nil, false, UnhandledTypeConversion.New(src.String(), destTi.String())
|
||||
}
|
||||
}
|
||||
207
go/libraries/doltcore/schema/typeinfo/point.go
Normal file
207
go/libraries/doltcore/schema/typeinfo/point.go
Normal file
@@ -0,0 +1,207 @@
|
||||
// Copyright 2020 Dolthub, Inc.
|
||||
//
|
||||
// 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 typeinfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
)
|
||||
|
||||
// This is a dolt implementation of the MySQL type Point, thus most of the functionality
|
||||
// within is directly reliant on the go-mysql-server implementation.
|
||||
type pointType struct {
|
||||
sqlPointType sql.PointType
|
||||
}
|
||||
|
||||
var _ TypeInfo = (*pointType)(nil)
|
||||
|
||||
var PointType = &pointType{sql.PointType{}}
|
||||
|
||||
// ConvertTypesPointToSQLPoint basically makes a deep copy of sql.Point
|
||||
func ConvertTypesPointToSQLPoint(p types.Point) sql.Point {
|
||||
return sql.Point{SRID: p.SRID, X: p.X, Y: p.Y}
|
||||
}
|
||||
|
||||
// ConvertNomsValueToValue implements TypeInfo interface.
|
||||
func (ti *pointType) ConvertNomsValueToValue(v types.Value) (interface{}, error) {
|
||||
// Expect a types.Point, return a sql.Point
|
||||
if val, ok := v.(types.Point); ok {
|
||||
return ConvertTypesPointToSQLPoint(val), nil
|
||||
}
|
||||
// Check for null
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`"%v" cannot convert NomsKind "%v" to a value`, ti.String(), v.Kind())
|
||||
}
|
||||
|
||||
// ReadFrom reads a go value from a noms types.CodecReader directly
|
||||
func (ti *pointType) ReadFrom(nbf *types.NomsBinFormat, reader types.CodecReader) (interface{}, error) {
|
||||
k := reader.ReadKind()
|
||||
switch k {
|
||||
case types.PointKind:
|
||||
p, err := reader.ReadPoint()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ti.ConvertNomsValueToValue(p)
|
||||
case types.NullKind:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`"%v" cannot convert NomsKind "%v" to a value`, ti.String(), k)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: define constants for WKB?
|
||||
|
||||
func ConvertSQLPointToTypesPoint(p sql.Point) types.Point {
|
||||
return types.Point{SRID: p.SRID, X: p.X, Y: p.Y}
|
||||
}
|
||||
|
||||
// ConvertValueToNomsValue implements TypeInfo interface.
|
||||
func (ti *pointType) ConvertValueToNomsValue(ctx context.Context, vrw types.ValueReadWriter, v interface{}) (types.Value, error) {
|
||||
// Check for null
|
||||
if v == nil {
|
||||
return types.NullValue, nil
|
||||
}
|
||||
|
||||
// Convert to sql.PointType
|
||||
point, err := ti.sqlPointType.Convert(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ConvertSQLPointToTypesPoint(point.(sql.Point)), nil
|
||||
}
|
||||
|
||||
// Equals implements TypeInfo interface.
|
||||
func (ti *pointType) Equals(other TypeInfo) bool {
|
||||
if other == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := other.(*pointType)
|
||||
return ok
|
||||
}
|
||||
|
||||
// FormatValue implements TypeInfo interface.
|
||||
func (ti *pointType) FormatValue(v types.Value) (*string, error) {
|
||||
if val, ok := v.(types.Point); ok {
|
||||
buf := make([]byte, types.EWKBHeaderSize+types.PointDataSize)
|
||||
types.WriteEWKBHeader(val, buf[:types.EWKBHeaderSize])
|
||||
types.WriteEWKBPointData(val, buf[types.EWKBHeaderSize:])
|
||||
resStr := string(buf)
|
||||
return &resStr, nil
|
||||
}
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`"%v" has unexpectedly encountered a value of type "%T" from embedded type`, ti.String(), v.Kind())
|
||||
}
|
||||
|
||||
// GetTypeIdentifier implements TypeInfo interface.
|
||||
func (ti *pointType) GetTypeIdentifier() Identifier {
|
||||
return PointTypeIdentifier
|
||||
}
|
||||
|
||||
// GetTypeParams implements TypeInfo interface.
|
||||
func (ti *pointType) GetTypeParams() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
// IsValid implements TypeInfo interface.
|
||||
func (ti *pointType) IsValid(v types.Value) bool {
|
||||
if _, ok := v.(types.Point); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NomsKind implements TypeInfo interface.
|
||||
func (ti *pointType) NomsKind() types.NomsKind {
|
||||
return types.PointKind
|
||||
}
|
||||
|
||||
// Promote implements TypeInfo interface.
|
||||
func (ti *pointType) Promote() TypeInfo {
|
||||
return &pointType{ti.sqlPointType.Promote().(sql.PointType)}
|
||||
}
|
||||
|
||||
// String implements TypeInfo interface.
|
||||
func (ti *pointType) String() string {
|
||||
return "Point"
|
||||
}
|
||||
|
||||
// ToSqlType implements TypeInfo interface.
|
||||
func (ti *pointType) ToSqlType() sql.Type {
|
||||
return ti.sqlPointType
|
||||
}
|
||||
|
||||
// pointTypeConverter is an internal function for GetTypeConverter that handles the specific type as the source TypeInfo.
|
||||
func pointTypeConverter(ctx context.Context, src *pointType, destTi TypeInfo) (tc TypeConverter, needsConversion bool, err error) {
|
||||
switch dest := destTi.(type) {
|
||||
case *bitType:
|
||||
return func(ctx context.Context, vrw types.ValueReadWriter, v types.Value) (types.Value, error) {
|
||||
return types.Uint(0), nil
|
||||
}, true, nil
|
||||
case *blobStringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *boolType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *datetimeType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *decimalType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *enumType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *floatType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *inlineBlobType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *intType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return identityTypeConverter, false, nil
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *uintType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *uuidType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *varBinaryType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *varStringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *yearType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
default:
|
||||
return nil, false, UnhandledTypeConversion.New(src.String(), destTi.String())
|
||||
}
|
||||
}
|
||||
217
go/libraries/doltcore/schema/typeinfo/polygon.go
Normal file
217
go/libraries/doltcore/schema/typeinfo/polygon.go
Normal file
@@ -0,0 +1,217 @@
|
||||
// Copyright 2020 Dolthub, Inc.
|
||||
//
|
||||
// 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 typeinfo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
)
|
||||
|
||||
// This is a dolt implementation of the MySQL type Point, thus most of the functionality
|
||||
// within is directly reliant on the go-mysql-server implementation.
|
||||
type polygonType struct {
|
||||
sqlPolygonType sql.PolygonType
|
||||
}
|
||||
|
||||
var _ TypeInfo = (*polygonType)(nil)
|
||||
|
||||
var PolygonType = &polygonType{sql.PolygonType{}}
|
||||
|
||||
// ConvertTypesPolygonToSQLPolygon basically makes a deep copy of sql.Linestring
|
||||
func ConvertTypesPolygonToSQLPolygon(p types.Polygon) sql.Polygon {
|
||||
lines := make([]sql.Linestring, len(p.Lines))
|
||||
for i, l := range p.Lines {
|
||||
lines[i] = ConvertTypesLinestringToSQLLinestring(l)
|
||||
}
|
||||
return sql.Polygon{SRID: p.SRID, Lines: lines}
|
||||
}
|
||||
|
||||
// ConvertNomsValueToValue implements TypeInfo interface.
|
||||
func (ti *polygonType) ConvertNomsValueToValue(v types.Value) (interface{}, error) {
|
||||
// Expect a types.Polygon, return a sql.Polygon
|
||||
if val, ok := v.(types.Polygon); ok {
|
||||
return ConvertTypesPolygonToSQLPolygon(val), nil
|
||||
}
|
||||
// Check for null
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf(`"%v" cannot convert NomsKind "%v" to a value`, ti.String(), v.Kind())
|
||||
}
|
||||
|
||||
// ReadFrom reads a go value from a noms types.CodecReader directly
|
||||
func (ti *polygonType) ReadFrom(nbf *types.NomsBinFormat, reader types.CodecReader) (interface{}, error) {
|
||||
k := reader.ReadKind()
|
||||
switch k {
|
||||
case types.PolygonKind:
|
||||
p, err := reader.ReadPolygon()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ti.ConvertNomsValueToValue(p)
|
||||
case types.NullKind:
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`"%v" cannot convert NomsKind "%v" to a value`, ti.String(), k)
|
||||
}
|
||||
|
||||
func ConvertSQLPolygonToTypesPolygon(p sql.Polygon) types.Polygon {
|
||||
lines := make([]types.Linestring, len(p.Lines))
|
||||
for i, l := range p.Lines {
|
||||
lines[i] = ConvertSQLLinestringToTypesLinestring(l)
|
||||
}
|
||||
return types.Polygon{SRID: p.SRID, Lines: lines}
|
||||
}
|
||||
|
||||
// ConvertValueToNomsValue implements TypeInfo interface.
|
||||
func (ti *polygonType) ConvertValueToNomsValue(ctx context.Context, vrw types.ValueReadWriter, v interface{}) (types.Value, error) {
|
||||
// Check for null
|
||||
if v == nil {
|
||||
return types.NullValue, nil
|
||||
}
|
||||
|
||||
// Convert to sql.PolygonType
|
||||
poly, err := ti.sqlPolygonType.Convert(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ConvertSQLPolygonToTypesPolygon(poly.(sql.Polygon)), nil
|
||||
}
|
||||
|
||||
// Equals implements TypeInfo interface.
|
||||
func (ti *polygonType) Equals(other TypeInfo) bool {
|
||||
if other == nil {
|
||||
return false
|
||||
}
|
||||
_, ok := other.(*polygonType)
|
||||
return ok
|
||||
}
|
||||
|
||||
// FormatValue implements TypeInfo interface.
|
||||
func (ti *polygonType) FormatValue(v types.Value) (*string, error) {
|
||||
if val, ok := v.(types.Polygon); ok {
|
||||
size := types.EWKBHeaderSize + types.LengthSize
|
||||
for _, l := range val.Lines {
|
||||
size += types.LengthSize + types.PointDataSize*len(l.Points)
|
||||
}
|
||||
buf := make([]byte, size)
|
||||
types.WriteEWKBHeader(val, buf[:types.EWKBHeaderSize])
|
||||
types.WriteEWKBPolyData(val, buf[types.EWKBHeaderSize:])
|
||||
resStr := string(buf)
|
||||
return &resStr, nil
|
||||
}
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf(`"%v" has unexpectedly encountered a value of type "%T" from embedded type`, ti.String(), v.Kind())
|
||||
}
|
||||
|
||||
// GetTypeIdentifier implements TypeInfo interface.
|
||||
func (ti *polygonType) GetTypeIdentifier() Identifier {
|
||||
return PolygonTypeIdentifier
|
||||
}
|
||||
|
||||
// GetTypeParams implements TypeInfo interface.
|
||||
func (ti *polygonType) GetTypeParams() map[string]string {
|
||||
return map[string]string{}
|
||||
}
|
||||
|
||||
// IsValid implements TypeInfo interface.
|
||||
func (ti *polygonType) IsValid(v types.Value) bool {
|
||||
if _, ok := v.(types.Polygon); ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := v.(types.Null); ok || v == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NomsKind implements TypeInfo interface.
|
||||
func (ti *polygonType) NomsKind() types.NomsKind {
|
||||
return types.PolygonKind
|
||||
}
|
||||
|
||||
// Promote implements TypeInfo interface.
|
||||
func (ti *polygonType) Promote() TypeInfo {
|
||||
return &polygonType{ti.sqlPolygonType.Promote().(sql.PolygonType)}
|
||||
}
|
||||
|
||||
// String implements TypeInfo interface.
|
||||
func (ti *polygonType) String() string {
|
||||
return "Polygon"
|
||||
}
|
||||
|
||||
// ToSqlType implements TypeInfo interface.
|
||||
func (ti *polygonType) ToSqlType() sql.Type {
|
||||
return ti.sqlPolygonType
|
||||
}
|
||||
|
||||
// polygonTypeConverter is an internal function for GetTypeConverter that handles the specific type as the source TypeInfo.
|
||||
func polygonTypeConverter(ctx context.Context, src *polygonType, destTi TypeInfo) (tc TypeConverter, needsConversion bool, err error) {
|
||||
switch dest := destTi.(type) {
|
||||
case *bitType:
|
||||
return func(ctx context.Context, vrw types.ValueReadWriter, v types.Value) (types.Value, error) {
|
||||
return types.Uint(0), nil
|
||||
}, true, nil
|
||||
case *blobStringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *boolType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *datetimeType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *decimalType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *enumType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *floatType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *inlineBlobType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *intType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return identityTypeConverter, false, nil
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *uintType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *uuidType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *varBinaryType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *varStringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *yearType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
default:
|
||||
return nil, false, UnhandledTypeConversion.New(src.String(), destTi.String())
|
||||
}
|
||||
}
|
||||
@@ -223,6 +223,12 @@ func setTypeConverter(ctx context.Context, src *setType, destTi TypeInfo) (tc Ty
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapIsValid(dest.IsValid, src, dest)
|
||||
case *timeType:
|
||||
|
||||
@@ -159,6 +159,12 @@ func timeTypeConverter(ctx context.Context, src *timeType, destTi TypeInfo) (tc
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -71,6 +71,12 @@ func GetTypeConverter(ctx context.Context, srcTi TypeInfo, destTi TypeInfo) (tc
|
||||
return intTypeConverter(ctx, src, destTi)
|
||||
case *jsonType:
|
||||
return jsonTypeConverter(ctx, src, destTi)
|
||||
case *linestringType:
|
||||
return linestringTypeConverter(ctx, src, destTi)
|
||||
case *pointType:
|
||||
return pointTypeConverter(ctx, src, destTi)
|
||||
case *polygonType:
|
||||
return polygonTypeConverter(ctx, src, destTi)
|
||||
case *setType:
|
||||
return setTypeConverter(ctx, src, destTi)
|
||||
case *timeType:
|
||||
@@ -131,6 +137,12 @@ func wrapConvertValueToNomsValue(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case types.Linestring:
|
||||
vInt = ConvertTypesLinestringToSQLLinestring(val)
|
||||
case types.Point:
|
||||
vInt = ConvertTypesPointToSQLPoint(val)
|
||||
case types.Polygon:
|
||||
vInt = ConvertTypesPolygonToSQLPolygon(val)
|
||||
case types.String:
|
||||
vInt = string(val)
|
||||
case types.Timestamp:
|
||||
|
||||
@@ -18,6 +18,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/dolthub/go-mysql-server/sql"
|
||||
"github.com/dolthub/vitess/go/sqltypes"
|
||||
@@ -25,6 +27,33 @@ import (
|
||||
"github.com/dolthub/dolt/go/store/types"
|
||||
)
|
||||
|
||||
const spatialTypesFeatureFlagKey = "DOLT_ENABLE_SPATIAL_TYPES"
|
||||
|
||||
// use SpatialTypesEnabled() to check, don't access directly
|
||||
var spatialTypesFeatureFlag = false
|
||||
|
||||
func init() {
|
||||
// set the spatial types feature flag to true if the env var is set
|
||||
if v, ok := os.LookupEnv(spatialTypesFeatureFlagKey); ok && v != "" {
|
||||
spatialTypesFeatureFlag = true
|
||||
}
|
||||
}
|
||||
|
||||
var spatialTypesLock = &sync.RWMutex{}
|
||||
|
||||
func SpatialTypesEnabled() bool {
|
||||
return spatialTypesFeatureFlag
|
||||
}
|
||||
|
||||
func TestWithSpatialTypesEnabled(cb func()) {
|
||||
spatialTypesLock.Lock()
|
||||
defer spatialTypesLock.Unlock()
|
||||
|
||||
spatialTypesFeatureFlag = true
|
||||
cb()
|
||||
spatialTypesFeatureFlag = false
|
||||
}
|
||||
|
||||
type Identifier string
|
||||
|
||||
const (
|
||||
@@ -47,6 +76,9 @@ const (
|
||||
VarBinaryTypeIdentifier Identifier = "varbinary"
|
||||
VarStringTypeIdentifier Identifier = "varstring"
|
||||
YearTypeIdentifier Identifier = "year"
|
||||
PointTypeIdentifier Identifier = "point"
|
||||
LinestringTypeIdentifier Identifier = "linestring"
|
||||
PolygonTypeIdentifier Identifier = "polygon"
|
||||
)
|
||||
|
||||
var Identifiers = map[Identifier]struct{}{
|
||||
@@ -69,6 +101,9 @@ var Identifiers = map[Identifier]struct{}{
|
||||
VarBinaryTypeIdentifier: {},
|
||||
VarStringTypeIdentifier: {},
|
||||
YearTypeIdentifier: {},
|
||||
PointTypeIdentifier: {},
|
||||
LinestringTypeIdentifier: {},
|
||||
PolygonTypeIdentifier: {},
|
||||
}
|
||||
|
||||
// TypeInfo is an interface used for encoding type information.
|
||||
@@ -155,6 +190,18 @@ func FromSqlType(sqlType sql.Type) (TypeInfo, error) {
|
||||
return DatetimeType, nil
|
||||
case sqltypes.Year:
|
||||
return YearType, nil
|
||||
case sqltypes.Geometry:
|
||||
// TODO: bad, but working way to determine which specific geometry type
|
||||
switch sqlType.String() {
|
||||
case sql.PolygonType{}.String():
|
||||
return &polygonType{sqlType.(sql.PolygonType)}, nil
|
||||
case sql.LinestringType{}.String():
|
||||
return &linestringType{sqlType.(sql.LinestringType)}, nil
|
||||
case sql.PointType{}.String():
|
||||
return &pointType{sqlType.(sql.PointType)}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf(`expected "PointTypeIdentifier" from SQL basetype "Geometry"`)
|
||||
}
|
||||
case sqltypes.Decimal:
|
||||
decimalSQLType, ok := sqlType.(sql.DecimalType)
|
||||
if !ok {
|
||||
@@ -228,6 +275,17 @@ func FromSqlType(sqlType sql.Type) (TypeInfo, error) {
|
||||
|
||||
// FromTypeParams constructs a TypeInfo from the given identifier and parameters.
|
||||
func FromTypeParams(id Identifier, params map[string]string) (TypeInfo, error) {
|
||||
if SpatialTypesEnabled() {
|
||||
switch id {
|
||||
case PointTypeIdentifier:
|
||||
return PointType, nil
|
||||
case LinestringTypeIdentifier:
|
||||
return LinestringType, nil
|
||||
case PolygonTypeIdentifier:
|
||||
return PolygonType, nil
|
||||
}
|
||||
}
|
||||
|
||||
switch id {
|
||||
case BitTypeIdentifier:
|
||||
return CreateBitTypeFromParams(params)
|
||||
@@ -285,8 +343,14 @@ func FromKind(kind types.NomsKind) TypeInfo {
|
||||
return Int64Type
|
||||
case types.JSONKind:
|
||||
return JSONType
|
||||
case types.LinestringKind:
|
||||
return LinestringType
|
||||
case types.NullKind:
|
||||
return UnknownType
|
||||
case types.PointKind:
|
||||
return PointType
|
||||
case types.PolygonKind:
|
||||
return PolygonType
|
||||
case types.StringKind:
|
||||
return StringDefaultType
|
||||
case types.TimestampKind:
|
||||
|
||||
@@ -221,11 +221,23 @@ func testTypeInfoGetTypeParams(t *testing.T, tiArrays [][]TypeInfo) {
|
||||
for _, tiArray := range tiArrays {
|
||||
t.Run(tiArray[0].GetTypeIdentifier().String(), func(t *testing.T) {
|
||||
for _, ti := range tiArray {
|
||||
t.Run(ti.String(), func(t *testing.T) {
|
||||
newTi, err := FromTypeParams(ti.GetTypeIdentifier(), ti.GetTypeParams())
|
||||
require.NoError(t, err)
|
||||
require.True(t, ti.Equals(newTi), "%v\n%v", ti.String(), newTi.String())
|
||||
})
|
||||
if ti.GetTypeIdentifier() == PointTypeIdentifier ||
|
||||
ti.GetTypeIdentifier() == LinestringTypeIdentifier ||
|
||||
ti.GetTypeIdentifier() == PolygonTypeIdentifier {
|
||||
t.Run(ti.String(), func(t *testing.T) {
|
||||
TestWithSpatialTypesEnabled(func() {
|
||||
newTi, err := FromTypeParams(ti.GetTypeIdentifier(), ti.GetTypeParams())
|
||||
require.NoError(t, err)
|
||||
require.True(t, ti.Equals(newTi), "%v\n%v", ti.String(), newTi.String())
|
||||
})
|
||||
})
|
||||
} else {
|
||||
t.Run(ti.String(), func(t *testing.T) {
|
||||
newTi, err := FromTypeParams(ti.GetTypeIdentifier(), ti.GetTypeParams())
|
||||
require.NoError(t, err)
|
||||
require.True(t, ti.Equals(newTi), "%v\n%v", ti.String(), newTi.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -330,6 +342,9 @@ func generateTypeInfoArrays(t *testing.T) ([][]TypeInfo, [][]types.Value) {
|
||||
{DefaultInlineBlobType},
|
||||
{Int8Type, Int16Type, Int24Type, Int32Type, Int64Type},
|
||||
{JSONType},
|
||||
{LinestringType},
|
||||
{PointType},
|
||||
{PolygonType},
|
||||
generateSetTypes(t, 16),
|
||||
{TimeType},
|
||||
{Uint8Type, Uint16Type, Uint24Type, Uint32Type, Uint64Type},
|
||||
@@ -362,10 +377,13 @@ func generateTypeInfoArrays(t *testing.T) ([][]TypeInfo, [][]types.Value) {
|
||||
{types.Int(20), types.Int(215), types.Int(237493), types.Int(2035753568), types.Int(2384384576063)}, //Int
|
||||
{json.MustTypesJSON(`null`), json.MustTypesJSON(`[]`), json.MustTypesJSON(`"lorem ipsum"`), json.MustTypesJSON(`2.71`),
|
||||
json.MustTypesJSON(`false`), json.MustTypesJSON(`{"a": 1, "b": []}`)}, //JSON
|
||||
{types.Uint(1), types.Uint(5), types.Uint(64), types.Uint(42), types.Uint(192)}, //Set
|
||||
{types.Int(0), types.Int(1000000 /*"00:00:01"*/), types.Int(113000000 /*"00:01:53"*/), types.Int(247019000000 /*"68:36:59"*/), types.Int(458830485214 /*"127:27:10.485214"*/)}, //Time
|
||||
{types.Uint(20), types.Uint(275), types.Uint(328395), types.Uint(630257298), types.Uint(93897259874)}, //Uint
|
||||
{types.UUID{3}, types.UUID{3, 13}, types.UUID{128, 238, 82, 12}, types.UUID{31, 54, 23, 13, 63, 43}, types.UUID{83, 64, 21, 14, 42, 6, 35, 7, 54, 234, 6, 32, 1, 4, 2, 4}}, //Uuid
|
||||
{types.Linestring{SRID: 0, Points: []types.Point{{SRID: 0, X: 1, Y: 2}, {SRID: 0, X: 3, Y: 4}}}}, // Linestring
|
||||
{types.Point{SRID: 0, X: 1, Y: 2}}, // Point
|
||||
{types.Polygon{SRID: 0, Lines: []types.Linestring{{SRID: 0, Points: []types.Point{{SRID: 0, X: 0, Y: 0}, {SRID: 0, X: 0, Y: 1}, {SRID: 0, X: 1, Y: 1}, {SRID: 0, X: 0, Y: 0}}}}}}, // Polygon
|
||||
{types.Uint(1), types.Uint(5), types.Uint(64), types.Uint(42), types.Uint(192)}, //Set
|
||||
{types.Int(0), types.Int(1000000 /*"00:00:01"*/), types.Int(113000000 /*"00:01:53"*/), types.Int(247019000000 /*"68:36:59"*/), types.Int(458830485214 /*"127:27:10.485214"*/)}, //Time
|
||||
{types.Uint(20), types.Uint(275), types.Uint(328395), types.Uint(630257298), types.Uint(93897259874)}, //Uint
|
||||
{types.UUID{3}, types.UUID{3, 13}, types.UUID{128, 238, 82, 12}, types.UUID{31, 54, 23, 13, 63, 43}, types.UUID{83, 64, 21, 14, 42, 6, 35, 7, 54, 234, 6, 32, 1, 4, 2, 4}}, //Uuid
|
||||
{mustBlobBytes(t, []byte{1}), mustBlobBytes(t, []byte{42, 52}), mustBlobBytes(t, []byte{84, 32, 13, 63, 12, 86}), //VarBinary
|
||||
mustBlobBytes(t, []byte{1, 32, 235, 64, 32, 23, 45, 76}), mustBlobBytes(t, []byte{123, 234, 34, 223, 76, 35, 32, 12, 84, 26, 15, 34, 65, 86, 45, 23, 43, 12, 76, 154, 234, 76, 34})},
|
||||
{types.String(""), types.String("a"), types.String("abc"), //VarString
|
||||
|
||||
@@ -283,6 +283,12 @@ func uintTypeConverter(ctx context.Context, src *uintType, destTi TypeInfo) (tc
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -161,6 +161,12 @@ func uuidTypeConverter(ctx context.Context, src *uuidType, destTi TypeInfo) (tc
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -266,6 +266,12 @@ func varBinaryTypeConverter(ctx context.Context, src *varBinaryType, destTi Type
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -297,6 +297,12 @@ func varStringTypeConverter(ctx context.Context, src *varStringType, destTi Type
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -172,6 +172,12 @@ func yearTypeConverter(ctx context.Context, src *yearType, destTi TypeInfo) (tc
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *jsonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *linestringType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *pointType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *polygonType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *setType:
|
||||
return wrapConvertValueToNomsValue(dest.ConvertValueToNomsValue)
|
||||
case *timeType:
|
||||
|
||||
@@ -16,8 +16,10 @@ package sqlutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -252,6 +254,56 @@ func keylessDoltRowFromSqlRow(ctx context.Context, vrw types.ValueReadWriter, sq
|
||||
return row.KeylessRow(vrw.Format(), vals[:j]...)
|
||||
}
|
||||
|
||||
// WriteEWKBHeader writes the SRID, endianness, and type to the byte buffer
|
||||
// This function assumes v is a valid spatial type
|
||||
func WriteEWKBHeader(v interface{}, buf []byte) {
|
||||
// Write endianness byte (always little endian)
|
||||
buf[4] = 1
|
||||
|
||||
// Parse data
|
||||
switch v := v.(type) {
|
||||
case sql.Point:
|
||||
// Write SRID and type
|
||||
binary.LittleEndian.PutUint32(buf[0:4], v.SRID)
|
||||
binary.LittleEndian.PutUint32(buf[5:9], 1)
|
||||
case sql.Linestring:
|
||||
binary.LittleEndian.PutUint32(buf[0:4], v.SRID)
|
||||
binary.LittleEndian.PutUint32(buf[5:9], 2)
|
||||
case sql.Polygon:
|
||||
binary.LittleEndian.PutUint32(buf[0:4], v.SRID)
|
||||
binary.LittleEndian.PutUint32(buf[5:9], 3)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteEWKBPointData converts a Point into a byte array in EWKB format
|
||||
// Very similar to function in GMS
|
||||
func WriteEWKBPointData(p sql.Point, buf []byte) {
|
||||
binary.LittleEndian.PutUint64(buf[0:8], math.Float64bits(p.X))
|
||||
binary.LittleEndian.PutUint64(buf[8:16], math.Float64bits(p.Y))
|
||||
}
|
||||
|
||||
// WriteEWKBLineData converts a Line into a byte array in EWKB format
|
||||
func WriteEWKBLineData(l sql.Linestring, buf []byte) {
|
||||
// Write length of linestring
|
||||
binary.LittleEndian.PutUint32(buf[:4], uint32(len(l.Points)))
|
||||
// Append each point
|
||||
for i, p := range l.Points {
|
||||
WriteEWKBPointData(p, buf[4+16*i:4+16*(i+1)])
|
||||
}
|
||||
}
|
||||
|
||||
// WriteEWKBPolyData converts a Polygon into a byte array in EWKB format
|
||||
func WriteEWKBPolyData(p sql.Polygon, buf []byte) {
|
||||
// Write length of polygon
|
||||
binary.LittleEndian.PutUint32(buf[:4], uint32(len(p.Lines)))
|
||||
// Write each line
|
||||
start, stop := 0, 4
|
||||
for _, l := range p.Lines {
|
||||
start, stop = stop, stop+4+16*len(l.Points)
|
||||
WriteEWKBLineData(l, buf[start:stop])
|
||||
}
|
||||
}
|
||||
|
||||
// SqlColToStr is a utility function for converting a sql column of type interface{} to a string
|
||||
func SqlColToStr(ctx context.Context, col interface{}) string {
|
||||
if col != nil {
|
||||
@@ -292,6 +344,25 @@ func SqlColToStr(ctx context.Context, col interface{}) string {
|
||||
}
|
||||
case time.Time:
|
||||
return typedCol.Format("2006-01-02 15:04:05.999999 -0700 MST")
|
||||
case sql.Point:
|
||||
buf := make([]byte, 25)
|
||||
WriteEWKBHeader(typedCol, buf)
|
||||
WriteEWKBPointData(typedCol, buf[9:])
|
||||
return SqlColToStr(ctx, buf)
|
||||
case sql.Linestring:
|
||||
buf := make([]byte, 9+4+16*len(typedCol.Points))
|
||||
WriteEWKBHeader(typedCol, buf)
|
||||
WriteEWKBLineData(typedCol, buf[9:])
|
||||
return SqlColToStr(ctx, buf)
|
||||
case sql.Polygon:
|
||||
size := 0
|
||||
for _, l := range typedCol.Lines {
|
||||
size += 4 + 16*len(l.Points)
|
||||
}
|
||||
buf := make([]byte, 9+4+size)
|
||||
WriteEWKBHeader(typedCol, buf)
|
||||
WriteEWKBPolyData(typedCol, buf[9:])
|
||||
return SqlColToStr(ctx, buf)
|
||||
case sql.JSONValue:
|
||||
s, err := typedCol.ToString(sql.NewContext(ctx))
|
||||
if err != nil {
|
||||
@@ -299,7 +370,7 @@ func SqlColToStr(ctx context.Context, col interface{}) string {
|
||||
}
|
||||
return s
|
||||
default:
|
||||
return fmt.Sprintf("%v", typedCol)
|
||||
return fmt.Sprintf("no match: %v", typedCol)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
202
go/store/types/linestring.go
Normal file
202
go/store/types/linestring.go
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright 2021 Dolthub, Inc.
|
||||
//
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
LengthSize = 4
|
||||
)
|
||||
|
||||
// Linestring is a Noms Value wrapper around a string.
|
||||
type Linestring struct {
|
||||
SRID uint32
|
||||
Points []Point
|
||||
}
|
||||
|
||||
// Value interface
|
||||
func (v Linestring) Value(ctx context.Context) (Value, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (v Linestring) Equals(other Value) bool {
|
||||
// Compare types
|
||||
v2, ok := other.(Linestring)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Compare SRID
|
||||
if v.SRID != v2.SRID {
|
||||
return false
|
||||
}
|
||||
// Compare lengths of points
|
||||
if len(v.Points) != len(v2.Points) {
|
||||
return false
|
||||
}
|
||||
// Compare each point
|
||||
for i := 0; i < len(v.Points); i++ {
|
||||
if !v.Points[i].Equals(v2.Points[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Linestring) Less(nbf *NomsBinFormat, other LesserValuable) (bool, error) {
|
||||
// Compare types
|
||||
v2, ok := other.(Linestring)
|
||||
if !ok {
|
||||
return LinestringKind < other.Kind(), nil
|
||||
}
|
||||
// TODO: should I even take this into account?
|
||||
// Compare SRID
|
||||
if v.SRID != v2.SRID {
|
||||
return v.SRID < v2.SRID, nil
|
||||
}
|
||||
// Get shorter length
|
||||
var n int
|
||||
len1 := len(v.Points)
|
||||
len2 := len(v2.Points)
|
||||
if len1 < len2 {
|
||||
n = len1
|
||||
} else {
|
||||
n = len2
|
||||
}
|
||||
|
||||
// Compare each point until there's one that is less than
|
||||
for i := 0; i < n; i++ {
|
||||
if !v.Points[i].Equals(v2.Points[i]) {
|
||||
return v.Points[i].Less(nbf, v2.Points[i])
|
||||
}
|
||||
}
|
||||
|
||||
// Determine based off length
|
||||
return len1 < len2, nil
|
||||
}
|
||||
|
||||
func (v Linestring) Hash(nbf *NomsBinFormat) (hash.Hash, error) {
|
||||
return getHash(v, nbf)
|
||||
}
|
||||
|
||||
func (v Linestring) isPrimitive() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Linestring) WalkValues(ctx context.Context, cb ValueCallback) error {
|
||||
for _, p := range v.Points {
|
||||
if err := p.WalkValues(ctx, cb); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Linestring) WalkRefs(nbf *NomsBinFormat, cb RefCallback) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Linestring) typeOf() (*Type, error) {
|
||||
return PrimitiveTypeMap[LinestringKind], nil
|
||||
}
|
||||
|
||||
func (v Linestring) Kind() NomsKind {
|
||||
return LinestringKind
|
||||
}
|
||||
|
||||
func (v Linestring) valueReadWriter() ValueReadWriter {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEWKBLineData converts a Line into a byte array in EWKB format
|
||||
func WriteEWKBLineData(l Linestring, buf []byte) {
|
||||
// Write length of linestring
|
||||
binary.LittleEndian.PutUint32(buf[:LengthSize], uint32(len(l.Points)))
|
||||
// Append each point
|
||||
for i, p := range l.Points {
|
||||
WriteEWKBPointData(p, buf[LengthSize+PointDataSize*i:LengthSize+PointDataSize*(i+1)])
|
||||
}
|
||||
}
|
||||
|
||||
func (v Linestring) writeTo(w nomsWriter, nbf *NomsBinFormat) error {
|
||||
err := LinestringKind.writeTo(w, nbf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Allocate buffer for linestring
|
||||
buf := make([]byte, EWKBHeaderSize+LengthSize+PointDataSize*len(v.Points))
|
||||
|
||||
// Write header and data to buffer
|
||||
WriteEWKBHeader(v, buf)
|
||||
WriteEWKBLineData(v, buf[EWKBHeaderSize:])
|
||||
|
||||
w.writeString(string(buf))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseEWKBLine converts the data portion of a WKB point to Linestring
|
||||
// Very similar logic to the function in GMS
|
||||
func ParseEWKBLine(buf []byte, srid uint32) Linestring {
|
||||
// Read length of linestring
|
||||
numPoints := binary.LittleEndian.Uint32(buf[:4])
|
||||
|
||||
// Parse points
|
||||
points := make([]Point, numPoints)
|
||||
for i := uint32(0); i < numPoints; i++ {
|
||||
points[i] = ParseEWKBPoint(buf[LengthSize+PointDataSize*i:LengthSize+PointDataSize*(i+1)], srid)
|
||||
}
|
||||
|
||||
return Linestring{SRID: srid, Points: points}
|
||||
}
|
||||
|
||||
func readLinestring(nbf *NomsBinFormat, b *valueDecoder) (Linestring, error) {
|
||||
buf := []byte(b.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf)
|
||||
if geomType != LinestringID {
|
||||
return Linestring{}, errors.New("not a linestring")
|
||||
}
|
||||
return ParseEWKBLine(buf[EWKBHeaderSize:], srid), nil
|
||||
}
|
||||
|
||||
func (v Linestring) readFrom(nbf *NomsBinFormat, b *binaryNomsReader) (Value, error) {
|
||||
buf := []byte(b.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf)
|
||||
if geomType != LinestringID {
|
||||
return nil, errors.New("not a linestring")
|
||||
}
|
||||
return ParseEWKBLine(buf[EWKBHeaderSize:], srid), nil
|
||||
}
|
||||
|
||||
func (v Linestring) skip(nbf *NomsBinFormat, b *binaryNomsReader) {
|
||||
b.skipString()
|
||||
}
|
||||
|
||||
func (v Linestring) HumanReadableString() string {
|
||||
points := make([]string, len(v.Points))
|
||||
for i, p := range v.Points {
|
||||
points[i] = p.HumanReadableString()
|
||||
}
|
||||
s := fmt.Sprintf("SRID: %d LINESTRING(%s)", v.SRID, strings.Join(points, ","))
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
@@ -56,6 +56,9 @@ const (
|
||||
TimestampKind
|
||||
DecimalKind
|
||||
JSONKind
|
||||
PointKind
|
||||
LinestringKind
|
||||
PolygonKind
|
||||
|
||||
UnknownKind NomsKind = 255
|
||||
)
|
||||
@@ -83,6 +86,9 @@ func init() {
|
||||
KindToType[TimestampKind] = Timestamp{}
|
||||
KindToType[DecimalKind] = Decimal{}
|
||||
KindToType[JSONKind] = JSON{}
|
||||
KindToType[PointKind] = Point{}
|
||||
KindToType[LinestringKind] = Linestring{}
|
||||
KindToType[PolygonKind] = Polygon{}
|
||||
|
||||
SupportedKinds[BlobKind] = true
|
||||
SupportedKinds[BoolKind] = true
|
||||
@@ -106,6 +112,9 @@ func init() {
|
||||
SupportedKinds[TimestampKind] = true
|
||||
SupportedKinds[DecimalKind] = true
|
||||
SupportedKinds[JSONKind] = true
|
||||
SupportedKinds[PointKind] = true
|
||||
SupportedKinds[LinestringKind] = true
|
||||
SupportedKinds[PolygonKind] = true
|
||||
}
|
||||
|
||||
var KindToTypeSlice []Value
|
||||
@@ -134,6 +143,9 @@ var KindToString = map[NomsKind]string{
|
||||
TimestampKind: "Timestamp",
|
||||
DecimalKind: "Decimal",
|
||||
JSONKind: "JSON",
|
||||
PointKind: "Point",
|
||||
LinestringKind: "Linestring",
|
||||
PolygonKind: "Polygon",
|
||||
}
|
||||
|
||||
// String returns the name of the kind.
|
||||
|
||||
181
go/store/types/point.go
Normal file
181
go/store/types/point.go
Normal file
@@ -0,0 +1,181 @@
|
||||
// Copyright 2021 Dolthub, Inc.
|
||||
//
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
SRIDSize = 4
|
||||
EndianSize = 1
|
||||
TypeSize = 4
|
||||
EWKBHeaderSize = SRIDSize + EndianSize + TypeSize
|
||||
PointDataSize = 16
|
||||
PointID = 1
|
||||
LinestringID = 2
|
||||
PolygonID = 3
|
||||
)
|
||||
|
||||
// Point is a Noms Value wrapper around the primitive string type (for now).
|
||||
type Point struct {
|
||||
SRID uint32
|
||||
X float64
|
||||
Y float64
|
||||
}
|
||||
|
||||
// Value interface
|
||||
func (v Point) Value(ctx context.Context) (Value, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (v Point) Equals(other Value) bool {
|
||||
if v2, ok := other.(Point); ok {
|
||||
return v.SRID == v2.SRID && v.X == v2.X && v.Y == v2.Y
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v Point) Less(nbf *NomsBinFormat, other LesserValuable) (bool, error) {
|
||||
if v2, ok := other.(Point); ok {
|
||||
return v.SRID < v2.SRID || v.X < v2.X || v.Y < v2.Y, nil
|
||||
}
|
||||
return PointKind < other.Kind(), nil
|
||||
}
|
||||
|
||||
func (v Point) Hash(nbf *NomsBinFormat) (hash.Hash, error) {
|
||||
return getHash(v, nbf)
|
||||
}
|
||||
|
||||
func (v Point) isPrimitive() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Point) WalkValues(ctx context.Context, cb ValueCallback) error {
|
||||
return cb(v)
|
||||
}
|
||||
|
||||
func (v Point) WalkRefs(nbf *NomsBinFormat, cb RefCallback) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Point) typeOf() (*Type, error) {
|
||||
return PrimitiveTypeMap[PointKind], nil
|
||||
}
|
||||
|
||||
func (v Point) Kind() NomsKind {
|
||||
return PointKind
|
||||
}
|
||||
|
||||
func (v Point) valueReadWriter() ValueReadWriter {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEWKBHeader writes the SRID, endianness, and type to the byte buffer
|
||||
// This function assumes v is a valid spatial type
|
||||
func WriteEWKBHeader(v interface{}, buf []byte) {
|
||||
// Write endianness byte (always little endian)
|
||||
buf[SRIDSize] = 1
|
||||
|
||||
// Parse data
|
||||
switch v := v.(type) {
|
||||
case Point:
|
||||
// Write SRID and type
|
||||
binary.LittleEndian.PutUint32(buf[0:SRIDSize], v.SRID)
|
||||
binary.LittleEndian.PutUint32(buf[SRIDSize+EndianSize:EWKBHeaderSize], PointID)
|
||||
case Linestring:
|
||||
binary.LittleEndian.PutUint32(buf[0:SRIDSize], v.SRID)
|
||||
binary.LittleEndian.PutUint32(buf[SRIDSize+EndianSize:EWKBHeaderSize], LinestringID)
|
||||
case Polygon:
|
||||
binary.LittleEndian.PutUint32(buf[0:SRIDSize], v.SRID)
|
||||
binary.LittleEndian.PutUint32(buf[SRIDSize+EndianSize:EWKBHeaderSize], PolygonID)
|
||||
}
|
||||
}
|
||||
|
||||
// WriteEWKBPointData converts a Point into a byte array in EWKB format
|
||||
// Very similar to function in GMS
|
||||
func WriteEWKBPointData(p Point, buf []byte) {
|
||||
binary.LittleEndian.PutUint64(buf[:PointDataSize/2], math.Float64bits(p.X))
|
||||
binary.LittleEndian.PutUint64(buf[PointDataSize/2:], math.Float64bits(p.Y))
|
||||
}
|
||||
|
||||
func (v Point) writeTo(w nomsWriter, nbf *NomsBinFormat) error {
|
||||
// Mark as PointKind
|
||||
err := PointKind.writeTo(w, nbf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Allocate buffer for point 4 + 1 + 4 + 16
|
||||
buf := make([]byte, EWKBHeaderSize+PointDataSize)
|
||||
|
||||
// Write header and data to buffer
|
||||
WriteEWKBHeader(v, buf)
|
||||
WriteEWKBPointData(v, buf[EWKBHeaderSize:])
|
||||
|
||||
w.writeString(string(buf))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseEWKBHeader converts the header potion of a EWKB byte array to srid, endianness, and geometry type
|
||||
func ParseEWKBHeader(buf []byte) (uint32, bool, uint32) {
|
||||
srid := binary.LittleEndian.Uint32(buf[0:SRIDSize]) // First 4 bytes is SRID always in little endian
|
||||
isBig := buf[SRIDSize] == 0 // Next byte is endianness
|
||||
geomType := binary.LittleEndian.Uint32(buf[SRIDSize+EndianSize : EWKBHeaderSize]) // Next 4 bytes is type
|
||||
return srid, isBig, geomType
|
||||
}
|
||||
|
||||
// ParseEWKBPoint converts the data portion of a WKB point to Point
|
||||
// Very similar logic to the function in GMS
|
||||
func ParseEWKBPoint(buf []byte, srid uint32) Point {
|
||||
// Read floats x and y
|
||||
x := math.Float64frombits(binary.LittleEndian.Uint64(buf[:PointDataSize/2]))
|
||||
y := math.Float64frombits(binary.LittleEndian.Uint64(buf[PointDataSize/2:]))
|
||||
return Point{SRID: srid, X: x, Y: y}
|
||||
}
|
||||
|
||||
func readPoint(nbf *NomsBinFormat, b *valueDecoder) (Point, error) {
|
||||
buf := []byte(b.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf) // Assume it's always little endian
|
||||
if geomType != PointID {
|
||||
return Point{}, errors.New("not a point")
|
||||
}
|
||||
return ParseEWKBPoint(buf[EWKBHeaderSize:], srid), nil
|
||||
}
|
||||
|
||||
func (v Point) readFrom(nbf *NomsBinFormat, b *binaryNomsReader) (Value, error) {
|
||||
buf := []byte(b.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf) // Assume it's always little endian
|
||||
if geomType != PointID {
|
||||
return Point{}, errors.New("not a point")
|
||||
}
|
||||
return ParseEWKBPoint(buf[EWKBHeaderSize:], srid), nil
|
||||
}
|
||||
|
||||
func (v Point) skip(nbf *NomsBinFormat, b *binaryNomsReader) {
|
||||
b.skipString()
|
||||
}
|
||||
|
||||
func (v Point) HumanReadableString() string {
|
||||
s := fmt.Sprintf("SRID: %d POINT(%s %s)", v.SRID, strconv.FormatFloat(v.X, 'g', -1, 64), strconv.FormatFloat(v.Y, 'g', -1, 64))
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
205
go/store/types/polygon.go
Normal file
205
go/store/types/polygon.go
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright 2021 Dolthub, Inc.
|
||||
//
|
||||
// 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 types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/dolthub/dolt/go/store/hash"
|
||||
)
|
||||
|
||||
// Polygon is a Noms Value wrapper around a string.
|
||||
type Polygon struct {
|
||||
SRID uint32
|
||||
Lines []Linestring
|
||||
}
|
||||
|
||||
// Value interface
|
||||
func (v Polygon) Value(ctx context.Context) (Value, error) {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
func (v Polygon) Equals(other Value) bool {
|
||||
// Compare types
|
||||
v2, ok := other.(Polygon)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Compare SRID
|
||||
if v.SRID != v2.SRID {
|
||||
return false
|
||||
}
|
||||
// Compare lengths of lines
|
||||
if len(v.Lines) != len(v2.Lines) {
|
||||
return false
|
||||
}
|
||||
// Compare each line
|
||||
for i := 0; i < len(v.Lines); i++ {
|
||||
if !v.Lines[i].Equals(v2.Lines[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Polygon) Less(nbf *NomsBinFormat, other LesserValuable) (bool, error) {
|
||||
// Compare types
|
||||
v2, ok := other.(Polygon)
|
||||
if !ok {
|
||||
return PolygonKind < other.Kind(), nil
|
||||
}
|
||||
// Compare SRID
|
||||
if v.SRID != v2.SRID {
|
||||
return v.SRID < v2.SRID, nil
|
||||
}
|
||||
// Get shorter length
|
||||
var n int
|
||||
len1 := len(v.Lines)
|
||||
len2 := len(v2.Lines)
|
||||
if len1 < len2 {
|
||||
n = len1
|
||||
} else {
|
||||
n = len2
|
||||
}
|
||||
// Compare each point until there is one that is less
|
||||
for i := 0; i < n; i++ {
|
||||
if !v.Lines[i].Equals(v2.Lines[i]) {
|
||||
return v.Lines[i].Less(nbf, v2.Lines[i])
|
||||
}
|
||||
}
|
||||
// Determine based off length
|
||||
return len1 < len2, nil
|
||||
}
|
||||
|
||||
func (v Polygon) Hash(nbf *NomsBinFormat) (hash.Hash, error) {
|
||||
return getHash(v, nbf)
|
||||
}
|
||||
|
||||
func (v Polygon) isPrimitive() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (v Polygon) WalkValues(ctx context.Context, cb ValueCallback) error {
|
||||
for _, l := range v.Lines {
|
||||
if err := l.WalkValues(ctx, cb); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Polygon) WalkRefs(nbf *NomsBinFormat, cb RefCallback) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v Polygon) typeOf() (*Type, error) {
|
||||
return PrimitiveTypeMap[PolygonKind], nil
|
||||
}
|
||||
|
||||
func (v Polygon) Kind() NomsKind {
|
||||
return PolygonKind
|
||||
}
|
||||
|
||||
func (v Polygon) valueReadWriter() ValueReadWriter {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteEWKBPolyData converts a Polygon into a byte array in EWKB format
|
||||
func WriteEWKBPolyData(p Polygon, buf []byte) {
|
||||
// Write length of polygon
|
||||
binary.LittleEndian.PutUint32(buf[:LengthSize], uint32(len(p.Lines)))
|
||||
// Write each line
|
||||
start, stop := 0, LengthSize
|
||||
for _, l := range p.Lines {
|
||||
start, stop = stop, stop+LengthSize+PointDataSize*len(l.Points)
|
||||
WriteEWKBLineData(l, buf[start:stop])
|
||||
}
|
||||
}
|
||||
|
||||
func (v Polygon) writeTo(w nomsWriter, nbf *NomsBinFormat) error {
|
||||
err := PolygonKind.writeTo(w, nbf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate space for polygon buffer
|
||||
size := 0
|
||||
for _, l := range v.Lines {
|
||||
size += LengthSize + PointDataSize*len(l.Points)
|
||||
}
|
||||
|
||||
// Allocate buffer for poly
|
||||
buf := make([]byte, EWKBHeaderSize+LengthSize+size)
|
||||
|
||||
// Write header and data to buffer
|
||||
WriteEWKBHeader(v, buf)
|
||||
WriteEWKBPolyData(v, buf[EWKBHeaderSize:])
|
||||
|
||||
w.writeString(string(buf))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseEWKBPoly converts the data portions of a WKB polygon to Polygon
|
||||
// Very similar logic to the function in GMS
|
||||
func ParseEWKBPoly(buf []byte, srid uint32) Polygon {
|
||||
// Read length of Polygon
|
||||
numLines := binary.LittleEndian.Uint32(buf[:LengthSize])
|
||||
|
||||
// Parse lines
|
||||
s := LengthSize
|
||||
lines := make([]Linestring, numLines)
|
||||
for i := uint32(0); i < numLines; i++ {
|
||||
lines[i] = ParseEWKBLine(buf[s:], srid)
|
||||
s += LengthSize * PointDataSize * len(lines[i].Points)
|
||||
}
|
||||
|
||||
return Polygon{SRID: srid, Lines: lines}
|
||||
}
|
||||
|
||||
func readPolygon(nbf *NomsBinFormat, b *valueDecoder) (Polygon, error) {
|
||||
buf := []byte(b.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf)
|
||||
if geomType != PolygonID {
|
||||
return Polygon{}, errors.New("not a polygon")
|
||||
}
|
||||
return ParseEWKBPoly(buf[EWKBHeaderSize:], srid), nil
|
||||
}
|
||||
|
||||
func (v Polygon) readFrom(nbf *NomsBinFormat, b *binaryNomsReader) (Value, error) {
|
||||
buf := []byte(b.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf)
|
||||
if geomType != PolygonID {
|
||||
return nil, errors.New("not a polygon")
|
||||
}
|
||||
return ParseEWKBPoly(buf[EWKBHeaderSize:], srid), nil
|
||||
}
|
||||
|
||||
func (v Polygon) skip(nbf *NomsBinFormat, b *binaryNomsReader) {
|
||||
b.skipString()
|
||||
}
|
||||
|
||||
func (v Polygon) HumanReadableString() string {
|
||||
lines := make([]string, len(v.Lines))
|
||||
for i, l := range v.Lines {
|
||||
lines[i] = l.HumanReadableString()
|
||||
}
|
||||
s := fmt.Sprintf("SRID: %d POLYGON(%s)", v.SRID, strings.Join(lines, ","))
|
||||
return strconv.Quote(s)
|
||||
}
|
||||
@@ -46,6 +46,9 @@ type CodecReader interface {
|
||||
ReadInlineBlob() []byte
|
||||
ReadTimestamp() (time.Time, error)
|
||||
ReadDecimal() (decimal.Decimal, error)
|
||||
ReadPoint() (Point, error)
|
||||
ReadLinestring() (Linestring, error)
|
||||
ReadPolygon() (Polygon, error)
|
||||
ReadBlob() (Blob, error)
|
||||
ReadJSON() (JSON, error)
|
||||
}
|
||||
@@ -80,6 +83,18 @@ func (r *valueDecoder) ReadBlob() (Blob, error) {
|
||||
return newBlob(seq), nil
|
||||
}
|
||||
|
||||
func (r *valueDecoder) ReadPoint() (Point, error) {
|
||||
return readPoint(nil, r)
|
||||
}
|
||||
|
||||
func (r *valueDecoder) ReadLinestring() (Linestring, error) {
|
||||
return readLinestring(nil, r)
|
||||
}
|
||||
|
||||
func (r *valueDecoder) ReadPolygon() (Polygon, error) {
|
||||
return readPolygon(nil, r)
|
||||
}
|
||||
|
||||
func (r *valueDecoder) ReadJSON() (JSON, error) {
|
||||
return readJSON(r.vrw.Format(), r)
|
||||
}
|
||||
@@ -356,6 +371,30 @@ func (r *valueDecoder) readValue(nbf *NomsBinFormat) (Value, error) {
|
||||
return r.readTuple(nbf)
|
||||
case JSONKind:
|
||||
return r.ReadJSON()
|
||||
case PointKind:
|
||||
r.skipKind()
|
||||
buf := []byte(r.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf)
|
||||
if geomType != PointID {
|
||||
return nil, ErrUnknownType
|
||||
}
|
||||
return ParseEWKBPoint(buf[EWKBHeaderSize:], srid), nil
|
||||
case LinestringKind:
|
||||
r.skipKind()
|
||||
buf := []byte(r.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf)
|
||||
if geomType != LinestringID {
|
||||
return nil, ErrUnknownType
|
||||
}
|
||||
return ParseEWKBLine(buf[EWKBHeaderSize:], srid), nil
|
||||
case PolygonKind:
|
||||
r.skipKind()
|
||||
buf := []byte(r.ReadString())
|
||||
srid, _, geomType := ParseEWKBHeader(buf)
|
||||
if geomType != PolygonID {
|
||||
return nil, ErrUnknownType
|
||||
}
|
||||
return ParseEWKBPoly(buf[EWKBHeaderSize:], srid), nil
|
||||
case TypeKind:
|
||||
r.skipKind()
|
||||
return r.readType()
|
||||
@@ -405,6 +444,15 @@ func (r *valueDecoder) SkipValue(nbf *NomsBinFormat) error {
|
||||
case StringKind:
|
||||
r.skipKind()
|
||||
r.skipString()
|
||||
case PointKind:
|
||||
r.skipKind()
|
||||
r.skipString()
|
||||
case LinestringKind:
|
||||
r.skipKind()
|
||||
r.skipString()
|
||||
case PolygonKind:
|
||||
r.skipKind()
|
||||
r.skipString()
|
||||
case ListKind:
|
||||
err := r.skipList(nbf)
|
||||
if err != nil {
|
||||
|
||||
23
integration-tests/bats/sql-spatial-types.bats
Normal file
23
integration-tests/bats/sql-spatial-types.bats
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bats
|
||||
load $BATS_TEST_DIRNAME/helper/common.bash
|
||||
|
||||
setup() {
|
||||
setup_common
|
||||
}
|
||||
|
||||
teardown() {
|
||||
assert_feature_version
|
||||
teardown_common
|
||||
}
|
||||
|
||||
@test "sql-spatial-types: can't make spatial types without enabling feature flag" {
|
||||
run dolt sql -q "create table point_tbl (p point)"
|
||||
[ "$status" -eq 1 ]
|
||||
[[ "$output" =~ "cannot be made" ]] || false
|
||||
}
|
||||
|
||||
@test "sql-spatial-types: can make spatial types with flag" {
|
||||
DOLT_ENABLE_SPATIAL_TYPES=true run dolt sql -q "create table point_tbl (p point)"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" = "" ]] || false
|
||||
}
|
||||
Reference in New Issue
Block a user