// Copyright 2023 PingCAP, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package importer import ( "fmt" "math" "runtime" "testing" "github.com/pingcap/errors" berrors "github.com/pingcap/tidb/br/pkg/errors" "github.com/pingcap/tidb/br/pkg/lightning/config" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/parser" "github.com/pingcap/tidb/parser/ast" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/util/dbterror/exeerrors" "github.com/pingcap/tidb/util/mock" "github.com/stretchr/testify/require" ) func TestInitDefaultOptions(t *testing.T) { ignoreInTest = true t.Cleanup(func() { ignoreInTest = false }) e := LoadDataController{} e.initDefaultOptions() require.Equal(t, LogicalImportMode, e.ImportMode) require.Equal(t, config.ByteSize(50<<30), e.diskQuota) require.Equal(t, config.OpLevelRequired, e.checksum) require.Equal(t, true, e.addIndex) require.Equal(t, config.OpLevelOptional, e.analyze) require.Equal(t, int64(runtime.NumCPU()), e.ThreadCnt) require.Equal(t, int64(1000), e.BatchSize) require.Equal(t, unlimitedWriteSpeed, e.maxWriteSpeed) require.Equal(t, false, e.splitFile) require.Equal(t, int64(100), e.maxRecordedErrors) require.Equal(t, false, e.Detached) e = LoadDataController{Format: LoadDataFormatParquet} e.initDefaultOptions() require.Greater(t, e.ThreadCnt, int64(0)) require.Equal(t, int64(math.Max(1, float64(runtime.NumCPU())*0.75)), e.ThreadCnt) } func TestInitOptions(t *testing.T) { cases := []struct { OptionStr string Err error }{ {OptionStr: "xx=1", Err: exeerrors.ErrUnknownOption}, {OptionStr: detachedOption + "=1", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: addIndexOption, Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: detachedOption + ", " + detachedOption, Err: exeerrors.ErrDuplicateOption}, {OptionStr: importModeOption + "='logical', " + diskQuotaOption + "='100GiB'", Err: exeerrors.ErrLoadDataUnsupportedOption}, {OptionStr: importModeOption + "='logical', " + checksumOption + "='optional'", Err: exeerrors.ErrLoadDataUnsupportedOption}, {OptionStr: importModeOption + "='logical', " + addIndexOption + "=false", Err: exeerrors.ErrLoadDataUnsupportedOption}, {OptionStr: importModeOption + "='logical', " + analyzeOption + "='optional'", Err: exeerrors.ErrLoadDataUnsupportedOption}, {OptionStr: importModeOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "=1", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "=false", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + diskQuotaOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + diskQuotaOption + "='220MiBxxx'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + diskQuotaOption + "=false", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + diskQuotaOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + checksumOption + "=''", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + checksumOption + "=123", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + checksumOption + "=false", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + checksumOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + addIndexOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + addIndexOption + "=123", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + addIndexOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + analyzeOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + analyzeOption + "=123", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + analyzeOption + "=false", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: importModeOption + "='physical', " + analyzeOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: threadOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: threadOption + "=0", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: threadOption + "=false", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: threadOption + "=-100", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: threadOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: batchSizeOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: batchSizeOption + "='11aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: batchSizeOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: maxWriteSpeedOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: maxWriteSpeedOption + "='11aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: maxWriteSpeedOption + "=false", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: maxWriteSpeedOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: splitFileOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: splitFileOption + "=111", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: splitFileOption + "='false'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: splitFileOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: recordErrorsOption + "='aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: recordErrorsOption + "='111aa'", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: recordErrorsOption + "=-123", Err: exeerrors.ErrInvalidOptionVal}, {OptionStr: recordErrorsOption + "=null", Err: exeerrors.ErrInvalidOptionVal}, } ctx := mock.NewContext() defer ctx.Close() convertOptions := func(inOptions []*ast.LoadDataOpt) []*plannercore.LoadDataOpt { options := []*plannercore.LoadDataOpt{} var err error for _, opt := range inOptions { loadDataOpt := plannercore.LoadDataOpt{Name: opt.Name} if opt.Value != nil { loadDataOpt.Value, err = expression.RewriteSimpleExprWithNames(ctx, opt.Value, nil, nil) require.NoError(t, err) } options = append(options, &loadDataOpt) } return options } sqlTemplate := "load data infile '/xx' into table t with %s" p := parser.New() for _, c := range cases { sql := fmt.Sprintf(sqlTemplate, c.OptionStr) stmt, err2 := p.ParseOneStmt(sql, "", "") require.NoError(t, err2, sql) e := LoadDataController{} err := e.initOptions(ctx, convertOptions(stmt.(*ast.LoadDataStmt).Options)) require.ErrorIs(t, err, c.Err, sql) } e := LoadDataController{} sql := fmt.Sprintf(sqlTemplate, importModeOption+"='physical', "+ diskQuotaOption+"='100gib', "+ checksumOption+"='optional', "+ addIndexOption+"=false, "+ analyzeOption+"='required', "+ threadOption+"='100000', "+ batchSizeOption+"=2000, "+ maxWriteSpeedOption+"='200mib', "+ splitFileOption+"=true, "+ recordErrorsOption+"=123, "+ detachedOption) stmt, err := p.ParseOneStmt(sql, "", "") require.NoError(t, err, sql) err = e.initOptions(ctx, convertOptions(stmt.(*ast.LoadDataStmt).Options)) require.NoError(t, err, sql) require.Equal(t, PhysicalImportMode, e.ImportMode, sql) require.Equal(t, config.ByteSize(100<<30), e.diskQuota, sql) require.Equal(t, config.OpLevelOptional, e.checksum, sql) require.False(t, e.addIndex, sql) require.Equal(t, config.OpLevelRequired, e.analyze, sql) require.Equal(t, int64(runtime.NumCPU()), e.ThreadCnt, sql) require.Equal(t, int64(2000), e.BatchSize, sql) require.Equal(t, config.ByteSize(200<<20), e.maxWriteSpeed, sql) require.True(t, e.splitFile, sql) require.Equal(t, int64(123), e.maxRecordedErrors, sql) require.True(t, e.Detached, sql) } func TestAdjustOptions(t *testing.T) { e := LoadDataController{ diskQuota: 1, ThreadCnt: 100000000, maxWriteSpeed: 10, } e.adjustOptions() require.Equal(t, minDiskQuota, e.diskQuota) require.Equal(t, int64(runtime.NumCPU()), e.ThreadCnt) require.Equal(t, minWriteSpeed, e.maxWriteSpeed) } func TestGetMsgFromBRError(t *testing.T) { var berr error = berrors.ErrStorageInvalidConfig require.Equal(t, "[BR:ExternalStorage:ErrStorageInvalidConfig]invalid external storage config", berr.Error()) require.Equal(t, "invalid external storage config", GetMsgFromBRError(berr)) berr = errors.Annotatef(berr, "some message about error reason") require.Equal(t, "some message about error reason: [BR:ExternalStorage:ErrStorageInvalidConfig]invalid external storage config", berr.Error()) require.Equal(t, "some message about error reason", GetMsgFromBRError(berr)) }