Parents with child slices: CockroachDB and TimescaleDB
CockroachDB and TimescaleDB run the same files for this example: the SQL and the Go are identical.
Configuration
sgl.yamlidentical
CockroachDB
TimescaleDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: chippy4
- name: chippy5
dialect: postgres5
dialect: postgres6
migrations: db/migrations6
migrations: db/migrations7
queries: db/queries7
queries: db/queries8
go:8
go:9
package: store9
package: store10
out: internal/store10
out: internal/store11
driver: database/sql11
driver: database/sql12
migrate:12
migrate:13
dsn: ${env:DATABASE_URL}13
dsn: ${env:DATABASE_URL}Migrations
db/migrations/0001_orders.up.sqlidentical
CockroachDB
TimescaleDB
1
CREATE TABLE orders (1
CREATE TABLE orders (2
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,2
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,3
customer TEXT NOT NULL3
customer TEXT NOT NULL4
);4
);5
5
6
CREATE TABLE order_lines (6
CREATE TABLE order_lines (7
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,7
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,8
order_id BIGINT NOT NULL REFERENCES orders (id),8
order_id BIGINT NOT NULL REFERENCES orders (id),9
dish TEXT NOT NULL,9
dish TEXT NOT NULL,10
quantity BIGINT NOT NULL10
quantity BIGINT NOT NULL11
);11
);db/migrations/0001_orders.down.sqlidentical
CockroachDB
TimescaleDB
1
DROP TABLE order_lines;1
DROP TABLE order_lines;2
DROP TABLE orders;2
DROP TABLE orders;Queries
OpenOrderidentical
CockroachDB · db/queries/orders.sql
TimescaleDB · db/queries/orders.sql
1
-- sgl.query(OpenOrder, exec)1
-- sgl.query(OpenOrder, exec)2
INSERT INTO orders (customer) VALUES ($1);2
INSERT INTO orders (customer) VALUES ($1);AddLineidentical
CockroachDB · db/queries/orders.sql
TimescaleDB · db/queries/orders.sql
1
-- sgl.query(AddLine, exec)1
-- sgl.query(AddLine, exec)2
INSERT INTO order_lines (order_id, dish, quantity) VALUES ($1, $2, $3);2
INSERT INTO order_lines (order_id, dish, quantity) VALUES ($1, $2, $3);ListOrdersidentical
CockroachDB · db/queries/orders.sql
TimescaleDB · db/queries/orders.sql
1
-- sgl.query(ListOrders, many, group_by: orders.id)1
-- sgl.query(ListOrders, many, group_by: orders.id)2
-- sgl.embed(orders)2
-- sgl.embed(orders)3
-- sgl.embed(order_lines)3
-- sgl.embed(order_lines)4
SELECT orders.id, orders.customer, order_lines.id, order_lines.dish, order_lines.quantity4
SELECT orders.id, orders.customer, order_lines.id, order_lines.dish, order_lines.quantity5
FROM orders5
FROM orders6
LEFT JOIN order_lines ON order_lines.order_id = orders.id6
LEFT JOIN order_lines ON order_lines.order_id = orders.id7
ORDER BY orders.id, order_lines.id;7
ORDER BY orders.id, order_lines.id;What sgl generates
OpenOrderidentical
CockroachDB
TimescaleDB
1
func (queries *Queries) OpenOrder(ctx context.Context, customer string) error1
func (queries *Queries) OpenOrder(ctx context.Context, customer string) errorAddLineidentical
CockroachDB
TimescaleDB
1
func (queries *Queries) AddLine(ctx context.Context, params AddLineParams) error1
func (queries *Queries) AddLine(ctx context.Context, params AddLineParams) error2
2
3
type AddLineParams struct {3
type AddLineParams struct {4
OrderID int644
OrderID int645
Dish string5
Dish string6
Quantity int646
Quantity int647
}7
}ListOrdersidentical
CockroachDB
TimescaleDB
1
func (queries *Queries) ListOrders(ctx context.Context) ([]ListOrdersRow, error)1
func (queries *Queries) ListOrders(ctx context.Context) ([]ListOrdersRow, error)2
2
3
type ListOrdersRow struct {3
type ListOrdersRow struct {4
Orders ListOrdersOrders4
Orders ListOrdersOrders5
OrderLines []ListOrdersOrderLines5
OrderLines []ListOrdersOrderLines6
}6
}7
7
8
type ListOrdersOrders struct {8
type ListOrdersOrders struct {9
ID int64 `json:"id"`9
ID int64 `json:"id"`10
Customer string `json:"customer"`10
Customer string `json:"customer"`11
}11
}12
12
13
type ListOrdersOrderLines struct {13
type ListOrdersOrderLines struct {14
ID *int64 `json:"id"`14
ID *int64 `json:"id"`15
Dish *string `json:"dish"`15
Dish *string `json:"dish"`16
Quantity *int64 `json:"quantity"`16
Quantity *int64 `json:"quantity"`17
}17
}The program
example.goidentical
CockroachDB
TimescaleDB
1
package main1
package main2
2
3
import (3
import (4
"context"4
"context"5
"database/sql"5
"database/sql"6
"fmt"6
"fmt"7
"io"7
"io"8
8
9
"seagull.sh/seagull/examples/postgres/016_one_to_many/internal/store"9
"seagull.sh/seagull/examples/postgres/016_one_to_many/internal/store"10
)10
)11
11
12
// run takes three chip shop orders and lists them with their lines. The join in12
// run takes three chip shop orders and lists them with their lines. The join in13
// ListOrders returns one row per order line, repeating the order's columns each time.13
// ListOrders returns one row per order line, repeating the order's columns each time.14
// group_by: orders.id collapses those rows: each distinct orders.id becomes one14
// group_by: orders.id collapses those rows: each distinct orders.id becomes one15
// ListOrdersRow, holding the order's columns in Orders and every matching line in the15
// ListOrdersRow, holding the order's columns in Orders and every matching line in the16
// OrderLines slice. Groups keep the order the rows arrive in.16
// OrderLines slice. Groups keep the order the rows arrive in.17
//17
//18
// It is a LEFT JOIN, so an order with no lines still appears, with an empty slice, and18
// It is a LEFT JOIN, so an order with no lines still appears, with an empty slice, and19
// the line fields are pointers. sgl vet checks the directive: group_by must name a column19
// the line fields are pointers. sgl vet checks the directive: group_by must name a column20
// the query returns (Q014), the other tables need sgl.embed (Q015), and the command must20
// the query returns (Q014), the other tables need sgl.embed (Q015), and the command must21
// be many (Q016).21
// be many (Q016).22
//22
//23
// Takes database (*sql.DB) which is a migrated database.23
// Takes database (*sql.DB) which is a migrated database.24
// Takes output (io.Writer) which receives each order and its lines.24
// Takes output (io.Writer) which receives each order and its lines.25
//25
//26
// Returns error when a query fails.26
// Returns error when a query fails.27
func run(ctx context.Context, database *sql.DB, output io.Writer) error {27
func run(ctx context.Context, database *sql.DB, output io.Writer) error {28
queries := store.New(database)28
queries := store.New(database)29
29
30
for _, customer := range []string{"Steven", "Gwen", "Jago"} {30
for _, customer := range []string{"Steven", "Gwen", "Jago"} {31
if err := queries.OpenOrder(ctx, customer); err != nil {31
if err := queries.OpenOrder(ctx, customer); err != nil {32
return fmt.Errorf("opening %s's order: %w", customer, err)32
return fmt.Errorf("opening %s's order: %w", customer, err)33
}33
}34
}34
}35
lines := []store.AddLineParams{35
lines := []store.AddLineParams{36
{OrderID: 1, Dish: "cod and chips", Quantity: 2},36
{OrderID: 1, Dish: "cod and chips", Quantity: 2},37
{OrderID: 1, Dish: "mushy peas", Quantity: 1},37
{OrderID: 1, Dish: "mushy peas", Quantity: 1},38
{OrderID: 1, Dish: "pickled egg", Quantity: 3},38
{OrderID: 1, Dish: "pickled egg", Quantity: 3},39
{OrderID: 2, Dish: "chips", Quantity: 1},39
{OrderID: 2, Dish: "chips", Quantity: 1},40
}40
}41
for _, line := range lines {41
for _, line := range lines {42
if err := queries.AddLine(ctx, line); err != nil {42
if err := queries.AddLine(ctx, line); err != nil {43
return fmt.Errorf("adding %s to order %d: %w", line.Dish, line.OrderID, err)43
return fmt.Errorf("adding %s to order %d: %w", line.Dish, line.OrderID, err)44
}44
}45
}45
}46
46
47
orders, err := queries.ListOrders(ctx)47
orders, err := queries.ListOrders(ctx)48
if err != nil {48
if err != nil {49
return fmt.Errorf("listing orders: %w", err)49
return fmt.Errorf("listing orders: %w", err)50
}50
}51
for _, order := range orders {51
for _, order := range orders {52
fmt.Fprintf(output, "order %d for %s\n", order.Orders.ID, order.Orders.Customer)52
fmt.Fprintf(output, "order %d for %s\n", order.Orders.ID, order.Orders.Customer)53
if len(order.OrderLines) == 0 {53
if len(order.OrderLines) == 0 {54
fmt.Fprintln(output, " nothing ordered yet")54
fmt.Fprintln(output, " nothing ordered yet")55
}55
}56
for _, line := range order.OrderLines {56
for _, line := range order.OrderLines {57
fmt.Fprintf(output, " %d x %s\n", *line.Quantity, *line.Dish)57
fmt.Fprintf(output, " %d x %s\n", *line.Quantity, *line.Dish)58
}58
}59
}59
}60
return nil60
return nil61
}61
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | CockroachDB | TimescaleDB |
|---|---|---|---|
| ListOrders | column id | *int64 | int64 |