forked from
tangled.org/core
Monorepo for Tangled — https://tangled.org
1package orm
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "log/slog"
8 "reflect"
9 "strings"
10)
11
12type migrationFn = func(*sql.Tx) error
13
14func RunMigration(c *sql.Conn, logger *slog.Logger, name string, migrationFn migrationFn) error {
15 logger = logger.With("migration", name)
16
17 tx, err := c.BeginTx(context.Background(), nil)
18 if err != nil {
19 return err
20 }
21 defer tx.Rollback()
22
23 var exists bool
24 err = tx.QueryRow("select exists (select 1 from migrations where name = ?)", name).Scan(&exists)
25 if err != nil {
26 return err
27 }
28
29 if !exists {
30 // run migration
31 err = migrationFn(tx)
32 if err != nil {
33 logger.Error("failed to run migration", "err", err)
34 return err
35 }
36
37 // mark migration as complete
38 _, err = tx.Exec("insert into migrations (name) values (?)", name)
39 if err != nil {
40 logger.Error("failed to mark migration as complete", "err", err)
41 return err
42 }
43
44 // commit the transaction
45 if err := tx.Commit(); err != nil {
46 return err
47 }
48
49 logger.Info("migration applied successfully")
50 } else {
51 logger.Warn("skipped migration, already applied")
52 }
53
54 return nil
55}
56
57type Filter struct {
58 Key string
59 arg any
60 Cmp string
61}
62
63func newFilter(key, cmp string, arg any) Filter {
64 return Filter{
65 Key: key,
66 arg: arg,
67 Cmp: cmp,
68 }
69}
70
71func FilterEq(key string, arg any) Filter { return newFilter(key, "=", arg) }
72func FilterNotEq(key string, arg any) Filter { return newFilter(key, "<>", arg) }
73func FilterGte(key string, arg any) Filter { return newFilter(key, ">=", arg) }
74func FilterLte(key string, arg any) Filter { return newFilter(key, "<=", arg) }
75func FilterIs(key string, arg any) Filter { return newFilter(key, "is", arg) }
76func FilterIsNot(key string, arg any) Filter { return newFilter(key, "is not", arg) }
77func FilterIn(key string, arg any) Filter { return newFilter(key, "in", arg) }
78func FilterLike(key string, arg any) Filter { return newFilter(key, "like", arg) }
79func FilterNotLike(key string, arg any) Filter { return newFilter(key, "not like", arg) }
80func FilterContains(key string, arg any) Filter {
81 return newFilter(key, "like", fmt.Sprintf("%%%v%%", arg))
82}
83
84func (f Filter) Condition() string {
85 rv := reflect.ValueOf(f.arg)
86 kind := rv.Kind()
87
88 // if we have `FilterIn(k, [1, 2, 3])`, compile it down to `k in (?, ?, ?)`
89 if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array {
90 if rv.Len() == 0 {
91 // always false
92 return "1 = 0"
93 }
94
95 placeholders := make([]string, rv.Len())
96 for i := range placeholders {
97 placeholders[i] = "?"
98 }
99
100 return fmt.Sprintf("%s %s (%s)", f.Key, f.Cmp, strings.Join(placeholders, ", "))
101 }
102
103 return fmt.Sprintf("%s %s ?", f.Key, f.Cmp)
104}
105
106func (f Filter) Arg() []any {
107 rv := reflect.ValueOf(f.arg)
108 kind := rv.Kind()
109 if (kind == reflect.Slice && rv.Type().Elem().Kind() != reflect.Uint8) || kind == reflect.Array {
110 if rv.Len() == 0 {
111 return nil
112 }
113
114 out := make([]any, rv.Len())
115 for i := range rv.Len() {
116 out[i] = rv.Index(i).Interface()
117 }
118 return out
119 }
120
121 return []any{f.arg}
122}