github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/engine/pkg/orm/client.go (about)

     1  // Copyright 2022 PingCAP, 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  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package orm
    15  
    16  import (
    17  	"context"
    18  	"database/sql"
    19  	"time"
    20  
    21  	frameModel "github.com/pingcap/tiflow/engine/framework/model"
    22  	engineModel "github.com/pingcap/tiflow/engine/model"
    23  	resModel "github.com/pingcap/tiflow/engine/pkg/externalresource/model"
    24  	metaModel "github.com/pingcap/tiflow/engine/pkg/meta/model"
    25  	"github.com/pingcap/tiflow/engine/pkg/orm/model"
    26  	"github.com/pingcap/tiflow/pkg/errors"
    27  	"gorm.io/gorm"
    28  	"gorm.io/gorm/clause"
    29  )
    30  
    31  var globalModels = []interface{}{
    32  	&model.ProjectInfo{},
    33  	&model.ProjectOperation{},
    34  	&frameModel.MasterMeta{},
    35  	&frameModel.WorkerStatus{},
    36  	&resModel.ResourceMeta{},
    37  	&model.LogicEpoch{},
    38  	&model.JobOp{},
    39  	&model.Executor{},
    40  }
    41  
    42  // TODO: retry and idempotent??
    43  // TODO: split different client to module
    44  
    45  type (
    46  	// ResourceMeta is the alias of resModel.ResourceMeta
    47  	ResourceMeta = resModel.ResourceMeta
    48  	// ResourceKey is the alias of resModel.ResourceKey
    49  	ResourceKey = resModel.ResourceKey
    50  )
    51  
    52  // TimeRange defines a time range with [start, end] time
    53  type TimeRange struct {
    54  	start time.Time
    55  	end   time.Time
    56  }
    57  
    58  // Client defines an interface that has the ability to manage every kind of
    59  // logic abstraction in metastore, including project, project op, job, worker
    60  // and resource
    61  type Client interface {
    62  	metaModel.Client
    63  	// ProjectClient is the interface to operate project.
    64  	ProjectClient
    65  	// ProjectOperationClient is the client to operate project operation.
    66  	ProjectOperationClient
    67  	// JobClient is the interface to operate job info.
    68  	JobClient
    69  	// WorkerClient is the client to operate worker info.
    70  	WorkerClient
    71  	// ResourceClient is the interface to operate resource.
    72  	ResourceClient
    73  	// JobOpClient is the client to operate job operation.
    74  	JobOpClient
    75  	// ExecutorClient is the client to operate executor info.
    76  	ExecutorClient
    77  }
    78  
    79  // ProjectClient defines interface that manages project in metastore
    80  type ProjectClient interface {
    81  	CreateProject(ctx context.Context, project *model.ProjectInfo) error
    82  	DeleteProject(ctx context.Context, projectID string) error
    83  	QueryProjects(ctx context.Context) ([]*model.ProjectInfo, error)
    84  	GetProjectByID(ctx context.Context, projectID string) (*model.ProjectInfo, error)
    85  }
    86  
    87  // ProjectOperationClient defines interface that manages project operation in metastore
    88  // TODO: support pagination and cursor here
    89  // support `order by time desc limit N`
    90  type ProjectOperationClient interface {
    91  	CreateProjectOperation(ctx context.Context, op *model.ProjectOperation) error
    92  	QueryProjectOperations(ctx context.Context, projectID string) ([]*model.ProjectOperation, error)
    93  	QueryProjectOperationsByTimeRange(ctx context.Context, projectID string, tr TimeRange) ([]*model.ProjectOperation, error)
    94  }
    95  
    96  // JobClient defines interface that manages job in metastore
    97  type JobClient interface {
    98  	InsertJob(ctx context.Context, job *frameModel.MasterMeta) error
    99  	UpsertJob(ctx context.Context, job *frameModel.MasterMeta) error
   100  	UpdateJob(ctx context.Context, jobID string, values model.KeyValueMap) error
   101  	DeleteJob(ctx context.Context, jobID string) (Result, error)
   102  
   103  	GetJobByID(ctx context.Context, jobID string) (*frameModel.MasterMeta, error)
   104  	QueryJobs(ctx context.Context) ([]*frameModel.MasterMeta, error)
   105  	QueryJobsByProjectID(ctx context.Context, projectID string) ([]*frameModel.MasterMeta, error)
   106  	QueryJobsByState(ctx context.Context, jobID string, state int) ([]*frameModel.MasterMeta, error)
   107  }
   108  
   109  // WorkerClient defines interface that manages worker in metastore
   110  type WorkerClient interface {
   111  	UpsertWorker(ctx context.Context, worker *frameModel.WorkerStatus) error
   112  	UpdateWorker(ctx context.Context, worker *frameModel.WorkerStatus) error
   113  	DeleteWorker(ctx context.Context, masterID string, workerID string) (Result, error)
   114  	GetWorkerByID(ctx context.Context, masterID string, workerID string) (*frameModel.WorkerStatus, error)
   115  	QueryWorkersByMasterID(ctx context.Context, masterID string) ([]*frameModel.WorkerStatus, error)
   116  	QueryWorkersByState(ctx context.Context, masterID string, state int) ([]*frameModel.WorkerStatus, error)
   117  }
   118  
   119  // ResourceClient defines interface that manages resource in metastore
   120  type ResourceClient interface {
   121  	CreateResource(ctx context.Context, resource *ResourceMeta) error
   122  	UpsertResource(ctx context.Context, resource *ResourceMeta) error
   123  	UpdateResource(ctx context.Context, resource *ResourceMeta) error
   124  
   125  	GetResourceByID(ctx context.Context, resourceKey ResourceKey) (*ResourceMeta, error)
   126  	QueryResources(ctx context.Context) ([]*ResourceMeta, error)
   127  	QueryResourcesByJobID(ctx context.Context, jobID string) ([]*ResourceMeta, error)
   128  	QueryResourcesByExecutorIDs(ctx context.Context,
   129  		executorID ...engineModel.ExecutorID) ([]*ResourceMeta, error)
   130  
   131  	SetGCPendingByJobs(ctx context.Context, jobIDs ...engineModel.JobID) error
   132  	GetOneResourceForGC(ctx context.Context) (*ResourceMeta, error)
   133  
   134  	DeleteResource(ctx context.Context, resourceKey ResourceKey) (Result, error)
   135  	DeleteResourcesByTypeAndExecutorIDs(ctx context.Context,
   136  		resType resModel.ResourceType, executorID ...engineModel.ExecutorID) (Result, error)
   137  }
   138  
   139  // JobOpClient defines interface that operates job status (upper logic oriented)
   140  type JobOpClient interface {
   141  	SetJobNoop(ctx context.Context, jobID string) (Result, error)
   142  	SetJobCanceling(ctx context.Context, JobID string) (Result, error)
   143  	SetJobCanceled(ctx context.Context, jobID string) (Result, error)
   144  	QueryJobOp(ctx context.Context, jobID string) (*model.JobOp, error)
   145  	QueryJobOpsByStatus(ctx context.Context, op model.JobOpStatus) ([]*model.JobOp, error)
   146  }
   147  
   148  // ExecutorClient defines interface that manages executor information in metastore.
   149  type ExecutorClient interface {
   150  	CreateExecutor(ctx context.Context, executor *model.Executor) error
   151  	UpdateExecutor(ctx context.Context, executor *model.Executor) error
   152  	DeleteExecutor(ctx context.Context, executorID engineModel.ExecutorID) error
   153  	QueryExecutors(ctx context.Context) ([]*model.Executor, error)
   154  }
   155  
   156  // NewClient return the client to operate framework metastore
   157  func NewClient(cc metaModel.ClientConn) (Client, error) {
   158  	if cc == nil {
   159  		return nil, errors.ErrMetaParamsInvalid.GenWithStackByArgs("input client conn is nil")
   160  	}
   161  
   162  	conn, err := cc.GetConn()
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	sqlDB, ok := conn.(*sql.DB)
   168  	if !ok {
   169  		return nil, errors.ErrMetaParamsInvalid.GenWithStack("input client conn is not a sql type:%s",
   170  			cc.StoreType())
   171  	}
   172  
   173  	return newClient(sqlDB, cc.StoreType())
   174  }
   175  
   176  func newClient(db *sql.DB, storeType metaModel.StoreType) (Client, error) {
   177  	ormDB, err := NewGormDB(db, storeType)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	epCli, err := model.NewEpochClient("" /*jobID*/, ormDB)
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  
   187  	return &metaOpsClient{
   188  		db:          ormDB,
   189  		epochClient: epCli,
   190  	}, nil
   191  }
   192  
   193  // metaOpsClient is the meta operations client for framework metastore
   194  type metaOpsClient struct {
   195  	// gorm claim to be thread safe
   196  	db          *gorm.DB
   197  	epochClient model.EpochClient
   198  }
   199  
   200  func (c *metaOpsClient) Close() error {
   201  	// DON NOT CLOSE the underlying connection
   202  	return nil
   203  }
   204  
   205  // ///////////////////////////// Logic Epoch
   206  func (c *metaOpsClient) GenEpoch(ctx context.Context) (frameModel.Epoch, error) {
   207  	return c.epochClient.GenEpoch(ctx)
   208  }
   209  
   210  // /////////////////////// Project Operation
   211  // CreateProject insert the model.ProjectInfo
   212  func (c *metaOpsClient) CreateProject(ctx context.Context, project *model.ProjectInfo) error {
   213  	if project == nil {
   214  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input project info is nil")
   215  	}
   216  	if err := c.db.WithContext(ctx).
   217  		Create(project).Error; err != nil {
   218  		return errors.ErrMetaOpFail.Wrap(err)
   219  	}
   220  
   221  	return nil
   222  }
   223  
   224  // DeleteProject delete the model.ProjectInfo
   225  func (c *metaOpsClient) DeleteProject(ctx context.Context, projectID string) error {
   226  	if err := c.db.WithContext(ctx).
   227  		Where("id=?", projectID).
   228  		Delete(&model.ProjectInfo{}).Error; err != nil {
   229  		return errors.ErrMetaOpFail.Wrap(err)
   230  	}
   231  
   232  	return nil
   233  }
   234  
   235  // QueryProject query all projects
   236  func (c *metaOpsClient) QueryProjects(ctx context.Context) ([]*model.ProjectInfo, error) {
   237  	var projects []*model.ProjectInfo
   238  	if err := c.db.WithContext(ctx).
   239  		Find(&projects).Error; err != nil {
   240  		return nil, errors.ErrMetaOpFail.Wrap(err)
   241  	}
   242  
   243  	return projects, nil
   244  }
   245  
   246  // GetProjectByID query project by projectID
   247  func (c *metaOpsClient) GetProjectByID(ctx context.Context, projectID string) (*model.ProjectInfo, error) {
   248  	var project model.ProjectInfo
   249  	if err := c.db.WithContext(ctx).
   250  		Where("id = ?", projectID).
   251  		First(&project).Error; err != nil {
   252  		if err == gorm.ErrRecordNotFound {
   253  			return nil, errors.ErrMetaEntryNotFound.Wrap(err)
   254  		}
   255  
   256  		return nil, errors.ErrMetaOpFail.Wrap(err)
   257  	}
   258  
   259  	return &project, nil
   260  }
   261  
   262  // CreateProjectOperation insert the operation
   263  func (c *metaOpsClient) CreateProjectOperation(ctx context.Context, op *model.ProjectOperation) error {
   264  	if op == nil {
   265  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input project operation is nil")
   266  	}
   267  
   268  	if err := c.db.WithContext(ctx).
   269  		Create(op).Error; err != nil {
   270  		return errors.ErrMetaOpFail.Wrap(err)
   271  	}
   272  
   273  	return nil
   274  }
   275  
   276  // QueryProjectOperations query all operations of the projectID
   277  func (c *metaOpsClient) QueryProjectOperations(ctx context.Context, projectID string) ([]*model.ProjectOperation, error) {
   278  	var projectOps []*model.ProjectOperation
   279  	if err := c.db.WithContext(ctx).
   280  		Where("project_id = ?", projectID).
   281  		Find(&projectOps).Error; err != nil {
   282  		return nil, errors.ErrMetaOpFail.Wrap(err)
   283  	}
   284  
   285  	return projectOps, nil
   286  }
   287  
   288  // QueryProjectOperationsByTimeRange query project operation betweem a time range of the projectID
   289  func (c *metaOpsClient) QueryProjectOperationsByTimeRange(ctx context.Context,
   290  	projectID string, tr TimeRange,
   291  ) ([]*model.ProjectOperation, error) {
   292  	var projectOps []*model.ProjectOperation
   293  	if err := c.db.WithContext(ctx).
   294  		Where("project_id = ? AND created_at >= ? AND created_at <= ?", projectID, tr.start, tr.end).
   295  		Find(&projectOps).Error; err != nil {
   296  		return nil, errors.ErrMetaOpFail.Wrap(err)
   297  	}
   298  
   299  	return projectOps, nil
   300  }
   301  
   302  // ///////////////////////////// Job Operation
   303  // InsertJob insert the jobInfo
   304  func (c *metaOpsClient) InsertJob(ctx context.Context, job *frameModel.MasterMeta) error {
   305  	if job == nil {
   306  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input master meta is nil")
   307  	}
   308  
   309  	if err := c.db.WithContext(ctx).Create(job).Error; err != nil {
   310  		return errors.ErrMetaOpFail.Wrap(err)
   311  	}
   312  
   313  	return nil
   314  }
   315  
   316  // UpsertJob upsert the jobInfo
   317  func (c *metaOpsClient) UpsertJob(ctx context.Context, job *frameModel.MasterMeta) error {
   318  	if job == nil {
   319  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input master meta is nil")
   320  	}
   321  
   322  	if err := c.db.WithContext(ctx).
   323  		Clauses(clause.OnConflict{
   324  			Columns:   []clause.Column{{Name: "id"}},
   325  			DoUpdates: clause.AssignmentColumns(frameModel.MasterUpdateColumns),
   326  		}).Create(job).Error; err != nil {
   327  		return errors.ErrMetaOpFail.Wrap(err)
   328  	}
   329  
   330  	return nil
   331  }
   332  
   333  // UpdateJob update the jobInfo
   334  func (c *metaOpsClient) UpdateJob(
   335  	ctx context.Context, jobID string, values model.KeyValueMap,
   336  ) error {
   337  	if err := c.db.WithContext(ctx).
   338  		Model(&frameModel.MasterMeta{}).
   339  		Where("id = ?", jobID).
   340  		Updates(values).Error; err != nil {
   341  		return errors.ErrMetaOpFail.Wrap(err)
   342  	}
   343  
   344  	return nil
   345  }
   346  
   347  // DeleteJob delete the specified jobInfo
   348  func (c *metaOpsClient) DeleteJob(ctx context.Context, jobID string) (Result, error) {
   349  	result := c.db.WithContext(ctx).
   350  		Where("id = ?", jobID).
   351  		Delete(&frameModel.MasterMeta{})
   352  	if result.Error != nil {
   353  		return nil, errors.ErrMetaOpFail.Wrap(result.Error)
   354  	}
   355  
   356  	return &ormResult{rowsAffected: result.RowsAffected}, nil
   357  }
   358  
   359  // GetJobByID query job by `jobID`
   360  func (c *metaOpsClient) GetJobByID(ctx context.Context, jobID string) (*frameModel.MasterMeta, error) {
   361  	var job frameModel.MasterMeta
   362  	if err := c.db.WithContext(ctx).
   363  		Where("id = ?", jobID).
   364  		First(&job).Error; err != nil {
   365  		if err == gorm.ErrRecordNotFound {
   366  			return nil, errors.ErrMetaEntryNotFound.Wrap(err)
   367  		}
   368  
   369  		return nil, errors.ErrMetaOpFail.Wrap(err)
   370  	}
   371  
   372  	return &job, nil
   373  }
   374  
   375  // QueryJobsByProjectID query all jobs of projectID
   376  func (c *metaOpsClient) QueryJobs(ctx context.Context) ([]*frameModel.MasterMeta, error) {
   377  	var jobs []*frameModel.MasterMeta
   378  	if err := c.db.WithContext(ctx).
   379  		Find(&jobs).Error; err != nil {
   380  		return nil, errors.ErrMetaOpFail.Wrap(err)
   381  	}
   382  
   383  	return jobs, nil
   384  }
   385  
   386  // QueryJobsByProjectID query all jobs of projectID
   387  func (c *metaOpsClient) QueryJobsByProjectID(ctx context.Context, projectID string) ([]*frameModel.MasterMeta, error) {
   388  	var jobs []*frameModel.MasterMeta
   389  	if err := c.db.WithContext(ctx).
   390  		Where("project_id = ?", projectID).
   391  		Find(&jobs).Error; err != nil {
   392  		return nil, errors.ErrMetaOpFail.Wrap(err)
   393  	}
   394  
   395  	return jobs, nil
   396  }
   397  
   398  // QueryJobsByState query all jobs with `state` of the projectID
   399  func (c *metaOpsClient) QueryJobsByState(ctx context.Context,
   400  	jobID string, state int,
   401  ) ([]*frameModel.MasterMeta, error) {
   402  	var jobs []*frameModel.MasterMeta
   403  	if err := c.db.WithContext(ctx).
   404  		Where("project_id = ? AND state = ?", jobID, state).
   405  		Find(&jobs).Error; err != nil {
   406  		return nil, errors.ErrMetaOpFail.Wrap(err)
   407  	}
   408  
   409  	return jobs, nil
   410  }
   411  
   412  // ///////////////////////////// Worker Operation
   413  // UpsertWorker insert the workerInfo
   414  func (c *metaOpsClient) UpsertWorker(ctx context.Context, worker *frameModel.WorkerStatus) error {
   415  	if worker == nil {
   416  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input worker meta is nil")
   417  	}
   418  
   419  	if err := c.db.WithContext(ctx).
   420  		Clauses(clause.OnConflict{
   421  			Columns:   []clause.Column{{Name: "id"}, {Name: "job_id"}},
   422  			DoUpdates: clause.AssignmentColumns(frameModel.WorkerUpdateColumns),
   423  		}).Create(worker).Error; err != nil {
   424  		return errors.ErrMetaOpFail.Wrap(err)
   425  	}
   426  
   427  	return nil
   428  }
   429  
   430  func (c *metaOpsClient) UpdateWorker(ctx context.Context, worker *frameModel.WorkerStatus) error {
   431  	if worker == nil {
   432  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input worker meta is nil")
   433  	}
   434  	// we don't use `Save` here to avoid user dealing with the basic model
   435  	if err := c.db.WithContext(ctx).
   436  		Model(&frameModel.WorkerStatus{}).
   437  		Where("job_id = ? AND id = ?", worker.JobID, worker.ID).
   438  		Updates(worker.Map()).Error; err != nil {
   439  		return errors.ErrMetaOpFail.Wrap(err)
   440  	}
   441  
   442  	return nil
   443  }
   444  
   445  // DeleteWorker delete the specified workInfo
   446  func (c *metaOpsClient) DeleteWorker(ctx context.Context, masterID string, workerID string) (Result, error) {
   447  	result := c.db.WithContext(ctx).
   448  		Where("job_id = ? AND id = ?", masterID, workerID).
   449  		Delete(&frameModel.WorkerStatus{})
   450  	if result.Error != nil {
   451  		return nil, errors.ErrMetaOpFail.Wrap(result.Error)
   452  	}
   453  
   454  	return &ormResult{rowsAffected: result.RowsAffected}, nil
   455  }
   456  
   457  // GetWorkerByID query worker info by workerID
   458  func (c *metaOpsClient) GetWorkerByID(ctx context.Context, masterID string, workerID string) (*frameModel.WorkerStatus, error) {
   459  	var worker frameModel.WorkerStatus
   460  	if err := c.db.WithContext(ctx).
   461  		Where("job_id = ? AND id = ?", masterID, workerID).
   462  		First(&worker).Error; err != nil {
   463  		if err == gorm.ErrRecordNotFound {
   464  			return nil, errors.ErrMetaEntryNotFound.Wrap(err)
   465  		}
   466  
   467  		return nil, errors.ErrMetaOpFail.Wrap(err)
   468  	}
   469  
   470  	return &worker, nil
   471  }
   472  
   473  // QueryWorkersByMasterID query all workers of masterID
   474  func (c *metaOpsClient) QueryWorkersByMasterID(ctx context.Context, masterID string) ([]*frameModel.WorkerStatus, error) {
   475  	var workers []*frameModel.WorkerStatus
   476  	if err := c.db.WithContext(ctx).
   477  		Where("job_id = ?", masterID).
   478  		Find(&workers).Error; err != nil {
   479  		return nil, errors.ErrMetaOpFail.Wrap(err)
   480  	}
   481  
   482  	return workers, nil
   483  }
   484  
   485  // QueryWorkersByState query all workers with specified state of masterID
   486  func (c *metaOpsClient) QueryWorkersByState(ctx context.Context, masterID string, state int) ([]*frameModel.WorkerStatus, error) {
   487  	var workers []*frameModel.WorkerStatus
   488  	if err := c.db.WithContext(ctx).
   489  		Where("job_id = ? AND state = ?", masterID, state).
   490  		Find(&workers).Error; err != nil {
   491  		return nil, errors.ErrMetaOpFail.Wrap(err)
   492  	}
   493  
   494  	return workers, nil
   495  }
   496  
   497  // ///////////////////////////// Resource Operation
   498  // UpsertResource upsert the ResourceMeta
   499  func (c *metaOpsClient) UpsertResource(ctx context.Context, resource *resModel.ResourceMeta) error {
   500  	if resource == nil {
   501  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input resource meta is nil")
   502  	}
   503  
   504  	if err := c.db.WithContext(ctx).
   505  		Clauses(clause.OnConflict{
   506  			Columns:   []clause.Column{{Name: "job_id"}, {Name: "id"}},
   507  			DoUpdates: clause.AssignmentColumns(resModel.ResourceUpdateColumns),
   508  		}).Create(resource).Error; err != nil {
   509  		return errors.ErrMetaOpFail.Wrap(err)
   510  	}
   511  
   512  	return nil
   513  }
   514  
   515  // CreateResource insert a resource meta.
   516  // Return 'ErrDuplicateResourceID' error if it already exists.
   517  func (c *metaOpsClient) CreateResource(ctx context.Context, resource *resModel.ResourceMeta) error {
   518  	if resource == nil {
   519  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input resource meta is nil")
   520  	}
   521  
   522  	err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
   523  		var count int64
   524  		err := tx.Model(&resModel.ResourceMeta{}).
   525  			Where("job_id = ? AND id = ?", resource.Job, resource.ID).
   526  			Count(&count).Error
   527  		if err != nil {
   528  			return err
   529  		}
   530  
   531  		if count > 0 {
   532  			return errors.ErrDuplicateResourceID.GenWithStackByArgs(resource.ID)
   533  		}
   534  
   535  		if err := tx.Create(resource).Error; err != nil {
   536  			return errors.ErrMetaOpFail.Wrap(err)
   537  		}
   538  		return nil
   539  	})
   540  	if err != nil {
   541  		return errors.ErrMetaOpFail.Wrap(err)
   542  	}
   543  	return nil
   544  }
   545  
   546  // UpdateResource update the resModel
   547  func (c *metaOpsClient) UpdateResource(ctx context.Context, resource *resModel.ResourceMeta) error {
   548  	if resource == nil {
   549  		return errors.ErrMetaParamsInvalid.GenWithStackByArgs("input resource meta is nil")
   550  	}
   551  	// we don't use `Save` here to avoid user dealing with the basic model
   552  	if err := c.db.WithContext(ctx).
   553  		Model(&resModel.ResourceMeta{}).
   554  		Where("job_id = ? AND id = ?", resource.Job, resource.ID).
   555  		Updates(resource.Map()).Error; err != nil {
   556  		return errors.ErrMetaOpFail.Wrap(err)
   557  	}
   558  
   559  	return nil
   560  }
   561  
   562  // DeleteResource delete the resource meta of specified resourceKey
   563  func (c *metaOpsClient) DeleteResource(ctx context.Context, resourceKey ResourceKey) (Result, error) {
   564  	result := c.db.WithContext(ctx).
   565  		Where("job_id = ? AND id = ?", resourceKey.JobID, resourceKey.ID).
   566  		Delete(&resModel.ResourceMeta{})
   567  	if result.Error != nil {
   568  		return nil, errors.ErrMetaOpFail.Wrap(result.Error)
   569  	}
   570  
   571  	return &ormResult{rowsAffected: result.RowsAffected}, nil
   572  }
   573  
   574  // GetResourceByID query resource of the resourceKey
   575  func (c *metaOpsClient) GetResourceByID(ctx context.Context, resourceKey ResourceKey) (*resModel.ResourceMeta, error) {
   576  	var resource resModel.ResourceMeta
   577  	if err := c.db.WithContext(ctx).
   578  		Where("job_id = ? AND id = ?", resourceKey.JobID, resourceKey.ID).
   579  		First(&resource).Error; err != nil {
   580  		if err == gorm.ErrRecordNotFound {
   581  			return nil, errors.ErrMetaEntryNotFound.Wrap(err)
   582  		}
   583  
   584  		return nil, errors.ErrMetaOpFail.Wrap(err)
   585  	}
   586  
   587  	return &resource, nil
   588  }
   589  
   590  // QueryResources get all resource meta
   591  func (c *metaOpsClient) QueryResources(ctx context.Context) ([]*resModel.ResourceMeta, error) {
   592  	var resources []*resModel.ResourceMeta
   593  	if err := c.db.WithContext(ctx).
   594  		Find(&resources).Error; err != nil {
   595  		return nil, errors.ErrMetaOpFail.Wrap(err)
   596  	}
   597  
   598  	return resources, nil
   599  }
   600  
   601  // QueryResourcesByJobID query all resources of the jobID
   602  func (c *metaOpsClient) QueryResourcesByJobID(ctx context.Context, jobID string) ([]*resModel.ResourceMeta, error) {
   603  	var resources []*resModel.ResourceMeta
   604  	if err := c.db.WithContext(ctx).
   605  		Where("job_id = ?", jobID).
   606  		Find(&resources).Error; err != nil {
   607  		return nil, errors.ErrMetaOpFail.Wrap(err)
   608  	}
   609  
   610  	return resources, nil
   611  }
   612  
   613  // QueryResourcesByExecutorIDs query all resources of the executorIDs
   614  func (c *metaOpsClient) QueryResourcesByExecutorIDs(
   615  	ctx context.Context, executorIDs ...engineModel.ExecutorID,
   616  ) ([]*resModel.ResourceMeta, error) {
   617  	var resources []*resModel.ResourceMeta
   618  	if err := c.db.WithContext(ctx).
   619  		Where("executor_id in ?", executorIDs).
   620  		Find(&resources).Error; err != nil {
   621  		return nil, errors.ErrMetaOpFail.Wrap(err)
   622  	}
   623  
   624  	return resources, nil
   625  }
   626  
   627  // DeleteResourcesByTypeAndExecutorIDs delete a specific type of resources of executorID
   628  func (c *metaOpsClient) DeleteResourcesByTypeAndExecutorIDs(
   629  	ctx context.Context, resType resModel.ResourceType, executorIDs ...engineModel.ExecutorID,
   630  ) (Result, error) {
   631  	var result *gorm.DB
   632  	if len(executorIDs) == 1 {
   633  		result = c.db.WithContext(ctx).
   634  			Where("executor_id = ? and id like ?", executorIDs[0], resType.BuildPrefix()+"%").
   635  			Delete(&resModel.ResourceMeta{})
   636  	} else {
   637  		result = c.db.WithContext(ctx).
   638  			Where("executor_id in ? and id like ?", executorIDs, resType.BuildPrefix()+"%").
   639  			Delete(&resModel.ResourceMeta{})
   640  	}
   641  	if result.Error == nil {
   642  		return &ormResult{rowsAffected: result.RowsAffected}, nil
   643  	}
   644  
   645  	return nil, errors.ErrMetaOpFail.Wrap(result.Error)
   646  }
   647  
   648  // SetGCPendingByJobs set the resourceIDs to the state `waiting to gc`
   649  func (c *metaOpsClient) SetGCPendingByJobs(ctx context.Context, jobIDs ...engineModel.JobID) error {
   650  	err := c.db.WithContext(ctx).
   651  		Model(&resModel.ResourceMeta{}).
   652  		Where("job_id in ?", jobIDs).
   653  		Update("gc_pending", true).Error
   654  	if err == nil {
   655  		return nil
   656  	}
   657  	return errors.ErrMetaOpFail.Wrap(err)
   658  }
   659  
   660  // GetOneResourceForGC get one resource ready for gc
   661  func (c *metaOpsClient) GetOneResourceForGC(ctx context.Context) (*resModel.ResourceMeta, error) {
   662  	var ret resModel.ResourceMeta
   663  	err := c.db.WithContext(ctx).
   664  		Order("updated_at asc").
   665  		Where("gc_pending = true").
   666  		First(&ret).Error
   667  	if err != nil {
   668  		if errors.Is(err, gorm.ErrRecordNotFound) {
   669  			return nil, errors.ErrMetaEntryNotFound.Wrap(err)
   670  		}
   671  		return nil, errors.ErrMetaOpFail.Wrap(err)
   672  	}
   673  	return &ret, nil
   674  }
   675  
   676  // SetJobNoop sets a job noop status if a job op record exists and status is
   677  // canceling. This API is used when processing a job operation but the metadata
   678  // of this job is not found (or deleted manually by accident).
   679  func (c *metaOpsClient) SetJobNoop(ctx context.Context, jobID string) (Result, error) {
   680  	result := &ormResult{}
   681  	ops := &model.JobOp{
   682  		Op: model.JobOpStatusNoop,
   683  	}
   684  	exec := c.db.WithContext(ctx).
   685  		Model(&model.JobOp{}).
   686  		Where("job_id = ? AND op = ?", jobID, model.JobOpStatusCanceling).
   687  		Updates(ops.Map())
   688  	if err := exec.Error; err != nil {
   689  		return result, errors.WrapError(errors.ErrMetaOpFail, err)
   690  	}
   691  	result.rowsAffected = exec.RowsAffected
   692  	return result, nil
   693  }
   694  
   695  // SetJobCanceling sets a job cancelling status if this op record doesn't exist.
   696  // If a job cancelling op already exists, does nothing.
   697  // If the job is already cancelled, return ErrJobAlreadyCanceled error.
   698  func (c *metaOpsClient) SetJobCanceling(ctx context.Context, jobID string) (Result, error) {
   699  	result := &ormResult{}
   700  	err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
   701  		var count int64
   702  		var ops model.JobOp
   703  		query := tx.Model(&model.JobOp{}).Where("job_id = ?", jobID)
   704  		if err := query.Count(&count).Error; err != nil {
   705  			return errors.WrapError(errors.ErrMetaOpFail, err)
   706  		}
   707  		if count > 0 {
   708  			if err := query.First(&ops).Error; err != nil {
   709  				return errors.WrapError(errors.ErrMetaOpFail, err)
   710  			}
   711  			switch ops.Op {
   712  			case model.JobOpStatusCanceling:
   713  				return nil
   714  			case model.JobOpStatusCanceled:
   715  				return errors.ErrJobAlreadyCanceled.GenWithStackByArgs(jobID)
   716  			default:
   717  			}
   718  		}
   719  		ops = model.JobOp{
   720  			Op:    model.JobOpStatusCanceling,
   721  			JobID: jobID,
   722  		}
   723  		exec := tx.Clauses(
   724  			clause.OnConflict{
   725  				Columns:   []clause.Column{{Name: "job_id"}},
   726  				DoUpdates: clause.AssignmentColumns(model.JobOpUpdateColumns),
   727  			}).Create(&ops)
   728  		if err := exec.Error; err != nil {
   729  			return errors.WrapError(errors.ErrMetaOpFail, err)
   730  		}
   731  		result.rowsAffected = exec.RowsAffected
   732  		return nil
   733  	})
   734  	return result, errors.WrapError(errors.ErrMetaOpFail, err)
   735  }
   736  
   737  // SetJobCanceled sets a cancelled status if a cancelling op exists.
   738  //   - If cancelling operation is not found, it can be triggered by unexpected
   739  //     SetJobCanceled don't make any change and return nil error.
   740  //   - If a job is already cancelled, don't make any change and return nil error.
   741  func (c *metaOpsClient) SetJobCanceled(ctx context.Context, jobID string) (Result, error) {
   742  	result := &ormResult{}
   743  	ops := &model.JobOp{
   744  		Op: model.JobOpStatusCanceled,
   745  	}
   746  	exec := c.db.WithContext(ctx).
   747  		Model(&model.JobOp{}).
   748  		Where("job_id = ? AND op = ?", jobID, model.JobOpStatusCanceling).
   749  		Updates(ops.Map())
   750  	if err := exec.Error; err != nil {
   751  		return result, errors.WrapError(errors.ErrMetaOpFail, err)
   752  	}
   753  	result.rowsAffected = exec.RowsAffected
   754  	return result, nil
   755  }
   756  
   757  // QueryJobOp queries a JobOp based on jobID
   758  func (c *metaOpsClient) QueryJobOp(
   759  	ctx context.Context, jobID string,
   760  ) (*model.JobOp, error) {
   761  	var op *model.JobOp
   762  	err := c.db.WithContext(ctx).Where("job_id = ?", jobID).Find(&op).Error
   763  	if err != nil {
   764  		return nil, err
   765  	}
   766  	return op, nil
   767  }
   768  
   769  // QueryJobOpsByStatus query all jobOps with given `op`
   770  func (c *metaOpsClient) QueryJobOpsByStatus(
   771  	ctx context.Context, op model.JobOpStatus,
   772  ) ([]*model.JobOp, error) {
   773  	var ops []*model.JobOp
   774  	if err := c.db.WithContext(ctx).
   775  		Where("op = ?", op).
   776  		Find(&ops).Error; err != nil {
   777  		return nil, errors.ErrMetaOpFail.Wrap(err)
   778  	}
   779  	return ops, nil
   780  }
   781  
   782  // CreateExecutor creates an executor in the metastore.
   783  func (c *metaOpsClient) CreateExecutor(ctx context.Context, executor *model.Executor) error {
   784  	if err := c.db.WithContext(ctx).
   785  		Create(executor).Error; err != nil {
   786  		return errors.ErrMetaOpFail.Wrap(err)
   787  	}
   788  	return nil
   789  }
   790  
   791  // UpdateExecutor updates an executor in the metastore.
   792  func (c *metaOpsClient) UpdateExecutor(ctx context.Context, executor *model.Executor) error {
   793  	if err := c.db.WithContext(ctx).
   794  		Model(&model.Executor{}).
   795  		Where("id = ?", executor.ID).
   796  		Updates(executor.Map()).Error; err != nil {
   797  		return errors.ErrMetaOpFail.Wrap(err)
   798  	}
   799  	return nil
   800  }
   801  
   802  // DeleteExecutor deletes an executor in the metastore.
   803  func (c *metaOpsClient) DeleteExecutor(ctx context.Context, executorID engineModel.ExecutorID) error {
   804  	if err := c.db.WithContext(ctx).
   805  		Where("id = ?", executorID).
   806  		Delete(&model.Executor{}).Error; err != nil {
   807  		return errors.ErrMetaOpFail.Wrap(err)
   808  	}
   809  	return nil
   810  }
   811  
   812  // QueryExecutors query all executors in the metastore.
   813  func (c *metaOpsClient) QueryExecutors(ctx context.Context) ([]*model.Executor, error) {
   814  	var executors []*model.Executor
   815  	if err := c.db.WithContext(ctx).
   816  		Find(&executors).Error; err != nil {
   817  		return nil, errors.ErrMetaOpFail.Wrap(err)
   818  	}
   819  	return executors, nil
   820  }
   821  
   822  // Result defines a query result interface
   823  type Result interface {
   824  	RowsAffected() int64
   825  }
   826  
   827  type ormResult struct {
   828  	rowsAffected int64
   829  }
   830  
   831  // RowsAffected return the affected rows of an execution
   832  func (r ormResult) RowsAffected() int64 {
   833  	return r.rowsAffected
   834  }