diff --git a/go/libraries/doltcore/schema/typeinfo/bit.go b/go/libraries/doltcore/schema/typeinfo/bit.go index 487c99eb61..5856a3d9c6 100644 --- a/go/libraries/doltcore/schema/typeinfo/bit.go +++ b/go/libraries/doltcore/schema/typeinfo/bit.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/blobstring.go b/go/libraries/doltcore/schema/typeinfo/blobstring.go index 08900bed46..1c2dc513ee 100644 --- a/go/libraries/doltcore/schema/typeinfo/blobstring.go +++ b/go/libraries/doltcore/schema/typeinfo/blobstring.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/bool.go b/go/libraries/doltcore/schema/typeinfo/bool.go index 5c47e7b826..11777964c5 100644 --- a/go/libraries/doltcore/schema/typeinfo/bool.go +++ b/go/libraries/doltcore/schema/typeinfo/bool.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/datetime.go b/go/libraries/doltcore/schema/typeinfo/datetime.go index 63904c9d0c..3d217f1dba 100644 --- a/go/libraries/doltcore/schema/typeinfo/datetime.go +++ b/go/libraries/doltcore/schema/typeinfo/datetime.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/decimal.go b/go/libraries/doltcore/schema/typeinfo/decimal.go index 6b855d7471..49adf60e93 100644 --- a/go/libraries/doltcore/schema/typeinfo/decimal.go +++ b/go/libraries/doltcore/schema/typeinfo/decimal.go @@ -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) diff --git a/go/libraries/doltcore/schema/typeinfo/enum.go b/go/libraries/doltcore/schema/typeinfo/enum.go index af323fb140..0228619c96 100644 --- a/go/libraries/doltcore/schema/typeinfo/enum.go +++ b/go/libraries/doltcore/schema/typeinfo/enum.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/float.go b/go/libraries/doltcore/schema/typeinfo/float.go index a41181353c..0cd23e2b2c 100644 --- a/go/libraries/doltcore/schema/typeinfo/float.go +++ b/go/libraries/doltcore/schema/typeinfo/float.go @@ -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 { diff --git a/go/libraries/doltcore/schema/typeinfo/inlineblob.go b/go/libraries/doltcore/schema/typeinfo/inlineblob.go index 785fe2f3c7..edf9a17fd2 100644 --- a/go/libraries/doltcore/schema/typeinfo/inlineblob.go +++ b/go/libraries/doltcore/schema/typeinfo/inlineblob.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/int.go b/go/libraries/doltcore/schema/typeinfo/int.go index a56f0fee39..a721f4a258 100644 --- a/go/libraries/doltcore/schema/typeinfo/int.go +++ b/go/libraries/doltcore/schema/typeinfo/int.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/json.go b/go/libraries/doltcore/schema/typeinfo/json.go index 2c08112e4b..d2a71a1c46 100644 --- a/go/libraries/doltcore/schema/typeinfo/json.go +++ b/go/libraries/doltcore/schema/typeinfo/json.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/linestring.go b/go/libraries/doltcore/schema/typeinfo/linestring.go new file mode 100644 index 0000000000..f51a34b542 --- /dev/null +++ b/go/libraries/doltcore/schema/typeinfo/linestring.go @@ -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()) + } +} diff --git a/go/libraries/doltcore/schema/typeinfo/point.go b/go/libraries/doltcore/schema/typeinfo/point.go new file mode 100644 index 0000000000..0ddfec4154 --- /dev/null +++ b/go/libraries/doltcore/schema/typeinfo/point.go @@ -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()) + } +} diff --git a/go/libraries/doltcore/schema/typeinfo/polygon.go b/go/libraries/doltcore/schema/typeinfo/polygon.go new file mode 100644 index 0000000000..5c572f28b3 --- /dev/null +++ b/go/libraries/doltcore/schema/typeinfo/polygon.go @@ -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()) + } +} diff --git a/go/libraries/doltcore/schema/typeinfo/set.go b/go/libraries/doltcore/schema/typeinfo/set.go index c5732bf937..f883936226 100644 --- a/go/libraries/doltcore/schema/typeinfo/set.go +++ b/go/libraries/doltcore/schema/typeinfo/set.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/time.go b/go/libraries/doltcore/schema/typeinfo/time.go index a6cc092027..edd024a4e4 100644 --- a/go/libraries/doltcore/schema/typeinfo/time.go +++ b/go/libraries/doltcore/schema/typeinfo/time.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/typeconverter.go b/go/libraries/doltcore/schema/typeinfo/typeconverter.go index 7a09a76716..05ceb5b411 100644 --- a/go/libraries/doltcore/schema/typeinfo/typeconverter.go +++ b/go/libraries/doltcore/schema/typeinfo/typeconverter.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/typeinfo.go b/go/libraries/doltcore/schema/typeinfo/typeinfo.go index f672d01771..a973bb92d0 100644 --- a/go/libraries/doltcore/schema/typeinfo/typeinfo.go +++ b/go/libraries/doltcore/schema/typeinfo/typeinfo.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/typeinfo_test.go b/go/libraries/doltcore/schema/typeinfo/typeinfo_test.go index 4a36bd51d2..2024c7b602 100644 --- a/go/libraries/doltcore/schema/typeinfo/typeinfo_test.go +++ b/go/libraries/doltcore/schema/typeinfo/typeinfo_test.go @@ -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 diff --git a/go/libraries/doltcore/schema/typeinfo/uint.go b/go/libraries/doltcore/schema/typeinfo/uint.go index f5b8f4b468..bdcb41a772 100644 --- a/go/libraries/doltcore/schema/typeinfo/uint.go +++ b/go/libraries/doltcore/schema/typeinfo/uint.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/uuid.go b/go/libraries/doltcore/schema/typeinfo/uuid.go index d1246139ea..9cfa77d242 100644 --- a/go/libraries/doltcore/schema/typeinfo/uuid.go +++ b/go/libraries/doltcore/schema/typeinfo/uuid.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/varbinary.go b/go/libraries/doltcore/schema/typeinfo/varbinary.go index 452b6b9e28..0bcb5bcfbe 100644 --- a/go/libraries/doltcore/schema/typeinfo/varbinary.go +++ b/go/libraries/doltcore/schema/typeinfo/varbinary.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/varstring.go b/go/libraries/doltcore/schema/typeinfo/varstring.go index 1db4dc24a6..55b05243bc 100644 --- a/go/libraries/doltcore/schema/typeinfo/varstring.go +++ b/go/libraries/doltcore/schema/typeinfo/varstring.go @@ -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: diff --git a/go/libraries/doltcore/schema/typeinfo/year.go b/go/libraries/doltcore/schema/typeinfo/year.go index 360f1d4b3f..c55609efbb 100644 --- a/go/libraries/doltcore/schema/typeinfo/year.go +++ b/go/libraries/doltcore/schema/typeinfo/year.go @@ -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: diff --git a/go/libraries/doltcore/sqle/sqlutil/sql_row.go b/go/libraries/doltcore/sqle/sqlutil/sql_row.go index 89d8da4dd4..4f5c9a3e41 100644 --- a/go/libraries/doltcore/sqle/sqlutil/sql_row.go +++ b/go/libraries/doltcore/sqle/sqlutil/sql_row.go @@ -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) } } diff --git a/go/store/types/linestring.go b/go/store/types/linestring.go new file mode 100644 index 0000000000..a0957a0e5d --- /dev/null +++ b/go/store/types/linestring.go @@ -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) +} diff --git a/go/store/types/noms_kind.go b/go/store/types/noms_kind.go index 499c8a1830..ad573fd37c 100644 --- a/go/store/types/noms_kind.go +++ b/go/store/types/noms_kind.go @@ -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. diff --git a/go/store/types/point.go b/go/store/types/point.go new file mode 100644 index 0000000000..bf27a7d51e --- /dev/null +++ b/go/store/types/point.go @@ -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) +} diff --git a/go/store/types/polygon.go b/go/store/types/polygon.go new file mode 100644 index 0000000000..4baf4db7bc --- /dev/null +++ b/go/store/types/polygon.go @@ -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) +} diff --git a/go/store/types/value_decoder.go b/go/store/types/value_decoder.go index ea9b0ddcee..9da6e17e93 100644 --- a/go/store/types/value_decoder.go +++ b/go/store/types/value_decoder.go @@ -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 { diff --git a/integration-tests/bats/sql-spatial-types.bats b/integration-tests/bats/sql-spatial-types.bats new file mode 100644 index 0000000000..49baf5e187 --- /dev/null +++ b/integration-tests/bats/sql-spatial-types.bats @@ -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 +} \ No newline at end of file