Rows back from INSERT, UPDATE and DELETE: SQLite and MariaDB
Every file of the SQLite and MariaDB versions side by side, with the lines that differ marked. 3 parameter or column types differ in Go.
Configuration
sgl.yamldiffers
SQLite
MariaDB
1
version: 11
version: 12
2
3
databases:3
databases:4
- name: orders4
- name: orders5
dialect: sqlite5
dialect: mariadb6
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/storeMigrations
db/migrations/0001_orders.up.sqldiffers
SQLite
MariaDB
1
CREATE TABLE orders (1
CREATE TABLE orders (2
id INTEGER PRIMARY KEY,2
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,3
item TEXT NOT NULL,3
item VARCHAR(50) NOT NULL,4
quantity INTEGER NOT NULL DEFAULT 1,4
quantity INT NOT NULL DEFAULT 1,5
status TEXT NOT NULL DEFAULT 'waiting'5
status VARCHAR(20) NOT NULL DEFAULT 'waiting'6
);6
);db/migrations/0001_orders.down.sqlidentical
SQLite
MariaDB
1
DROP TABLE orders;1
DROP TABLE orders;Queries
PlaceOrderidentical
SQLite · db/queries/orders.sql
MariaDB · db/queries/orders.sql
1
-- sgl.query(PlaceOrder, one)1
-- sgl.query(PlaceOrder, one)2
INSERT INTO orders (item, quantity) VALUES (?, ?)2
INSERT INTO orders (item, quantity) VALUES (?, ?)3
RETURNING id, status, quantity * 250 AS price_pence;3
RETURNING id, status, quantity * 250 AS price_pence;ServeWaitingOrdersdiffers
SQLite · db/queries/orders.sql
MariaDB · db/queries/orders.sql
1
-- sgl.query(ServeWaitingOrders, many)1
-- sgl.query(ServeWaitingOrders, execrows)2
UPDATE orders SET status = 'served' WHERE status = 'waiting'2
UPDATE orders SET status = 'served' WHERE status = 'waiting';3
RETURNING id, item, quantity;CancelOrderidentical
SQLite · db/queries/orders.sql
MariaDB · db/queries/orders.sql
1
-- sgl.query(CancelOrder, one)1
-- sgl.query(CancelOrder, one)2
DELETE FROM orders WHERE id = ? AND status = 'waiting'2
DELETE FROM orders WHERE id = ? AND status = 'waiting'3
RETURNING item, quantity;3
RETURNING item, quantity;ServedOrdersonly MariaDB
SQLite · not in this version
MariaDB · db/queries/orders.sql
1
-- sgl.query(ServedOrders, many)2
SELECT id, item, quantity FROM orders WHERE status = 'served' ORDER BY id;What sgl generates
PlaceOrderdiffers
SQLite
MariaDB
1
func (queries *Queries) PlaceOrder(ctx context.Context, params PlaceOrderParams) (PlaceOrderRow, error)1
func (queries *Queries) PlaceOrder(ctx context.Context, params PlaceOrderParams) (PlaceOrderRow, error)2
2
3
type PlaceOrderParams struct {3
type PlaceOrderParams struct {4
Item string4
Item string5
Quantity int645
Quantity int326
}6
}7
7
8
type PlaceOrderRow struct {8
type PlaceOrderRow struct {9
ID int64 `json:"id"`9
ID int64 `json:"id"`10
Status string `json:"status"`10
Status string `json:"status"`11
PricePence int64 `json:"price_pence"`11
PricePence int32 `json:"price_pence"`12
}12
}ServeWaitingOrdersdiffers
SQLite
MariaDB
1
func (queries *Queries) ServeWaitingOrders(ctx context.Context) ([]ServeWaitingOrdersRow, error)1
func (queries *Queries) ServeWaitingOrders(ctx context.Context) (int64, error)2
3
type ServeWaitingOrdersRow struct {4
ID int64 `json:"id"`5
Item string `json:"item"`6
Quantity int64 `json:"quantity"`7
}CancelOrderdiffers
SQLite
MariaDB
1
func (queries *Queries) CancelOrder(ctx context.Context, id int64) (CancelOrderRow, error)1
func (queries *Queries) CancelOrder(ctx context.Context, id int64) (CancelOrderRow, error)2
2
3
type CancelOrderRow struct {3
type CancelOrderRow struct {4
Item string `json:"item"`4
Item string `json:"item"`5
Quantity int64 `json:"quantity"`5
Quantity int32 `json:"quantity"`6
}6
}ServedOrdersonly MariaDB
SQLite · not in this version
MariaDB
1
func (queries *Queries) ServedOrders(ctx context.Context) ([]ServedOrdersRow, error)2
3
type ServedOrdersRow struct {4
ID int64 `json:"id"`5
Item string `json:"item"`6
Quantity int32 `json:"quantity"`7
}The program
example.godiffers
SQLite
MariaDB
1
package main1
package main2
2
3
import (3
import (4
"cmp"5
"context"4
"context"6
"database/sql"5
"database/sql"7
"errors"6
"errors"8
"fmt"7
"fmt"9
"io"8
"io"10
"slices"11
9
12
"seagull.sh/seagull/examples/sqlite/020_returning/internal/store"10
"seagull.sh/seagull/examples/mariadb/020_returning/internal/store"13
)11
)14
12
15
// run places three orders, cancels one, serves the rest and then tries to cancel an order13
// run places three orders, cancels one, serves the rest and then tries to cancel an order16
// that has already been served. Every write uses RETURNING, so the rows it touched come14
// that has already been served. The INSERT and the DELETE use RETURNING, so the rows they17
// back from the same statement.15
// touched come back from the same statement.18
//16
//19
// Takes database (*sql.DB) which is a migrated database.17
// Takes database (*sql.DB) which is a migrated database.20
// Takes output (io.Writer) which receives what each write returned.18
// Takes output (io.Writer) which receives what each write returned.21
//19
//22
// Returns error when a query fails.20
// Returns error when a query fails.23
func run(ctx context.Context, database *sql.DB, output io.Writer) error {21
func run(ctx context.Context, database *sql.DB, output io.Writer) error {24
queries := store.New(database)22
queries := store.New(database)25
23
26
// PlaceOrder is an INSERT declared as one: the id SQLite assigned, the default status24
// PlaceOrder is an INSERT declared as one: the id MariaDB assigned, the default status27
// and a computed price all come back in a PlaceOrderRow.25
// and a computed price all come back in a PlaceOrderRow.28
orders := []store.PlaceOrderParams{26
orders := []store.PlaceOrderParams{29
{Item: "chips", Quantity: 2},27
{Item: "chips", Quantity: 2},30
{Item: "battered cod", Quantity: 1},28
{Item: "battered cod", Quantity: 1},31
{Item: "mushy peas", Quantity: 1},29
{Item: "mushy peas", Quantity: 1},32
}30
}33
for _, order := range orders {31
for _, order := range orders {34
placed, err := queries.PlaceOrder(ctx, order)32
placed, err := queries.PlaceOrder(ctx, order)35
if err != nil {33
if err != nil {36
return fmt.Errorf("placing an order for %s: %w", order.Item, err)34
return fmt.Errorf("placing an order for %s: %w", order.Item, err)37
}35
}38
fmt.Fprintf(output, "placed #%d %-12s %s, %dp\n", placed.ID, order.Item, placed.Status, placed.PricePence)36
fmt.Fprintf(output, "placed #%d %-12s %s, %dp\n", placed.ID, order.Item, placed.Status, placed.PricePence)39
}37
}40
38
41
// CancelOrder is a DELETE declared as one, so it returns the row it removed.39
// CancelOrder is a DELETE declared as one, so it returns the row it removed.42
cancelled, err := queries.CancelOrder(ctx, 2)40
cancelled, err := queries.CancelOrder(ctx, 2)43
if err != nil {41
if err != nil {44
return fmt.Errorf("cancelling order 2: %w", err)42
return fmt.Errorf("cancelling order 2: %w", err)45
}43
}46
fmt.Fprintf(output, "cancelled #2 %-12s x%d\n", cancelled.Item, cancelled.Quantity)44
fmt.Fprintf(output, "cancelled #2 %-12s x%d\n", cancelled.Item, cancelled.Quantity)47
45
48
// ServeWaitingOrders is an UPDATE declared as many: one row for every order it changed.46
// MariaDB has RETURNING on INSERT, REPLACE and DELETE, but not on UPDATE, so49
// SQLite does not promise an order for RETURNING rows, so sort them.47
// ServeWaitingOrders is execrows and ServedOrders reads the changed rows back.50
served, err := queries.ServeWaitingOrders(ctx)48
if _, err := queries.ServeWaitingOrders(ctx); err != nil {51
if err != nil {52
return fmt.Errorf("serving orders: %w", err)49
return fmt.Errorf("serving orders: %w", err)53
}50
}54
slices.SortFunc(served, func(a, b store.ServeWaitingOrdersRow) int { return cmp.Compare(a.ID, b.ID) })51
served, err := queries.ServedOrders(ctx)52
if err != nil {53
return fmt.Errorf("listing served orders: %w", err)54
}55
for _, order := range served {55
for _, order := range served {56
fmt.Fprintf(output, "served #%d %-12s x%d\n", order.ID, order.Item, order.Quantity)56
fmt.Fprintf(output, "served #%d %-12s x%d\n", order.ID, order.Item, order.Quantity)57
}57
}58
58
59
// A one query that matches no row returns sql.ErrNoRows, writes included: order 1 has59
// A one query that matches no row returns sql.ErrNoRows, writes included: order 1 has60
// been served, so the DELETE's WHERE clause no longer matches it.60
// been served, so the DELETE's WHERE clause no longer matches it.61
_, err = queries.CancelOrder(ctx, 1)61
_, err = queries.CancelOrder(ctx, 1)62
if errors.Is(err, sql.ErrNoRows) {62
if errors.Is(err, sql.ErrNoRows) {63
fmt.Fprintln(output, "cannot cancel #1: it is no longer waiting")63
fmt.Fprintln(output, "cannot cancel #1: it is no longer waiting")64
return nil64
return nil65
}65
}66
if err != nil {66
if err != nil {67
return fmt.Errorf("cancelling order 1: %w", err)67
return fmt.Errorf("cancelling order 1: %w", err)68
}68
}69
return errors.New("cancelled an order that had already been served")69
return errors.New("cancelled an order that had already been served")70
}70
}Go type differences
The same SQL types map to different Go types, because each database reports them differently.
| Query | Field | SQLite | MariaDB |
|---|---|---|---|
| PlaceOrder | parameter quantity | int64 | int32 |
| PlaceOrder | column price_pence | int64 | int32 |
| CancelOrder | column quantity | int64 | int32 |