Skip to content

Commit bffc08e

Browse files
committed
feat: use sqlc to write queries
1 parent 11a3627 commit bffc08e

File tree

12 files changed

+1330
-0
lines changed

12 files changed

+1330
-0
lines changed

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require (
1414
github.com/goto/salt v0.3.3
1515
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
1616
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0
17+
github.com/jackc/pgx/v5 v5.4.3
1718
github.com/jmoiron/sqlx v1.3.5
1819
github.com/lib/pq v1.10.9
1920
github.com/mcuadros/go-defaults v1.2.0
@@ -202,6 +203,8 @@ require (
202203
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
203204
github.com/hashicorp/errwrap v1.1.0 // indirect
204205
github.com/hashicorp/go-multierror v1.1.1 // indirect
206+
github.com/jackc/pgpassfile v1.0.0 // indirect
207+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
205208
github.com/leodido/go-urn v1.2.4 // indirect
206209
github.com/newrelic/csec-go-agent v0.4.0 // indirect
207210
github.com/newrelic/newrelic-telemetry-sdk-go v0.8.1 // indirect

go.sum

+5
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,7 @@ github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfG
912912
github.com/jackc/pgerrcode v0.0.0-20201024163028-a0d42d470451/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
913913
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
914914
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
915+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
915916
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
916917
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
917918
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
@@ -923,6 +924,8 @@ github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwX
923924
github.com/jackc/pgproto3/v2 v2.0.7/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
924925
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
925926
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
927+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
928+
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
926929
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
927930
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
928931
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
@@ -937,6 +940,8 @@ github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXg
937940
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
938941
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
939942
github.com/jackc/pgx/v4 v4.10.1/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA=
943+
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
944+
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
940945
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
941946
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
942947
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=

internal/store/pgsql/modules.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package pgsql
2+
3+
import (
4+
"context"
5+
6+
"github.com/goto/entropy/core/module"
7+
"github.com/goto/entropy/internal/store/pgsql/queries"
8+
)
9+
10+
func (st *Store) GetModule(ctx context.Context, urn string) (*module.Module, error) {
11+
mod, err := st.qu.GetModuleByURN(ctx, urn)
12+
if err != nil {
13+
return nil, translateSQLErr(err)
14+
}
15+
16+
return &module.Module{
17+
URN: mod.Urn,
18+
Name: mod.Name,
19+
Project: mod.Project,
20+
Configs: mod.Configs,
21+
CreatedAt: mod.CreatedAt.Time,
22+
UpdatedAt: mod.UpdatedAt.Time,
23+
}, nil
24+
}
25+
26+
func (st *Store) ListModules(ctx context.Context, project string) ([]module.Module, error) {
27+
mods, err := st.qu.ListAllModulesForProject(ctx, project)
28+
if err != nil {
29+
return nil, translateSQLErr(err)
30+
}
31+
32+
var modules []module.Module
33+
for _, mod := range mods {
34+
modules = append(modules, module.Module{
35+
URN: mod.Urn,
36+
Name: mod.Name,
37+
Project: mod.Project,
38+
Configs: mod.Configs,
39+
CreatedAt: mod.CreatedAt.Time,
40+
UpdatedAt: mod.UpdatedAt.Time,
41+
})
42+
}
43+
44+
return modules, nil
45+
}
46+
47+
func (st *Store) CreateModule(ctx context.Context, m module.Module) error {
48+
params := queries.InsertModuleParams{
49+
Urn: m.URN,
50+
Project: m.Project,
51+
Name: m.Name,
52+
Configs: m.Configs,
53+
}
54+
55+
if err := st.qu.InsertModule(ctx, params); err != nil {
56+
return translateSQLErr(err)
57+
}
58+
return nil
59+
}
60+
61+
func (st *Store) UpdateModule(ctx context.Context, m module.Module) error {
62+
params := queries.UpdateModuleParams{
63+
Urn: m.URN,
64+
Configs: m.Configs,
65+
}
66+
if err := st.qu.UpdateModule(ctx, params); err != nil {
67+
return translateSQLErr(err)
68+
}
69+
return nil
70+
}
71+
72+
func (st *Store) DeleteModule(ctx context.Context, urn string) error {
73+
if err := st.qu.DeleteModule(ctx, urn); err != nil {
74+
return translateSQLErr(err)
75+
}
76+
return nil
77+
}

internal/store/pgsql/pgsql.go

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package pgsql
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"strings"
8+
"time"
9+
10+
"github.com/jackc/pgx/v5"
11+
_ "github.com/jackc/pgx/v5"
12+
"github.com/jackc/pgx/v5/pgconn"
13+
14+
"github.com/goto/entropy/core/resource"
15+
"github.com/goto/entropy/internal/store/pgsql/queries"
16+
"github.com/goto/entropy/pkg/errors"
17+
)
18+
19+
//go:generate sqlc generate
20+
21+
type Store struct {
22+
qu *queries.Queries
23+
pgx *pgx.Conn
24+
}
25+
26+
func (st *Store) Close() error { return st.pgx.Close(context.Background()) }
27+
28+
// Open returns store instance backed by PostgresQL.
29+
func Open(conStr string, refreshInterval, extendInterval time.Duration) (*Store, error) {
30+
conn, err := pgx.Connect(context.Background(), conStr)
31+
if err != nil {
32+
return nil, err
33+
} else if err := conn.Ping(context.Background()); err != nil {
34+
_ = conn.Close(context.Background())
35+
return nil, err
36+
}
37+
38+
if refreshInterval >= extendInterval {
39+
return nil, errors.New("refreshInterval must be lower than extendInterval")
40+
}
41+
42+
return &Store{
43+
qu: queries.New(conn),
44+
pgx: conn,
45+
}, nil
46+
}
47+
48+
func translateSQLErr(err error) error {
49+
if errors.Is(err, sql.ErrNoRows) {
50+
return errors.ErrNotFound.WithCausef(err.Error())
51+
}
52+
53+
var pgErr *pgconn.PgError
54+
if errors.As(err, &pgErr) {
55+
// Refer http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html
56+
switch pgErr.Code {
57+
case "unique_violation":
58+
return errors.ErrConflict.WithCausef(err.Error())
59+
60+
default:
61+
return errors.ErrInternal.WithCausef(err.Error())
62+
}
63+
}
64+
65+
return err
66+
}
67+
68+
func tagsToLabelMap(tags []string) map[string]string {
69+
const keyValueParts = 2
70+
71+
labels := map[string]string{}
72+
for _, tag := range tags {
73+
parts := strings.SplitN(tag, "=", keyValueParts)
74+
key, val := parts[0], parts[1]
75+
labels[key] = val
76+
}
77+
return labels
78+
}
79+
80+
func labelToTag(k, v string) string {
81+
return fmt.Sprintf("%s=%s", k, v)
82+
}
83+
84+
type TxFunc func(ctx context.Context, tx pgx.Tx) error
85+
86+
func withinTx(ctx context.Context, db *pgx.Conn, readOnly bool, fns ...TxFunc) error {
87+
var opts pgx.TxOptions
88+
if readOnly {
89+
opts.AccessMode = pgx.ReadOnly
90+
} else {
91+
opts.AccessMode = pgx.ReadWrite
92+
}
93+
94+
tx, err := db.BeginTx(ctx, opts)
95+
if err != nil {
96+
return err
97+
}
98+
99+
for _, fn := range fns {
100+
if err := fn(ctx, tx); err != nil {
101+
_ = tx.Rollback(ctx)
102+
return err
103+
}
104+
}
105+
106+
return tx.Commit(ctx)
107+
}
108+
109+
func runAllHooks(ctx context.Context, hooks []resource.MutationHook) error {
110+
for _, hook := range hooks {
111+
if err := hook(ctx); err != nil {
112+
return err
113+
}
114+
}
115+
return nil
116+
}

internal/store/pgsql/queries.sql

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
-- =================== Modules ===================
2+
3+
-- name: GetModuleByURN :one
4+
SELECT *
5+
FROM modules
6+
WHERE urn = $1;
7+
8+
-- name: ListAllModulesForProject :many
9+
SELECT *
10+
FROM modules
11+
WHERE project = $1;
12+
13+
-- name: InsertModule :exec
14+
INSERT INTO modules (urn, project, name, configs)
15+
VALUES ($1, $2, $3, $4);
16+
17+
-- name: UpdateModule :exec
18+
UPDATE modules
19+
SET configs = $2,
20+
updated_at = current_timestamp
21+
WHERE urn = $1;
22+
23+
-- name: DeleteModule :exec
24+
DELETE
25+
FROM modules
26+
WHERE urn = $1;
27+
28+
-- =================== Resources ===================
29+
30+
-- name: GetResourceByURN :one
31+
SELECT r.*,
32+
array_agg(rt.tag)::text[] AS tags,
33+
(CASE
34+
WHEN COUNT(rd.dependency_key) > 0 THEN
35+
json_object_agg(rd.dependency_key, d.urn)
36+
ELSE
37+
'{}'::json
38+
END) AS dependencies
39+
FROM resources r
40+
LEFT JOIN resource_tags rt ON r.id = rt.resource_id
41+
LEFT JOIN resource_dependencies rd ON r.id = rd.resource_id
42+
LEFT JOIN resources d ON rd.depends_on = d.id
43+
WHERE r.urn = $1
44+
GROUP BY r.id;
45+
46+
-- name: GetResourceDependencies :one
47+
SELECT (CASE
48+
WHEN COUNT(rd.dependency_key) > 0 THEN
49+
json_object_agg(rd.dependency_key, d.urn)
50+
ELSE
51+
'{}'::json
52+
END) AS dependencies
53+
FROM resources r
54+
LEFT JOIN resource_dependencies rd ON r.id = rd.resource_id
55+
LEFT JOIN resources d ON rd.depends_on = d.id
56+
WHERE r.urn = $1
57+
GROUP BY r.id;
58+
59+
-- name: ListResourceURNsByFilter :many
60+
SELECT r.*,
61+
array_agg(rt.tag)::text[] AS tags
62+
FROM resources r
63+
JOIN resource_tags rt ON r.id = rt.resource_id
64+
WHERE (sqlc.narg('project')::text IS NULL OR r.project = sqlc.narg('project'))
65+
AND (sqlc.narg('kind')::text IS NULL OR r.kind = sqlc.narg('kind'))
66+
GROUP BY r.id;
67+
68+
-- name: DeleteResourceDependenciesByURN :exec
69+
DELETE
70+
FROM resource_dependencies
71+
WHERE resource_id = (SELECT id FROM resources WHERE urn = $1);
72+
73+
-- name: DeleteResourceTagsByURN :exec
74+
DELETE
75+
FROM resource_tags
76+
WHERE resource_id = (SELECT id FROM resources WHERE urn = $1);
77+
78+
-- name: DeleteResourceByURN :exec
79+
DELETE
80+
FROM resources
81+
WHERE urn = $1;
82+
83+
-- name: InsertResource :one
84+
INSERT INTO resources ("urn", "kind", "project", "name", "created_at", "updated_at", "created_by", "updated_by",
85+
"spec_configs", "state_status", "state_output", "state_module_data",
86+
"state_next_sync", "state_sync_result")
87+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
88+
RETURNING id;
89+
90+
-- name: InsertResourceTags :copyfrom
91+
INSERT INTO resource_tags (resource_id, tag)
92+
VALUES ($1, $2);
93+
94+
-- name: InsertResourceDependency :exec
95+
INSERT INTO resource_dependencies (resource_id, dependency_key, depends_on)
96+
VALUES ($1, $2, (SELECT id FROM resources WHERE urn = $3));
97+
98+
-- name: UpdateResource :one
99+
UPDATE resources
100+
SET updated_at = current_timestamp,
101+
updated_by = $2,
102+
spec_configs = $3,
103+
state_status = $4,
104+
state_output = $5,
105+
state_module_data = $6,
106+
state_next_sync = $7,
107+
state_sync_result = $8
108+
WHERE urn = $1
109+
RETURNING id;
110+
111+
-- =================== Revisions ===================
112+
113+
-- name: ListResourceRevisions :many
114+
SELECT rev.*, array_agg(distinct rt.tag)::text[] AS tags
115+
FROM resources r
116+
JOIN revisions rev ON r.id = rev.resource_id
117+
JOIN revision_tags rt ON rev.id = rt.revision_id
118+
WHERE r.urn = $1
119+
GROUP BY rev.id;
120+
121+
-- name: InsertRevision :exec
122+
INSERT INTO revisions ("resource_id", "reason", "spec_configs", "created_by")
123+
VALUES ($1, $2, $3, $4);

0 commit comments

Comments
 (0)