Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Make selectors internal to the compiler package
This is based on original code review feedback that I'd misread
initially. The selector interface doesn't need to be an outside package
for any reason (it's used only internal to the compiler), and this lets
us improve it somewhat by taking a full `*Column` struct rather than
having to make it a `dataType string` (because `Column` is internal to
`compiler` and it would otherwise introduce dependency cycles).
  • Loading branch information
brandur committed May 25, 2025
commit c62c6e78843ada0f137960d455baf638390b5c7b
9 changes: 4 additions & 5 deletions internal/compiler/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/sqlc-dev/sqlc/internal/engine/sqlite"
"github.com/sqlc-dev/sqlc/internal/opts"
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
"github.com/sqlc-dev/sqlc/internal/sql/selector"
)

type Compiler struct {
Expand All @@ -24,7 +23,7 @@ type Compiler struct {
result *Result
analyzer analyzer.Analyzer
client dbmanager.Client
selector selector.Selector
selector selector

schema []string
}
Expand All @@ -41,15 +40,15 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err
case config.EngineSQLite:
c.parser = sqlite.NewParser()
c.catalog = sqlite.NewCatalog()
c.selector = sqlite.NewSelector()
c.selector = newSQLiteSelector()
case config.EngineMySQL:
c.parser = dolphin.NewParser()
c.catalog = dolphin.NewCatalog()
c.selector = selector.NewDefaultSelector()
c.selector = newDefaultSelector()
case config.EnginePostgreSQL:
c.parser = postgresql.NewParser()
c.catalog = postgresql.NewCatalog()
c.selector = selector.NewDefaultSelector()
c.selector = newDefaultSelector()
if conf.Database != nil {
if conf.Analyzer.Database == nil || *conf.Analyzer.Database {
c.analyzer = analyzer.Cached(
Expand Down
2 changes: 1 addition & 1 deletion internal/compiler/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node)
// This is important for SQLite in particular which needs to
// wrap jsonb column values with `json(colname)` so they're in a
// publicly usable format (i.e. not jsonb).
cname = c.selector.ColumnExpr(cname, column.DataType)
cname = c.selector.ColumnExpr(cname, column)
cols = append(cols, cname)
}
}
Expand Down
46 changes: 46 additions & 0 deletions internal/compiler/selector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package compiler

// selector is an interface used by a compiler for generating expressions for
// output columns in a `SELECT ...` or `RETURNING ...` statement.
//
// This interface is exclusively needed at the moment for SQLite, which must
// wrap output `jsonb` columns with a `json(column_name)` invocation so that a
// publicly consumable format (i.e. not jsonb) is returned.
type selector interface {
// ColumnExpr generates output to be used in a `SELECT ...` or `RETURNING
// ...` statement based on input column name and metadata.
ColumnExpr(name string, column *Column) string
}

// defaultSelector is a selector implementation that does the simpliest possible
// pass through when generating column expressions. Its use is suitable for all
// database engines not requiring additional customization.
type defaultSelector struct{}

func newDefaultSelector() *defaultSelector {
return &defaultSelector{}
}

func (s *defaultSelector) ColumnExpr(name string, column *Column) string {
return name
}

type sqliteSelector struct{}

func newSQLiteSelector() *sqliteSelector {
return &sqliteSelector{}
}

func (s *sqliteSelector) ColumnExpr(name string, column *Column) string {
// Under SQLite, neither json nor jsonb are real data types, and rather just
// of type blob, so database drivers just return whatever raw binary is
// stored as values. This is a problem for jsonb, which is considered an
// internal format to SQLite and no attempt should be made to parse it
// outside of the database itself. For jsonb columns in SQLite, wrap values
// in `json(col)` to coerce the internal binary format to JSON parsable by
// the user-space application.
if column.DataType == "jsonb" {
return "json(" + name + ")"
}
return name
}
35 changes: 35 additions & 0 deletions internal/compiler/selector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package compiler

import "testing"

func TestSelector(t *testing.T) {
t.Parallel()

selectorExpectColumnExpr := func(t *testing.T, selector selector, expected, name string, column *Column) {
if actual := selector.ColumnExpr(name, column); expected != actual {
t.Errorf("Expected %v, got %v for data type %v", expected, actual, column.DataType)
}
}

t.Run("DefaultSelectorColumnExpr", func(t *testing.T) {
t.Parallel()

selector := newDefaultSelector()

selectorExpectColumnExpr(t, selector, "my_column", "my_column", &Column{DataType: "integer"})
selectorExpectColumnExpr(t, selector, "my_column", "my_column", &Column{DataType: "json"})
selectorExpectColumnExpr(t, selector, "my_column", "my_column", &Column{DataType: "jsonb"})
selectorExpectColumnExpr(t, selector, "my_column", "my_column", &Column{DataType: "text"})
})

t.Run("SQLiteSelectorColumnExpr", func(t *testing.T) {
t.Parallel()

selector := newSQLiteSelector()

selectorExpectColumnExpr(t, selector, "my_column", "my_column", &Column{DataType: "integer"})
selectorExpectColumnExpr(t, selector, "my_column", "my_column", &Column{DataType: "json"})
selectorExpectColumnExpr(t, selector, "json(my_column)", "my_column", &Column{DataType: "jsonb"})
selectorExpectColumnExpr(t, selector, "my_column", "my_column", &Column{DataType: "text"})
})
}
21 changes: 0 additions & 21 deletions internal/engine/sqlite/selector.go

This file was deleted.

20 changes: 0 additions & 20 deletions internal/engine/sqlite/selector_test.go

This file was deleted.

26 changes: 0 additions & 26 deletions internal/sql/selector/selector.go

This file was deleted.

20 changes: 0 additions & 20 deletions internal/sql/selector/selector_test.go

This file was deleted.

Loading