github.com/matrixorigin/matrixone@v1.2.0/pkg/util/export/table/table_test.go (about) 1 // Copyright 2022 Matrix Origin 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 table 16 17 import ( 18 "context" 19 "errors" 20 "testing" 21 "time" 22 23 "github.com/google/uuid" 24 "github.com/matrixorigin/matrixone/pkg/common/moerr" 25 "github.com/matrixorigin/matrixone/pkg/common/util" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 ) 29 30 func init() { 31 time.Local = time.FixedZone("CST", 0) // set time-zone +0000 32 } 33 34 func TestNoopTableOptions_FormatDdl(t *testing.T) { 35 type args struct { 36 ddl string 37 } 38 tests := []struct { 39 name string 40 args args 41 wantDdl string 42 wantCreateOpt string 43 wantTableOpt string 44 }{ 45 { 46 name: "normal", 47 args: args{ 48 ddl: "create table ...", 49 }, 50 wantDdl: "create table ...", 51 wantCreateOpt: "", 52 wantTableOpt: "", 53 }, 54 } 55 for _, tt := range tests { 56 t.Run(tt.name, func(t *testing.T) { 57 o := NoopTableOptions{} 58 assert.Equalf(t, tt.wantDdl, o.FormatDdl(tt.args.ddl), "FormatDdl(%v)", tt.args.ddl) 59 assert.Equalf(t, tt.wantCreateOpt, o.GetCreateOptions(), "GetCreateOptions()") 60 assert.Equalf(t, tt.wantTableOpt, o.GetTableOptions(nil), "GetTableOptions()") 61 }) 62 } 63 } 64 65 var dummyStrColumn = Column{Name: "str", ColType: TVarchar, Scale: 32, Default: "", Comment: "str column"} 66 var dummyStrCreateSql = "`str` VARCHAR(32) NOT NULL COMMENT \"str column\"" 67 var dummyInt64Column = Column{Name: "int64", ColType: TInt64, Default: "0", Comment: "int64 column"} 68 var dummyInt64CreateSql = "`int64` BIGINT DEFAULT \"0\" COMMENT \"int64 column\"" 69 var dummyFloat64Column = Column{Name: "float64", ColType: TFloat64, Default: "0.0", Comment: "float64 column"} 70 var dummyFloat64CreateSql = "`float64` DOUBLE DEFAULT \"0.0\" COMMENT \"float64 column\"" 71 72 var dummyTable = &Table{ 73 Account: "test", 74 Database: "db_dummy", 75 Table: "tbl_dummy", 76 Columns: []Column{dummyStrColumn, dummyInt64Column, dummyFloat64Column}, 77 PrimaryKeyColumn: []Column{dummyStrColumn, dummyInt64Column}, 78 Engine: ExternalTableEngine, 79 Comment: "dummy table", 80 PathBuilder: NewAccountDatePathBuilder(), 81 TableOptions: nil, 82 } 83 84 var dummyTableCreateExistsSql = "CREATE EXTERNAL TABLE IF NOT EXISTS `db_dummy`.`tbl_dummy`(" + 85 "\n" + dummyStrCreateSql + 86 ",\n" + dummyInt64CreateSql + 87 ",\n" + dummyFloat64CreateSql + 88 "\n) " + `infile{"filepath"="etl:/test/*/*/*/*/tbl_dummy/*","compression"="none"} FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 0 lines` 89 var dummyTableCreateSql = "CREATE EXTERNAL TABLE `db_dummy`.`tbl_dummy`(" + 90 "\n" + dummyStrCreateSql + 91 ",\n" + dummyInt64CreateSql + 92 ",\n" + dummyFloat64CreateSql + 93 "\n) " + `infile{"filepath"="etl:/test/*/*/*/*/tbl_dummy/*","compression"="none"} FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 0 lines` 94 95 type dummyCondition struct{} 96 97 func (c dummyCondition) String() string { 98 return "`str` = \"NIL\"" 99 } 100 101 var dummyView = &View{ 102 Database: dummyTable.Database, 103 Table: "view", 104 OriginTable: dummyTable, 105 Columns: []Column{}, 106 Condition: dummyCondition{}, 107 } 108 var dummyViewCreateSql = "CREATE VIEW IF NOT EXISTS `db_dummy`.`view` as select `str`, mo_log_date(`__mo_filepath`) as `log_date`, `__mo_filepath` from `db_dummy`.`tbl_dummy` where `str` = \"NIL\"" 109 110 func TestRow_SetFloat64(t *testing.T) { 111 type fields struct { 112 Table *Table 113 } 114 type args struct { 115 col Column 116 val float64 117 } 118 tests := []struct { 119 name string 120 fields fields 121 args args 122 }{ 123 { 124 name: "normal", 125 fields: fields{ 126 Table: dummyTable, 127 }, 128 args: args{ 129 col: dummyFloat64Column, 130 val: 1.1, 131 }, 132 }, 133 } 134 for _, tt := range tests { 135 t.Run(tt.name, func(t *testing.T) { 136 r := tt.fields.Table.GetRow(context.TODO()) 137 defer r.Free() 138 r.SetColumnVal(tt.args.col, Float64Field(tt.args.val)) 139 }) 140 } 141 } 142 143 func TestRow_ToStrings(t *testing.T) { 144 type fields struct { 145 Table *Table 146 prepare func(*Row) 147 } 148 tests := []struct { 149 name string 150 fields fields 151 want []string 152 }{ 153 { 154 name: "nil", 155 fields: fields{Table: dummyTable, prepare: func(r *Row) { r.Reset() }}, 156 want: []string{"", "0", "0"}, 157 }, 158 { 159 name: "nil", 160 fields: fields{Table: dummyTable, 161 prepare: func(r *Row) { 162 r.SetColumnVal(dummyStrColumn, StringField("0")) 163 r.SetColumnVal(dummyFloat64Column, Float64Field(1.1234567)) 164 r.SetColumnVal(dummyInt64Column, Int64Field(1)) 165 }}, 166 want: []string{"0", "1", "1.1234567"}, 167 }, 168 { 169 name: "json", 170 fields: fields{ 171 Table: &Table{ 172 Columns: []Column{ 173 JsonColumn("json1", ""), 174 JsonColumn("json2", ""), 175 JsonColumn("json3", ""), 176 }, 177 }, 178 prepare: func(r *Row) { 179 r.SetColumnVal(JsonColumn("json1", ""), StringField(`{"key":"str"}`)) 180 r.SetColumnVal(JsonColumn("json2", ""), JsonField(`{"key":"json"}`)) 181 r.SetColumnVal(JsonColumn("json3", ""), BytesField([]byte(`{"key":"byte"}`))) 182 }}, 183 want: []string{`{"key":"str"}`, `{"key":"json"}`, `{"key":"byte"}`}, 184 }, 185 } 186 for _, tt := range tests { 187 t.Run(tt.name, func(t *testing.T) { 188 r := tt.fields.Table.GetRow(context.TODO()) 189 defer r.Free() 190 tt.fields.prepare(r) 191 assert.Equalf(t, tt.want, r.ToStrings(), "ToStrings()") 192 }) 193 } 194 } 195 196 func TestTable_GetName(t *testing.T) { 197 type fields struct { 198 Table *Table 199 } 200 tests := []struct { 201 name string 202 fields fields 203 want string 204 }{ 205 { 206 name: "dummy", 207 fields: fields{Table: dummyTable}, 208 want: dummyTable.Table, 209 }, 210 } 211 for _, tt := range tests { 212 t.Run(tt.name, func(t *testing.T) { 213 assert.Equalf(t, tt.want, tt.fields.Table.GetName(), "GetName()") 214 }) 215 } 216 } 217 218 func TestTable_ToCreateSql(t *testing.T) { 219 type fields struct { 220 Table *Table 221 } 222 type args struct { 223 ifNotExists bool 224 } 225 tests := []struct { 226 name string 227 fields fields 228 args args 229 want string 230 }{ 231 { 232 name: "dummy", 233 fields: fields{Table: dummyTable}, 234 args: args{ifNotExists: false}, 235 want: dummyTableCreateSql, 236 }, 237 { 238 name: "dummy_if_not_exist", 239 fields: fields{Table: dummyTable}, 240 args: args{ifNotExists: true}, 241 want: dummyTableCreateExistsSql, 242 }, 243 } 244 ctx := context.Background() 245 for _, tt := range tests { 246 t.Run(tt.name, func(t *testing.T) { 247 tbl := tt.fields.Table 248 got := tbl.ToCreateSql(ctx, tt.args.ifNotExists) 249 t.Logf("create sql: %s", got) 250 require.Equalf(t, tt.want, got, "ToCreateSql(%v)", tt.args.ifNotExists) 251 }) 252 } 253 } 254 func TestViewOption_Apply(t *testing.T) { 255 type args struct { 256 view *View 257 } 258 tests := []struct { 259 name string 260 opt ViewOption 261 args args 262 wantCreate string 263 }{ 264 { 265 name: "normal", 266 opt: WithColumn(dummyStrColumn), 267 args: args{view: dummyView}, 268 wantCreate: dummyViewCreateSql, 269 }, 270 } 271 for _, tt := range tests { 272 t.Run(tt.name, func(t *testing.T) { 273 tt.opt.Apply(tt.args.view) 274 got := tt.args.view.ToCreateSql(context.TODO(), true) 275 assert.Equalf(t, tt.wantCreate, got, "ToCreateSql(%v)", true) 276 }) 277 } 278 } 279 280 func TestTable_GetTableOptions(t *testing.T) { 281 type fields struct { 282 Account string 283 Database string 284 Table string 285 Columns []Column 286 PrimaryKeyColumn []Column 287 Engine string 288 Comment string 289 PathBuilder PathBuilder 290 AccountColumn *Column 291 TableOptions TableOptions 292 SupportUserAccess bool 293 } 294 tests := []struct { 295 name string 296 fields fields 297 want TableOptions 298 }{ 299 // TODO: Add test cases. 300 } 301 for _, tt := range tests { 302 t.Run(tt.name, func(t *testing.T) { 303 tbl := &Table{ 304 Account: tt.fields.Account, 305 Database: tt.fields.Database, 306 Table: tt.fields.Table, 307 Columns: tt.fields.Columns, 308 PrimaryKeyColumn: tt.fields.PrimaryKeyColumn, 309 Engine: tt.fields.Engine, 310 Comment: tt.fields.Comment, 311 PathBuilder: tt.fields.PathBuilder, 312 AccountColumn: tt.fields.AccountColumn, 313 TableOptions: tt.fields.TableOptions, 314 SupportUserAccess: tt.fields.SupportUserAccess, 315 } 316 assert.Equalf(t, tt.want, tbl.GetTableOptions(context.TODO()), "GetTableOptions()") 317 }) 318 } 319 } 320 321 func TestSetPathBuilder(t *testing.T) { 322 var err error 323 ctx := context.Background() 324 err = SetPathBuilder(ctx, (*DBTablePathBuilder)(nil).GetName()) 325 require.Nil(t, err) 326 err = SetPathBuilder(ctx, "AccountDate") 327 require.Nil(t, err) 328 err = SetPathBuilder(ctx, "unknown") 329 var moErr *moerr.Error 330 if errors.As(err, &moErr) && moerr.IsMoErrCode(moErr, moerr.ErrNotSupported) { 331 t.Logf("got ErrNotSupported normally") 332 } else { 333 t.Errorf("unexpect err: %v", err) 334 } 335 } 336 337 func TestColumnField_EncodedDatetime(t *testing.T) { 338 type fields struct { 339 cf ColumnField 340 } 341 tests := []struct { 342 name string 343 fields fields 344 wantT time.Time 345 want string 346 }{ 347 { 348 name: "zero", 349 fields: fields{ 350 cf: TimeField(ZeroTime), 351 }, 352 wantT: ZeroTime, 353 want: "0001-01-01 00:00:00.000000", 354 }, 355 { 356 name: "empty", 357 fields: fields{ 358 cf: TimeField(time.Time{}), 359 }, 360 wantT: time.Time{}, 361 want: "0001-01-01 00:00:00.000000", 362 }, 363 { 364 name: "Unix_Zero", 365 fields: fields{ 366 cf: TimeField(time.Unix(0, 0)), 367 }, 368 wantT: time.Unix(0, 0), 369 want: "1970-01-01 00:00:00.000000", 370 }, 371 } 372 for _, tt := range tests { 373 t.Run(tt.name, func(t *testing.T) { 374 buf := tt.fields.cf.GetTime() 375 require.Equal(t, tt.wantT, buf) 376 var bbuf [64]byte 377 dst := tt.fields.cf.EncodedDatetime(bbuf[:0]) 378 require.Equal(t, tt.want, string(dst)) 379 }) 380 } 381 } 382 383 func TestTimeField(t *testing.T) { 384 type args struct { 385 val time.Time 386 } 387 tests := []struct { 388 name string 389 args args 390 want time.Time 391 }{ 392 { 393 name: "empty", 394 args: args{}, 395 want: time.Time{}, 396 }, 397 } 398 for _, tt := range tests { 399 t.Run(tt.name, func(t *testing.T) { 400 f := TimeField(tt.args.val) 401 assert.Equalf(t, tt.want, f.GetTime(), "TimeField(%v)", tt.args.val) 402 }) 403 } 404 } 405 406 // BenchmarkTimeField 407 // 408 // goos: darwin 409 // goarch: arm64 410 // pkg: github.com/matrixorigin/matrixone/pkg/util/export/table 411 // BenchmarkTimeField 412 // BenchmarkTimeField/uuid.string 413 // BenchmarkTimeField/uuid.string-10 31779484 39.32 ns/op 414 // BenchmarkTimeField/EncodeUUIDHex 415 // BenchmarkTimeField/EncodeUUIDHex-10 33151078 37.44 ns/op 416 // PASS 417 func BenchmarkTimeField(b *testing.B) { 418 type args struct { 419 val [16]byte 420 } 421 benchmarks := []struct { 422 name string 423 args args 424 op func(val [16]byte) 425 }{ 426 { 427 name: "uuid.string", 428 args: args{ 429 val: uuid.New(), 430 }, 431 op: func(val [16]byte) { 432 _ = uuid.UUID(val).String() 433 }, 434 }, 435 { 436 name: "EncodeUUIDHex", 437 args: args{ 438 val: uuid.New(), 439 }, 440 op: func(val [16]byte) { 441 var bytes [36]byte 442 util.EncodeUUIDHex(bytes[:], val[:]) 443 _ = string(bytes[:]) 444 }, 445 }, 446 } 447 for _, bm := range benchmarks { 448 b.Run(bm.name, func(b *testing.B) { 449 b.ResetTimer() 450 for i := 0; i < b.N; i++ { 451 bm.op(bm.args.val) 452 } 453 }) 454 } 455 }