-
Notifications
You must be signed in to change notification settings - Fork 928
Option to output additional read only client #694
-
It would be great if we could set an option to additionally generate an interface that only has read only functions on it.
This would be extremely handy for situations where you have read replica endpoints on your database and create a separate connection to them and you only ever want to read instead of write. Your application would then have two different clients.
Beta Was this translation helpful? Give feedback.
All reactions
Hey, thanks for the suggestion. This is a really good idea.
I think we can get away with generating a read-only interface instead of a different struct. Here's a simple example, given the following two input files:
-- query.sql CREATE TABLE foo (bar text); -- name: ListFoo :many SELECT * FROM foo; -- name: CreateFoo :exec INSERT INTO foo (bar) VALUES ($1);
{ "version": "1", "packages": [ { "path": "go", "name": "db", "schema": "query.sql", "queries": "query.sql", "engine": "postgresql", "emit_interface": true } ] }
Today, this generates the following four Go files:
// Code generated by sqlc. DO NOT EDIT. // models.go package db import (
Replies: 1 comment 1 reply
-
Hey, thanks for the suggestion. This is a really good idea.
I think we can get away with generating a read-only interface instead of a different struct. Here's a simple example, given the following two input files:
-- query.sql CREATE TABLE foo (bar text); -- name: ListFoo :many SELECT * FROM foo; -- name: CreateFoo :exec INSERT INTO foo (bar) VALUES ($1);
{ "version": "1", "packages": [ { "path": "go", "name": "db", "schema": "query.sql", "queries": "query.sql", "engine": "postgresql", "emit_interface": true } ] }
Today, this generates the following four Go files:
// Code generated by sqlc. DO NOT EDIT. // models.go package db import ( "database/sql" ) type Foo struct { Bar sql.NullString }
// Code generated by sqlc. DO NOT EDIT. // querier.go package db import ( "context" "database/sql" ) type Querier interface { CreateFoo(ctx context.Context, bar sql.NullString) error ListFoo(ctx context.Context) ([]sql.NullString, error) } var _ Querier = (*Queries)(nil)
// Code generated by sqlc. DO NOT EDIT. // source: query.sql package db import ( "context" "database/sql" ) const createFoo = `-- name: CreateFoo :exec INSERT INTO foo (bar) VALUES (1ドル) ` func (q *Queries) CreateFoo(ctx context.Context, bar sql.NullString) error { _, err := q.db.ExecContext(ctx, createFoo, bar) return err } const listFoo = `-- name: ListFoo :many SELECT bar FROM foo ` func (q *Queries) ListFoo(ctx context.Context) ([]sql.NullString, error) { rows, err := q.db.QueryContext(ctx, listFoo) if err != nil { return nil, err } defer rows.Close() var items []sql.NullString for rows.Next() { var bar sql.NullString if err := rows.Scan(&bar); err != nil { return nil, err } items = append(items, bar) } if err := rows.Close(); err != nil { return nil, err } if err := rows.Err(); err != nil { return nil, err } return items, nil }
// Code generated by sqlc. DO NOT EDIT. // db.go package db import ( "context" "database/sql" ) type DBTX interface { ExecContext(context.Context, string, ...interface{}) (sql.Result, error) PrepareContext(context.Context, string) (*sql.Stmt, error) QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) QueryRowContext(context.Context, string, ...interface{}) *sql.Row } func New(db DBTX) *Queries { return &Queries{db: db} } type Queries struct { db DBTX } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ db: tx, } }
Let's say we add an "emit_readonly_interface" configuration option. I'm envisioning that querier.go
would have the following interface added to it:
type ReadOnlyQuerier interface { ListFoo(ctx context.Context) ([]sql.NullString, error) } var _ ReadOnlyQuerier = (*Queries)(nil)
I'm not sure what if we'd need a constructor, as you can just cast an existing Queries instance to a ReadOnlyQuerier.
var reader ReadOnlyQuerier reader = New(db)
Thoughts?
Beta Was this translation helpful? Give feedback.
All reactions
-
This is exactly what I was hoping to be able to do. And I agree with the point of not having a different constructor. Since it's an interface, we just make sure that anywhere we know we only need a read-only client, we just pass in the ReadOnlyQuerier
interface.
Beta Was this translation helpful? Give feedback.