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  }