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  }