github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/sqlfmt/row_fmt_test.go (about)

     1  // Copyright 2020 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 sqlfmt_test
    16  
    17  import (
    18  	"testing"
    19  
    20  	"github.com/google/uuid"
    21  	"github.com/stretchr/testify/assert"
    22  	"github.com/stretchr/testify/require"
    23  
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/dtestutils"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/row"
    26  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/sqle/sqlfmt"
    29  	"github.com/dolthub/dolt/go/libraries/utils/set"
    30  	"github.com/dolthub/dolt/go/store/types"
    31  )
    32  
    33  const expectedDropSql = "DROP TABLE `table_name`;"
    34  const expectedDropIfExistsSql = "DROP TABLE IF EXISTS `table_name`;"
    35  const expectedAddColSql = "ALTER TABLE `table_name` ADD `c0` BIGINT NOT NULL;"
    36  const expectedDropColSql = "ALTER TABLE `table_name` DROP `first_name`;"
    37  const expectedRenameColSql = "ALTER TABLE `table_name` RENAME COLUMN `id` TO `pk`;"
    38  const expectedRenameTableSql = "RENAME TABLE `table_name` TO `new_table_name`;"
    39  
    40  type test struct {
    41  	name           string
    42  	row            row.Row
    43  	sch            schema.Schema
    44  	expectedOutput string
    45  }
    46  
    47  type updateTest struct {
    48  	name           string
    49  	row            row.Row
    50  	sch            schema.Schema
    51  	expectedOutput string
    52  	collDiff       *set.StrSet
    53  }
    54  
    55  func TestTableDropStmt(t *testing.T) {
    56  	stmt := sqlfmt.DropTableStmt("table_name")
    57  
    58  	assert.Equal(t, expectedDropSql, stmt)
    59  }
    60  
    61  func TestTableDropIfExistsStmt(t *testing.T) {
    62  	stmt := sqlfmt.DropTableIfExistsStmt("table_name")
    63  
    64  	assert.Equal(t, expectedDropIfExistsSql, stmt)
    65  }
    66  
    67  func TestAlterTableAddColStmt(t *testing.T) {
    68  	newColDef := "`c0` BIGINT NOT NULL"
    69  	stmt := sqlfmt.AlterTableAddColStmt("table_name", newColDef)
    70  
    71  	assert.Equal(t, expectedAddColSql, stmt)
    72  }
    73  
    74  func TestAlterTableDropColStmt(t *testing.T) {
    75  	stmt := sqlfmt.AlterTableDropColStmt("table_name", "first_name")
    76  
    77  	assert.Equal(t, expectedDropColSql, stmt)
    78  }
    79  
    80  func TestAlterTableRenameColStmt(t *testing.T) {
    81  	stmt := sqlfmt.AlterTableRenameColStmt("table_name", "id", "pk")
    82  
    83  	assert.Equal(t, expectedRenameColSql, stmt)
    84  }
    85  
    86  func TestRenameTableStmt(t *testing.T) {
    87  	stmt := sqlfmt.RenameTableStmt("table_name", "new_table_name")
    88  
    89  	assert.Equal(t, expectedRenameTableSql, stmt)
    90  }
    91  
    92  func newRow(sch schema.Schema, id uuid.UUID, name string, age uint, isMarried bool, title *string) row.Row {
    93  	var titleVal types.Value
    94  	if title != nil {
    95  		titleVal = types.String(*title)
    96  	}
    97  
    98  	married := types.Int(0)
    99  	if isMarried {
   100  		married = types.Int(1)
   101  	}
   102  
   103  	taggedVals := row.TaggedValues{
   104  		dtestutils.IdTag:        types.String(id.String()),
   105  		dtestutils.NameTag:      types.String(name),
   106  		dtestutils.AgeTag:       types.Uint(age),
   107  		dtestutils.IsMarriedTag: married,
   108  		dtestutils.TitleTag:     titleVal,
   109  	}
   110  
   111  	r, err := row.New(types.Format_Default, sch, taggedVals)
   112  
   113  	if err != nil {
   114  		panic(err)
   115  	}
   116  
   117  	return r
   118  }
   119  
   120  func TestRowAsInsertStmt(t *testing.T) {
   121  	id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
   122  	tableName := "people"
   123  
   124  	sch, err := dtestutils.Schema()
   125  	require.NoError(t, err)
   126  
   127  	tests := []test{
   128  		{
   129  			name:           "simple row",
   130  			row:            newRow(sch, id, "some guy", 100, false, strPointer("normie")),
   131  			sch:            sch,
   132  			expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) VALUES ('00000000-0000-0000-0000-000000000000','some guy',100,0,'normie');",
   133  		},
   134  		{
   135  			name:           "embedded quotes",
   136  			row:            newRow(sch, id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie")),
   137  			sch:            sch,
   138  			expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) VALUES ('00000000-0000-0000-0000-000000000000','It\\'s \\\"Mister Perfect\\\" to you',100,0,'normie');",
   139  		},
   140  		{
   141  			name:           "null values",
   142  			row:            newRow(sch, id, "some guy", 100, false, nil),
   143  			sch:            sch,
   144  			expectedOutput: "INSERT INTO `people` (`id`,`name`,`age`,`is_married`,`title`) VALUES ('00000000-0000-0000-0000-000000000000','some guy',100,0,NULL);",
   145  		},
   146  	}
   147  
   148  	trickySch := dtestutils.CreateSchema(
   149  		schema.NewColumn("a name with spaces", 0, types.FloatKind, false),
   150  		schema.NewColumn("anotherColumn", 1, types.IntKind, true),
   151  	)
   152  
   153  	tests = append(tests, test{
   154  		name:           "negative values and columns with spaces",
   155  		row:            dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)),
   156  		sch:            trickySch,
   157  		expectedOutput: "INSERT INTO `people` (`a name with spaces`,`anotherColumn`) VALUES (-3.14,-42);",
   158  	})
   159  
   160  	for _, tt := range tests {
   161  		t.Run(tt.name, func(t *testing.T) {
   162  			stmt, err := sqlfmt.RowAsInsertStmt(tt.row, tableName, tt.sch)
   163  			assert.NoError(t, err)
   164  			assert.Equal(t, tt.expectedOutput, stmt)
   165  		})
   166  	}
   167  }
   168  
   169  func TestRowAsDeleteStmt(t *testing.T) {
   170  	tableName := "tricky"
   171  	trickySch := dtestutils.CreateSchema(
   172  		schema.NewColumn("anotherCol", 0, types.FloatKind, false),
   173  		schema.NewColumn("a name with spaces", 1, types.IntKind, true),
   174  	)
   175  
   176  	tests := []test{
   177  		{
   178  			name:           "negative values and columns with spaces",
   179  			row:            dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)),
   180  			sch:            trickySch,
   181  			expectedOutput: "DELETE FROM `tricky` WHERE (`a name with spaces`=-42);",
   182  		},
   183  	}
   184  
   185  	for _, tt := range tests {
   186  		t.Run(tt.name, func(t *testing.T) {
   187  			stmt, err := sqlfmt.RowAsDeleteStmt(tt.row, tableName, tt.sch)
   188  			assert.NoError(t, err)
   189  			assert.Equal(t, tt.expectedOutput, stmt)
   190  		})
   191  	}
   192  }
   193  
   194  func TestRowAsUpdateStmt(t *testing.T) {
   195  	id := uuid.MustParse("00000000-0000-0000-0000-000000000000")
   196  	tableName := "people"
   197  
   198  	sch, err := dtestutils.Schema()
   199  	require.NoError(t, err)
   200  
   201  	tests := []updateTest{
   202  		{
   203  			name:           "simple row",
   204  			row:            newRow(sch, id, "some guy", 100, false, strPointer("normie")),
   205  			sch:            sch,
   206  			expectedOutput: "UPDATE `people` SET `name`='some guy',`age`=100,`is_married`=0,`title`='normie' WHERE (`id`='00000000-0000-0000-0000-000000000000');",
   207  			collDiff:       set.NewStrSet([]string{"name", "age", "is_married", "title"}),
   208  		},
   209  		{
   210  			name:           "embedded quotes",
   211  			row:            newRow(sch, id, `It's "Mister Perfect" to you`, 100, false, strPointer("normie")),
   212  			sch:            sch,
   213  			expectedOutput: "UPDATE `people` SET `name`='It\\'s \\\"Mister Perfect\\\" to you',`age`=100,`is_married`=0,`title`='normie' WHERE (`id`='00000000-0000-0000-0000-000000000000');",
   214  			collDiff:       set.NewStrSet([]string{"name", "age", "is_married", "title"}),
   215  		},
   216  		{
   217  			name:           "null values",
   218  			row:            newRow(sch, id, "some guy", 100, false, nil),
   219  			sch:            sch,
   220  			expectedOutput: "UPDATE `people` SET `name`='some guy',`age`=100,`is_married`=0,`title`=NULL WHERE (`id`='00000000-0000-0000-0000-000000000000');",
   221  			collDiff:       set.NewStrSet([]string{"name", "age", "is_married", "title"}),
   222  		},
   223  		{
   224  			name:           "partial update",
   225  			row:            newRow(sch, id, "some guy", 100, false, nil),
   226  			sch:            sch,
   227  			expectedOutput: "UPDATE `people` SET `name`='some guy' WHERE (`id`='00000000-0000-0000-0000-000000000000');",
   228  			collDiff:       set.NewStrSet([]string{"name"}),
   229  		},
   230  	}
   231  
   232  	trickySch := dtestutils.CreateSchema(
   233  		schema.NewColumn("a name with spaces", 0, types.FloatKind, false),
   234  		schema.NewColumn("anotherColumn", 1, types.IntKind, true),
   235  	)
   236  
   237  	tests = append(tests, updateTest{
   238  		name:           "negative values and columns with spaces",
   239  		row:            dtestutils.NewRow(trickySch, types.Float(-3.14), types.Int(-42)),
   240  		sch:            trickySch,
   241  		expectedOutput: "UPDATE `people` SET `a name with spaces`=-3.14 WHERE (`anotherColumn`=-42);",
   242  		collDiff:       set.NewStrSet([]string{"a name with spaces"}),
   243  	})
   244  
   245  	for _, tt := range tests {
   246  		t.Run(tt.name, func(t *testing.T) {
   247  			stmt, err := sqlfmt.RowAsUpdateStmt(tt.row, tableName, tt.sch, tt.collDiff)
   248  			assert.NoError(t, err)
   249  			assert.Equal(t, tt.expectedOutput, stmt)
   250  		})
   251  	}
   252  }
   253  
   254  func TestValueAsSqlString(t *testing.T) {
   255  	tu, _ := uuid.Parse("00000000-0000-0000-0000-000000000000")
   256  
   257  	tests := []struct {
   258  		name string
   259  		val  types.Value
   260  		ti   typeinfo.TypeInfo
   261  		exp  string
   262  	}{
   263  		{
   264  			name: "bool(true)",
   265  			val:  types.Bool(true),
   266  			ti:   typeinfo.BoolType,
   267  			exp:  "TRUE",
   268  		},
   269  		{
   270  			name: "bool(false)",
   271  			val:  types.Bool(false),
   272  			ti:   typeinfo.BoolType,
   273  			exp:  "FALSE",
   274  		},
   275  		{
   276  			name: "uuid",
   277  			val:  types.UUID(tu),
   278  			ti:   typeinfo.UuidType,
   279  			exp:  "'00000000-0000-0000-0000-000000000000'",
   280  		},
   281  		{
   282  			name: "string",
   283  			val:  types.String("leviosa"),
   284  			ti:   typeinfo.StringDefaultType,
   285  			exp:  "'leviosa'",
   286  		},
   287  		{
   288  			// borrowed from vitess
   289  			name: "escape string",
   290  			val:  types.String("\x00'\"\b\n\r\t\x1A\\"),
   291  			ti:   typeinfo.StringDefaultType,
   292  			exp:  "'\\0\\'\\\"\\b\\n\\r\\t\\Z\\\\'",
   293  		},
   294  		// using only string and int types as an example, but includes all types
   295  		{
   296  			name: "NULL value for typeinfo.string types",
   297  			val:  nil,
   298  			ti:   typeinfo.StringDefaultType,
   299  			exp:  "NULL",
   300  		},
   301  		{
   302  			name: "NULL value for typeinfo.int types",
   303  			val:  nil,
   304  			ti:   typeinfo.Int64Type,
   305  			exp:  "NULL",
   306  		},
   307  	}
   308  
   309  	for _, test := range tests {
   310  		t.Run(test.name, func(t *testing.T) {
   311  			act, err := sqlfmt.ValueAsSqlString(test.ti, test.val)
   312  			require.NoError(t, err)
   313  			assert.Equal(t, test.exp, act)
   314  		})
   315  	}
   316  }
   317  
   318  func strPointer(s string) *string {
   319  	return &s
   320  }