Insert or update: TimescaleDB and CockroachDB
TimescaleDB and CockroachDB run the same files for this example: the SQL and the Go are identical.
Configuration
sgl.yamlidentical
TimescaleDB
CockroachDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: stock4
- name: stock5
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_stock.up.sqlidentical
TimescaleDB
CockroachDB
1
CREATE TABLE stock (1
CREATE TABLE stock (2
item TEXT PRIMARY KEY,2
item TEXT PRIMARY KEY,3
quantity BIGINT NOT NULL,3
quantity BIGINT NOT NULL,4
deliveries BIGINT NOT NULL DEFAULT 04
deliveries BIGINT NOT NULL DEFAULT 05
);5
);db/migrations/0001_stock.down.sqlidentical
TimescaleDB
CockroachDB
1
DROP TABLE stock;1
DROP TABLE stock;Queries
ReceiveDeliveryidentical
TimescaleDB · db/queries/stock.sql
CockroachDB · db/queries/stock.sql
1
-- sgl.query(ReceiveDelivery, one)1
-- sgl.query(ReceiveDelivery, one)2
INSERT INTO stock (item, quantity, deliveries) VALUES ($1, $2, 1)2
INSERT INTO stock (item, quantity, deliveries) VALUES ($1, $2, 1)3
ON CONFLICT (item) DO UPDATE SET3
ON CONFLICT (item) DO UPDATE SET4
quantity = stock.quantity + EXCLUDED.quantity,4
quantity = stock.quantity + EXCLUDED.quantity,5
deliveries = stock.deliveries + 15
deliveries = stock.deliveries + 16
RETURNING item, quantity, deliveries;6
RETURNING item, quantity, deliveries;AddItemidentical
TimescaleDB · db/queries/stock.sql
CockroachDB · db/queries/stock.sql
1
-- sgl.query(AddItem, execrows)1
-- sgl.query(AddItem, execrows)2
INSERT INTO stock (item, quantity) VALUES ($1, 0)2
INSERT INTO stock (item, quantity) VALUES ($1, 0)3
ON CONFLICT (item) DO NOTHING;3
ON CONFLICT (item) DO NOTHING;ListStockidentical
TimescaleDB · db/queries/stock.sql
CockroachDB · db/queries/stock.sql
1
-- sgl.query(ListStock, many)1
-- sgl.query(ListStock, many)2
SELECT item, quantity, deliveries FROM stock ORDER BY item;2
SELECT item, quantity, deliveries FROM stock ORDER BY item;What sgl generates
ReceiveDeliveryidentical
TimescaleDB
CockroachDB
1
func (queries *Queries) ReceiveDelivery(ctx context.Context, params ReceiveDeliveryParams) (ReceiveDeliveryRow, error)1
func (queries *Queries) ReceiveDelivery(ctx context.Context, params ReceiveDeliveryParams) (ReceiveDeliveryRow, error)2
2
3
type ReceiveDeliveryParams struct {3
type ReceiveDeliveryParams struct {4
Item string4
Item string5
Quantity int645
Quantity int646
}6
}7
7
8
type ReceiveDeliveryRow struct {8
type ReceiveDeliveryRow struct {9
Item string `json:"item"`9
Item string `json:"item"`10
Quantity int64 `json:"quantity"`10
Quantity int64 `json:"quantity"`11
Deliveries int64 `json:"deliveries"`11
Deliveries int64 `json:"deliveries"`12
}12
}AddItemidentical
TimescaleDB
CockroachDB
1
func (queries *Queries) AddItem(ctx context.Context, item string) (int64, error)1
func (queries *Queries) AddItem(ctx context.Context, item string) (int64, error)ListStockidentical
TimescaleDB
CockroachDB
1
func (queries *Queries) ListStock(ctx context.Context) ([]ListStockRow, error)1
func (queries *Queries) ListStock(ctx context.Context) ([]ListStockRow, error)2
2
3
type ListStockRow struct {3
type ListStockRow struct {4
Item string `json:"item"`4
Item string `json:"item"`5
Quantity int64 `json:"quantity"`5
Quantity int64 `json:"quantity"`6
Deliveries int64 `json:"deliveries"`6
Deliveries int64 `json:"deliveries"`7
}7
}The program
example.goidentical
TimescaleDB
CockroachDB
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/021_upserts/internal/store"9
"seagull.sh/seagull/examples/postgres/021_upserts/internal/store"10
)10
)11
11
12
// run books three deliveries into the stock table, two of them for the same item, then12
// run books three deliveries into the stock table, two of them for the same item, then13
// adds two items only if they are new, and prints the stock that results.13
// adds two items only if they are new, and prints the stock that results.14
//14
//15
// Takes database (*sql.DB) which is a migrated database.15
// Takes database (*sql.DB) which is a migrated database.16
// Takes output (io.Writer) which receives each upsert's outcome and the final stock.16
// Takes output (io.Writer) which receives each upsert's outcome and the final stock.17
//17
//18
// Returns error when a query fails.18
// Returns error when a query fails.19
func run(ctx context.Context, database *sql.DB, output io.Writer) error {19
func run(ctx context.Context, database *sql.DB, output io.Writer) error {20
queries := store.New(database)20
queries := store.New(database)21
21
22
// ReceiveDelivery inserts a new item, or adds to the quantity of one already in stock.22
// ReceiveDelivery inserts a new item, or adds to the quantity of one already in stock.23
// EXCLUDED.quantity is the value the INSERT tried to write, so the parameters stay plain23
// EXCLUDED.quantity is the value the INSERT tried to write, so the parameters stay plain24
// Item and Quantity fields.24
// Item and Quantity fields.25
deliveries := []store.ReceiveDeliveryParams{25
deliveries := []store.ReceiveDeliveryParams{26
{Item: "cod", Quantity: 10},26
{Item: "cod", Quantity: 10},27
{Item: "potatoes", Quantity: 40},27
{Item: "potatoes", Quantity: 40},28
{Item: "cod", Quantity: 5},28
{Item: "cod", Quantity: 5},29
}29
}30
for _, delivery := range deliveries {30
for _, delivery := range deliveries {31
row, err := queries.ReceiveDelivery(ctx, delivery)31
row, err := queries.ReceiveDelivery(ctx, delivery)32
if err != nil {32
if err != nil {33
return fmt.Errorf("receiving %s: %w", delivery.Item, err)33
return fmt.Errorf("receiving %s: %w", delivery.Item, err)34
}34
}35
fmt.Fprintf(output, "%-8s delivery %d: +%d, now %d in stock\n",35
fmt.Fprintf(output, "%-8s delivery %d: +%d, now %d in stock\n",36
row.Item, row.Deliveries, delivery.Quantity, row.Quantity)36
row.Item, row.Deliveries, delivery.Quantity, row.Quantity)37
}37
}38
38
39
// AddItem uses DO NOTHING, and execrows reports whether the row went in: 1 for a new39
// AddItem uses DO NOTHING, and execrows reports whether the row went in: 1 for a new40
// item, 0 when the item was already there and the insert was skipped.40
// item, 0 when the item was already there and the insert was skipped.41
for _, item := range []string{"peas", "cod"} {41
for _, item := range []string{"peas", "cod"} {42
added, err := queries.AddItem(ctx, item)42
added, err := queries.AddItem(ctx, item)43
if err != nil {43
if err != nil {44
return fmt.Errorf("adding %s: %w", item, err)44
return fmt.Errorf("adding %s: %w", item, err)45
}45
}46
if added == 0 {46
if added == 0 {47
fmt.Fprintf(output, "%s is already stocked, nothing changed\n", item)47
fmt.Fprintf(output, "%s is already stocked, nothing changed\n", item)48
continue48
continue49
}49
}50
fmt.Fprintf(output, "added %s with no stock yet\n", item)50
fmt.Fprintf(output, "added %s with no stock yet\n", item)51
}51
}52
52
53
stock, err := queries.ListStock(ctx)53
stock, err := queries.ListStock(ctx)54
if err != nil {54
if err != nil {55
return fmt.Errorf("listing stock: %w", err)55
return fmt.Errorf("listing stock: %w", err)56
}56
}57
for _, row := range stock {57
for _, row := range stock {58
fmt.Fprintf(output, "%-8s %2d\n", row.Item, row.Quantity)58
fmt.Fprintf(output, "%-8s %2d\n", row.Item, row.Quantity)59
}59
}60
return nil60
return nil61
}61
}