Repository: cloudspannerecosystem/memefish (go)

Issue:
Support function hints
Function hints are not yet supported.

https://cloud.google.com/spanner/docs/reference/standard-sql/functions-reference#function_hints

```
$ go run ./tools/parse --mode query "SELECT
  SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
  SUBSTRING(CAST(x AS STRING), 3, 7) AS y
FROM (SELECT SHA512(z) @{DISABLE_INLINE = TRUE} AS x FROM t)"
2024/11/12 23:35:16 syntax error: :4:24: expected token: ), but: @

  4:  FROM (SELECT SHA512(z) @{DISABLE_INLINE = TRUE} AS x FROM t)
                             ^
exit status 1
```

Pull request description:
Support function hints
This PR implements function hints.

Note: The issue is reported by external developer, so it may be better to be prioritized.

## Related issues

- close #198

Patch:
diff --git a/ast/ast.go b/ast/ast.go
index 19980c7..b8ce7b0 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1145,9 +1145,10 @@ type SubscriptSpecifierKeyword struct {
 //		{{.NullHandling | sqlOpt}}
 //		{{.Having | sqlOpt}}
 //	)
+//	{{.Hint | sqlOpt}}
 type CallExpr struct {
 	// pos = Func.pos
-	// end = Rparen + 1
+	// end = Hint.end || Rparen + 1
 
 	Rparen token.Pos // position of ")"
 
@@ -1157,6 +1158,7 @@ type CallExpr struct {
 	NamedArgs    []*NamedArg
 	NullHandling NullHandlingModifier // optional
 	Having       HavingModifier       // optional
+	Hint         *Hint                // optional
 }
 
 // ExprArg is argument of the generic function call.
diff --git a/ast/pos.go b/ast/pos.go
index 0c26d41..2b455f4 100644
--- a/ast/pos.go
+++ b/ast/pos.go
@@ -395,7 +395,7 @@ func (c *CallExpr) Pos() token.Pos {
 }
 
 func (c *CallExpr) End() token.Pos {
-	return posAdd(c.Rparen, 1)
+	return posChoice(nodeEnd(wrapNode(c.Hint)), posAdd(c.Rparen, 1))
 }
 
 func (e *ExprArg) Pos() token.Pos {
diff --git a/ast/sql.go b/ast/sql.go
index 0ab1eea..9fad779 100644
--- a/ast/sql.go
+++ b/ast/sql.go
@@ -503,7 +503,8 @@ func (c *CallExpr) SQL() string {
 		sqlJoin(c.NamedArgs, ", ") +
 		sqlOpt(" ", c.NullHandling, "") +
 		sqlOpt(" ", c.Having, "") +
-		")"
+		")" +
+		sqlOpt(" ", c.Hint, "")
 }
 
 func (l *LambdaArg) SQL() string {
diff --git a/parser.go b/parser.go
index d41120a..fd97859 100644
--- a/parser.go
+++ b/parser.go
@@ -1553,6 +1553,8 @@ func (p *Parser) parseCall(id token.Token) ast.Expr {
 	having := p.tryParseHavingModifier()
 
 	rparen := p.expect(")").Pos
+	hint := p.tryParseHint()
+
 	return &ast.CallExpr{
 		Rparen:       rparen,
 		Func:         fn,
@@ -1561,6 +1563,7 @@ func (p *Parser) parseCall(id token.Token) ast.Expr {
 		NamedArgs:    namedArgs,
 		NullHandling: nullHandling,
 		Having:       having,
+		Hint:         hint,
 	}
 }
 


Test patch:
diff --git a/testdata/input/query/select_function_hints.sql b/testdata/input/query/select_function_hints.sql
new file mode 100644
index 0000000..6998ca9
--- /dev/null
+++ b/testdata/input/query/select_function_hints.sql
@@ -0,0 +1,5 @@
+-- https://cloud.google.com/spanner/docs/reference/standard-sql/functions-reference#function_hints
+SELECT
+    SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
+    SUBSTRING(CAST(x AS STRING), 3, 7) AS y
+FROM (SELECT SHA512(z) @{DISABLE_INLINE = TRUE} AS x FROM t)
\ No newline at end of file
diff --git a/testdata/result/query/select_function_hints.sql.txt b/testdata/result/query/select_function_hints.sql.txt
new file mode 100644
index 0000000..0aeff39
--- /dev/null
+++ b/testdata/result/query/select_function_hints.sql.txt
@@ -0,0 +1,185 @@
+--- select_function_hints.sql
+-- https://cloud.google.com/spanner/docs/reference/standard-sql/functions-reference#function_hints
+SELECT
+    SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
+    SUBSTRING(CAST(x AS STRING), 3, 7) AS y
+FROM (SELECT SHA512(z) @{DISABLE_INLINE = TRUE} AS x FROM t)
+--- AST
+&ast.QueryStatement{
+  Query: &ast.Select{
+    Select:  99,
+    Results: []ast.SelectItem{
+      &ast.Alias{
+        Expr: &ast.CallExpr{
+          Rparen: 143,
+          Func:   &ast.Ident{
+            NamePos: 110,
+            NameEnd: 119,
+            Name:    "SUBSTRING",
+          },
+          Args: []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.CastExpr{
+                Cast:   120,
+                Rparen: 136,
+                Expr:   &ast.Ident{
+                  NamePos: 125,
+                  NameEnd: 126,
+                  Name:    "x",
+                },
+                Type: &ast.SimpleType{
+                  NamePos: 130,
+                  Name:    "STRING",
+                },
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 139,
+                ValueEnd: 140,
+                Base:     10,
+                Value:    "2",
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 142,
+                ValueEnd: 143,
+                Base:     10,
+                Value:    "5",
+              },
+            },
+          },
+        },
+        As: &ast.AsAlias{
+          As:    145,
+          Alias: &ast.Ident{
+            NamePos: 148,
+            NameEnd: 149,
+            Name:    "w",
+          },
+        },
+      },
+      &ast.Alias{
+        Expr: &ast.CallExpr{
+          Rparen: 188,
+          Func:   &ast.Ident{
+            NamePos: 155,
+            NameEnd: 164,
+            Name:    "SUBSTRING",
+          },
+          Args: []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.CastExpr{
+                Cast:   165,
+                Rparen: 181,
+                Expr:   &ast.Ident{
+                  NamePos: 170,
+                  NameEnd: 171,
+                  Name:    "x",
+                },
+                Type: &ast.SimpleType{
+                  NamePos: 175,
+                  Name:    "STRING",
+                },
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 184,
+                ValueEnd: 185,
+                Base:     10,
+                Value:    "3",
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 187,
+                ValueEnd: 188,
+                Base:     10,
+                Value:    "7",
+              },
+            },
+          },
+        },
+        As: &ast.AsAlias{
+          As:    190,
+          Alias: &ast.Ident{
+            NamePos: 193,
+            NameEnd: 194,
+            Name:    "y",
+          },
+        },
+      },
+    },
+    From: &ast.From{
+      From:   195,
+      Source: &ast.SubQueryTableExpr{
+        Lparen: 200,
+        Rparen: 254,
+        Query:  &ast.Select{
+          Select:  201,
+          Results: []ast.SelectItem{
+            &ast.Alias{
+              Expr: &ast.CallExpr{
+                Rparen: 216,
+                Func:   &ast.Ident{
+                  NamePos: 208,
+                  NameEnd: 214,
+                  Name:    "SHA512",
+                },
+                Args: []ast.Arg{
+                  &ast.ExprArg{
+                    Expr: &ast.Ident{
+                      NamePos: 215,
+                      NameEnd: 216,
+                      Name:    "z",
+                    },
+                  },
+                },
+                Hint: &ast.Hint{
+                  Atmark:  218,
+                  Rbrace:  241,
+                  Records: []*ast.HintRecord{
+                    &ast.HintRecord{
+                      Key: &ast.Ident{
+                        NamePos: 220,
+                        NameEnd: 234,
+                        Name:    "DISABLE_INLINE",
+                      },
+                      Value: &ast.BoolLiteral{
+                        ValuePos: 237,
+                        Value:    true,
+                      },
+                    },
+                  },
+                },
+              },
+              As: &ast.AsAlias{
+                As:    243,
+                Alias: &ast.Ident{
+                  NamePos: 246,
+                  NameEnd: 247,
+                  Name:    "x",
+                },
+              },
+            },
+          },
+          From: &ast.From{
+            From:   248,
+            Source: &ast.TableName{
+              Table: &ast.Ident{
+                NamePos: 253,
+                NameEnd: 254,
+                Name:    "t",
+              },
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+SELECT SUBSTRING(CAST(x AS STRING), 2, 5) AS w, SUBSTRING(CAST(x AS STRING), 3, 7) AS y FROM (SELECT SHA512(z) @{DISABLE_INLINE=TRUE} AS x FROM t)
diff --git a/testdata/result/statement/select_function_hints.sql.txt b/testdata/result/statement/select_function_hints.sql.txt
new file mode 100644
index 0000000..0aeff39
--- /dev/null
+++ b/testdata/result/statement/select_function_hints.sql.txt
@@ -0,0 +1,185 @@
+--- select_function_hints.sql
+-- https://cloud.google.com/spanner/docs/reference/standard-sql/functions-reference#function_hints
+SELECT
+    SUBSTRING(CAST(x AS STRING), 2, 5) AS w,
+    SUBSTRING(CAST(x AS STRING), 3, 7) AS y
+FROM (SELECT SHA512(z) @{DISABLE_INLINE = TRUE} AS x FROM t)
+--- AST
+&ast.QueryStatement{
+  Query: &ast.Select{
+    Select:  99,
+    Results: []ast.SelectItem{
+      &ast.Alias{
+        Expr: &ast.CallExpr{
+          Rparen: 143,
+          Func:   &ast.Ident{
+            NamePos: 110,
+            NameEnd: 119,
+            Name:    "SUBSTRING",
+          },
+          Args: []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.CastExpr{
+                Cast:   120,
+                Rparen: 136,
+                Expr:   &ast.Ident{
+                  NamePos: 125,
+                  NameEnd: 126,
+                  Name:    "x",
+                },
+                Type: &ast.SimpleType{
+                  NamePos: 130,
+                  Name:    "STRING",
+                },
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 139,
+                ValueEnd: 140,
+                Base:     10,
+                Value:    "2",
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 142,
+                ValueEnd: 143,
+                Base:     10,
+                Value:    "5",
+              },
+            },
+          },
+        },
+        As: &ast.AsAlias{
+          As:    145,
+          Alias: &ast.Ident{
+            NamePos: 148,
+            NameEnd: 149,
+            Name:    "w",
+          },
+        },
+      },
+      &ast.Alias{
+        Expr: &ast.CallExpr{
+          Rparen: 188,
+          Func:   &ast.Ident{
+            NamePos: 155,
+            NameEnd: 164,
+            Name:    "SUBSTRING",
+          },
+          Args: []ast.Arg{
+            &ast.ExprArg{
+              Expr: &ast.CastExpr{
+                Cast:   165,
+                Rparen: 181,
+                Expr:   &ast.Ident{
+                  NamePos: 170,
+                  NameEnd: 171,
+                  Name:    "x",
+                },
+                Type: &ast.SimpleType{
+                  NamePos: 175,
+                  Name:    "STRING",
+                },
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 184,
+                ValueEnd: 185,
+                Base:     10,
+                Value:    "3",
+              },
+            },
+            &ast.ExprArg{
+              Expr: &ast.IntLiteral{
+                ValuePos: 187,
+                ValueEnd: 188,
+                Base:     10,
+                Value:    "7",
+              },
+            },
+          },
+        },
+        As: &ast.AsAlias{
+          As:    190,
+          Alias: &ast.Ident{
+            NamePos: 193,
+            NameEnd: 194,
+            Name:    "y",
+          },
+        },
+      },
+    },
+    From: &ast.From{
+      From:   195,
+      Source: &ast.SubQueryTableExpr{
+        Lparen: 200,
+        Rparen: 254,
+        Query:  &ast.Select{
+          Select:  201,
+          Results: []ast.SelectItem{
+            &ast.Alias{
+              Expr: &ast.CallExpr{
+                Rparen: 216,
+                Func:   &ast.Ident{
+                  NamePos: 208,
+                  NameEnd: 214,
+                  Name:    "SHA512",
+                },
+                Args: []ast.Arg{
+                  &ast.ExprArg{
+                    Expr: &ast.Ident{
+                      NamePos: 215,
+                      NameEnd: 216,
+                      Name:    "z",
+                    },
+                  },
+                },
+                Hint: &ast.Hint{
+                  Atmark:  218,
+                  Rbrace:  241,
+                  Records: []*ast.HintRecord{
+                    &ast.HintRecord{
+                      Key: &ast.Ident{
+                        NamePos: 220,
+                        NameEnd: 234,
+                        Name:    "DISABLE_INLINE",
+                      },
+                      Value: &ast.BoolLiteral{
+                        ValuePos: 237,
+                        Value:    true,
+                      },
+                    },
+                  },
+                },
+              },
+              As: &ast.AsAlias{
+                As:    243,
+                Alias: &ast.Ident{
+                  NamePos: 246,
+                  NameEnd: 247,
+                  Name:    "x",
+                },
+              },
+            },
+          },
+          From: &ast.From{
+            From:   248,
+            Source: &ast.TableName{
+              Table: &ast.Ident{
+                NamePos: 253,
+                NameEnd: 254,
+                Name:    "t",
+              },
+            },
+          },
+        },
+      },
+    },
+  },
+}
+
+--- SQL
+SELECT SUBSTRING(CAST(x AS STRING), 2, 5) AS w, SUBSTRING(CAST(x AS STRING), 3, 7) AS y FROM (SELECT SHA512(z) @{DISABLE_INLINE=TRUE} AS x FROM t)