forked from tangled.org/core
Monorepo for Tangled — https://tangled.org
at master 3.1 kB view raw
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}