Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/codegen/golang/sqlite_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func sqliteType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.
}
return "sql.NullTime"

case "json", "jsonb":
return "json.RawMessage"

case "any":
return "interface{}"

Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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 @@ -23,6 +24,7 @@ type Compiler struct {
result *Result
analyzer analyzer.Analyzer
client dbmanager.Client
selector selector.Selector

schema []string
}
Expand All @@ -39,12 +41,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()
case config.EngineMySQL:
c.parser = dolphin.NewParser()
c.catalog = dolphin.NewCatalog()
c.selector = selector.NewDefaultSelector()
case config.EnginePostgreSQL:
c.parser = postgresql.NewParser()
c.catalog = postgresql.NewCatalog()
c.selector = selector.NewDefaultSelector()
if conf.Database != nil {
if conf.Analyzer.Database == nil || *conf.Analyzer.Database {
c.analyzer = analyzer.Cached(
Expand Down
5 changes: 5 additions & 0 deletions internal/compiler/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ func (c *Compiler) expandStmt(qc *QueryCatalog, raw *ast.RawStmt, node ast.Node)
if counts[cname] > 1 {
cname = tableName + "." + cname
}

// 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)
cols = append(cols, cname)
}
}
Expand Down
32 changes: 32 additions & 0 deletions internal/endtoend/testdata/jsonb/pgx/go/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions internal/endtoend/testdata/jsonb/pgx/go/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 50 additions & 0 deletions internal/endtoend/testdata/jsonb/pgx/go/query.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions internal/endtoend/testdata/jsonb/pgx/query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- name: InsertFoo :exec
INSERT INTO foo (
a,
b,
c,
d
) VALUES (
@a,
@b,
@c,
@d
) RETURNING *;

-- name: SelectFoo :exec
SELECT * FROM foo;
7 changes: 7 additions & 0 deletions internal/endtoend/testdata/jsonb/pgx/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE foo (
a json not null,
b jsonb not null,
c json,
d jsonb
);

13 changes: 13 additions & 0 deletions internal/endtoend/testdata/jsonb/pgx/sqlc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "postgresql",
"sql_package": "pgx/v5",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}
31 changes: 31 additions & 0 deletions internal/endtoend/testdata/jsonb/sqlite/go/db.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions internal/endtoend/testdata/jsonb/sqlite/go/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions internal/endtoend/testdata/jsonb/sqlite/go/query.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions internal/endtoend/testdata/jsonb/sqlite/query.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- name: InsertFoo :exec
INSERT INTO foo (
a,
b,
c,
d
) VALUES (
@a,
@b,
@c,
@d
) RETURNING *;

-- name: SelectFoo :exec
SELECT * FROM foo;
7 changes: 7 additions & 0 deletions internal/endtoend/testdata/jsonb/sqlite/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE foo (
a json not null,
b jsonb not null,
c json,
d jsonb
);

12 changes: 12 additions & 0 deletions internal/endtoend/testdata/jsonb/sqlite/sqlc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "1",
"packages": [
{
"path": "go",
"engine": "sqlite",
"name": "querytest",
"schema": "schema.sql",
"queries": "query.sql"
}
]
}
21 changes: 21 additions & 0 deletions internal/engine/sqlite/selector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package sqlite

type Selector struct{}

func NewSelector() *Selector {
return &Selector{}
}

func (s *Selector) ColumnExpr(name string, dataType string) 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 dataType == "jsonb" {
return "json(" + name + ")"
}
return name
}
20 changes: 20 additions & 0 deletions internal/engine/sqlite/selector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package sqlite

import "testing"

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

selector := NewSelector()

expectExpr := func(expected, name, dataType string) {
if actual := selector.ColumnExpr(name, dataType); expected != actual {
t.Errorf("Expected %v, got %v for data type %v", expected, actual, dataType)
}
}

expectExpr("my_column", "my_column", "integer")
expectExpr("my_column", "my_column", "json")
expectExpr("json(my_column)", "my_column", "jsonb")
expectExpr("my_column", "my_column", "text")
}
26 changes: 26 additions & 0 deletions internal/sql/selector/selector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package selector

// 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, dataType string) 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, dataType string) string {
return name
}
Loading
Loading