github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/prolly/tree/prolly_fields_test.go (about) 1 // Copyright 2022 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tree 16 17 import ( 18 "context" 19 "encoding/json" 20 "math" 21 "testing" 22 "time" 23 24 "github.com/dolthub/go-mysql-server/sql" 25 "github.com/dolthub/go-mysql-server/sql/expression/function/spatial" 26 "github.com/dolthub/go-mysql-server/sql/types" 27 "github.com/shopspring/decimal" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/require" 30 31 "github.com/dolthub/dolt/go/store/pool" 32 "github.com/dolthub/dolt/go/store/val" 33 ) 34 35 type prollyFieldTest struct { 36 name string 37 value interface{} 38 typ val.Type 39 } 40 41 func TestRoundTripProllyFields(t *testing.T) { 42 tests := []prollyFieldTest{ 43 { 44 name: "null", 45 typ: val.Type{ 46 Enc: val.Int8Enc, 47 Nullable: true, 48 }, 49 value: nil, 50 }, 51 { 52 name: "int8", 53 typ: val.Type{Enc: val.Int8Enc}, 54 value: int8(-42), 55 }, 56 { 57 name: "uint8", 58 typ: val.Type{Enc: val.Uint8Enc}, 59 value: uint8(42), 60 }, 61 { 62 name: "int16", 63 typ: val.Type{Enc: val.Int16Enc}, 64 value: int16(-42), 65 }, 66 { 67 name: "uint16", 68 typ: val.Type{Enc: val.Uint16Enc}, 69 value: uint16(42), 70 }, 71 { 72 name: "int32", 73 typ: val.Type{Enc: val.Int32Enc}, 74 value: int32(-42), 75 }, 76 { 77 name: "uint32", 78 typ: val.Type{Enc: val.Uint32Enc}, 79 value: uint32(42), 80 }, 81 { 82 name: "int64", 83 typ: val.Type{Enc: val.Int64Enc}, 84 value: int64(-42), 85 }, 86 { 87 name: "uint64", 88 typ: val.Type{Enc: val.Uint64Enc}, 89 value: uint64(42), 90 }, 91 { 92 name: "float32", 93 typ: val.Type{Enc: val.Float32Enc}, 94 value: float32(math.Pi), 95 }, 96 { 97 name: "float64", 98 typ: val.Type{Enc: val.Float64Enc}, 99 value: float64(-math.Pi), 100 }, 101 { 102 name: "bit", 103 typ: val.Type{Enc: val.Bit64Enc}, 104 value: uint64(42), 105 }, 106 { 107 name: "decimal", 108 typ: val.Type{Enc: val.DecimalEnc}, 109 value: mustParseDecimal("0.263419374632932747932030573792"), 110 }, 111 { 112 name: "string", 113 typ: val.Type{Enc: val.StringEnc}, 114 value: "lorem ipsum", 115 }, 116 { 117 name: "string", 118 typ: val.Type{Enc: val.StringAddrEnc}, 119 value: "lorem ipsum", 120 }, 121 { 122 name: "bytes", 123 typ: val.Type{Enc: val.ByteStringEnc}, 124 value: []byte("lorem ipsum"), 125 }, 126 { 127 name: "year", 128 typ: val.Type{Enc: val.YearEnc}, 129 value: int16(2022), 130 }, 131 { 132 name: "date", 133 typ: val.Type{Enc: val.DateEnc}, 134 value: dateFromTime(time.Now().UTC()), 135 }, 136 { 137 name: "time", 138 typ: val.Type{Enc: val.TimeEnc}, 139 value: mustParseTime(t, "11:22:00"), 140 }, 141 { 142 name: "datetime", 143 typ: val.Type{Enc: val.DatetimeEnc}, 144 value: time.UnixMicro(time.Now().UTC().UnixMicro()).UTC(), 145 }, 146 { 147 name: "timestamp", 148 typ: val.Type{Enc: val.DatetimeEnc}, 149 value: time.UnixMicro(time.Now().UTC().UnixMicro()).UTC(), 150 }, 151 { 152 name: "json", 153 typ: val.Type{Enc: val.JSONAddrEnc}, 154 value: mustParseJson(t, `{"a": 1, "b": false}`), 155 }, 156 { 157 name: "point", 158 typ: val.Type{Enc: val.GeomAddrEnc}, 159 value: mustParseGeometryType(t, "POINT(1 2)"), 160 }, 161 { 162 name: "linestring", 163 typ: val.Type{Enc: val.GeomAddrEnc}, 164 value: mustParseGeometryType(t, "LINESTRING(1 2,3 4)"), 165 }, 166 { 167 name: "polygon", 168 typ: val.Type{Enc: val.GeomAddrEnc}, 169 value: mustParseGeometryType(t, "POLYGON((0 0,1 1,1 0,0 0))"), 170 }, 171 { 172 name: "binary", 173 typ: val.Type{Enc: val.BytesAddrEnc}, 174 value: []byte("lorem ipsum"), 175 }, 176 } 177 178 for _, test := range tests { 179 t.Run(test.name, func(t *testing.T) { 180 testRoundTripProllyFields(t, test) 181 }) 182 } 183 } 184 185 var testPool = pool.NewBuffPool() 186 187 func testRoundTripProllyFields(t *testing.T, test prollyFieldTest) { 188 desc := val.NewTupleDescriptor(test.typ) 189 builder := val.NewTupleBuilder(desc) 190 ns := NewTestNodeStore() 191 192 err := PutField(context.Background(), ns, builder, 0, test.value) 193 assert.NoError(t, err) 194 195 tup := builder.Build(testPool) 196 197 v, err := GetField(context.Background(), desc, 0, tup, ns) 198 assert.NoError(t, err) 199 jsonType := val.Type{Enc: val.JSONAddrEnc} 200 if test.typ == jsonType { 201 getJson := func(field interface{}) interface{} { 202 jsonWrapper, ok := field.(sql.JSONWrapper) 203 require.Equal(t, ok, true) 204 val, err := jsonWrapper.ToInterface() 205 require.NoError(t, err) 206 return val 207 } 208 assert.Equal(t, getJson(test.value), getJson(v)) 209 } else { 210 assert.Equal(t, test.value, v) 211 } 212 } 213 214 func mustParseGeometryType(t *testing.T, s string) (v interface{}) { 215 // Determine type, and get data 216 geomType, data, _, err := spatial.ParseWKTHeader(s) 217 require.NoError(t, err) 218 219 srid, order := uint32(0), false 220 switch geomType { 221 case "point": 222 v, err = spatial.WKTToPoint(data, srid, order) 223 case "linestring": 224 v, err = spatial.WKTToLine(data, srid, order) 225 case "polygon": 226 v, err = spatial.WKTToPoly(data, srid, order) 227 default: 228 panic("unknown geometry type") 229 } 230 require.NoError(t, err) 231 return 232 } 233 234 func mustParseJson(t *testing.T, s string) types.JSONDocument { 235 var v interface{} 236 err := json.Unmarshal([]byte(s), &v) 237 require.NoError(t, err) 238 return types.JSONDocument{Val: v} 239 } 240 241 func mustParseDecimal(s string) decimal.Decimal { 242 d, err := decimal.NewFromString(s) 243 if err != nil { 244 panic(err) 245 } 246 return d 247 } 248 249 func mustParseTime(t *testing.T, s string) types.Timespan { 250 val, err := types.Time.ConvertToTimespan(s) 251 require.NoError(t, err) 252 return val 253 } 254 255 func dateFromTime(t time.Time) time.Time { 256 y, m, d := t.Year(), t.Month(), t.Day() 257 return time.Date(y, m, d, 0, 0, 0, 0, time.UTC) 258 } 259 260 // TestGeometryEncoding contains tests that ensure backwards compatibility with the old geometry encoding. 261 // 262 // Initially, Geometries were stored in line, but now they are stored out of band as BLOBs. 263 func TestGeometryEncoding(t *testing.T) { 264 tests := []struct { 265 name string 266 value interface{} 267 }{ 268 { 269 name: "point", 270 value: mustParseGeometryType(t, "POINT(1 2)"), 271 }, 272 { 273 name: "linestring", 274 value: mustParseGeometryType(t, "LINESTRING(1 2,3 4)"), 275 }, 276 { 277 name: "polygon", 278 value: mustParseGeometryType(t, "POLYGON((0 0,1 1,1 0,0 0))"), 279 }, 280 } 281 282 for _, test := range tests { 283 t.Run(test.name, func(t *testing.T) { 284 ns := NewTestNodeStore() 285 oldDesc := val.NewTupleDescriptor(val.Type{Enc: val.GeometryEnc}) 286 builder := val.NewTupleBuilder(oldDesc) 287 b := serializeGeometry(test.value) 288 builder.PutGeometry(0, b) 289 tup := builder.Build(testPool) 290 291 var v interface{} 292 var err error 293 294 v, err = GetField(context.Background(), oldDesc, 0, tup, ns) 295 assert.NoError(t, err) 296 assert.Equal(t, test.value, v) 297 298 newDesc := val.NewTupleDescriptor(val.Type{Enc: val.GeometryEnc}) 299 v, err = GetField(context.Background(), newDesc, 0, tup, ns) 300 assert.NoError(t, err) 301 assert.Equal(t, test.value, v) 302 }) 303 } 304 }