github.com/matrixorigin/matrixone@v1.2.0/pkg/taskservice/mysql_task_storage_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 taskservice
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"github.com/DATA-DOG/go-sqlmock"
    21  	"github.com/matrixorigin/matrixone/pkg/pb/task"
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  	"slices"
    25  	"strings"
    26  	"testing"
    27  )
    28  
    29  func TestBuildWhereClause(t *testing.T) {
    30  	cases := []struct {
    31  		condition conditions
    32  
    33  		expected string
    34  	}{
    35  		{
    36  			condition: conditions(map[condCode]condition{CondTaskID: &taskIDCond{op: EQ, taskID: 1}}),
    37  			expected:  " AND task_id=1",
    38  		},
    39  		{
    40  			condition: conditions(
    41  				map[condCode]condition{
    42  					CondTaskID:           &taskIDCond{op: EQ, taskID: 1},
    43  					CondTaskRunner:       &taskRunnerCond{op: EQ, taskRunner: "abc"},
    44  					CondTaskStatus:       &taskStatusCond{op: IN, taskStatus: []task.TaskStatus{task.TaskStatus_Created}},
    45  					CondTaskEpoch:        &taskEpochCond{op: LE, taskEpoch: 100},
    46  					CondTaskParentTaskID: &taskParentTaskIDCond{op: GE, taskParentTaskID: "ab"},
    47  					CondTaskExecutor:     &taskExecutorCond{op: GE, taskExecutor: 1},
    48  				},
    49  			),
    50  			expected: " AND task_id=1 AND task_runner='abc' AND task_status IN (0) AND task_epoch<=100 AND task_parent_id>='ab' AND task_metadata_executor>=1",
    51  		},
    52  		{
    53  			condition: conditions(map[condCode]condition{
    54  				CondTaskRunner:       &taskRunnerCond{op: EQ, taskRunner: "abc"},
    55  				CondTaskStatus:       &taskStatusCond{op: IN, taskStatus: []task.TaskStatus{task.TaskStatus_Created}},
    56  				CondTaskParentTaskID: &taskParentTaskIDCond{op: GE, taskParentTaskID: "ab"},
    57  				CondTaskExecutor:     &taskExecutorCond{op: LE, taskExecutor: 1},
    58  			},
    59  			),
    60  			expected: " AND task_runner='abc' AND task_status IN (0) AND task_parent_id>='ab' AND task_metadata_executor<=1",
    61  		},
    62  	}
    63  
    64  	for _, c := range cases {
    65  		result := buildWhereClause(&c.condition)
    66  		actual := strings.Split(result, " AND ")
    67  		expected := strings.Split(c.expected, " AND ")
    68  		slices.Sort(actual)
    69  		slices.Sort(expected)
    70  		assert.Equal(t, expected, actual)
    71  	}
    72  }
    73  
    74  const (
    75  	useDB    = "use mo_task"
    76  	setTrace = "set session disable_txn_trace=1"
    77  )
    78  
    79  var (
    80  	asyncRows = []string{
    81  		"task_id",
    82  		"task_metadata_id",
    83  		"task_metadata_executor",
    84  		"task_metadata_context",
    85  		"task_metadata_option",
    86  		"task_parent_id",
    87  		"task_status",
    88  		"task_runner",
    89  		"task_epoch",
    90  		"last_heartbeat",
    91  		"result_code",
    92  		"error_msg",
    93  		"create_at",
    94  		"end_at"}
    95  
    96  	cronRows = []string{
    97  		"task_id",
    98  		"task_metadata_id",
    99  		"task_metadata_executor",
   100  		"task_metadata_context",
   101  		"task_metadata_option",
   102  		"cron_expr",
   103  		"next_time",
   104  		"trigger_times",
   105  		"create_at",
   106  		"update_at",
   107  	}
   108  
   109  	expectUseDB = func(mock sqlmock.Sqlmock) {
   110  		mock.ExpectExec(useDB).WillReturnResult(sqlmock.NewResult(0, 1))
   111  		mock.ExpectExec(setTrace).WillReturnResult(sqlmock.NewResult(0, 1))
   112  	}
   113  )
   114  
   115  func TestAsyncTaskInSqlMock(t *testing.T) {
   116  	storage, mock := newMockStorage(t, "sqlmock", "mo_task")
   117  	expectUseDB(mock)
   118  	mock.ExpectPrepare(fmt.Sprintf(insertAsyncTask, storage.dbname) + "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
   119  	mock.ExpectExec(fmt.Sprintf(insertAsyncTask, storage.dbname)+"(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)").
   120  		WithArgs("a", 0, []byte(nil), "{}", "", 0, "", 0, 0, sqlmock.AnyArg(), 0).
   121  		WillReturnResult(sqlmock.NewResult(1, 1))
   122  
   123  	affected, err := storage.AddAsyncTask(context.Background(), newTaskFromMetadata(task.TaskMetadata{ID: "a"}))
   124  	assert.NoError(t, err)
   125  	assert.Equal(t, 1, affected)
   126  
   127  	expectUseDB(mock)
   128  	mock.ExpectQuery(fmt.Sprintf(selectAsyncTask, storage.dbname) + " AND task_id=1 order by task_id").
   129  		WillReturnRows(sqlmock.NewRows(asyncRows).
   130  			AddRow(1, "a", 0, []byte(nil), "{}", "", 0, "", 0, 0, 0, "", 0, 0))
   131  	asyncTask, err := storage.QueryAsyncTask(context.Background(), WithTaskIDCond(EQ, 1))
   132  	assert.NoError(t, err)
   133  	assert.Equal(t, 1, len(asyncTask))
   134  	assert.Equal(t, "a", asyncTask[0].Metadata.ID)
   135  
   136  	expectUseDB(mock)
   137  	mock.ExpectBegin()
   138  	mock.ExpectPrepare(fmt.Sprintf(updateAsyncTask, storage.dbname) + " AND task_epoch=0")
   139  	mock.ExpectExec(fmt.Sprintf(updateAsyncTask, storage.dbname)+" AND task_epoch=0").
   140  		WithArgs(0, []byte(nil), "{}", "", 0, "c1", 0, 0, 0, "", 0, 0, 1).
   141  		WillReturnResult(sqlmock.NewResult(0, 1))
   142  	mock.ExpectCommit()
   143  	asyncTask[0].TaskRunner = "c1"
   144  	affected, err = storage.UpdateAsyncTask(context.Background(), []task.AsyncTask{asyncTask[0]}, WithTaskEpochCond(EQ, 0))
   145  	assert.NoError(t, err)
   146  	assert.Equal(t, 1, affected)
   147  
   148  	mock.ExpectClose()
   149  	require.NoError(t, storage.Close())
   150  }
   151  
   152  func TestCronTaskInSqlMock(t *testing.T) {
   153  	storage, mock := newMockStorage(t, "sqlmock", "mo_task")
   154  	expectUseDB(mock)
   155  	mock.ExpectPrepare(fmt.Sprintf(insertCronTask, storage.dbname) + "(?, ?, ?, ?, ?, ?, ?, ?, ?)")
   156  	mock.ExpectExec(fmt.Sprintf(insertCronTask, storage.dbname)+"(?, ?, ?, ?, ?, ?, ?, ?, ?)").
   157  		WithArgs("a", 0, []byte(nil), "{}", "mock_cron_expr", sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg()).
   158  		WillReturnResult(sqlmock.NewResult(1, 1))
   159  
   160  	affected, err := storage.AddCronTask(context.Background(), newTestCronTask("a", "mock_cron_expr"))
   161  	assert.NoError(t, err)
   162  	assert.Equal(t, 1, affected)
   163  
   164  	expectUseDB(mock)
   165  	mock.ExpectQuery(fmt.Sprintf(selectCronTask, storage.dbname) + " AND cron_task_id=1").
   166  		WillReturnRows(sqlmock.NewRows(cronRows).
   167  			AddRow(1, "a", 0, []byte(nil), "{}", "mock_cron_expr", 0, 0, 0, 0))
   168  	cronTask, err := storage.QueryCronTask(context.Background(), WithCronTaskId(EQ, 1))
   169  	assert.NoError(t, err)
   170  	assert.Equal(t, 1, len(cronTask))
   171  	assert.Equal(t, "a", cronTask[0].Metadata.ID)
   172  
   173  	mock.ExpectClose()
   174  	require.NoError(t, storage.Close())
   175  }
   176  
   177  func newMockStorage(t *testing.T, dsn, dbname string) (*mysqlTaskStorage, sqlmock.Sqlmock) {
   178  	db, mock, err := sqlmock.NewWithDSN(dsn, sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
   179  	require.NoError(t, err)
   180  	return &mysqlTaskStorage{
   181  		dsn:    dsn,
   182  		db:     db,
   183  		dbname: dbname,
   184  	}, mock
   185  }