164 lines
5.1 KiB
Go
164 lines
5.1 KiB
Go
// Copyright 2022 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 cache
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/pingcap/errors"
|
|
"github.com/pingcap/tidb/parser/ast"
|
|
"github.com/pingcap/tidb/parser/model"
|
|
"github.com/pingcap/tidb/parser/mysql"
|
|
"github.com/pingcap/tidb/table/tables"
|
|
"github.com/pingcap/tidb/ttl/session"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/chunk"
|
|
)
|
|
|
|
func getTableKeyColumns(tbl *model.TableInfo) ([]*model.ColumnInfo, []*types.FieldType, error) {
|
|
if tbl.PKIsHandle {
|
|
for i, col := range tbl.Columns {
|
|
if mysql.HasPriKeyFlag(col.GetFlag()) {
|
|
return []*model.ColumnInfo{tbl.Columns[i]}, []*types.FieldType{&tbl.Columns[i].FieldType}, nil
|
|
}
|
|
}
|
|
return nil, nil, errors.Errorf("Cannot find primary key for table: %s", tbl.Name)
|
|
}
|
|
|
|
if tbl.IsCommonHandle {
|
|
idxInfo := tables.FindPrimaryIndex(tbl)
|
|
columns := make([]*model.ColumnInfo, len(idxInfo.Columns))
|
|
fieldTypes := make([]*types.FieldType, len(idxInfo.Columns))
|
|
for i, idxCol := range idxInfo.Columns {
|
|
columns[i] = tbl.Columns[idxCol.Offset]
|
|
fieldTypes[i] = &tbl.Columns[idxCol.Offset].FieldType
|
|
}
|
|
return columns, fieldTypes, nil
|
|
}
|
|
|
|
extraHandleColInfo := model.NewExtraHandleColInfo()
|
|
return []*model.ColumnInfo{extraHandleColInfo}, []*types.FieldType{&extraHandleColInfo.FieldType}, nil
|
|
}
|
|
|
|
// PhysicalTable is used to provide some information for a physical table in TTL job
|
|
type PhysicalTable struct {
|
|
// ID is the physical ID of the table
|
|
ID int64
|
|
// Schema is the database name of the table
|
|
Schema model.CIStr
|
|
*model.TableInfo
|
|
// Partition is the partition name
|
|
Partition model.CIStr
|
|
// PartitionDef is the partition definition
|
|
PartitionDef *model.PartitionDefinition
|
|
// KeyColumns is the cluster index key columns for the table
|
|
KeyColumns []*model.ColumnInfo
|
|
// KeyColumnTypes is the types of the key columns
|
|
KeyColumnTypes []*types.FieldType
|
|
// TimeColum is the time column used for TTL
|
|
TimeColumn *model.ColumnInfo
|
|
}
|
|
|
|
// NewPhysicalTable create a new PhysicalTable
|
|
func NewPhysicalTable(schema model.CIStr, tbl *model.TableInfo, partition model.CIStr) (*PhysicalTable, error) {
|
|
if tbl.State != model.StatePublic {
|
|
return nil, errors.Errorf("table '%s.%s' is not a public table", schema, tbl.Name)
|
|
}
|
|
|
|
ttlInfo := tbl.TTLInfo
|
|
if ttlInfo == nil {
|
|
return nil, errors.Errorf("table '%s.%s' is not a ttl table", schema, tbl.Name)
|
|
}
|
|
|
|
timeColumn := tbl.FindPublicColumnByName(ttlInfo.ColumnName.L)
|
|
if timeColumn == nil {
|
|
return nil, errors.Errorf("time column '%s' is not public in ttl table '%s.%s'", ttlInfo.ColumnName, schema, tbl.Name)
|
|
}
|
|
|
|
keyColumns, keyColumTypes, err := getTableKeyColumns(tbl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var physicalID int64
|
|
var partitionDef *model.PartitionDefinition
|
|
if tbl.Partition == nil {
|
|
if partition.L != "" {
|
|
return nil, errors.Errorf("table '%s.%s' is not a partitioned table", schema, tbl.Name)
|
|
}
|
|
physicalID = tbl.ID
|
|
} else {
|
|
if partition.L == "" {
|
|
return nil, errors.Errorf("partition name is required, table '%s.%s' is a partitioned table", schema, tbl.Name)
|
|
}
|
|
|
|
for i := range tbl.Partition.Definitions {
|
|
def := &tbl.Partition.Definitions[i]
|
|
if def.Name.L == partition.L {
|
|
partitionDef = def
|
|
}
|
|
}
|
|
|
|
if partitionDef == nil {
|
|
return nil, errors.Errorf("partition '%s' is not found in ttl table '%s.%s'", partition.O, schema, tbl.Name)
|
|
}
|
|
|
|
physicalID = partitionDef.ID
|
|
}
|
|
|
|
return &PhysicalTable{
|
|
ID: physicalID,
|
|
Schema: schema,
|
|
TableInfo: tbl,
|
|
Partition: partition,
|
|
PartitionDef: partitionDef,
|
|
KeyColumns: keyColumns,
|
|
KeyColumnTypes: keyColumTypes,
|
|
TimeColumn: timeColumn,
|
|
}, nil
|
|
}
|
|
|
|
// ValidateKey validates a key
|
|
func (t *PhysicalTable) ValidateKey(key []types.Datum) error {
|
|
if len(t.KeyColumns) != len(key) {
|
|
return errors.Errorf("invalid key length: %d, expected %d", len(key), len(t.KeyColumns))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EvalExpireTime returns the expired time
|
|
func (t *PhysicalTable) EvalExpireTime(ctx context.Context, se session.Session, now time.Time) (expire time.Time, err error) {
|
|
tz := se.GetSessionVars().TimeZone
|
|
|
|
expireExpr := t.TTLInfo.IntervalExprStr
|
|
unit := ast.TimeUnitType(t.TTLInfo.IntervalTimeUnit)
|
|
|
|
var rows []chunk.Row
|
|
rows, err = se.ExecuteSQL(
|
|
ctx,
|
|
// FROM_UNIXTIME does not support negative value, so we use `FROM_UNIXTIME(0) + INTERVAL <current_ts>` to present current time
|
|
fmt.Sprintf("SELECT FROM_UNIXTIME(0) + INTERVAL %d SECOND - INTERVAL %s %s", now.Unix(), expireExpr, unit.String()),
|
|
)
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
tm := rows[0].GetTime(0)
|
|
return tm.CoreTime().GoTime(tz)
|
|
}
|