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

restore: support restoring SQL wrapping with TiDB special comments #1287

Merged
merged 3 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
53 changes: 36 additions & 17 deletions ast/ddl.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/pingcap/parser/model"
"github.com/pingcap/parser/mysql"
"github.com/pingcap/parser/terror"
"github.com/pingcap/parser/tidb"
"github.com/pingcap/parser/types"
)

Expand Down Expand Up @@ -501,7 +502,9 @@ func (n *ColumnOption) Restore(ctx *format.RestoreCtx) error {
pkTp := n.PrimaryKeyTp.String()
if len(pkTp) != 0 {
ctx.WritePlain(" ")
ctx.WriteKeyWord(pkTp)
ctx.WriteWithSpecialComments(tidb.FeatureIDClusteredIndex, func() {
ctx.WriteKeyWord(pkTp)
})
}
case ColumnOptionNotNull:
ctx.WriteKeyWord("NOT NULL")
Expand Down Expand Up @@ -574,10 +577,12 @@ func (n *ColumnOption) Restore(ctx *format.RestoreCtx) error {
ctx.WriteKeyWord("STORAGE ")
ctx.WriteKeyWord(n.StrValue)
case ColumnOptionAutoRandom:
ctx.WriteKeyWord("AUTO_RANDOM")
if n.AutoRandomBitLength != types.UnspecifiedLength {
ctx.WritePlainf("(%d)", n.AutoRandomBitLength)
}
ctx.WriteWithSpecialComments(tidb.FeatureIDAutoRandom, func() {
ctx.WriteKeyWord("AUTO_RANDOM")
if n.AutoRandomBitLength != types.UnspecifiedLength {
ctx.WritePlainf("(%d)", n.AutoRandomBitLength)
}
})
default:
return errors.New("An error occurred while splicing ColumnOption")
}
Expand Down Expand Up @@ -1891,22 +1896,32 @@ func (n *TableOption) Restore(ctx *format.RestoreCtx) error {
ctx.WriteKeyWord(n.StrValue)
case TableOptionAutoIncrement:
if n.BoolValue {
ctx.WriteKeyWord("FORCE ")
ctx.WriteWithSpecialComments(tidb.FeatureIDForceAutoInc, func() {
ctx.WriteKeyWord("FORCE")
})
ctx.WritePlain(" ")
}
ctx.WriteKeyWord("AUTO_INCREMENT ")
ctx.WritePlain("= ")
ctx.WritePlainf("%d", n.UintValue)
case TableOptionAutoIdCache:
ctx.WriteKeyWord("AUTO_ID_CACHE ")
ctx.WritePlain("= ")
ctx.WritePlainf("%d", n.UintValue)
ctx.WriteWithSpecialComments(tidb.FeatureIDAutoIDCache, func() {
ctx.WriteKeyWord("AUTO_ID_CACHE ")
ctx.WritePlain("= ")
ctx.WritePlainf("%d", n.UintValue)
})
case TableOptionAutoRandomBase:
if n.BoolValue {
ctx.WriteKeyWord("FORCE ")
ctx.WriteWithSpecialComments(tidb.FeatureIDForceAutoInc, func() {
ctx.WriteKeyWord("FORCE")
})
ctx.WritePlain(" ")
}
ctx.WriteKeyWord("AUTO_RANDOM_BASE ")
ctx.WritePlain("= ")
ctx.WritePlainf("%d", n.UintValue)
ctx.WriteWithSpecialComments(tidb.FeatureIDAutoRandomBase, func() {
ctx.WriteKeyWord("AUTO_RANDOM_BASE ")
ctx.WritePlain("= ")
ctx.WritePlainf("%d", n.UintValue)
})
case TableOptionComment:
ctx.WriteKeyWord("COMMENT ")
ctx.WritePlain("= ")
Expand Down Expand Up @@ -1997,11 +2012,15 @@ func (n *TableOption) Restore(ctx *format.RestoreCtx) error {
ctx.WritePlainf("%d", n.UintValue)
}
case TableOptionShardRowID:
ctx.WriteKeyWord("SHARD_ROW_ID_BITS ")
ctx.WritePlainf("= %d", n.UintValue)
ctx.WriteWithSpecialComments(tidb.FeatureIDTiDB, func() {
ctx.WriteKeyWord("SHARD_ROW_ID_BITS ")
ctx.WritePlainf("= %d", n.UintValue)
})
case TableOptionPreSplitRegion:
ctx.WriteKeyWord("PRE_SPLIT_REGIONS ")
ctx.WritePlainf("= %d", n.UintValue)
ctx.WriteWithSpecialComments(tidb.FeatureIDTiDB, func() {
ctx.WriteKeyWord("PRE_SPLIT_REGIONS ")
ctx.WritePlainf("= %d", n.UintValue)
})
case TableOptionPackKeys:
// TODO: not support
ctx.WriteKeyWord("PACK_KEYS ")
Expand Down
20 changes: 20 additions & 0 deletions format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,8 @@ const (

RestoreStringWithoutCharset
RestoreStringWithoutDefaultCharset

RestoreTiDBSpecialComment
)

const (
Expand Down Expand Up @@ -289,6 +291,10 @@ func (rf RestoreFlags) HasStringWithoutCharset() bool {
return rf.has(RestoreStringWithoutCharset)
}

func (rf RestoreFlags) HasTiDBSpecialCommentFlag() bool {
return rf.has(RestoreTiDBSpecialComment)
}

// RestoreCtx is `Restore` context to hold flags and writer.
type RestoreCtx struct {
Flags RestoreFlags
Expand All @@ -314,6 +320,20 @@ func (ctx *RestoreCtx) WriteKeyWord(keyWord string) {
fmt.Fprint(ctx.In, keyWord)
}

func (ctx *RestoreCtx) WriteWithSpecialComments(featureID string, fn func()) {
if !ctx.Flags.HasTiDBSpecialCommentFlag() {
fn()
return
}
ctx.WritePlain("/*T!")
if len(featureID) != 0 {
ctx.WritePlainf("[%s]", featureID)
}
ctx.WritePlain(" ")
fn()
ctx.WritePlain(" */")
}

// WriteString writes the string into writer
// `str` may be wrapped in quotes and escaped according to RestoreFlags.
func (ctx *RestoreCtx) WriteString(str string) {
Expand Down
16 changes: 16 additions & 0 deletions format/format_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,19 @@ func (s *testRestoreCtxSuite) TestRestoreCtx(c *C) {
c.Assert(sb.String(), Equals, testCase.expect, Commentf("case: %#v", testCase))
}
}

func (s *testRestoreCtxSuite) TestRestoreSpecialComment(c *C) {
var sb strings.Builder
sb.Reset()
ctx := NewRestoreCtx(RestoreTiDBSpecialComment, &sb)
ctx.WriteWithSpecialComments("fea_id", func() {
ctx.WritePlain("content")
})
c.Assert(sb.String(), Equals, "/*T![fea_id] content */")

sb.Reset()
ctx.WriteWithSpecialComments("", func() {
ctx.WritePlain("shard_row_id_bits")
})
c.Assert(sb.String(), Equals, "/*T! shard_row_id_bits */")
}
4 changes: 2 additions & 2 deletions lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"unicode/utf8"

"github.com/pingcap/parser/mysql"
tidbfeature "github.com/pingcap/parser/tidb"
)

var _ = yyLexer(&Scanner{})
Expand Down Expand Up @@ -397,11 +398,10 @@ func startWithSlash(s *Scanner) (tok int, pos Pos, lit string) {
s.r.inc()
// in '/*T!', try to match the pattern '/*T![feature1,feature2,...]'.
features := s.scanFeatureIDs()
if SpecialCommentsController.ContainsAll(features) {
if tidbfeature.CanParseFeature(features...) {
s.inBangComment = true
return s.scan()
}

case 'M': // '/*M' maybe MariaDB-specific comments
// no special treatment for now.
break
Expand Down
9 changes: 3 additions & 6 deletions lexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ func runTest(c *C, table []testCaseItem) {
}

func (s *testLexerSuite) TestComment(c *C) {
SpecialCommentsController.Register("test")
table := []testCaseItem{
{"-- select --\n1", intLit},
{"/*!40101 SET character_set_client = utf8 */;", set},
Expand All @@ -178,8 +177,8 @@ SELECT`, selectKwd},

// The odd behavior of '*/' inside conditional comment is the same as
// that of MySQL.
{"/*T![unsupported] '*/0 -- ' */", intLit}, // equivalent to 0
{"/*T![test] '*/0 -- ' */", stringLit}, // equivalent to '*/0 -- '
{"/*T![unsupported] '*/0 -- ' */", intLit}, // equivalent to 0
{"/*T![auto_rand] '*/0 -- ' */", stringLit}, // equivalent to '*/0 -- '
}
runTest(c, table)
}
Expand Down Expand Up @@ -273,7 +272,6 @@ func (s *testLexerSuite) TestSpecialComment(c *C) {
}

func (s *testLexerSuite) TestFeatureIDsComment(c *C) {
SpecialCommentsController.Register("auto_rand")
l := NewScanner("/*T![auto_rand] auto_random(5) */")
tok, pos, lit := l.scan()
c.Assert(tok, Equals, identifier)
Expand Down Expand Up @@ -317,7 +315,6 @@ func (s *testLexerSuite) TestOptimizerHint(c *C) {
}

func (s *testLexerSuite) TestOptimizerHintAfterCertainKeywordOnly(c *C) {
SpecialCommentsController.Register("test")
tests := []struct {
input string
tokens []int
Expand Down Expand Up @@ -359,7 +356,7 @@ func (s *testLexerSuite) TestOptimizerHintAfterCertainKeywordOnly(c *C) {
tokens: []int{selectKwd, '*', 0},
},
{
input: "SELECT /*T![test] * */ /*+ hint */",
input: "SELECT /*T![auto_rand] * */ /*+ hint */",
tokens: []int{selectKwd, '*', 0},
},
{
Expand Down
35 changes: 0 additions & 35 deletions misc.go
Original file line number Diff line number Diff line change
Expand Up @@ -993,38 +993,3 @@ func handleIdent(lval *yySymType) int {
lval.ident = cs
return underscoreCS
}

// SpecialCommentsController controls whether special comments like `/*T![xxx] yyy */`
// can be parsed as `yyy`. To add such rules, please use SpecialCommentsController.Register().
// For example:
// SpecialCommentsController.Register("30100");
// Now the parser will treat
// select a, /*T![30100] mysterious_keyword */ from t;
// and
// select a, mysterious_keyword from t;
// equally.
// Similar special comments without registration are ignored by parser.
var SpecialCommentsController = specialCommentsCtrl{
supportedFeatures: map[string]struct{}{},
}

type specialCommentsCtrl struct {
supportedFeatures map[string]struct{}
}

func (s *specialCommentsCtrl) Register(featureID string) {
s.supportedFeatures[featureID] = struct{}{}
}

func (s *specialCommentsCtrl) Unregister(featureID string) {
delete(s.supportedFeatures, featureID)
}

func (s *specialCommentsCtrl) ContainsAll(featureIDs []string) bool {
for _, f := range featureIDs {
if _, found := s.supportedFeatures[f]; !found {
return false
}
}
return true
}
46 changes: 46 additions & 0 deletions tidb/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2021 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package tidb

const (
// FeatureIDTiDB represents the general TiDB-specific features.
FeatureIDTiDB = ""
// FeatureIDAutoRandom is the `auto_random` feature.
FeatureIDAutoRandom = "auto_rand"
// FeatureIDAutoIDCache is the `auto_id_cache` feature.
FeatureIDAutoIDCache = "auto_id_cache"
// FeatureIDAutoRandomBase is the `auto_random_base` feature.
FeatureIDAutoRandomBase = "auto_rand_base"
// FeatureIDClusteredIndex is the `clustered_index` feature.
FeatureIDClusteredIndex = "clustered_index"
// FeatureIDForceAutoInc is the `force auto_increment` feature.
FeatureIDForceAutoInc = "force_inc"
)

var featureIDs = map[string]struct{}{
FeatureIDAutoRandom: {},
FeatureIDAutoIDCache: {},
FeatureIDAutoRandomBase: {},
FeatureIDClusteredIndex: {},
FeatureIDForceAutoInc: {},
}

func CanParseFeature(fs ...string) bool {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The old code is more general, the parser is not specific to TiDB and you can register the feature to control it's behavior ...

Copy link
Collaborator

Choose a reason for hiding this comment

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

OK, since you are the original author of this code, if you thinks it's OK, I have no problem with it.

Copy link
Contributor Author

@tangenta tangenta Jul 28, 2021

Choose a reason for hiding this comment

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

the parser is not specific to TiDB

But the keywords(AUTO_RANDOM, CLUSTERED_INDEX...) are TiDB-specific. It is reasonable to attach these keywords to fixed feature IDs.

you can register the feature to control it's behavior

The register mechanism is not that convenient. If the user registers a new feature ID(/*T![new_feature_id] */), he must also modify the code in the parser to add a new keyword to wrap. If the user doesn't need the new keyword, he should use /*T! xxx */ instead.

for _, f := range fs {
if _, ok := featureIDs[f]; !ok {
return false
}
}
return true
}