Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

*: support create a local temporary table #25851

Merged
merged 11 commits into from
Jul 5, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions ddl/db_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2791,6 +2791,44 @@ func (s *testIntegrationSuite3) TestCreateTemporaryTable(c *C) {
tk.MustGetErrCode("create temporary table t(id int)", errno.ErrNotSupportedYet)

tk.MustExec("set @@tidb_enable_noop_functions = 1")
tk.MustExec("create temporary table t (id int)")
tk.MustQuery("show warnings").Check(testutil.RowsWithSep("|", "Warning 1105 local TEMPORARY TABLE is not supported yet, TEMPORARY will be parsed but ignored"))

// Create local temporary table.
tk.MustExec("set @@tidb_enable_noop_functions = 1")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we still need to set tidb_enable_noop_functions=1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to touch more places than necessary. For example, in this pr I just want to add the create local temporary table.
If a remove this switch, I need to update some of the tests, I prefer to do it in another PR.

tk.MustExec("create database tmp_db")
tk.MustExec("use tmp_db")
tk.MustExec("create temporary table t1 (id int)")
tk.MustExec("create temporary table tmp_db.t2 (id int)")
tk.MustQuery("select * from t1") // No error
tk.MustExec("drop database tmp_db")
_, err := tk.Exec("select * from t1")
c.Assert(err, NotNil)
// In MySQL, drop DB does not really drop the table, it's back!
tk.MustExec("create database tmp_db")
tk.MustExec("use tmp_db")
tk.MustQuery("select * from t1") // No error
tiancaiamao marked this conversation as resolved.
Show resolved Hide resolved

// When local temporary table overlap the normal table, it takes a higher priority.
tiancaiamao marked this conversation as resolved.
Show resolved Hide resolved
tk.MustExec("create table overlap (id int)")
tk.MustExec("create temporary table overlap (a int, b int)")
_, err = tk.Exec("insert into overlap values (1)") // column not match
c.Assert(err, NotNil)
tk.MustExec("insert into overlap values (1, 1)")

// Stale read see the local temporary table but can't read on it.
tk.MustExec("START TRANSACTION READ ONLY AS OF TIMESTAMP NOW(3)")
tk.MustGetErrMsg("select * from overlap", "can not stale read temporary table")
tk.MustExec("rollback")

// For mocktikv, safe point is not initialized, we manually insert it for snapshot to use.
safePointName := "tikv_gc_safe_point"
safePointValue := "20060102-15:04:05 -0700"
safePointComment := "All versions after safe point can be accessed. (DO NOT EDIT)"
updateSafePoint := fmt.Sprintf(`INSERT INTO mysql.tidb VALUES ('%[1]s', '%[2]s', '%[3]s')
ON DUPLICATE KEY
UPDATE variable_value = '%[2]s', comment = '%[3]s'`, safePointName, safePointValue, safePointComment)
tk.MustExec(updateSafePoint)

// Considering snapshot, local temporary table is always visible.
tk.MustExec("set @@tidb_snapshot = '2016-01-01 15:04:05.999999'")
tk.MustExec("select * from overlap")
}
12 changes: 5 additions & 7 deletions ddl/ddl_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1682,12 +1682,12 @@ func buildTableInfoWithLike(ctx sessionctx.Context, ident ast.Ident, referTblInf
// BuildTableInfoFromAST builds model.TableInfo from a SQL statement.
// Note: TableID and PartitionID are left as uninitialized value.
func BuildTableInfoFromAST(s *ast.CreateTableStmt) (*model.TableInfo, error) {
return buildTableInfoWithCheck(mock.NewContext(), s, mysql.DefaultCharset, "")
return BuildTableInfoWithCheck(mock.NewContext(), s, mysql.DefaultCharset, "")
}

// buildTableInfoWithCheck builds model.TableInfo from a SQL statement.
// BuildTableInfoWithCheck builds model.TableInfo from a SQL statement.
// Note: TableID and PartitionIDs are left as uninitialized value.
func buildTableInfoWithCheck(ctx sessionctx.Context, s *ast.CreateTableStmt, dbCharset, dbCollate string) (*model.TableInfo, error) {
func BuildTableInfoWithCheck(ctx sessionctx.Context, s *ast.CreateTableStmt, dbCharset, dbCollate string) (*model.TableInfo, error) {
tbInfo, err := buildTableInfoWithStmt(ctx, s, dbCharset, dbCollate)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1829,9 +1829,7 @@ func setTemporaryType(ctx sessionctx.Context, tbInfo *model.TableInfo, s *ast.Cr
return errors.Trace(errUnsupportedOnCommitPreserve)
}
case ast.TemporaryLocal:
// TODO: set "tbInfo.TempTableType = model.TempTableLocal" after local temporary table is supported.
tbInfo.TempTableType = model.TempTableNone
ctx.GetSessionVars().StmtCtx.AppendWarning(errors.New("local TEMPORARY TABLE is not supported yet, TEMPORARY will be parsed but ignored"))
tbInfo.TempTableType = model.TempTableLocal
default:
tbInfo.TempTableType = model.TempTableNone
}
Expand Down Expand Up @@ -5725,7 +5723,7 @@ func (d *ddl) RepairTable(ctx sessionctx.Context, table *ast.TableName, createSt
}

// It is necessary to specify the table.ID and partition.ID manually.
newTableInfo, err := buildTableInfoWithCheck(ctx, createStmt, oldTableInfo.Charset, oldTableInfo.Collate)
newTableInfo, err := BuildTableInfoWithCheck(ctx, createStmt, oldTableInfo.Charset, oldTableInfo.Collate)
if err != nil {
return errors.Trace(err)
}
Expand Down
52 changes: 52 additions & 0 deletions executor/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ import (
"github.com/pingcap/tidb/ddl"
"github.com/pingcap/tidb/domain"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/meta"
"github.com/pingcap/tidb/meta/autoid"
"github.com/pingcap/tidb/planner/core"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/table/tables"
"github.com/pingcap/tidb/util/admin"
"github.com/pingcap/tidb/util/chunk"
"github.com/pingcap/tidb/util/gcutil"
Expand Down Expand Up @@ -200,10 +203,59 @@ func (e *DDLExec) executeAlterDatabase(s *ast.AlterDatabaseStmt) error {
}

func (e *DDLExec) executeCreateTable(s *ast.CreateTableStmt) error {
if s.TemporaryKeyword == ast.TemporaryLocal {
return e.createSessionTemporaryTable(s)
}

err := domain.GetDomain(e.ctx).DDL().CreateTable(e.ctx, s)
return err
}

func (e *DDLExec) createSessionTemporaryTable(s *ast.CreateTableStmt) error {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it better to modify the method ddl.CreateTableWithInfo , it can reduce many dup codes like alloc table id, generate table info, etc...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Local temporary table is not a normal table, it doesn't need to do the DDL schema change and store table info.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked the function ddl.CreateTable, ddl.CreateTableWithInfo, most codes can be reused here. We can just return if local temporary if founded before creating table in tikv. Current modification is easy to lost some checks. For example,
create temporary table if not exists will still return error because you didn't check IfNotExists in statement

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check the table options will be done in some other PR.
That will add up to a lot of work, and (more importantly) the tests should also be included.

is := e.ctx.GetInfoSchema().(infoschema.InfoSchema)
dbInfo, ok := is.SchemaByName(s.Table.Schema)
if !ok {
return infoschema.ErrDatabaseNotExists.GenWithStackByArgs(s.Table.Schema.O)
}
tbInfo, err := ddl.BuildTableInfoWithCheck(e.ctx, s, dbInfo.Charset, dbInfo.Collate)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does BuildTableInfoWithCheck support create table like ... ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, create table like ... will be handle in some other PR, this one focus on creating a table.

if err != nil {
return err
}

dom := domain.GetDomain(e.ctx)
// Local temporary table uses a real table ID.
// We could mock a table ID, but the mocked ID might be identical to an existing
// real table, and then we'll get into trouble.
err = kv.RunInNewTxn(context.Background(), dom.Store(), true, func(ctx context.Context, txn kv.Transaction) error {
m := meta.NewMeta(txn)
tblID, err := m.GenGlobalID()
if err != nil {
return errors.Trace(err)
}
tbInfo.ID = tblID
tbInfo.State = model.StatePublic
return nil
})
if err != nil {
return err
}

// AutoID is allocated in mocked..
alloc := autoid.NewAllocatorFromTempTblInfo(tbInfo)
tbl, err := tables.TableFromMeta([]autoid.Allocator{alloc}, tbInfo)
if err != nil {
return err
}

// Store this temporary table to the session.
sessVars := e.ctx.GetSessionVars()
if sessVars.LocalTemporaryTables == nil {
sessVars.LocalTemporaryTables = infoschema.NewLocalTemporaryTables()
}
localTempTables := sessVars.LocalTemporaryTables.(*infoschema.LocalTemporaryTables)
return localTempTables.AddTable(dbInfo, tbl)
}

func (e *DDLExec) executeCreateView(s *ast.CreateViewStmt) error {
err := domain.GetDomain(e.ctx).DDL().CreateView(e.ctx, s)
return err
Expand Down
9 changes: 9 additions & 0 deletions executor/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/pingcap/parser/mysql"
"github.com/pingcap/tidb/domain"
"github.com/pingcap/tidb/expression"
"github.com/pingcap/tidb/infoschema"
"github.com/pingcap/tidb/plugin"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/variable"
Expand Down Expand Up @@ -265,6 +266,14 @@ func (e *SetExecutor) loadSnapshotInfoSchemaIfNeeded(snapshotTS uint64) error {
if err != nil {
return err
}

if local := vars.LocalTemporaryTables; local != nil {
snapInfo = &infoschema.TemporaryTableAttachedInfoSchema{
InfoSchema: snapInfo,
LocalTemporaryTables: local.(*infoschema.LocalTemporaryTables),
}
}

vars.SnapshotInfoschema = snapInfo
return nil
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ require (
golang.org/x/tools v0.1.4
google.golang.org/grpc v1.27.1
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
honnef.co/go/tools v0.2.0 // indirect
modernc.org/mathutil v1.2.2 // indirect
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67
Expand Down
4 changes: 2 additions & 2 deletions infoschema/infoschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,8 @@ type LocalTemporaryTables struct {
idx2table map[int64]table.Table
}

// NewLocalTemporaryTableInfoSchema creates a new LocalTemporaryTableInfoSchema object
func NewLocalTemporaryTableInfoSchema() *LocalTemporaryTables {
// NewLocalTemporaryTables creates a new NewLocalTemporaryTables object
func NewLocalTemporaryTables() *LocalTemporaryTables {
return &LocalTemporaryTables{
schemaMap: make(map[string]*schemaLocalTempSchemaTables),
idx2table: make(map[int64]table.Table),
Expand Down
2 changes: 1 addition & 1 deletion infoschema/infoschema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ func (*testSuite) TestLocalTemporaryTables(c *C) {
}
}

sc := infoschema.NewLocalTemporaryTableInfoSchema()
sc := infoschema.NewLocalTemporaryTables()
db1 := createNewSchemaInfo("db1")
tb11 := createNewTable(db1.ID, "tb1", model.TempTableLocal)
tb12 := createNewTable(db1.ID, "Tb2", model.TempTableLocal)
Expand Down
34 changes: 29 additions & 5 deletions session/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -1419,7 +1419,7 @@ func (s *session) ExecRestrictedStmt(ctx context.Context, stmtNode ast.StmtNode,
}()

if execOption.SnapshotTS != 0 {
se.sessionVars.SnapshotInfoschema, err = domain.GetDomain(s).GetSnapshotInfoSchema(execOption.SnapshotTS)
se.sessionVars.SnapshotInfoschema, err = getSnapshotInfoSchema(s, execOption.SnapshotTS)
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -1919,7 +1919,7 @@ func (s *session) ExecutePreparedStmt(ctx context.Context, stmtID uint32, args [
if err != nil {
return nil, errors.Trace(err)
}
is, err = domain.GetDomain(s).GetSnapshotInfoSchema(snapshotTS)
is, err = getSnapshotInfoSchema(s, snapshotTS)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down Expand Up @@ -2074,7 +2074,7 @@ func (s *session) NewStaleTxnWithStartTS(ctx context.Context, startTS uint64) er
txn.SetOption(kv.IsStalenessReadOnly, true)
txn.SetOption(kv.TxnScope, txnScope)
s.txn.changeInvalidToValid(txn)
is, err := domain.GetDomain(s).GetSnapshotInfoSchema(txn.StartTS())
is, err := getSnapshotInfoSchema(s, txn.StartTS())
if err != nil {
return errors.Trace(err)
}
Expand Down Expand Up @@ -2689,7 +2689,7 @@ func (s *session) PrepareTxnCtx(ctx context.Context) {
return
}

is := domain.GetDomain(s).InfoSchema()
is := s.GetInfoSchema()
s.sessionVars.TxnCtx = &variable.TransactionContext{
InfoSchema: is,
CreateTime: time.Now(),
Expand Down Expand Up @@ -2946,7 +2946,31 @@ func (s *session) GetInfoSchema() sessionctx.InfoschemaMetaVersion {
return is
}
}
return domain.GetDomain(s).InfoSchema()

is := domain.GetDomain(s).InfoSchema()
tiancaiamao marked this conversation as resolved.
Show resolved Hide resolved
// Override the infoschema if the session has temporary table.
return wrapWithTemporaryTable(s, is)
}

func getSnapshotInfoSchema(s sessionctx.Context, snapshotTS uint64) (infoschema.InfoSchema, error) {
is, err := domain.GetDomain(s).GetSnapshotInfoSchema(snapshotTS)
if err != nil {
return nil, err
}
// Set snapshot does not affect the witness of the local temporary table.
// The session always see the latest temporary tables.
return wrapWithTemporaryTable(s, is), nil
}

func wrapWithTemporaryTable(s sessionctx.Context, is infoschema.InfoSchema) infoschema.InfoSchema {
local := s.GetSessionVars().LocalTemporaryTables
if local == nil {
return is
}
return &infoschema.TemporaryTableAttachedInfoSchema{
InfoSchema: is,
LocalTemporaryTables: local.(*infoschema.LocalTemporaryTables),
}
}

func (s *session) updateTelemetryMetric(es *executor.ExecStmt) {
Expand Down
4 changes: 4 additions & 0 deletions sessionctx/variable/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,10 @@ type SessionVars struct {

// EnableGlobalTemporaryTable indicates whether to enable global temporary table
EnableGlobalTemporaryTable bool

// LocalTemporaryTables is *infoschema.LocalTemporaryTables, use interface to avoid circle dependency.
// It's nil if there is no local temporary table.
LocalTemporaryTables interface{}
}

// AllocMPPTaskID allocates task id for mpp tasks. It will reset the task id if the query's
Expand Down