Repository: cloudspannerecosystem/memefish
Pull request #272 (2025-01-16 22:06:04)

Update dependency go to v1.23.5
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [go](https://go.dev/) ([source](https://redirect.github.com/golang/go)) | toolchain | patch | `1.23.4` -> `1.23.5` |

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency Dashboard for more information.

---

### Release Notes

<details>
<summary>golang/go (go)</summary>

### [`v1.23.5`](https://redirect.github.com/golang/go/compare/go1.23.4...go1.23.5)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/cloudspannerecosystem/memefish).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS4xMDcuMCIsInVwZGF0ZWRJblZlciI6IjM5LjEwNy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->



Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 0d94110..3cfd937 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -433,7 +433,7 @@ func (ReplaceRowDeletionPolicy) isTableAlteration() {}
 func (SetOnDelete) isTableAlteration()              {}
 func (AlterColumn) isTableAlteration()              {}
 
-// ColumnDefaultSemantics is interface of DefaultExpr, GeneratedColumnExpr, IdentityColumn.
+// ColumnDefaultSemantics is interface of DefaultExpr, GeneratedColumnExpr, IdentityColumn, AutoIncrement.
 // They are change default value of column and mutually exclusive.
 type ColumnDefaultSemantics interface {
 	Node
@@ -443,6 +443,7 @@ type ColumnDefaultSemantics interface {
 func (ColumnDefaultExpr) isColumnDefaultSemantics()   {}
 func (GeneratedColumnExpr) isColumnDefaultSemantics() {}
 func (IdentityColumn) isColumnDefaultSemantics()      {}
+func (AutoIncrement) isColumnDefaultSemantics()      {}
 
 type SequenceParam interface {
 	Node
@@ -668,17 +669,27 @@ type QueryStatement struct {
 // https://cloud.google.com/spanner/docs/query-syntax
 type Query struct {
 	// pos = (With ?? Query).pos
-	// end = (PipeOperators[$] ?? Limit ?? OrderBy ?? Query).end
+	// end = (PipeOperators[$] ?? ForUpdate ?? Limit ?? OrderBy ?? Query).end
 
 	With  *With
 	Query QueryExpr
 
-	OrderBy *OrderBy // optional
-	Limit   *Limit   // optional
-
+	OrderBy       *OrderBy   // optional
+	Limit         *Limit     // optional
+	ForUpdate     *ForUpdate // optional
 	PipeOperators []PipeOperator
 }
 
+// ForUpdate is FOR UPDATE node.
+//
+//	FOR UPDATE
+type ForUpdate struct {
+	// pos = For
+	// end = Update + 6
+
+	For, Update token.Pos
+}
+
 // Hint is hint node.
 //
 //	@{{"{"}}{{.Records | sqlJoin ","}}{{"}"}}
@@ -2533,6 +2544,16 @@ type IdentityColumn struct {
 	Params []SequenceParam //  if Rparen.Invalid() then len(Param) = 0 else len(Param) > 0
 }
 
+// AutoIncrement is AUTO_INCREMENT node.
+//
+//	AUTO_INCREMENT
+type AutoIncrement struct {
+	// pos = AutoIncrement
+	// end = AutoIncrement + 14
+
+	AutoIncrement token.Pos // position of "AUTO_INCREMENT"
+}
+
 // TableConstraint is table constraint in CREATE TABLE and ALTER TABLE.
 //
 //	{{if .Name}}CONSTRAINT {{.Name}}{{end}}{{.Constraint | sql}}
@@ -3941,7 +3962,7 @@ type ThenReturn struct {
 // Insert is INSERT statement node.
 //
 //	{{.Hint | sqlOpt}}
-//	INSERT {{if .InsertOrType}}OR .InsertOrType{{end}}INTO {{.TableName | sql}} ({{.Columns | sqlJoin ","}}) {{.Input | sql}}
+//	INSERT {{if .InsertOrType}}OR .InsertOrType{{end}}INTO {{.TableName | sql}}{{.TableHint | sqlOpt}} ({{.Columns | sqlJoin ","}}) {{.Input | sql}}
 //	{{.ThenReturn | sqlOpt}}
 type Insert struct {
 	// pos = Hint.pos || Insert
@@ -3953,6 +3974,7 @@ type Insert struct {
 
 	Hint       *Hint // optional
 	TableName  *Path
+	TableHint  *Hint // optional
 	Columns    []*Ident
 	Input      InsertInput
 	ThenReturn *ThenReturn // optional
@@ -4008,7 +4030,7 @@ type SubQueryInput struct {
 // Delete is DELETE statement.
 //
 //	{{.Hint | sqlOpt}}
-//	DELETE FROM {{.TableName | sql}} {{.As | sqlOpt}} {{.Where | sql}}
+//	DELETE FROM {{.TableName | sql}}{{.TableHint | sqlOpt}} {{.As | sqlOpt}} {{.Where | sql}}
 //	{{.ThenReturn | sqlOpt}}
 type Delete struct {
 	// pos = Hint.pos || Delete
@@ -4018,6 +4040,7 @@ type Delete struct {
 
 	Hint       *Hint // optional
 	TableName  *Path
+	TableHint  *Hint    // optional
 	As         *AsAlias // optional
 	Where      *Where
 	ThenReturn *ThenReturn // optional
@@ -4026,7 +4049,7 @@ type Delete struct {
 // Update is UPDATE statement.
 //
 //	{{.Hint | sqlOpt}}
-//	UPDATE {{.TableName | sql}} {{.As | sqlOpt}}
+//	UPDATE {{.TableName | sql}}{{.TableHint | sqlOpt}} {{.As | sqlOpt}}
 //	SET {{.Updates | sqlJoin ","}} {{.Where | sql}}
 //	{{.ThenReturn | sqlOpt}}
 type Update struct {
@@ -4037,6 +4060,7 @@ type Update struct {
 
 	Hint       *Hint // optional
 	TableName  *Path
+	TableHint  *Hint         // optional
 	As         *AsAlias      // optional
 	Updates    []*UpdateItem // len(Updates) > 0
 	Where      *Where
diff --git a/ast/ast_const.go b/ast/ast_const.go
index eb62932..0d47194 100644
--- a/ast/ast_const.go
+++ b/ast/ast_const.go
@@ -17,21 +17,6 @@ const (
 	AllOrDistinctDistinct AllOrDistinct = "DISTINCT"
 )
 
-type TableHintKey string
-
-const (
-	ForceIndexTableHint     TableHintKey = "FORCE_INDEX"
-	GroupScanByOptimization TableHintKey = "GROUPBY_SCAN_OPTIMIZATION"
-)
-
-type JoinHintKey string
-
-const (
-	ForceJoinOrderJoinHint JoinHintKey = "FORCE_JOIN_ORDER"
-	JoinTypeJoinHint       JoinHintKey = "JOIN_TYPE"
-)
-
-// JoinMethod is used for prefix of JOIN, not for hint.
 type JoinMethod string
 
 const (
diff --git a/ast/pos.go b/ast/pos.go
index b6ff62b..c4cc8b6 100644
--- a/ast/pos.go
+++ b/ast/pos.go
@@ -75,7 +75,15 @@ func (q *Query) Pos() token.Pos {
 }
 
 func (q *Query) End() token.Pos {
-	return nodeEnd(nodeChoice(nodeSliceLast(q.PipeOperators), wrapNode(q.Limit), wrapNode(q.OrderBy), wrapNode(q.Query)))
+	return nodeEnd(nodeChoice(nodeSliceLast(q.PipeOperators), wrapNode(q.ForUpdate), wrapNode(q.Limit), wrapNode(q.OrderBy), wrapNode(q.Query)))
+}
+
+func (f *ForUpdate) Pos() token.Pos {
+	return f.For
+}
+
+func (f *ForUpdate) End() token.Pos {
+	return posAdd(f.Update, 6)
 }
 
 func (h *Hint) Pos() token.Pos {
@@ -1158,6 +1166,14 @@ func (i *IdentityColumn) End() token.Pos {
 	return posChoice(posAdd(i.Rparen, 1), posAdd(i.Identity, 8))
 }
 
+func (a *AutoIncrement) Pos() token.Pos {
+	return a.AutoIncrement
+}
+
+func (a *AutoIncrement) End() token.Pos {
+	return posAdd(a.AutoIncrement, 14)
+}
+
 func (t *TableConstraint) Pos() token.Pos {
 	return posChoice(t.ConstraintPos, nodePos(wrapNode(t.Constraint)))
 }
diff --git a/ast/sql.go b/ast/sql.go
index 530d84a..ac67741 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -194,10 +194,13 @@ func (q *Query) SQL() string {
 		q.Query.SQL() +
 		sqlOpt(" ", q.OrderBy, "") +
 		sqlOpt(" ", q.Limit, "") +
+		sqlOpt(" ", q.ForUpdate, "") +
 		strOpt(len(q.PipeOperators) > 0, " ") +
 		sqlJoin(q.PipeOperators, " ")
 }
 
+func (f *ForUpdate) SQL() string { return "FOR UPDATE" }
+
 func (h *Hint) SQL() string {
 	return "@{" + sqlJoin(h.Records, ", ") + "}"
 }
@@ -868,6 +871,10 @@ func (i *IdentityColumn) SQL() string {
 	return "GENERATED BY DEFAULT AS IDENTITY" + strOpt(len(i.Params) > 0, " ("+sqlJoin(i.Params, " ")+")")
 }
 
+func (a *AutoIncrement) SQL() string {
+	return "AUTO_INCREMENT"
+}
+
 func (i *IndexKey) SQL() string {
 	return i.Name.SQL() + strOpt(i.Dir != "", " "+string(i.Dir))
 }
@@ -1309,7 +1316,8 @@ func (i *Insert) SQL() string {
 	return sqlOpt("", i.Hint, " ") +
 		"INSERT " +
 		strOpt(i.InsertOrType != "", "OR "+string(i.InsertOrType)+" ") +
-		"INTO " + i.TableName.SQL() + " (" +
+		"INTO " + i.TableName.SQL() +
+		sqlOpt("", i.TableHint, "") + " (" +
 		sqlJoin(i.Columns, ", ") +
 		") " +
 		i.Input.SQL() +
@@ -1338,7 +1346,8 @@ func (s *SubQueryInput) SQL() string {
 func (d *Delete) SQL() string {
 	return sqlOpt("", d.Hint, " ") +
 		"DELETE FROM " +
-		d.TableName.SQL() + " " +
+		d.TableName.SQL() +
+		sqlOpt("", d.TableHint, "") + " " +
 		sqlOpt("", d.As, " ") +
 		d.Where.SQL() +
 		sqlOpt(" ", d.ThenReturn, "")
@@ -1346,7 +1355,8 @@ func (d *Delete) SQL() string {
 
 func (u *Update) SQL() string {
 	return sqlOpt("", u.Hint, " ") +
-		"UPDATE " + u.TableName.SQL() + " " +
+		"UPDATE " + u.TableName.SQL() +
+		sqlOpt("", u.TableHint, "") + " " +
 		sqlOpt("", u.As, " ") +
 		"SET " +
 		sqlJoin(u.Updates, ", ") +
diff --git a/ast/walk_internal.go b/ast/walk_internal.go
index 5a351eb..7104968 100644
--- a/ast/walk_internal.go
+++ b/ast/walk_internal.go
@@ -34,11 +34,15 @@ func walkInternal(node Node, v Visitor, stack []*stackItem) []*stackItem {
 
 	case *Query:
 		stack = append(stack, &stackItem{nodes: wrapNodes(n.PipeOperators), visitor: v.Field("PipeOperators")})
+		stack = append(stack, &stackItem{node: wrapNode(n.ForUpdate), visitor: v.Field("ForUpdate")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Limit), visitor: v.Field("Limit")})
 		stack = append(stack, &stackItem{node: wrapNode(n.OrderBy), visitor: v.Field("OrderBy")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Query), visitor: v.Field("Query")})
 		stack = append(stack, &stackItem{node: wrapNode(n.With), visitor: v.Field("With")})
 
+	case *ForUpdate:
+		// nothing to do
+
 	case *Hint:
 		stack = append(stack, &stackItem{nodes: wrapNodes(n.Records), visitor: v.Field("Records")})
 
@@ -530,6 +534,9 @@ func walkInternal(node Node, v Visitor, stack []*stackItem) []*stackItem {
 	case *IdentityColumn:
 		stack = append(stack, &stackItem{nodes: wrapNodes(n.Params), visitor: v.Field("Params")})
 
+	case *AutoIncrement:
+		// nothing to do
+
 	case *TableConstraint:
 		stack = append(stack, &stackItem{node: wrapNode(n.Constraint), visitor: v.Field("Constraint")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Name), visitor: v.Field("Name")})
@@ -907,6 +914,7 @@ func walkInternal(node Node, v Visitor, stack []*stackItem) []*stackItem {
 		stack = append(stack, &stackItem{node: wrapNode(n.ThenReturn), visitor: v.Field("ThenReturn")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Input), visitor: v.Field("Input")})
 		stack = append(stack, &stackItem{nodes: wrapNodes(n.Columns), visitor: v.Field("Columns")})
+		stack = append(stack, &stackItem{node: wrapNode(n.TableHint), visitor: v.Field("TableHint")})
 		stack = append(stack, &stackItem{node: wrapNode(n.TableName), visitor: v.Field("TableName")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Hint), visitor: v.Field("Hint")})
 
@@ -926,6 +934,7 @@ func walkInternal(node Node, v Visitor, stack []*stackItem) []*stackItem {
 		stack = append(stack, &stackItem{node: wrapNode(n.ThenReturn), visitor: v.Field("ThenReturn")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Where), visitor: v.Field("Where")})
 		stack = append(stack, &stackItem{node: wrapNode(n.As), visitor: v.Field("As")})
+		stack = append(stack, &stackItem{node: wrapNode(n.TableHint), visitor: v.Field("TableHint")})
 		stack = append(stack, &stackItem{node: wrapNode(n.TableName), visitor: v.Field("TableName")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Hint), visitor: v.Field("Hint")})
 
@@ -934,6 +943,7 @@ func walkInternal(node Node, v Visitor, stack []*stackItem) []*stackItem {
 		stack = append(stack, &stackItem{node: wrapNode(n.Where), visitor: v.Field("Where")})
 		stack = append(stack, &stackItem{nodes: wrapNodes(n.Updates), visitor: v.Field("Updates")})
 		stack = append(stack, &stackItem{node: wrapNode(n.As), visitor: v.Field("As")})
+		stack = append(stack, &stackItem{node: wrapNode(n.TableHint), visitor: v.Field("TableHint")})
 		stack = append(stack, &stackItem{node: wrapNode(n.TableName), visitor: v.Field("TableName")})
 		stack = append(stack, &stackItem{node: wrapNode(n.Hint), visitor: v.Field("Hint")})
 
diff --git a/examples/parse-unparse/main.go b/examples/parse-unparse/main.go
index 97b6d4a..71cddec 100644
--- a/examples/parse-unparse/main.go
+++ b/examples/parse-unparse/main.go
@@ -4,9 +4,10 @@ import (
 	"fmt"
 	"log"
 
+	"github.com/k0kubun/pp/v3"
+
 	"github.com/cloudspannerecosystem/memefish"
 	"github.com/cloudspannerecosystem/memefish/token"
-	"github.com/k0kubun/pp"
 )
 
 func main() {
diff --git a/go.mod b/go.mod
index 533a428..e5561b8 100644
--- a/go.mod
+++ b/go.mod
@@ -2,12 +2,11 @@ module github.com/cloudspannerecosystem/memefish
 
 go 1.23.0
 
-toolchain go1.23.4
+toolchain go1.23.5
 
 require (
 	github.com/MakeNowJust/heredoc/v2 v2.0.1
 	github.com/google/go-cmp v0.6.0
-	github.com/k0kubun/pp v1.3.1-0.20200204103551-99835366d1cc
 	github.com/k0kubun/pp/v3 v3.4.1
 	github.com/pmezard/go-difflib v1.0.0
 )
diff --git a/go.sum b/go.sum
index 595a205..4a48df6 100644
--- a/go.sum
+++ b/go.sum
@@ -2,21 +2,14 @@ github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZ
 github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM=
 github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 h1:uC1QfSlInpQF+M0ao65imhwqKnz3Q2z/d8PWZRMQvDM=
-github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
-github.com/k0kubun/pp v1.3.1-0.20200204103551-99835366d1cc h1:XLjmW07gT7cG/wb6mavIrvAIWBYaTacPo8UOnxGSspA=
-github.com/k0kubun/pp v1.3.1-0.20200204103551-99835366d1cc/go.mod h1:qK2ivXw91omfE1uXcpR5kWbAMZRdDOnGbqWlZ7reRFk=
 github.com/k0kubun/pp/v3 v3.4.1 h1:1WdFZDRRqe8UsR61N/2RoOZ3ziTEqgTPVqKrHeb779Y=
 github.com/k0kubun/pp/v3 v3.4.1/go.mod h1:+SiNiqKnBfw1Nkj82Lh5bIeKQOAkPy6Xw9CAZUZ8npI=
-github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
 github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
 golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/parser.go b/parser.go
index cb5f8eb..548d26c 100644
--- a/parser.go
+++ b/parser.go
@@ -328,12 +328,14 @@ func (p *Parser) parseQuery() *ast.Query {
 			Query:         q.Query,
 			OrderBy:       q.OrderBy,
 			Limit:         q.Limit,
+			ForUpdate:     q.ForUpdate,
 			PipeOperators: q.PipeOperators,
 		}
 	}
 
 	orderBy := p.tryParseOrderBy()
 	limit := p.tryParseLimit()
+	forUpdate := p.tryParseForUpdate()
 	pipeOps := p.parsePipeOperators()
 
 	return &ast.Query{
@@ -341,10 +343,25 @@ func (p *Parser) parseQuery() *ast.Query {
 		Query:         query,
 		OrderBy:       orderBy,
 		Limit:         limit,
+		ForUpdate:     forUpdate,
 		PipeOperators: pipeOps,
 	}
 }
 
+func (p *Parser) tryParseForUpdate() *ast.ForUpdate {
+	if p.Token.Kind != "FOR" {
+		return nil
+	}
+
+	forPos := p.expect("FOR").Pos
+	update := p.expectKeywordLike("UPDATE").Pos
+
+	return &ast.ForUpdate{
+		For:    forPos,
+		Update: update,
+	}
+}
+
 func (p *Parser) tryParseHint() *ast.Hint {
 	if p.Token.Kind != "@" {
 		return nil
@@ -423,7 +440,7 @@ func (p *Parser) parseQueryExpr() (query ast.QueryExpr) {
 
 	// If the query is directly followed by ORDER BY, LIMIT or pipe operators, it won't be a compound query
 	switch p.Token.Kind {
-	case "ORDER", "LIMIT", "|>":
+	case "ORDER", "LIMIT", "FOR", "|>":
 		return p.parseQueryExprSuffix(query)
 	}
 
@@ -776,15 +793,18 @@ func (p *Parser) parseQueryExprSuffix(e ast.QueryExpr) ast.QueryExpr {
 
 	orderBy := p.tryParseOrderBy()
 	limit := p.tryParseLimit()
+	forUpdate := p.tryParseForUpdate()
+
 	pipeOps := p.parsePipeOperators()
 
-	if orderBy == nil && limit == nil && len(pipeOps) == 0 {
+	if orderBy == nil && limit == nil && forUpdate == nil && len(pipeOps) == 0 {
 		return e
 	}
 	return &ast.Query{
 		Query:         e,
 		OrderBy:       orderBy,
 		Limit:         limit,
+		ForUpdate:     forUpdate,
 		PipeOperators: pipeOps,
 	}
 }
@@ -3073,7 +3093,6 @@ func (p *Parser) parseCreateTable(pos token.Pos) *ast.CreateTable {
 	name := p.parsePath()
 
 	// This loop allows parsing trailing comma intentionally.
-	// TODO: is this allowed by Spanner really?
 	p.expect("(")
 	var columns []*ast.ColumnDef
 	var constraints []*ast.TableConstraint
@@ -3259,6 +3278,9 @@ func (p *Parser) parseColumnDef() *ast.ColumnDef {
 		defaultSemantics = p.parseGeneratedColumnExpr()
 	case p.Token.IsKeywordLike("GENERATED"):
 		defaultSemantics = p.parseIdentityColumn()
+	case p.Token.IsKeywordLike("AUTO_INCREMENT"):
+		pos := p.expectKeywordLike("AUTO_INCREMENT").Pos
+		defaultSemantics = &ast.AutoIncrement{AutoIncrement: pos}
 	}
 
 	hiddenPos := token.InvalidPos
@@ -5105,6 +5127,7 @@ func (p *Parser) parseInsert(pos token.Pos, hint *ast.Hint) *ast.Insert {
 	}
 
 	name := p.parsePath()
+	tableHint := p.tryParseHint()
 
 	p.expect("(")
 	var columns []*ast.Ident
@@ -5133,6 +5156,7 @@ func (p *Parser) parseInsert(pos token.Pos, hint *ast.Hint) *ast.Insert {
 		Hint:         hint,
 		InsertOrType: insertOrType,
 		TableName:    name,
+		TableHint:    tableHint,
 		Columns:      columns,
 		Input:        input,
 		ThenReturn:   thenReturn,
@@ -5201,6 +5225,7 @@ func (p *Parser) parseDelete(pos token.Pos, hint *ast.Hint) *ast.Delete {
 	}
 
 	name := p.parsePath()
+	tableHint := p.tryParseHint()
 	as := p.tryParseAsAlias(withOptionalAs)
 	where := p.parseWhere()
 	thenReturn := p.tryParseThenReturn()
@@ -5209,6 +5234,7 @@ func (p *Parser) parseDelete(pos token.Pos, hint *ast.Hint) *ast.Delete {
 		Delete:     pos,
 		Hint:       hint,
 		TableName:  name,
+		TableHint:  tableHint,
 		As:         as,
 		Where:      where,
 		ThenReturn: thenReturn,
@@ -5217,6 +5243,7 @@ func (p *Parser) parseDelete(pos token.Pos, hint *ast.Hint) *ast.Delete {
 
 func (p *Parser) parseUpdate(pos token.Pos, hint *ast.Hint) *ast.Update {
 	name := p.parsePath()
+	tableHint := p.tryParseHint()
 	as := p.tryParseAsAlias(withOptionalAs)
 
 	p.expect("SET")
@@ -5230,6 +5257,7 @@ func (p *Parser) parseUpdate(pos token.Pos, hint *ast.Hint) *ast.Update {
 		Update:     pos,
 		Hint:       hint,
 		TableName:  name,
+		TableHint:  tableHint,
 		As:         as,
 		Updates:    items,
 		Where:      where,


Test patch:
diff --git a/testdata/input/ddl/create_table_with_auto_increment.sql b/testdata/input/ddl/create_table_with_auto_increment.sql
new file mode 100644
index 0000000..2612813
--- /dev/null
+++ b/testdata/input/ddl/create_table_with_auto_increment.sql
@@ -0,0 +1,3 @@
+create table foo (
+    id int64 not null auto_increment primary key
+)
diff --git a/testdata/input/dml/delete_with_hint.sql b/testdata/input/dml/delete_with_hint.sql
index cf4638f..8d3ce40 100644
--- a/testdata/input/dml/delete_with_hint.sql
+++ b/testdata/input/dml/delete_with_hint.sql
@@ -1,1 +1,1 @@
-@{pdml_max_parallelism=1} delete foo where foo = 1 and bar = 2
\ No newline at end of file
+@{pdml_max_parallelism=1} delete foo@{force_index=_base_table} where foo = 1 and bar = 2
\ No newline at end of file
diff --git a/testdata/input/dml/insert_with_hint.sql b/testdata/input/dml/insert_with_hint.sql
index 7f55e0e..8056ef2 100644
--- a/testdata/input/dml/insert_with_hint.sql
+++ b/testdata/input/dml/insert_with_hint.sql
@@ -1,4 +1,4 @@
 @{pdml_max_parallelism=1}
-insert into foo (foo, bar, baz)
+insert into foo@{force_index=_base_table} (foo, bar, baz)
 values (1, 2, 3),
        (4, 5, 6)
\ No newline at end of file
diff --git a/testdata/input/dml/update_where_select_for_update.sql b/testdata/input/dml/update_where_select_for_update.sql
new file mode 100644
index 0000000..0bb80fa
--- /dev/null
+++ b/testdata/input/dml/update_where_select_for_update.sql
@@ -0,0 +1,2 @@
+update Albums set MarketingBudget = MarketingBudget + 100
+where (SingerId, AlbumId) = (select as struct SingerId, AlbumId from Albums where AlbumTitle like "A%" for update)
\ No newline at end of file
diff --git a/testdata/input/dml/update_with_hint.sql b/testdata/input/dml/update_with_hint.sql
index f12abd8..8ecd72c 100644
--- a/testdata/input/dml/update_with_hint.sql
+++ b/testdata/input/dml/update_with_hint.sql
@@ -1,2 +1,2 @@
 @{pdml_max_parallelism=1}
-update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
\ No newline at end of file
+update foo@{force_index=_base_table} set foo = bar, bar = foo, baz = DEFAULT where foo = 1
\ No newline at end of file
diff --git a/testdata/input/query/select_for_update.sql b/testdata/input/query/select_for_update.sql
new file mode 100644
index 0000000..741bfa9
--- /dev/null
+++ b/testdata/input/query/select_for_update.sql
@@ -0,0 +1,4 @@
+SELECT MarketingBudget
+FROM Albums
+WHERE SingerId = 1 and AlbumId = 1
+FOR UPDATE
\ No newline at end of file
diff --git a/testdata/input/query/select_for_update_in_subquery.sql b/testdata/input/query/select_for_update_in_subquery.sql
new file mode 100644
index 0000000..a22e49f
--- /dev/null
+++ b/testdata/input/query/select_for_update_in_subquery.sql
@@ -0,0 +1,4 @@
+SELECT s.SingerId, s.FirstName, s.LastName FROM Singers AS s
+JOIN
+(SELECT SingerId FROM Albums WHERE MarketingBudget > 100000 FOR UPDATE) AS a
+ON a.SingerId = s.SingerId
diff --git a/testdata/result/ddl/create_table_with_auto_increment.sql.txt b/testdata/result/ddl/create_table_with_auto_increment.sql.txt
new file mode 100644
index 0000000..3e886cc
--- /dev/null
+++ b/testdata/result/ddl/create_table_with_auto_increment.sql.txt
@@ -0,0 +1,45 @@
+--- create_table_with_auto_increment.sql
+create table foo (
+    id int64 not null auto_increment primary key
+)
+
+--- AST
+&ast.CreateTable{
+  Rparen:           68,
+  PrimaryKeyRparen: -1,
+  Name:             &ast.Path{
+    Idents: []*ast.Ident{
+      &ast.Ident{
+        NamePos: 13,
+        NameEnd: 16,
+        Name:    "foo",
+      },
+    },
+  },
+  Columns: []*ast.ColumnDef{
+    &ast.ColumnDef{
+      Null: 36,
+      Key:  64,
+      Name: &ast.Ident{
+        NamePos: 23,
+        NameEnd: 25,
+        Name:    "id",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 26,
+        Name:    "INT64",
+      },
+      NotNull:          true,
+      PrimaryKey:       true,
+      DefaultSemantics: &ast.AutoIncrement{
+        AutoIncrement: 41,
+      },
+      Hidden: -1,
+    },
+  },
+}
+
+--- SQL
+CREATE TABLE foo (
+  id INT64 NOT NULL AUTO_INCREMENT PRIMARY KEY
+)
diff --git a/testdata/result/dml/delete_with_hint.sql.txt b/testdata/result/dml/delete_with_hint.sql.txt
index a255d5b..f1884ca 100644
--- a/testdata/result/dml/delete_with_hint.sql.txt
+++ b/testdata/result/dml/delete_with_hint.sql.txt
@@ -1,5 +1,5 @@
 --- delete_with_hint.sql
-@{pdml_max_parallelism=1} delete foo where foo = 1 and bar = 2
+@{pdml_max_parallelism=1} delete foo@{force_index=_base_table} where foo = 1 and bar = 2
 --- AST
 &ast.Delete{
   Delete: 26,
@@ -34,20 +34,42 @@
       },
     },
   },
+  TableHint: &ast.Hint{
+    Atmark:  36,
+    Rbrace:  61,
+    Records: []*ast.HintRecord{
+      &ast.HintRecord{
+        Key: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 38,
+              NameEnd: 49,
+              Name:    "force_index",
+            },
+          },
+        },
+        Value: &ast.Ident{
+          NamePos: 50,
+          NameEnd: 61,
+          Name:    "_base_table",
+        },
+      },
+    },
+  },
   Where: &ast.Where{
-    Where: 37,
+    Where: 63,
     Expr:  &ast.BinaryExpr{
       Op:   "AND",
       Left: &ast.BinaryExpr{
         Op:   "=",
         Left: &ast.Ident{
-          NamePos: 43,
-          NameEnd: 46,
+          NamePos: 69,
+          NameEnd: 72,
           Name:    "foo",
         },
         Right: &ast.IntLiteral{
-          ValuePos: 49,
-          ValueEnd: 50,
+          ValuePos: 75,
+          ValueEnd: 76,
           Base:     10,
           Value:    "1",
         },
@@ -55,13 +77,13 @@
       Right: &ast.BinaryExpr{
         Op:   "=",
         Left: &ast.Ident{
-          NamePos: 55,
-          NameEnd: 58,
+          NamePos: 81,
+          NameEnd: 84,
           Name:    "bar",
         },
         Right: &ast.IntLiteral{
-          ValuePos: 61,
-          ValueEnd: 62,
+          ValuePos: 87,
+          ValueEnd: 88,
           Base:     10,
           Value:    "2",
         },
@@ -71,4 +93,4 @@
 }
 
 --- SQL
-@{pdml_max_parallelism=1} DELETE FROM foo WHERE foo = 1 AND bar = 2
+@{pdml_max_parallelism=1} DELETE FROM foo@{force_index=_base_table} WHERE foo = 1 AND bar = 2
diff --git a/testdata/result/dml/insert_with_hint.sql.txt b/testdata/result/dml/insert_with_hint.sql.txt
index 463fac3..c64b701 100644
--- a/testdata/result/dml/insert_with_hint.sql.txt
+++ b/testdata/result/dml/insert_with_hint.sql.txt
@@ -1,6 +1,6 @@
 --- insert_with_hint.sql
 @{pdml_max_parallelism=1}
-insert into foo (foo, bar, baz)
+insert into foo@{force_index=_base_table} (foo, bar, baz)
 values (1, 2, 3),
        (4, 5, 6)
 --- AST
@@ -37,35 +37,57 @@ values (1, 2, 3),
       },
     },
   },
+  TableHint: &ast.Hint{
+    Atmark:  41,
+    Rbrace:  66,
+    Records: []*ast.HintRecord{
+      &ast.HintRecord{
+        Key: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 43,
+              NameEnd: 54,
+              Name:    "force_index",
+            },
+          },
+        },
+        Value: &ast.Ident{
+          NamePos: 55,
+          NameEnd: 66,
+          Name:    "_base_table",
+        },
+      },
+    },
+  },
   Columns: []*ast.Ident{
     &ast.Ident{
-      NamePos: 43,
-      NameEnd: 46,
+      NamePos: 69,
+      NameEnd: 72,
       Name:    "foo",
     },
     &ast.Ident{
-      NamePos: 48,
-      NameEnd: 51,
+      NamePos: 74,
+      NameEnd: 77,
       Name:    "bar",
     },
     &ast.Ident{
-      NamePos: 53,
-      NameEnd: 56,
+      NamePos: 79,
+      NameEnd: 82,
       Name:    "baz",
     },
   },
   Input: &ast.ValuesInput{
-    Values: 58,
+    Values: 84,
     Rows:   []*ast.ValuesRow{
       &ast.ValuesRow{
-        Lparen: 65,
-        Rparen: 73,
+        Lparen: 91,
+        Rparen: 99,
         Exprs:  []*ast.DefaultExpr{
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 66,
-              ValueEnd: 67,
+              ValuePos: 92,
+              ValueEnd: 93,
               Base:     10,
               Value:    "1",
             },
@@ -73,8 +95,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 69,
-              ValueEnd: 70,
+              ValuePos: 95,
+              ValueEnd: 96,
               Base:     10,
               Value:    "2",
             },
@@ -82,8 +104,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 72,
-              ValueEnd: 73,
+              ValuePos: 98,
+              ValueEnd: 99,
               Base:     10,
               Value:    "3",
             },
@@ -91,14 +113,14 @@ values (1, 2, 3),
         },
       },
       &ast.ValuesRow{
-        Lparen: 83,
-        Rparen: 91,
+        Lparen: 109,
+        Rparen: 117,
         Exprs:  []*ast.DefaultExpr{
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 84,
-              ValueEnd: 85,
+              ValuePos: 110,
+              ValueEnd: 111,
               Base:     10,
               Value:    "4",
             },
@@ -106,8 +128,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 87,
-              ValueEnd: 88,
+              ValuePos: 113,
+              ValueEnd: 114,
               Base:     10,
               Value:    "5",
             },
@@ -115,8 +137,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 90,
-              ValueEnd: 91,
+              ValuePos: 116,
+              ValueEnd: 117,
               Base:     10,
               Value:    "6",
             },
@@ -128,4 +150,4 @@ values (1, 2, 3),
 }
 
 --- SQL
-@{pdml_max_parallelism=1} INSERT INTO foo (foo, bar, baz) VALUES (1, 2, 3), (4, 5, 6)
+@{pdml_max_parallelism=1} INSERT INTO foo@{force_index=_base_table} (foo, bar, baz) VALUES (1, 2, 3), (4, 5, 6)
diff --git a/testdata/result/dml/update_where_select_for_update.sql.txt b/testdata/result/dml/update_where_select_for_update.sql.txt
new file mode 100644
index 0000000..0ca1240
--- /dev/null
+++ b/testdata/result/dml/update_where_select_for_update.sql.txt
@@ -0,0 +1,127 @@
+--- update_where_select_for_update.sql
+update Albums set MarketingBudget = MarketingBudget + 100
+where (SingerId, AlbumId) = (select as struct SingerId, AlbumId from Albums where AlbumTitle like "A%" for update)
+--- AST
+&ast.Update{
+  TableName: &ast.Path{
+    Idents: []*ast.Ident{
+      &ast.Ident{
+        NamePos: 7,
+        NameEnd: 13,
+        Name:    "Albums",
+      },
+    },
+  },
+  Updates: []*ast.UpdateItem{
+    &ast.UpdateItem{
+      Path: []*ast.Ident{
+        &ast.Ident{
+          NamePos: 18,
+          NameEnd: 33,
+          Name:    "MarketingBudget",
+        },
+      },
+      DefaultExpr: &ast.DefaultExpr{
+        DefaultPos: -1,
+        Expr:       &ast.BinaryExpr{
+          Op:   "+",
+          Left: &ast.Ident{
+            NamePos: 36,
+            NameEnd: 51,
+            Name:    "MarketingBudget",
+          },
+          Right: &ast.IntLiteral{
+            ValuePos: 54,
+            ValueEnd: 57,
+            Base:     10,
+            Value:    "100",
+          },
+        },
+      },
+    },
+  },
+  Where: &ast.Where{
+    Where: 58,
+    Expr:  &ast.BinaryExpr{
+      Op:   "=",
+      Left: &ast.TupleStructLiteral{
+        Lparen: 64,
+        Rparen: 82,
+        Values: []ast.Expr{
+          &ast.Ident{
+            NamePos: 65,
+            NameEnd: 73,
+            Name:    "SingerId",
+          },
+          &ast.Ident{
+            NamePos: 75,
+            NameEnd: 82,
+            Name:    "AlbumId",
+          },
+        },
+      },
+      Right: &ast.ScalarSubQuery{
+        Lparen: 86,
+        Rparen: 171,
+        Query:  &ast.Query{
+          Query: &ast.Select{
+            Select: 87,
+            As:     &ast.AsStruct{
+              As:     94,
+              Struct: 97,
+            },
+            Results: []ast.SelectItem{
+              &ast.ExprSelectItem{
+                Expr: &ast.Ident{
+                  NamePos: 104,
+                  NameEnd: 112,
+                  Name:    "SingerId",
+                },
+              },
+              &ast.ExprSelectItem{
+                Expr: &ast.Ident{
+                  NamePos: 114,
+                  NameEnd: 121,
+                  Name:    "AlbumId",
+                },
+              },
+            },
+            From: &ast.From{
+              From:   122,
+              Source: &ast.TableName{
+                Table: &ast.Ident{
+                  NamePos: 127,
+                  NameEnd: 133,
+                  Name:    "Albums",
+                },
+              },
+            },
+            Where: &ast.Where{
+              Where: 134,
+              Expr:  &ast.BinaryExpr{
+                Op:   "LIKE",
+                Left: &ast.Ident{
+                  NamePos: 140,
+                  NameEnd: 150,
+                  Name:    "AlbumTitle",
+                },
+                Right: &ast.StringLiteral{
+                  ValuePos: 156,
+                  ValueEnd: 160,
+                  Value:    "A%",
+                },
+              },
+            },
+          },
+          ForUpdate: &ast.ForUpdate{
+            For:    161,
+            Update: 165,
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+UPDATE Albums SET MarketingBudget = MarketingBudget + 100 WHERE (SingerId, AlbumId) = (SELECT AS STRUCT SingerId, AlbumId FROM Albums WHERE AlbumTitle LIKE "A%" FOR UPDATE)
diff --git a/testdata/result/dml/update_with_hint.sql.txt b/testdata/result/dml/update_with_hint.sql.txt
index a8829eb..2b75187 100644
--- a/testdata/result/dml/update_with_hint.sql.txt
+++ b/testdata/result/dml/update_with_hint.sql.txt
@@ -1,6 +1,6 @@
 --- update_with_hint.sql
 @{pdml_max_parallelism=1}
-update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
+update foo@{force_index=_base_table} set foo = bar, bar = foo, baz = DEFAULT where foo = 1
 --- AST
 &ast.Update{
   Update: 26,
@@ -35,20 +35,42 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
       },
     },
   },
+  TableHint: &ast.Hint{
+    Atmark:  36,
+    Rbrace:  61,
+    Records: []*ast.HintRecord{
+      &ast.HintRecord{
+        Key: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 38,
+              NameEnd: 49,
+              Name:    "force_index",
+            },
+          },
+        },
+        Value: &ast.Ident{
+          NamePos: 50,
+          NameEnd: 61,
+          Name:    "_base_table",
+        },
+      },
+    },
+  },
   Updates: []*ast.UpdateItem{
     &ast.UpdateItem{
       Path: []*ast.Ident{
         &ast.Ident{
-          NamePos: 41,
-          NameEnd: 44,
+          NamePos: 67,
+          NameEnd: 70,
           Name:    "foo",
         },
       },
       DefaultExpr: &ast.DefaultExpr{
         DefaultPos: -1,
         Expr:       &ast.Ident{
-          NamePos: 47,
-          NameEnd: 50,
+          NamePos: 73,
+          NameEnd: 76,
           Name:    "bar",
         },
       },
@@ -56,16 +78,16 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
     &ast.UpdateItem{
       Path: []*ast.Ident{
         &ast.Ident{
-          NamePos: 52,
-          NameEnd: 55,
+          NamePos: 78,
+          NameEnd: 81,
           Name:    "bar",
         },
       },
       DefaultExpr: &ast.DefaultExpr{
         DefaultPos: -1,
         Expr:       &ast.Ident{
-          NamePos: 58,
-          NameEnd: 61,
+          NamePos: 84,
+          NameEnd: 87,
           Name:    "foo",
         },
       },
@@ -73,29 +95,29 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
     &ast.UpdateItem{
       Path: []*ast.Ident{
         &ast.Ident{
-          NamePos: 63,
-          NameEnd: 66,
+          NamePos: 89,
+          NameEnd: 92,
           Name:    "baz",
         },
       },
       DefaultExpr: &ast.DefaultExpr{
-        DefaultPos: 69,
+        DefaultPos: 95,
         Default:    true,
       },
     },
   },
   Where: &ast.Where{
-    Where: 77,
+    Where: 103,
     Expr:  &ast.BinaryExpr{
       Op:   "=",
       Left: &ast.Ident{
-        NamePos: 83,
-        NameEnd: 86,
+        NamePos: 109,
+        NameEnd: 112,
         Name:    "foo",
       },
       Right: &ast.IntLiteral{
-        ValuePos: 89,
-        ValueEnd: 90,
+        ValuePos: 115,
+        ValueEnd: 116,
         Base:     10,
         Value:    "1",
       },
@@ -104,4 +126,4 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
 }
 
 --- SQL
-@{pdml_max_parallelism=1} UPDATE foo SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1
+@{pdml_max_parallelism=1} UPDATE foo@{force_index=_base_table} SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1
diff --git a/testdata/result/query/select_for_update.sql.txt b/testdata/result/query/select_for_update.sql.txt
new file mode 100644
index 0000000..c3a82a3
--- /dev/null
+++ b/testdata/result/query/select_for_update.sql.txt
@@ -0,0 +1,72 @@
+--- select_for_update.sql
+SELECT MarketingBudget
+FROM Albums
+WHERE SingerId = 1 and AlbumId = 1
+FOR UPDATE
+--- AST
+&ast.QueryStatement{
+  Query: &ast.Query{
+    Query: &ast.Select{
+      Results: []ast.SelectItem{
+        &ast.ExprSelectItem{
+          Expr: &ast.Ident{
+            NamePos: 7,
+            NameEnd: 22,
+            Name:    "MarketingBudget",
+          },
+        },
+      },
+      From: &ast.From{
+        From:   23,
+        Source: &ast.TableName{
+          Table: &ast.Ident{
+            NamePos: 28,
+            NameEnd: 34,
+            Name:    "Albums",
+          },
+        },
+      },
+      Where: &ast.Where{
+        Where: 35,
+        Expr:  &ast.BinaryExpr{
+          Op:   "AND",
+          Left: &ast.BinaryExpr{
+            Op:   "=",
+            Left: &ast.Ident{
+              NamePos: 41,
+              NameEnd: 49,
+              Name:    "SingerId",
+            },
+            Right: &ast.IntLiteral{
+              ValuePos: 52,
+              ValueEnd: 53,
+              Base:     10,
+              Value:    "1",
+            },
+          },
+          Right: &ast.BinaryExpr{
+            Op:   "=",
+            Left: &ast.Ident{
+              NamePos: 58,
+              NameEnd: 65,
+              Name:    "AlbumId",
+            },
+            Right: &ast.IntLiteral{
+              ValuePos: 68,
+              ValueEnd: 69,
+              Base:     10,
+              Value:    "1",
+            },
+          },
+        },
+      },
+    },
+    ForUpdate: &ast.ForUpdate{
+      For:    70,
+      Update: 74,
+    },
+  },
+}
+
+--- SQL
+SELECT MarketingBudget FROM Albums WHERE SingerId = 1 AND AlbumId = 1 FOR UPDATE
diff --git a/testdata/result/query/select_for_update_in_subquery.sql.txt b/testdata/result/query/select_for_update_in_subquery.sql.txt
new file mode 100644
index 0000000..63f4700
--- /dev/null
+++ b/testdata/result/query/select_for_update_in_subquery.sql.txt
@@ -0,0 +1,176 @@
+--- select_for_update_in_subquery.sql
+SELECT s.SingerId, s.FirstName, s.LastName FROM Singers AS s
+JOIN
+(SELECT SingerId FROM Albums WHERE MarketingBudget > 100000 FOR UPDATE) AS a
+ON a.SingerId = s.SingerId
+
+--- AST
+&ast.QueryStatement{
+  Query: &ast.Select{
+    Results: []ast.SelectItem{
+      &ast.ExprSelectItem{
+        Expr: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 7,
+              NameEnd: 8,
+              Name:    "s",
+            },
+            &ast.Ident{
+              NamePos: 9,
+              NameEnd: 17,
+              Name:    "SingerId",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 19,
+              NameEnd: 20,
+              Name:    "s",
+            },
+            &ast.Ident{
+              NamePos: 21,
+              NameEnd: 30,
+              Name:    "FirstName",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 32,
+              NameEnd: 33,
+              Name:    "s",
+            },
+            &ast.Ident{
+              NamePos: 34,
+              NameEnd: 42,
+              Name:    "LastName",
+            },
+          },
+        },
+      },
+    },
+    From: &ast.From{
+      From:   43,
+      Source: &ast.Join{
+        Left: &ast.TableName{
+          Table: &ast.Ident{
+            NamePos: 48,
+            NameEnd: 55,
+            Name:    "Singers",
+          },
+          As: &ast.AsAlias{
+            As:    56,
+            Alias: &ast.Ident{
+              NamePos: 59,
+              NameEnd: 60,
+              Name:    "s",
+            },
+          },
+        },
+        Op:    "INNER JOIN",
+        Right: &ast.SubQueryTableExpr{
+          Lparen: 66,
+          Rparen: 136,
+          Query:  &ast.Query{
+            Query: &ast.Select{
+              Select:  67,
+              Results: []ast.SelectItem{
+                &ast.ExprSelectItem{
+                  Expr: &ast.Ident{
+                    NamePos: 74,
+                    NameEnd: 82,
+                    Name:    "SingerId",
+                  },
+                },
+              },
+              From: &ast.From{
+                From:   83,
+                Source: &ast.TableName{
+                  Table: &ast.Ident{
+                    NamePos: 88,
+                    NameEnd: 94,
+                    Name:    "Albums",
+                  },
+                },
+              },
+              Where: &ast.Where{
+                Where: 95,
+                Expr:  &ast.BinaryExpr{
+                  Op:   ">",
+                  Left: &ast.Ident{
+                    NamePos: 101,
+                    NameEnd: 116,
+                    Name:    "MarketingBudget",
+                  },
+                  Right: &ast.IntLiteral{
+                    ValuePos: 119,
+                    ValueEnd: 125,
+                    Base:     10,
+                    Value:    "100000",
+                  },
+                },
+              },
+            },
+            ForUpdate: &ast.ForUpdate{
+              For:    126,
+              Update: 130,
+            },
+          },
+          As: &ast.AsAlias{
+            As:    138,
+            Alias: &ast.Ident{
+              NamePos: 141,
+              NameEnd: 142,
+              Name:    "a",
+            },
+          },
+        },
+        Cond: &ast.On{
+          On:   143,
+          Expr: &ast.BinaryExpr{
+            Op:   "=",
+            Left: &ast.Path{
+              Idents: []*ast.Ident{
+                &ast.Ident{
+                  NamePos: 146,
+                  NameEnd: 147,
+                  Name:    "a",
+                },
+                &ast.Ident{
+                  NamePos: 148,
+                  NameEnd: 156,
+                  Name:    "SingerId",
+                },
+              },
+            },
+            Right: &ast.Path{
+              Idents: []*ast.Ident{
+                &ast.Ident{
+                  NamePos: 159,
+                  NameEnd: 160,
+                  Name:    "s",
+                },
+                &ast.Ident{
+                  NamePos: 161,
+                  NameEnd: 169,
+                  Name:    "SingerId",
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+SELECT s.SingerId, s.FirstName, s.LastName FROM Singers AS s INNER JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000 FOR UPDATE) AS a ON a.SingerId = s.SingerId
diff --git a/testdata/result/statement/create_table_with_auto_increment.sql.txt b/testdata/result/statement/create_table_with_auto_increment.sql.txt
new file mode 100644
index 0000000..3e886cc
--- /dev/null
+++ b/testdata/result/statement/create_table_with_auto_increment.sql.txt
@@ -0,0 +1,45 @@
+--- create_table_with_auto_increment.sql
+create table foo (
+    id int64 not null auto_increment primary key
+)
+
+--- AST
+&ast.CreateTable{
+  Rparen:           68,
+  PrimaryKeyRparen: -1,
+  Name:             &ast.Path{
+    Idents: []*ast.Ident{
+      &ast.Ident{
+        NamePos: 13,
+        NameEnd: 16,
+        Name:    "foo",
+      },
+    },
+  },
+  Columns: []*ast.ColumnDef{
+    &ast.ColumnDef{
+      Null: 36,
+      Key:  64,
+      Name: &ast.Ident{
+        NamePos: 23,
+        NameEnd: 25,
+        Name:    "id",
+      },
+      Type: &ast.ScalarSchemaType{
+        NamePos: 26,
+        Name:    "INT64",
+      },
+      NotNull:          true,
+      PrimaryKey:       true,
+      DefaultSemantics: &ast.AutoIncrement{
+        AutoIncrement: 41,
+      },
+      Hidden: -1,
+    },
+  },
+}
+
+--- SQL
+CREATE TABLE foo (
+  id INT64 NOT NULL AUTO_INCREMENT PRIMARY KEY
+)
diff --git a/testdata/result/statement/delete_with_hint.sql.txt b/testdata/result/statement/delete_with_hint.sql.txt
index a255d5b..f1884ca 100644
--- a/testdata/result/statement/delete_with_hint.sql.txt
+++ b/testdata/result/statement/delete_with_hint.sql.txt
@@ -1,5 +1,5 @@
 --- delete_with_hint.sql
-@{pdml_max_parallelism=1} delete foo where foo = 1 and bar = 2
+@{pdml_max_parallelism=1} delete foo@{force_index=_base_table} where foo = 1 and bar = 2
 --- AST
 &ast.Delete{
   Delete: 26,
@@ -34,20 +34,42 @@
       },
     },
   },
+  TableHint: &ast.Hint{
+    Atmark:  36,
+    Rbrace:  61,
+    Records: []*ast.HintRecord{
+      &ast.HintRecord{
+        Key: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 38,
+              NameEnd: 49,
+              Name:    "force_index",
+            },
+          },
+        },
+        Value: &ast.Ident{
+          NamePos: 50,
+          NameEnd: 61,
+          Name:    "_base_table",
+        },
+      },
+    },
+  },
   Where: &ast.Where{
-    Where: 37,
+    Where: 63,
     Expr:  &ast.BinaryExpr{
       Op:   "AND",
       Left: &ast.BinaryExpr{
         Op:   "=",
         Left: &ast.Ident{
-          NamePos: 43,
-          NameEnd: 46,
+          NamePos: 69,
+          NameEnd: 72,
           Name:    "foo",
         },
         Right: &ast.IntLiteral{
-          ValuePos: 49,
-          ValueEnd: 50,
+          ValuePos: 75,
+          ValueEnd: 76,
           Base:     10,
           Value:    "1",
         },
@@ -55,13 +77,13 @@
       Right: &ast.BinaryExpr{
         Op:   "=",
         Left: &ast.Ident{
-          NamePos: 55,
-          NameEnd: 58,
+          NamePos: 81,
+          NameEnd: 84,
           Name:    "bar",
         },
         Right: &ast.IntLiteral{
-          ValuePos: 61,
-          ValueEnd: 62,
+          ValuePos: 87,
+          ValueEnd: 88,
           Base:     10,
           Value:    "2",
         },
@@ -71,4 +93,4 @@
 }
 
 --- SQL
-@{pdml_max_parallelism=1} DELETE FROM foo WHERE foo = 1 AND bar = 2
+@{pdml_max_parallelism=1} DELETE FROM foo@{force_index=_base_table} WHERE foo = 1 AND bar = 2
diff --git a/testdata/result/statement/insert_with_hint.sql.txt b/testdata/result/statement/insert_with_hint.sql.txt
index 463fac3..c64b701 100644
--- a/testdata/result/statement/insert_with_hint.sql.txt
+++ b/testdata/result/statement/insert_with_hint.sql.txt
@@ -1,6 +1,6 @@
 --- insert_with_hint.sql
 @{pdml_max_parallelism=1}
-insert into foo (foo, bar, baz)
+insert into foo@{force_index=_base_table} (foo, bar, baz)
 values (1, 2, 3),
        (4, 5, 6)
 --- AST
@@ -37,35 +37,57 @@ values (1, 2, 3),
       },
     },
   },
+  TableHint: &ast.Hint{
+    Atmark:  41,
+    Rbrace:  66,
+    Records: []*ast.HintRecord{
+      &ast.HintRecord{
+        Key: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 43,
+              NameEnd: 54,
+              Name:    "force_index",
+            },
+          },
+        },
+        Value: &ast.Ident{
+          NamePos: 55,
+          NameEnd: 66,
+          Name:    "_base_table",
+        },
+      },
+    },
+  },
   Columns: []*ast.Ident{
     &ast.Ident{
-      NamePos: 43,
-      NameEnd: 46,
+      NamePos: 69,
+      NameEnd: 72,
       Name:    "foo",
     },
     &ast.Ident{
-      NamePos: 48,
-      NameEnd: 51,
+      NamePos: 74,
+      NameEnd: 77,
       Name:    "bar",
     },
     &ast.Ident{
-      NamePos: 53,
-      NameEnd: 56,
+      NamePos: 79,
+      NameEnd: 82,
       Name:    "baz",
     },
   },
   Input: &ast.ValuesInput{
-    Values: 58,
+    Values: 84,
     Rows:   []*ast.ValuesRow{
       &ast.ValuesRow{
-        Lparen: 65,
-        Rparen: 73,
+        Lparen: 91,
+        Rparen: 99,
         Exprs:  []*ast.DefaultExpr{
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 66,
-              ValueEnd: 67,
+              ValuePos: 92,
+              ValueEnd: 93,
               Base:     10,
               Value:    "1",
             },
@@ -73,8 +95,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 69,
-              ValueEnd: 70,
+              ValuePos: 95,
+              ValueEnd: 96,
               Base:     10,
               Value:    "2",
             },
@@ -82,8 +104,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 72,
-              ValueEnd: 73,
+              ValuePos: 98,
+              ValueEnd: 99,
               Base:     10,
               Value:    "3",
             },
@@ -91,14 +113,14 @@ values (1, 2, 3),
         },
       },
       &ast.ValuesRow{
-        Lparen: 83,
-        Rparen: 91,
+        Lparen: 109,
+        Rparen: 117,
         Exprs:  []*ast.DefaultExpr{
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 84,
-              ValueEnd: 85,
+              ValuePos: 110,
+              ValueEnd: 111,
               Base:     10,
               Value:    "4",
             },
@@ -106,8 +128,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 87,
-              ValueEnd: 88,
+              ValuePos: 113,
+              ValueEnd: 114,
               Base:     10,
               Value:    "5",
             },
@@ -115,8 +137,8 @@ values (1, 2, 3),
           &ast.DefaultExpr{
             DefaultPos: -1,
             Expr:       &ast.IntLiteral{
-              ValuePos: 90,
-              ValueEnd: 91,
+              ValuePos: 116,
+              ValueEnd: 117,
               Base:     10,
               Value:    "6",
             },
@@ -128,4 +150,4 @@ values (1, 2, 3),
 }
 
 --- SQL
-@{pdml_max_parallelism=1} INSERT INTO foo (foo, bar, baz) VALUES (1, 2, 3), (4, 5, 6)
+@{pdml_max_parallelism=1} INSERT INTO foo@{force_index=_base_table} (foo, bar, baz) VALUES (1, 2, 3), (4, 5, 6)
diff --git a/testdata/result/statement/select_for_update.sql.txt b/testdata/result/statement/select_for_update.sql.txt
new file mode 100644
index 0000000..c3a82a3
--- /dev/null
+++ b/testdata/result/statement/select_for_update.sql.txt
@@ -0,0 +1,72 @@
+--- select_for_update.sql
+SELECT MarketingBudget
+FROM Albums
+WHERE SingerId = 1 and AlbumId = 1
+FOR UPDATE
+--- AST
+&ast.QueryStatement{
+  Query: &ast.Query{
+    Query: &ast.Select{
+      Results: []ast.SelectItem{
+        &ast.ExprSelectItem{
+          Expr: &ast.Ident{
+            NamePos: 7,
+            NameEnd: 22,
+            Name:    "MarketingBudget",
+          },
+        },
+      },
+      From: &ast.From{
+        From:   23,
+        Source: &ast.TableName{
+          Table: &ast.Ident{
+            NamePos: 28,
+            NameEnd: 34,
+            Name:    "Albums",
+          },
+        },
+      },
+      Where: &ast.Where{
+        Where: 35,
+        Expr:  &ast.BinaryExpr{
+          Op:   "AND",
+          Left: &ast.BinaryExpr{
+            Op:   "=",
+            Left: &ast.Ident{
+              NamePos: 41,
+              NameEnd: 49,
+              Name:    "SingerId",
+            },
+            Right: &ast.IntLiteral{
+              ValuePos: 52,
+              ValueEnd: 53,
+              Base:     10,
+              Value:    "1",
+            },
+          },
+          Right: &ast.BinaryExpr{
+            Op:   "=",
+            Left: &ast.Ident{
+              NamePos: 58,
+              NameEnd: 65,
+              Name:    "AlbumId",
+            },
+            Right: &ast.IntLiteral{
+              ValuePos: 68,
+              ValueEnd: 69,
+              Base:     10,
+              Value:    "1",
+            },
+          },
+        },
+      },
+    },
+    ForUpdate: &ast.ForUpdate{
+      For:    70,
+      Update: 74,
+    },
+  },
+}
+
+--- SQL
+SELECT MarketingBudget FROM Albums WHERE SingerId = 1 AND AlbumId = 1 FOR UPDATE
diff --git a/testdata/result/statement/select_for_update_in_subquery.sql.txt b/testdata/result/statement/select_for_update_in_subquery.sql.txt
new file mode 100644
index 0000000..63f4700
--- /dev/null
+++ b/testdata/result/statement/select_for_update_in_subquery.sql.txt
@@ -0,0 +1,176 @@
+--- select_for_update_in_subquery.sql
+SELECT s.SingerId, s.FirstName, s.LastName FROM Singers AS s
+JOIN
+(SELECT SingerId FROM Albums WHERE MarketingBudget > 100000 FOR UPDATE) AS a
+ON a.SingerId = s.SingerId
+
+--- AST
+&ast.QueryStatement{
+  Query: &ast.Select{
+    Results: []ast.SelectItem{
+      &ast.ExprSelectItem{
+        Expr: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 7,
+              NameEnd: 8,
+              Name:    "s",
+            },
+            &ast.Ident{
+              NamePos: 9,
+              NameEnd: 17,
+              Name:    "SingerId",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 19,
+              NameEnd: 20,
+              Name:    "s",
+            },
+            &ast.Ident{
+              NamePos: 21,
+              NameEnd: 30,
+              Name:    "FirstName",
+            },
+          },
+        },
+      },
+      &ast.ExprSelectItem{
+        Expr: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 32,
+              NameEnd: 33,
+              Name:    "s",
+            },
+            &ast.Ident{
+              NamePos: 34,
+              NameEnd: 42,
+              Name:    "LastName",
+            },
+          },
+        },
+      },
+    },
+    From: &ast.From{
+      From:   43,
+      Source: &ast.Join{
+        Left: &ast.TableName{
+          Table: &ast.Ident{
+            NamePos: 48,
+            NameEnd: 55,
+            Name:    "Singers",
+          },
+          As: &ast.AsAlias{
+            As:    56,
+            Alias: &ast.Ident{
+              NamePos: 59,
+              NameEnd: 60,
+              Name:    "s",
+            },
+          },
+        },
+        Op:    "INNER JOIN",
+        Right: &ast.SubQueryTableExpr{
+          Lparen: 66,
+          Rparen: 136,
+          Query:  &ast.Query{
+            Query: &ast.Select{
+              Select:  67,
+              Results: []ast.SelectItem{
+                &ast.ExprSelectItem{
+                  Expr: &ast.Ident{
+                    NamePos: 74,
+                    NameEnd: 82,
+                    Name:    "SingerId",
+                  },
+                },
+              },
+              From: &ast.From{
+                From:   83,
+                Source: &ast.TableName{
+                  Table: &ast.Ident{
+                    NamePos: 88,
+                    NameEnd: 94,
+                    Name:    "Albums",
+                  },
+                },
+              },
+              Where: &ast.Where{
+                Where: 95,
+                Expr:  &ast.BinaryExpr{
+                  Op:   ">",
+                  Left: &ast.Ident{
+                    NamePos: 101,
+                    NameEnd: 116,
+                    Name:    "MarketingBudget",
+                  },
+                  Right: &ast.IntLiteral{
+                    ValuePos: 119,
+                    ValueEnd: 125,
+                    Base:     10,
+                    Value:    "100000",
+                  },
+                },
+              },
+            },
+            ForUpdate: &ast.ForUpdate{
+              For:    126,
+              Update: 130,
+            },
+          },
+          As: &ast.AsAlias{
+            As:    138,
+            Alias: &ast.Ident{
+              NamePos: 141,
+              NameEnd: 142,
+              Name:    "a",
+            },
+          },
+        },
+        Cond: &ast.On{
+          On:   143,
+          Expr: &ast.BinaryExpr{
+            Op:   "=",
+            Left: &ast.Path{
+              Idents: []*ast.Ident{
+                &ast.Ident{
+                  NamePos: 146,
+                  NameEnd: 147,
+                  Name:    "a",
+                },
+                &ast.Ident{
+                  NamePos: 148,
+                  NameEnd: 156,
+                  Name:    "SingerId",
+                },
+              },
+            },
+            Right: &ast.Path{
+              Idents: []*ast.Ident{
+                &ast.Ident{
+                  NamePos: 159,
+                  NameEnd: 160,
+                  Name:    "s",
+                },
+                &ast.Ident{
+                  NamePos: 161,
+                  NameEnd: 169,
+                  Name:    "SingerId",
+                },
+              },
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+SELECT s.SingerId, s.FirstName, s.LastName FROM Singers AS s INNER JOIN (SELECT SingerId FROM Albums WHERE MarketingBudget > 100000 FOR UPDATE) AS a ON a.SingerId = s.SingerId
diff --git a/testdata/result/statement/update_where_select_for_update.sql.txt b/testdata/result/statement/update_where_select_for_update.sql.txt
new file mode 100644
index 0000000..0ca1240
--- /dev/null
+++ b/testdata/result/statement/update_where_select_for_update.sql.txt
@@ -0,0 +1,127 @@
+--- update_where_select_for_update.sql
+update Albums set MarketingBudget = MarketingBudget + 100
+where (SingerId, AlbumId) = (select as struct SingerId, AlbumId from Albums where AlbumTitle like "A%" for update)
+--- AST
+&ast.Update{
+  TableName: &ast.Path{
+    Idents: []*ast.Ident{
+      &ast.Ident{
+        NamePos: 7,
+        NameEnd: 13,
+        Name:    "Albums",
+      },
+    },
+  },
+  Updates: []*ast.UpdateItem{
+    &ast.UpdateItem{
+      Path: []*ast.Ident{
+        &ast.Ident{
+          NamePos: 18,
+          NameEnd: 33,
+          Name:    "MarketingBudget",
+        },
+      },
+      DefaultExpr: &ast.DefaultExpr{
+        DefaultPos: -1,
+        Expr:       &ast.BinaryExpr{
+          Op:   "+",
+          Left: &ast.Ident{
+            NamePos: 36,
+            NameEnd: 51,
+            Name:    "MarketingBudget",
+          },
+          Right: &ast.IntLiteral{
+            ValuePos: 54,
+            ValueEnd: 57,
+            Base:     10,
+            Value:    "100",
+          },
+        },
+      },
+    },
+  },
+  Where: &ast.Where{
+    Where: 58,
+    Expr:  &ast.BinaryExpr{
+      Op:   "=",
+      Left: &ast.TupleStructLiteral{
+        Lparen: 64,
+        Rparen: 82,
+        Values: []ast.Expr{
+          &ast.Ident{
+            NamePos: 65,
+            NameEnd: 73,
+            Name:    "SingerId",
+          },
+          &ast.Ident{
+            NamePos: 75,
+            NameEnd: 82,
+            Name:    "AlbumId",
+          },
+        },
+      },
+      Right: &ast.ScalarSubQuery{
+        Lparen: 86,
+        Rparen: 171,
+        Query:  &ast.Query{
+          Query: &ast.Select{
+            Select: 87,
+            As:     &ast.AsStruct{
+              As:     94,
+              Struct: 97,
+            },
+            Results: []ast.SelectItem{
+              &ast.ExprSelectItem{
+                Expr: &ast.Ident{
+                  NamePos: 104,
+                  NameEnd: 112,
+                  Name:    "SingerId",
+                },
+              },
+              &ast.ExprSelectItem{
+                Expr: &ast.Ident{
+                  NamePos: 114,
+                  NameEnd: 121,
+                  Name:    "AlbumId",
+                },
+              },
+            },
+            From: &ast.From{
+              From:   122,
+              Source: &ast.TableName{
+                Table: &ast.Ident{
+                  NamePos: 127,
+                  NameEnd: 133,
+                  Name:    "Albums",
+                },
+              },
+            },
+            Where: &ast.Where{
+              Where: 134,
+              Expr:  &ast.BinaryExpr{
+                Op:   "LIKE",
+                Left: &ast.Ident{
+                  NamePos: 140,
+                  NameEnd: 150,
+                  Name:    "AlbumTitle",
+                },
+                Right: &ast.StringLiteral{
+                  ValuePos: 156,
+                  ValueEnd: 160,
+                  Value:    "A%",
+                },
+              },
+            },
+          },
+          ForUpdate: &ast.ForUpdate{
+            For:    161,
+            Update: 165,
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+UPDATE Albums SET MarketingBudget = MarketingBudget + 100 WHERE (SingerId, AlbumId) = (SELECT AS STRUCT SingerId, AlbumId FROM Albums WHERE AlbumTitle LIKE "A%" FOR UPDATE)
diff --git a/testdata/result/statement/update_with_hint.sql.txt b/testdata/result/statement/update_with_hint.sql.txt
index a8829eb..2b75187 100644
--- a/testdata/result/statement/update_with_hint.sql.txt
+++ b/testdata/result/statement/update_with_hint.sql.txt
@@ -1,6 +1,6 @@
 --- update_with_hint.sql
 @{pdml_max_parallelism=1}
-update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
+update foo@{force_index=_base_table} set foo = bar, bar = foo, baz = DEFAULT where foo = 1
 --- AST
 &ast.Update{
   Update: 26,
@@ -35,20 +35,42 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
       },
     },
   },
+  TableHint: &ast.Hint{
+    Atmark:  36,
+    Rbrace:  61,
+    Records: []*ast.HintRecord{
+      &ast.HintRecord{
+        Key: &ast.Path{
+          Idents: []*ast.Ident{
+            &ast.Ident{
+              NamePos: 38,
+              NameEnd: 49,
+              Name:    "force_index",
+            },
+          },
+        },
+        Value: &ast.Ident{
+          NamePos: 50,
+          NameEnd: 61,
+          Name:    "_base_table",
+        },
+      },
+    },
+  },
   Updates: []*ast.UpdateItem{
     &ast.UpdateItem{
       Path: []*ast.Ident{
         &ast.Ident{
-          NamePos: 41,
-          NameEnd: 44,
+          NamePos: 67,
+          NameEnd: 70,
           Name:    "foo",
         },
       },
       DefaultExpr: &ast.DefaultExpr{
         DefaultPos: -1,
         Expr:       &ast.Ident{
-          NamePos: 47,
-          NameEnd: 50,
+          NamePos: 73,
+          NameEnd: 76,
           Name:    "bar",
         },
       },
@@ -56,16 +78,16 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
     &ast.UpdateItem{
       Path: []*ast.Ident{
         &ast.Ident{
-          NamePos: 52,
-          NameEnd: 55,
+          NamePos: 78,
+          NameEnd: 81,
           Name:    "bar",
         },
       },
       DefaultExpr: &ast.DefaultExpr{
         DefaultPos: -1,
         Expr:       &ast.Ident{
-          NamePos: 58,
-          NameEnd: 61,
+          NamePos: 84,
+          NameEnd: 87,
           Name:    "foo",
         },
       },
@@ -73,29 +95,29 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
     &ast.UpdateItem{
       Path: []*ast.Ident{
         &ast.Ident{
-          NamePos: 63,
-          NameEnd: 66,
+          NamePos: 89,
+          NameEnd: 92,
           Name:    "baz",
         },
       },
       DefaultExpr: &ast.DefaultExpr{
-        DefaultPos: 69,
+        DefaultPos: 95,
         Default:    true,
       },
     },
   },
   Where: &ast.Where{
-    Where: 77,
+    Where: 103,
     Expr:  &ast.BinaryExpr{
       Op:   "=",
       Left: &ast.Ident{
-        NamePos: 83,
-        NameEnd: 86,
+        NamePos: 109,
+        NameEnd: 112,
         Name:    "foo",
       },
       Right: &ast.IntLiteral{
-        ValuePos: 89,
-        ValueEnd: 90,
+        ValuePos: 115,
+        ValueEnd: 116,
         Base:     10,
         Value:    "1",
       },
@@ -104,4 +126,4 @@ update foo set foo = bar, bar = foo, baz = DEFAULT where foo = 1
 }
 
 --- SQL
-@{pdml_max_parallelism=1} UPDATE foo SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1
+@{pdml_max_parallelism=1} UPDATE foo@{force_index=_base_table} SET foo = bar, bar = foo, baz = DEFAULT WHERE foo = 1