snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
225
internal/engine/dolphin/CLAUDE.md
Normal file
225
internal/engine/dolphin/CLAUDE.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Dolphin Engine (MySQL) - Claude Code Guide
|
||||
|
||||
The dolphin engine handles MySQL parsing and AST conversion using the Marino parser
|
||||
(a sqlc-maintained fork of the TiDB / pingcap parser).
|
||||
|
||||
## Architecture
|
||||
|
||||
### Parser Flow
|
||||
```
|
||||
SQL String → Marino Parser → Marino AST → sqlc AST → Analysis/Codegen
|
||||
```
|
||||
|
||||
### Key Files
|
||||
- `convert.go` - Converts Marino AST nodes to sqlc AST nodes
|
||||
- `format.go` - MySQL-specific formatting (identifiers, types, parameters)
|
||||
- `parse.go` - Entry point for parsing MySQL SQL
|
||||
|
||||
## Marino Parser
|
||||
|
||||
The Marino parser (`github.com/sqlc-dev/marino`) is used for MySQL parsing. It is a
|
||||
hard fork of `github.com/pingcap/tidb/pkg/parser` with a flatter package layout
|
||||
(no `pkg/parser/...` prefix) and the former `test_driver` types merged into the
|
||||
`ast` package as `ValueExprBase` / `ParamMarkerExprBase`.
|
||||
|
||||
```go
|
||||
import (
|
||||
pcast "github.com/sqlc-dev/marino/ast"
|
||||
"github.com/sqlc-dev/marino/mysql"
|
||||
"github.com/sqlc-dev/marino/types"
|
||||
)
|
||||
```
|
||||
|
||||
### Common Marino Types
|
||||
- `pcast.SelectStmt`, `pcast.InsertStmt`, etc. - Statement types
|
||||
- `pcast.ColumnNameExpr` - Column reference
|
||||
- `pcast.FuncCallExpr` - Function call
|
||||
- `pcast.BinaryOperationExpr` - Binary expression
|
||||
- `pcast.VariableExpr` - MySQL user variable (@var)
|
||||
- `pcast.Join` - JOIN clause with Left, Right, On, Using
|
||||
|
||||
## Conversion Pattern
|
||||
|
||||
Each TiDB node type has a corresponding converter method:
|
||||
|
||||
```go
|
||||
func (c *cc) convertSelectStmt(n *pcast.SelectStmt) *ast.SelectStmt {
|
||||
return &ast.SelectStmt{
|
||||
FromClause: c.convertTableRefsClause(n.From),
|
||||
WhereClause: c.convert(n.Where),
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The main `convert()` method dispatches to specific converters:
|
||||
```go
|
||||
func (c *cc) convert(node pcast.Node) ast.Node {
|
||||
switch n := node.(type) {
|
||||
case *pcast.SelectStmt:
|
||||
return c.convertSelectStmt(n)
|
||||
case *pcast.InsertStmt:
|
||||
return c.convertInsertStmt(n)
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Key Conversions
|
||||
|
||||
### Column References
|
||||
```go
|
||||
func (c *cc) convertColumnNameExpr(n *pcast.ColumnNameExpr) *ast.ColumnRef {
|
||||
var items []ast.Node
|
||||
if schema := n.Name.Schema.String(); schema != "" {
|
||||
items = append(items, NewIdentifier(schema))
|
||||
}
|
||||
if table := n.Name.Table.String(); table != "" {
|
||||
items = append(items, NewIdentifier(table))
|
||||
}
|
||||
items = append(items, NewIdentifier(n.Name.Name.String()))
|
||||
return &ast.ColumnRef{Fields: &ast.List{Items: items}}
|
||||
}
|
||||
```
|
||||
|
||||
### JOINs
|
||||
```go
|
||||
func (c *cc) convertJoin(n *pcast.Join) *ast.List {
|
||||
if n.Right != nil && n.Left != nil {
|
||||
return &ast.List{
|
||||
Items: []ast.Node{&ast.JoinExpr{
|
||||
Jointype: ast.JoinType(n.Tp),
|
||||
Larg: c.convert(n.Left),
|
||||
Rarg: c.convert(n.Right),
|
||||
Quals: c.convert(n.On),
|
||||
UsingClause: convertUsing(n.Using),
|
||||
}},
|
||||
}
|
||||
}
|
||||
// No join - just return tables
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### MySQL User Variables
|
||||
MySQL user variables (`@var`) are different from sqlc's `@param` syntax:
|
||||
```go
|
||||
func (c *cc) convertVariableExpr(n *pcast.VariableExpr) ast.Node {
|
||||
// Use VariableExpr to preserve as-is (NOT A_Expr which would be treated as sqlc param)
|
||||
return &ast.VariableExpr{
|
||||
Name: n.Name,
|
||||
Location: n.OriginTextPosition(),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Type Casts (CAST AS)
|
||||
```go
|
||||
func (c *cc) convertFuncCastExpr(n *pcast.FuncCastExpr) ast.Node {
|
||||
typeName := types.TypeStr(n.Tp.GetType())
|
||||
// Handle UNSIGNED/SIGNED specially
|
||||
if typeName == "bigint" {
|
||||
if mysql.HasUnsignedFlag(n.Tp.GetFlag()) {
|
||||
typeName = "bigint unsigned"
|
||||
} else {
|
||||
typeName = "bigint signed"
|
||||
}
|
||||
}
|
||||
return &ast.TypeCast{
|
||||
Arg: c.convert(n.Expr),
|
||||
TypeName: &ast.TypeName{Name: typeName},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Column Definitions
|
||||
```go
|
||||
func convertColumnDef(def *pcast.ColumnDef) *ast.ColumnDef {
|
||||
typeName := &ast.TypeName{Name: types.TypeToStr(def.Tp.GetType(), def.Tp.GetCharset())}
|
||||
|
||||
// Only add Typmods for types where length is meaningful
|
||||
tp := def.Tp.GetType()
|
||||
flen := def.Tp.GetFlen()
|
||||
switch tp {
|
||||
case mysql.TypeVarchar, mysql.TypeString, mysql.TypeVarString:
|
||||
if flen >= 0 {
|
||||
typeName.Typmods = &ast.List{
|
||||
Items: []ast.Node{&ast.Integer{Ival: int64(flen)}},
|
||||
}
|
||||
}
|
||||
// Don't add for DATETIME, TIMESTAMP - internal flen is not user-specified
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Multi-Table DELETE
|
||||
MySQL supports `DELETE t1, t2 FROM t1 JOIN t2 ...`:
|
||||
```go
|
||||
func (c *cc) convertDeleteStmt(n *pcast.DeleteStmt) *ast.DeleteStmt {
|
||||
if n.IsMultiTable && n.Tables != nil {
|
||||
// Convert targets (t1.*, t2.*)
|
||||
targets := &ast.List{}
|
||||
for _, table := range n.Tables.Tables {
|
||||
// Build ColumnRef for each target
|
||||
}
|
||||
stmt.Targets = targets
|
||||
|
||||
// Preserve JOINs in FromClause
|
||||
stmt.FromClause = c.convertTableRefsClause(n.TableRefs).Items[0]
|
||||
} else {
|
||||
// Single-table DELETE
|
||||
stmt.Relations = c.convertTableRefsClause(n.TableRefs)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## MySQL-Specific Formatting
|
||||
|
||||
### format.go
|
||||
```go
|
||||
func (p *Parser) TypeName(ns, name string) string {
|
||||
switch name {
|
||||
case "bigint unsigned":
|
||||
return "UNSIGNED"
|
||||
case "bigint signed":
|
||||
return "SIGNED"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func (p *Parser) Param(n int) string {
|
||||
return "?" // MySQL uses ? for all parameters
|
||||
}
|
||||
```
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue: Panic in Walk/Apply
|
||||
**Cause**: New AST node type not handled in `astutils/walk.go` or `astutils/rewrite.go`
|
||||
**Solution**: Add case for the node type in both files
|
||||
|
||||
### Issue: MySQL @variable being treated as parameter
|
||||
**Cause**: The preprocessor's dialect said `@name` is sqlc syntax
|
||||
**Solution**: MySQL sets `AtSign: false` in `internal/sql/preprocess/dialect.go`, so
|
||||
`@user_id` is left alone before the parser ever sees it
|
||||
|
||||
### Issue: Type length appearing incorrectly (e.g., datetime(39))
|
||||
**Cause**: Using internal `flen` for all types
|
||||
**Solution**: Only populate `Typmods` for types where length is user-specified (varchar, char, etc.)
|
||||
|
||||
## Testing
|
||||
|
||||
### TestFormat
|
||||
Tests that SQL can be:
|
||||
1. Parsed
|
||||
2. Formatted back to SQL
|
||||
3. Re-parsed
|
||||
4. Re-formatted to match
|
||||
|
||||
### TestReplay
|
||||
Tests the full sqlc pipeline:
|
||||
1. Parse schema and queries
|
||||
2. Analyze
|
||||
3. Generate code
|
||||
4. Compare with expected output
|
||||
Reference in New Issue
Block a user