mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
Compare commits
3 Commits
80f2a46698
...
29415da692
| Author | SHA1 | Date | |
|---|---|---|---|
| 29415da692 | |||
| f703a8df5d | |||
| 2683e2a00f |
48
TODOS.md
48
TODOS.md
@@ -2,55 +2,55 @@
|
|||||||
|
|
||||||
1. Commands are still leaking.
|
1. Commands are still leaking.
|
||||||
|
|
||||||
28. **db.odin** — Inconsistencies in how struct vs sqlite are named.
|
2. **db.odin** — Inconsistencies in how struct vs sqlite are named.
|
||||||
|
|
||||||
29. Add color flag and support non colored output.
|
3. Add color flag and support non colored output.
|
||||||
|
|
||||||
30. Use text/tables for command output
|
4. Use text/tables for command output
|
||||||
|
|
||||||
2. Generate md and man pages again.
|
5. Generate md and man pages again.
|
||||||
|
|
||||||
3. **db.odin:324-327** — Map iteration (`remote_set`) is non-deterministic. Same file can produce different JSON on each backup, causing spurious DB diffs. Sort remotes before storing.
|
6. **db.odin:324-327** — Map iteration (`remote_set`) is non-deterministic. Same file can produce different JSON on each backup, causing spurious DB diffs. Sort remotes before storing.
|
||||||
|
|
||||||
4. Make sure official path separators are used when appropriate, rather than '/'.
|
7. Make sure official path separators are used when appropriate, rather than '/'.
|
||||||
|
|
||||||
5. **cmd_restore.odin:20-30 & cmd_remove.odin:19-29** — Identical path-resolution block copy-pasted. `is_abs` guard is redundant since `filepath.abs` is a no-op on absolute paths. Extract a helper.
|
8. **cmd_restore.odin:20-30 & cmd_remove.odin:19-29** — Identical path-resolution block copy-pasted. `is_abs` guard is redundant since `filepath.abs` is a no-op on absolute paths. Extract a helper.
|
||||||
|
|
||||||
6. **cmd_restore.odin:44** — `os.mkdir_all` error silently discarded. Subsequent write failure will be confusing.
|
9. **cmd_restore.odin:44** — `os.mkdir_all` error silently discarded. Subsequent write failure will be confusing.
|
||||||
|
|
||||||
8. **config.odin:178** — `search_paths` silently ignores `os.user_home_dir` error. If home is empty, `~` isn't expanded. Same class of bug as issue 3.
|
10. **config.odin:178** — `search_paths` silently ignores `os.user_home_dir` error. If home is empty, `~` isn't expanded. Same class of bug as issue 3.
|
||||||
|
|
||||||
10. **db.odin:115** — `json.unmarshal_string` error not checked. Malformed JSON silently produces empty/partial data.
|
11. **db.odin:115** — `json.unmarshal_string` error not checked. Malformed JSON silently produces empty/partial data.
|
||||||
|
|
||||||
11. **db.odin:352-353** — `hex.encode` error ignored. `string(hex_bytes)` aliases the byte slice.
|
12. **db.odin:352-353** — `hex.encode` error ignored. `string(hex_bytes)` aliases the byte slice.
|
||||||
|
|
||||||
12. **cmd_sync.odin:80, cmd_list.odin:33** — `make([]string, 2)` for table rows never freed. Leaks per row. Defer to memory pass.
|
13. **cmd_sync.odin:80, cmd_list.odin:33** — `make([]string, 2)` for table rows never freed. Leaks per row. Defer to memory pass.
|
||||||
|
|
||||||
13. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`.
|
14. Check for prealloc opportunities. i.e. `make([dynamic]string)` -> `make([dynamic]string, 5)`.
|
||||||
|
|
||||||
14. Add a text filter to the multi_select.
|
15. Add a text filter to the multi_select.
|
||||||
|
|
||||||
16. Add tests for untested commands.
|
16. Add tests for untested commands.
|
||||||
|
|
||||||
17. 2 scan tests silently skip when fd isn't installed, tests pass without actually testing anything. These should use #assert to be sure that fd is in path.
|
17. 2 scan tests silently skip when fd isn't installed, tests pass without actually testing anything. These should use #assert to be sure that fd is in path.
|
||||||
|
|
||||||
19. add --format -f flag to commands that draw tables.
|
18. add --format -f flag to commands that draw tables.
|
||||||
|
|
||||||
20. Replace `testing.expect` calls with `testing.expect_value` calls where appropriate.
|
19. Replace `testing.expect` calls with `testing.expect_value` calls where appropriate.
|
||||||
|
|
||||||
21. Change struct field names from PascalCase to snake_case.
|
20. Change struct field names from PascalCase to snake_case.
|
||||||
|
|
||||||
23. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
21. procedures should be ordered by use, main at the top, then in the order they are called from main.
|
||||||
|
|
||||||
24. Shell completion
|
22. Shell completion
|
||||||
|
|
||||||
25. Bring back windows support / cross-compilation.
|
23. Bring back windows support / cross-compilation.
|
||||||
|
|
||||||
26. Test all cmds / terminal branches.
|
24. Test all cmds / terminal branches.
|
||||||
|
|
||||||
27. Replace `fmt.tprintf("/tmp/envr-test-...-%d", os.get_pid())` + `os.mkdir_all` in test files with `os.mkdir_temp` (race-free, honors `$TMPDIR`, matches `findr/test_env.odin` pattern).
|
25. Replace `fmt.tprintf("/tmp/envr-test-...-%d", os.get_pid())` + `os.mkdir_all` in test files with `os.mkdir_temp` (race-free, honors `$TMPDIR`, matches `findr/test_env.odin` pattern).
|
||||||
|
|
||||||
28. Adopt `core:log` across `db.odin`, `crypto.odin`, `config.odin`, `ssh.odin` — replace ~30 scattered `fmt.printf("Error ...")` calls with leveled logging for consistent stderr routing and source locations.
|
26. Adopt `core:log` across `db.odin`, `crypto.odin`, `config.odin`, `ssh.odin` — replace ~30 scattered `fmt.printf("Error ...")` calls with leveled logging for consistent stderr routing and source locations.
|
||||||
|
|
||||||
## Double-check AI output
|
## Double-check AI output
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
- [ ] scan.odin
|
- [ ] scan.odin
|
||||||
- [ ] scan_test.odin
|
- [ ] scan_test.odin
|
||||||
- [ ] sodium.odin
|
- [ ] sodium.odin
|
||||||
- [ ] sqlite/sqlite.odin
|
- [x] sqlite/sqlite.odin
|
||||||
- [ ] ssh.odin
|
- [ ] ssh.odin
|
||||||
- [ ] ssh_test.odin
|
- [ ] ssh_test.odin
|
||||||
- [ ] table.odin
|
- [ ] table.odin
|
||||||
|
|||||||
164
db.odin
164
db.odin
@@ -31,8 +31,7 @@ SyncError :: enum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Db :: struct {
|
Db :: struct {
|
||||||
// Pointer to the sqlite db
|
conn: sqlite.Db,
|
||||||
db: ^rawptr,
|
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
changed: bool,
|
changed: bool,
|
||||||
arena: mem.Dynamic_Arena,
|
arena: mem.Dynamic_Arena,
|
||||||
@@ -57,47 +56,47 @@ delete_envfile :: proc(f: ^EnvFile) {
|
|||||||
delete(f.contents)
|
delete(f.contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
db_open :: proc(cfg_path: string) -> (database: Db, ok: bool) {
|
db_open :: proc(cfg_path: string) -> (db: Db, ok: bool) {
|
||||||
database = db_init() or_return
|
db = db_init() or_return
|
||||||
database.cfg = load_config(cfg_path, db_allocator(&database)) or_return
|
db.cfg = load_config(cfg_path, db_allocator(&db)) or_return
|
||||||
|
|
||||||
// TODO: Use different allocators?
|
// TODO: Use different allocators?
|
||||||
data_path := data_path(database.cfg.config_path, context.temp_allocator)
|
data_path := data_path(db.cfg.config_path, context.temp_allocator)
|
||||||
if os.exists(data_path) {
|
if os.exists(data_path) {
|
||||||
if ok = db_restore_from_encrypted(&database, data_path); !ok {
|
if ok = db_restore_from_encrypted(&db, data_path); !ok {
|
||||||
sqlite.close(database.db)
|
sqlite.close(db.conn)
|
||||||
return database, false
|
return db, false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// DB was created
|
// DB was created
|
||||||
database.changed = true
|
db.changed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return database, true
|
return db, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a database an allocator and fresh, empty table, with zero encryption.
|
// Creates a database an allocator and fresh, empty table, with zero encryption.
|
||||||
// In production, you most likely want to use `db_open`.
|
// In production, you most likely want to use `db_open`.
|
||||||
db_init :: proc() -> (database: Db, ok: bool) {
|
db_init :: proc() -> (db: Db, ok: bool) {
|
||||||
db: ^rawptr
|
conn: sqlite.Db
|
||||||
rc := sqlite.open(":memory:", &db)
|
rc := sqlite.open(":memory:", &conn)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error opening in-memory database: %s\n", sqlite.db_errmsg(db))
|
fmt.printf("Error opening in-memory database: %s\n", sqlite.db_errmsg(conn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
create_sql: cstring = "CREATE TABLE IF NOT EXISTS envr_env_files (path TEXT PRIMARY KEY NOT NULL, remotes TEXT, sha256 TEXT NOT NULL, contents TEXT NOT NULL)"
|
create_sql: cstring = "CREATE TABLE IF NOT EXISTS envr_env_files (path TEXT PRIMARY KEY NOT NULL, remotes TEXT, sha256 TEXT NOT NULL, contents TEXT NOT NULL)"
|
||||||
rc = sqlite.db_exec(db, create_sql, nil, nil, nil)
|
rc = sqlite.db_exec(conn, create_sql, nil, nil, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error creating table: %s\n", sqlite.db_errmsg(db))
|
fmt.printf("Error creating table: %s\n", sqlite.db_errmsg(conn))
|
||||||
sqlite.close(db)
|
sqlite.close(conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
database.db = db
|
db.conn = conn
|
||||||
|
|
||||||
mem.dynamic_arena_init(&database.arena)
|
mem.dynamic_arena_init(&db.arena)
|
||||||
|
|
||||||
return database, true
|
return db, true
|
||||||
}
|
}
|
||||||
|
|
||||||
db_allocator :: proc(db: ^Db) -> mem.Allocator {
|
db_allocator :: proc(db: ^Db) -> mem.Allocator {
|
||||||
@@ -127,43 +126,38 @@ db_restore_from_encrypted :: proc(db: ^Db, data_path: string) -> bool {
|
|||||||
}
|
}
|
||||||
copy(buf[:len(plaintext)], plaintext)
|
copy(buf[:len(plaintext)], plaintext)
|
||||||
|
|
||||||
rc := sqlite.deserialize(
|
flags: sqlite.DESERIALIZE_FLAGS = {.FREEONCLOSE, .RESIZEABLE}
|
||||||
db.db,
|
|
||||||
"main",
|
rc := sqlite.deserialize(db.conn, "main", buf, n, n, flags)
|
||||||
buf,
|
|
||||||
n,
|
|
||||||
n,
|
|
||||||
sqlite.DESERIALIZE_FREEONCLOSE | sqlite.DESERIALIZE_RESIZEABLE,
|
|
||||||
)
|
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
sqlite.free(buf)
|
sqlite.free(buf)
|
||||||
fmt.printf("Error deserializing database: %s\n", sqlite.db_errmsg(db.db))
|
fmt.printf("Error deserializing database: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
db_close :: proc(d: ^Db) {
|
db_close :: proc(db: ^Db) {
|
||||||
allocator := db_allocator(d)
|
allocator := db_allocator(db)
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
sqlite.close(d.db)
|
sqlite.close(db.conn)
|
||||||
|
|
||||||
delete_config(&d.cfg, allocator)
|
delete_config(&db.cfg, allocator)
|
||||||
|
|
||||||
mem.dynamic_arena_destroy(&d.arena)
|
mem.dynamic_arena_destroy(&db.arena)
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.changed {
|
if db.changed {
|
||||||
rc := sqlite.db_exec(d.db, "VACUUM", nil, nil, nil)
|
rc := sqlite.db_exec(db.conn, "VACUUM", nil, nil, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error vacuuming database: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error vacuuming database: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sz: i64
|
sz: i64
|
||||||
data := sqlite.serialize(d.db, "main", &sz, 0)
|
data := sqlite.serialize(db.conn, "main", &sz, 0)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
fmt.println("Error: failed to serialize database")
|
fmt.println("Error: failed to serialize database")
|
||||||
return
|
return
|
||||||
@@ -172,14 +166,14 @@ db_close :: proc(d: ^Db) {
|
|||||||
|
|
||||||
sqlite_data := data[:sz]
|
sqlite_data := data[:sz]
|
||||||
// TODO: PAss allocator chain
|
// TODO: PAss allocator chain
|
||||||
encrypted, enc_ok := encrypt(sqlite_data, d.cfg.Keys[:])
|
encrypted, enc_ok := encrypt(sqlite_data, db.cfg.Keys[:])
|
||||||
if !enc_ok {
|
if !enc_ok {
|
||||||
fmt.println("Error: encryption failed")
|
fmt.println("Error: encryption failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data_path := data_path(d.cfg.config_path, allocator)
|
data_path := data_path(db.cfg.config_path, allocator)
|
||||||
envr_d := envr_dir(d.cfg.config_path)
|
envr_d := envr_dir(db.cfg.config_path)
|
||||||
os.mkdir_all(envr_d)
|
os.mkdir_all(envr_d)
|
||||||
|
|
||||||
write_err := os.write_entire_file(data_path, encrypted)
|
write_err := os.write_entire_file(data_path, encrypted)
|
||||||
@@ -189,27 +183,27 @@ db_close :: proc(d: ^Db) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
d.changed = false
|
db.changed = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Results will be freed when `db_close` is called.
|
// Results will be freed when `db_close` is called.
|
||||||
db_list :: proc(d: ^Db) -> ([]EnvFile, bool) {
|
db_list :: proc(db: ^Db) -> ([]EnvFile, bool) {
|
||||||
stmt: ^rawptr
|
stmt: sqlite.Stmt
|
||||||
rc := sqlite.prepare_v2(
|
rc := sqlite.prepare_v2(
|
||||||
d.db,
|
db.conn,
|
||||||
"SELECT path, remotes, sha256, contents FROM envr_env_files",
|
"SELECT path, remotes, sha256, contents FROM envr_env_files",
|
||||||
-1,
|
-1,
|
||||||
&stmt,
|
&stmt,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error preparing query: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error preparing query: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return []EnvFile{}, false
|
return []EnvFile{}, false
|
||||||
}
|
}
|
||||||
defer sqlite.finalize(stmt)
|
defer sqlite.finalize(stmt)
|
||||||
|
|
||||||
allocator := db_allocator(d)
|
allocator := db_allocator(db)
|
||||||
results := make([dynamic]EnvFile, 0, 10, allocator)
|
results := make([dynamic]EnvFile, 0, 10, allocator)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -218,7 +212,7 @@ db_list :: proc(d: ^Db) -> ([]EnvFile, bool) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if rc != sqlite.ROW {
|
if rc != sqlite.ROW {
|
||||||
fmt.printf("Error stepping query: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error stepping query: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
#no_bounds_check return results[:], false
|
#no_bounds_check return results[:], false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,7 +239,7 @@ db_list :: proc(d: ^Db) -> ([]EnvFile, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should we use context.temp_allocator for proc scoped lifetimes?
|
// TODO: Should we use context.temp_allocator for proc scoped lifetimes?
|
||||||
db_insert :: proc(d: ^Db, file: EnvFile) -> bool {
|
db_insert :: proc(db: ^Db, file: EnvFile) -> bool {
|
||||||
remotes_json, marshal_err := json.marshal(file.Remotes, allocator = context.temp_allocator)
|
remotes_json, marshal_err := json.marshal(file.Remotes, allocator = context.temp_allocator)
|
||||||
if marshal_err != nil {
|
if marshal_err != nil {
|
||||||
fmt.printf("Error marshaling remotes: %v\n", marshal_err)
|
fmt.printf("Error marshaling remotes: %v\n", marshal_err)
|
||||||
@@ -255,10 +249,10 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool {
|
|||||||
sql: cstring =
|
sql: cstring =
|
||||||
"INSERT OR REPLACE INTO " +
|
"INSERT OR REPLACE INTO " +
|
||||||
"envr_env_files (path, remotes, sha256, contents) VALUES (?, ?, ?, ?)"
|
"envr_env_files (path, remotes, sha256, contents) VALUES (?, ?, ?, ?)"
|
||||||
stmt: ^rawptr
|
stmt: sqlite.Stmt
|
||||||
rc := sqlite.prepare_v2(d.db, sql, -1, &stmt, nil)
|
rc := sqlite.prepare_v2(db.conn, sql, -1, &stmt, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error preparing insert: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error preparing insert: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer sqlite.finalize(stmt)
|
defer sqlite.finalize(stmt)
|
||||||
@@ -268,7 +262,7 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool {
|
|||||||
defer delete(cpath)
|
defer delete(cpath)
|
||||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +270,7 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool {
|
|||||||
defer delete(cremotes)
|
defer delete(cremotes)
|
||||||
rc = sqlite.bind_text(stmt, 2, cremotes, -1, nil)
|
rc = sqlite.bind_text(stmt, 2, cremotes, -1, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error binding remotes: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error binding remotes: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,7 +278,7 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool {
|
|||||||
defer delete(csha)
|
defer delete(csha)
|
||||||
rc = sqlite.bind_text(stmt, 3, csha, -1, nil)
|
rc = sqlite.bind_text(stmt, 3, csha, -1, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error binding sha256: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error binding sha256: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,38 +286,38 @@ db_insert :: proc(d: ^Db, file: EnvFile) -> bool {
|
|||||||
defer delete(ccontents)
|
defer delete(ccontents)
|
||||||
rc = sqlite.bind_text(stmt, 4, ccontents, -1, nil)
|
rc = sqlite.bind_text(stmt, 4, ccontents, -1, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error binding contents: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error binding contents: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = sqlite.step(stmt)
|
rc = sqlite.step(stmt)
|
||||||
if rc != sqlite.DONE {
|
if rc != sqlite.DONE {
|
||||||
fmt.printf("Error inserting: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error inserting: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
d.changed = true
|
db.changed = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result will be freed when `db_close` is called.
|
// Result will be freed when `db_close` is called.
|
||||||
db_fetch :: proc(d: ^Db, path: string) -> (EnvFile, bool) {
|
db_fetch :: proc(db: ^Db, path: string) -> (EnvFile, bool) {
|
||||||
sql: cstring = "SELECT path, remotes, sha256, contents FROM envr_env_files WHERE path = ?"
|
sql: cstring = "SELECT path, remotes, sha256, contents FROM envr_env_files WHERE path = ?"
|
||||||
stmt: ^rawptr
|
stmt: sqlite.Stmt
|
||||||
rc := sqlite.prepare_v2(d.db, sql, -1, &stmt, nil)
|
rc := sqlite.prepare_v2(db.conn, sql, -1, &stmt, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error preparing fetch: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error preparing fetch: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return EnvFile{}, false
|
return EnvFile{}, false
|
||||||
}
|
}
|
||||||
defer sqlite.finalize(stmt)
|
defer sqlite.finalize(stmt)
|
||||||
|
|
||||||
allocator := db_allocator(d)
|
allocator := db_allocator(db)
|
||||||
|
|
||||||
cpath := to_cstring(path, allocator)
|
cpath := to_cstring(path, allocator)
|
||||||
defer delete(cpath, allocator)
|
defer delete(cpath, allocator)
|
||||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return EnvFile{}, false
|
return EnvFile{}, false
|
||||||
}
|
}
|
||||||
rc = sqlite.step(stmt)
|
rc = sqlite.step(stmt)
|
||||||
@@ -332,7 +326,7 @@ db_fetch :: proc(d: ^Db, path: string) -> (EnvFile, bool) {
|
|||||||
return EnvFile{}, false
|
return EnvFile{}, false
|
||||||
}
|
}
|
||||||
if rc != sqlite.ROW {
|
if rc != sqlite.ROW {
|
||||||
fmt.printf("Error fetching: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error fetching: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return EnvFile{}, false
|
return EnvFile{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,12 +348,12 @@ db_fetch :: proc(d: ^Db, path: string) -> (EnvFile, bool) {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
db_delete :: proc(d: ^Db, path: string) -> bool {
|
db_delete :: proc(db: ^Db, path: string) -> bool {
|
||||||
sql: cstring = "DELETE FROM envr_env_files WHERE path = ?"
|
sql: cstring = "DELETE FROM envr_env_files WHERE path = ?"
|
||||||
stmt: ^rawptr
|
stmt: sqlite.Stmt
|
||||||
rc := sqlite.prepare_v2(d.db, sql, -1, &stmt, nil)
|
rc := sqlite.prepare_v2(db.conn, sql, -1, &stmt, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error preparing delete: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error preparing delete: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer sqlite.finalize(stmt)
|
defer sqlite.finalize(stmt)
|
||||||
@@ -368,21 +362,21 @@ db_delete :: proc(d: ^Db, path: string) -> bool {
|
|||||||
defer delete(cpath)
|
defer delete(cpath)
|
||||||
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
rc = sqlite.bind_text(stmt, 1, cpath, -1, nil)
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error binding path: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
rc = sqlite.step(stmt)
|
rc = sqlite.step(stmt)
|
||||||
if rc != sqlite.DONE {
|
if rc != sqlite.DONE {
|
||||||
fmt.printf("Error deleting: %s\n", sqlite.db_errmsg(d.db))
|
fmt.printf("Error deleting: %s\n", sqlite.db_errmsg(db.conn))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if sqlite.changes(d.db) == 0 {
|
if sqlite.changes(db.conn) == 0 {
|
||||||
fmt.printf("No file found with path: %s\n", path)
|
fmt.printf("No file found with path: %s\n", path)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
d.changed = true
|
db.changed = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,13 +414,13 @@ new_env_file :: proc(path: string) -> (EnvFile, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reconciles `f` with the filesystem and persists changes to the database.
|
// Reconciles `f` with the filesystem and persists changes to the database.
|
||||||
db_sync :: proc(d: ^Db, f: ^EnvFile) -> (SyncFlag, SyncError) {
|
db_sync :: proc(db: ^Db, f: ^EnvFile) -> (SyncFlag, SyncError) {
|
||||||
allocator := db_allocator(d)
|
allocator := db_allocator(db)
|
||||||
result: SyncFlag = {}
|
result: SyncFlag = {}
|
||||||
old_path := f.Path
|
old_path := f.Path
|
||||||
|
|
||||||
if !os.exists(f.Dir) {
|
if !os.exists(f.Dir) {
|
||||||
moved, err := try_move_dir(d, f, allocator)
|
moved, err := try_move_dir(db, f, allocator)
|
||||||
if !moved {
|
if !moved {
|
||||||
return {}, err
|
return {}, err
|
||||||
}
|
}
|
||||||
@@ -440,7 +434,7 @@ db_sync :: proc(d: ^Db, f: ^EnvFile) -> (SyncFlag, SyncError) {
|
|||||||
return result, .WriteFailed
|
return result, .WriteFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
if !db_persist(d, f, old_path) {
|
if !db_persist(db, f, old_path) {
|
||||||
return result, .DbFailed
|
return result, .DbFailed
|
||||||
}
|
}
|
||||||
return result + {.Restored}, .None
|
return result + {.Restored}, .None
|
||||||
@@ -461,7 +455,7 @@ db_sync :: proc(d: ^Db, f: ^EnvFile) -> (SyncFlag, SyncError) {
|
|||||||
current_sha := string(hex_bytes)
|
current_sha := string(hex_bytes)
|
||||||
|
|
||||||
if current_sha == f.Sha256 {
|
if current_sha == f.Sha256 {
|
||||||
if !db_persist(d, f, old_path) {
|
if !db_persist(db, f, old_path) {
|
||||||
return result, .DbFailed
|
return result, .DbFailed
|
||||||
}
|
}
|
||||||
return result, .None
|
return result, .None
|
||||||
@@ -469,23 +463,23 @@ db_sync :: proc(d: ^Db, f: ^EnvFile) -> (SyncFlag, SyncError) {
|
|||||||
|
|
||||||
f.contents = string(data)
|
f.contents = string(data)
|
||||||
f.Sha256 = current_sha
|
f.Sha256 = current_sha
|
||||||
if !db_persist(d, f, old_path) {
|
if !db_persist(db, f, old_path) {
|
||||||
return result, .DbFailed
|
return result, .DbFailed
|
||||||
}
|
}
|
||||||
return result + {.BackedUp}, .None
|
return result + {.BackedUp}, .None
|
||||||
}
|
}
|
||||||
|
|
||||||
db_persist :: proc(d: ^Db, f: ^EnvFile, old_path: string) -> bool {
|
db_persist :: proc(db: ^Db, f: ^EnvFile, old_path: string) -> bool {
|
||||||
if f.Path != old_path {
|
if f.Path != old_path {
|
||||||
if !db_delete(d, old_path) {
|
if !db_delete(db, old_path) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return db_insert(d, f^)
|
return db_insert(db, f^)
|
||||||
}
|
}
|
||||||
|
|
||||||
try_move_dir :: proc(d: ^Db, f: ^EnvFile, allocator: mem.Allocator) -> (bool, SyncError) {
|
try_move_dir :: proc(db: ^Db, f: ^EnvFile, allocator: mem.Allocator) -> (bool, SyncError) {
|
||||||
roots, ok := find_git_roots(d.cfg)
|
roots, ok := find_git_roots(db.cfg)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, .GitRootFailed
|
return false, .GitRootFailed
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
defer delete(plaintext)
|
defer delete(plaintext)
|
||||||
|
|
||||||
mem_db: ^rawptr
|
mem_db: sqlite.Db
|
||||||
rc := sqlite.open(":memory:", &mem_db)
|
rc := sqlite.open(":memory:", &mem_db)
|
||||||
testing.expectf(t, rc == sqlite.OK, "failed to open in-memory db")
|
testing.expectf(t, rc == sqlite.OK, "failed to open in-memory db")
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
@@ -179,14 +179,7 @@ test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) {
|
|||||||
if buf == nil do return
|
if buf == nil do return
|
||||||
copy(buf[:len(plaintext)], plaintext)
|
copy(buf[:len(plaintext)], plaintext)
|
||||||
|
|
||||||
rc = sqlite.deserialize(
|
rc = sqlite.deserialize(mem_db, "main", buf, n, n, {.FREEONCLOSE, .RESIZEABLE})
|
||||||
mem_db,
|
|
||||||
"main",
|
|
||||||
buf,
|
|
||||||
n,
|
|
||||||
n,
|
|
||||||
sqlite.DESERIALIZE_FREEONCLOSE | sqlite.DESERIALIZE_RESIZEABLE,
|
|
||||||
)
|
|
||||||
testing.expect(t, rc == sqlite.OK, "deserialize should succeed")
|
testing.expect(t, rc == sqlite.OK, "deserialize should succeed")
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
sqlite.free(buf)
|
sqlite.free(buf)
|
||||||
@@ -194,7 +187,7 @@ test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sql: cstring = "SELECT path FROM envr_env_files"
|
sql: cstring = "SELECT path FROM envr_env_files"
|
||||||
stmt: ^rawptr
|
stmt: sqlite.Stmt
|
||||||
rc = sqlite.prepare_v2(mem_db, sql, -1, &stmt, nil)
|
rc = sqlite.prepare_v2(mem_db, sql, -1, &stmt, nil)
|
||||||
testing.expect(t, rc == sqlite.OK, "prepare failed")
|
testing.expect(t, rc == sqlite.OK, "prepare failed")
|
||||||
if rc != sqlite.OK {
|
if rc != sqlite.OK {
|
||||||
|
|||||||
138
db_test.odin
138
db_test.odin
@@ -27,10 +27,10 @@ make_test_env_file :: proc(path, sha, contents: string, remotes: []string = {})
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_insert_and_fetch :: proc(t: ^testing.T) {
|
test_db_insert_and_fetch :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
path := "/project/.env"
|
path := "/project/.env"
|
||||||
sha := "abc123"
|
sha := "abc123"
|
||||||
@@ -39,9 +39,9 @@ test_db_insert_and_fetch :: proc(t: ^testing.T) {
|
|||||||
f := make_test_env_file(path, sha, contents, []string{"git@github.com:user/repo.git"})
|
f := make_test_env_file(path, sha, contents, []string{"git@github.com:user/repo.git"})
|
||||||
defer delete(f.Remotes)
|
defer delete(f.Remotes)
|
||||||
|
|
||||||
testing.expect(t, db_insert(&d, f), "insert should succeed")
|
testing.expect(t, db_insert(&db, f), "insert should succeed")
|
||||||
|
|
||||||
fetched, fetch_ok := db_fetch(&d, "/project/.env")
|
fetched, fetch_ok := db_fetch(&db, "/project/.env")
|
||||||
// defer delete_envfile(&fetched)
|
// defer delete_envfile(&fetched)
|
||||||
testing.expect(t, fetch_ok, "fetch should succeed")
|
testing.expect(t, fetch_ok, "fetch should succeed")
|
||||||
if !fetch_ok do return
|
if !fetch_ok do return
|
||||||
@@ -55,35 +55,35 @@ test_db_insert_and_fetch :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_fetch_missing :: proc(t: ^testing.T) {
|
test_db_fetch_missing :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
_, fetch_ok := db_fetch(&d, "/nonexistent/.env")
|
_, fetch_ok := db_fetch(&db, "/nonexistent/.env")
|
||||||
testing.expect(t, !fetch_ok, "fetch missing should return false")
|
testing.expect(t, !fetch_ok, "fetch missing should return false")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_insert_or_replace :: proc(t: ^testing.T) {
|
test_db_insert_or_replace :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
|
|
||||||
f1 := make_test_env_file("/project/.env", "sha1", "KEY=old")
|
f1 := make_test_env_file("/project/.env", "sha1", "KEY=old")
|
||||||
defer delete(f1.Remotes)
|
defer delete(f1.Remotes)
|
||||||
testing.expect(t, db_insert(&d, f1), "first insert should succeed")
|
testing.expect(t, db_insert(&db, f1), "first insert should succeed")
|
||||||
|
|
||||||
f2 := make_test_env_file("/project/.env", "sha2", "KEY=new")
|
f2 := make_test_env_file("/project/.env", "sha2", "KEY=new")
|
||||||
defer delete(f2.Remotes)
|
defer delete(f2.Remotes)
|
||||||
testing.expect(t, db_insert(&d, f2), "second insert should succeed")
|
testing.expect(t, db_insert(&db, f2), "second insert should succeed")
|
||||||
|
|
||||||
results, list_ok := db_list(&d)
|
results, list_ok := db_list(&db)
|
||||||
testing.expect(t, list_ok, "list should succeed")
|
testing.expect(t, list_ok, "list should succeed")
|
||||||
|
|
||||||
testing.expect(t, len(results) == 1, "should have 1 row, not 2")
|
testing.expect(t, len(results) == 1, "should have 1 row, not 2")
|
||||||
|
|
||||||
fetched, fetch_ok := db_fetch(&d, "/project/.env")
|
fetched, fetch_ok := db_fetch(&db, "/project/.env")
|
||||||
testing.expect(t, fetch_ok, "fetch should succeed")
|
testing.expect(t, fetch_ok, "fetch should succeed")
|
||||||
if !fetch_ok do return
|
if !fetch_ok do return
|
||||||
// defer delete_envfile(&fetched)
|
// defer delete_envfile(&fetched)
|
||||||
@@ -94,36 +94,36 @@ test_db_insert_or_replace :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_delete_existing :: proc(t: ^testing.T) {
|
test_db_delete_existing :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
||||||
defer delete(f.Remotes)
|
defer delete(f.Remotes)
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
testing.expect(t, db_delete(&d, "/project/.env"), "delete should return true")
|
testing.expect(t, db_delete(&db, "/project/.env"), "delete should return true")
|
||||||
|
|
||||||
_, fetch_ok := db_fetch(&d, "/project/.env")
|
_, fetch_ok := db_fetch(&db, "/project/.env")
|
||||||
testing.expect(t, !fetch_ok, "row should be gone after delete")
|
testing.expect(t, !fetch_ok, "row should be gone after delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_delete_missing :: proc(t: ^testing.T) {
|
test_db_delete_missing :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
testing.expect(t, !db_delete(&d, "/nonexistent/.env"), "delete missing should return false")
|
testing.expect(t, !db_delete(&db, "/nonexistent/.env"), "delete missing should return false")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_list_multiple :: proc(t: ^testing.T) {
|
test_db_list_multiple :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f1 := make_test_env_file("/proj1/.env", "sha1", "A=1", []string{"git@github.com:a/repo.git"})
|
f1 := make_test_env_file("/proj1/.env", "sha1", "A=1", []string{"git@github.com:a/repo.git"})
|
||||||
defer delete(f1.Remotes)
|
defer delete(f1.Remotes)
|
||||||
@@ -131,11 +131,11 @@ test_db_list_multiple :: proc(t: ^testing.T) {
|
|||||||
defer delete(f2.Remotes)
|
defer delete(f2.Remotes)
|
||||||
f3 := make_test_env_file("/proj3/.env", "sha3", "C=3")
|
f3 := make_test_env_file("/proj3/.env", "sha3", "C=3")
|
||||||
|
|
||||||
db_insert(&d, f1)
|
db_insert(&db, f1)
|
||||||
db_insert(&d, f2)
|
db_insert(&db, f2)
|
||||||
db_insert(&d, f3)
|
db_insert(&db, f3)
|
||||||
|
|
||||||
results, list_ok := db_list(&d)
|
results, list_ok := db_list(&db)
|
||||||
testing.expect(t, list_ok, "list should succeed")
|
testing.expect(t, list_ok, "list should succeed")
|
||||||
|
|
||||||
testing.expect_value(t, len(results), 3)
|
testing.expect_value(t, len(results), 3)
|
||||||
@@ -143,60 +143,60 @@ test_db_list_multiple :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_list_empty :: proc(t: ^testing.T) {
|
test_db_list_empty :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
results, list_ok := db_list(&d)
|
results, list_ok := db_list(&db)
|
||||||
testing.expect(t, list_ok, "list should succeed on empty db")
|
testing.expect(t, list_ok, "list should succeed on empty db")
|
||||||
testing.expect(t, len(results) == 0, "should have 0 rows")
|
testing.expect(t, len(results) == 0, "should have 0 rows")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_insert_sets_changed :: proc(t: ^testing.T) {
|
test_db_insert_sets_changed :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
testing.expect(t, !d.changed, "changed should start false")
|
testing.expect(t, !db.changed, "changed should start false")
|
||||||
|
|
||||||
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
||||||
defer delete(f.Remotes)
|
defer delete(f.Remotes)
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
testing.expect(t, d.changed, "changed should be true after insert")
|
testing.expect(t, db.changed, "changed should be true after insert")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_delete_sets_changed :: proc(t: ^testing.T) {
|
test_db_delete_sets_changed :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
||||||
defer delete(f.Remotes)
|
defer delete(f.Remotes)
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
d.changed = false
|
db.changed = false
|
||||||
|
|
||||||
db_delete(&d, "/project/.env")
|
db_delete(&db, "/project/.env")
|
||||||
testing.expect(t, d.changed, "changed should be true after delete")
|
testing.expect(t, db.changed, "changed should be true after delete")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_serialize :: proc(t: ^testing.T) {
|
test_db_serialize :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
f := make_test_env_file("/project/.env", "sha", "KEY=val")
|
||||||
defer delete(f.Remotes)
|
defer delete(f.Remotes)
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
sz: i64
|
sz: i64
|
||||||
data := sqlite.serialize(d.db, "main", &sz, 0)
|
data := sqlite.serialize(db.conn, "main", &sz, 0)
|
||||||
testing.expect(t, data != nil, "serialize should return non-nil")
|
testing.expect(t, data != nil, "serialize should return non-nil")
|
||||||
if data == nil do return
|
if data == nil do return
|
||||||
defer sqlite.free(data)
|
defer sqlite.free(data)
|
||||||
@@ -439,15 +439,15 @@ test_db_sync_noop :: proc(t: ^testing.T) {
|
|||||||
hex_bytes, _ := hex.encode(digest, context.temp_allocator)
|
hex_bytes, _ := hex.encode(digest, context.temp_allocator)
|
||||||
sha := string(hex_bytes)
|
sha := string(hex_bytes)
|
||||||
|
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f := make_test_env_file(env_path, sha, content)
|
f := make_test_env_file(env_path, sha, content)
|
||||||
f.Dir = base
|
f.Dir = base
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
result, sync_err := db_sync(&d, &f)
|
result, sync_err := db_sync(&db, &f)
|
||||||
testing.expect(t, sync_err == .None, "sync should not error")
|
testing.expect(t, sync_err == .None, "sync should not error")
|
||||||
testing.expect(t, result == {}, "should be noop")
|
testing.expect(t, result == {}, "should be noop")
|
||||||
}
|
}
|
||||||
@@ -463,15 +463,15 @@ test_db_sync_backed_up :: proc(t: ^testing.T) {
|
|||||||
write_err := os.write_entire_file(env_path, transmute([]u8)changed_content)
|
write_err := os.write_entire_file(env_path, transmute([]u8)changed_content)
|
||||||
testing.expect(t, write_err == nil, "should write .env file")
|
testing.expect(t, write_err == nil, "should write .env file")
|
||||||
|
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f := make_test_env_file(env_path, "old_sha", "KEY=original")
|
f := make_test_env_file(env_path, "old_sha", "KEY=original")
|
||||||
f.Dir = base
|
f.Dir = base
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
result, sync_err := db_sync(&d, &f)
|
result, sync_err := db_sync(&db, &f)
|
||||||
testing.expect(t, sync_err == .None, "sync should not error")
|
testing.expect(t, sync_err == .None, "sync should not error")
|
||||||
testing.expect(t, .BackedUp in result, "should be backed up")
|
testing.expect(t, .BackedUp in result, "should be backed up")
|
||||||
}
|
}
|
||||||
@@ -484,16 +484,16 @@ test_db_sync_restored :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
env_path := fmt.tprintf("%s/.env", base)
|
env_path := fmt.tprintf("%s/.env", base)
|
||||||
|
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f := make_test_env_file(env_path, "some_sha", "SECRET=value")
|
f := make_test_env_file(env_path, "some_sha", "SECRET=value")
|
||||||
f.Dir = base
|
f.Dir = base
|
||||||
defer delete(f.Remotes)
|
defer delete(f.Remotes)
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
result, err := db_sync(&d, &f)
|
result, err := db_sync(&db, &f)
|
||||||
testing.expect(t, err == .None, "sync should not error")
|
testing.expect(t, err == .None, "sync should not error")
|
||||||
testing.expect(t, .Restored in result, "should be restored")
|
testing.expect(t, .Restored in result, "should be restored")
|
||||||
|
|
||||||
@@ -506,14 +506,14 @@ test_db_sync_restored :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_db_sync_dir_missing :: proc(t: ^testing.T) {
|
test_db_sync_dir_missing :: proc(t: ^testing.T) {
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
f := make_test_env_file("/nonexistent/path/.env", "sha", "KEY=val")
|
f := make_test_env_file("/nonexistent/path/.env", "sha", "KEY=val")
|
||||||
db_insert(&d, f)
|
db_insert(&db, f)
|
||||||
|
|
||||||
result, err := db_sync(&d, &f)
|
result, err := db_sync(&db, &f)
|
||||||
testing.expect_value(t, err, SyncError.DirMissing)
|
testing.expect_value(t, err, SyncError.DirMissing)
|
||||||
testing.expect_value(t, result, nil)
|
testing.expect_value(t, result, nil)
|
||||||
}
|
}
|
||||||
@@ -533,12 +533,12 @@ test_db_sync_moved :: proc(t: ^testing.T) {
|
|||||||
write_err := os.write_entire_file(config_path, transmute([]u8)config_content)
|
write_err := os.write_entire_file(config_path, transmute([]u8)config_content)
|
||||||
testing.expect(t, write_err == nil, "should write .git/config")
|
testing.expect(t, write_err == nil, "should write .git/config")
|
||||||
|
|
||||||
d, ok := db_init()
|
db, ok := db_init()
|
||||||
testing.expect(t, ok, "failed to create test db")
|
testing.expect(t, ok, "failed to create test db")
|
||||||
defer db_close(&d)
|
defer db_close(&db)
|
||||||
|
|
||||||
d.cfg.ScanConfig.Include = make([dynamic]string, 0, 1, context.temp_allocator)
|
db.cfg.ScanConfig.Include = make([dynamic]string, 0, 1, context.temp_allocator)
|
||||||
append(&d.cfg.ScanConfig.Include, search_root)
|
append(&db.cfg.ScanConfig.Include, search_root)
|
||||||
|
|
||||||
f := make_test_env_file(
|
f := make_test_env_file(
|
||||||
"/old/nonexistent/path/.env",
|
"/old/nonexistent/path/.env",
|
||||||
@@ -546,9 +546,9 @@ test_db_sync_moved :: proc(t: ^testing.T) {
|
|||||||
"SECRET=value",
|
"SECRET=value",
|
||||||
[]string{"git@github.com:user/repo.git"},
|
[]string{"git@github.com:user/repo.git"},
|
||||||
)
|
)
|
||||||
testing.expect(t, db_insert(&d, f), "insert should succeed")
|
testing.expect(t, db_insert(&db, f), "insert should succeed")
|
||||||
|
|
||||||
result, err := db_sync(&d, &f)
|
result, err := db_sync(&db, &f)
|
||||||
testing.expect(t, err == .None, "sync should not error")
|
testing.expect(t, err == .None, "sync should not error")
|
||||||
if err != .None do return
|
if err != .None do return
|
||||||
testing.expect(t, .DirUpdated in result, "should have DirUpdated flag")
|
testing.expect(t, .DirUpdated in result, "should have DirUpdated flag")
|
||||||
@@ -558,10 +558,10 @@ test_db_sync_moved :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, f.Path, expected_path)
|
testing.expect_value(t, f.Path, expected_path)
|
||||||
testing.expect_value(t, f.Dir, repo_dir)
|
testing.expect_value(t, f.Dir, repo_dir)
|
||||||
|
|
||||||
_, old_exists := db_fetch(&d, "/old/nonexistent/path/.env")
|
_, old_exists := db_fetch(&db, "/old/nonexistent/path/.env")
|
||||||
testing.expect(t, !old_exists, "old path should be deleted from db")
|
testing.expect(t, !old_exists, "old path should be deleted from db")
|
||||||
|
|
||||||
new_fetched, new_ok := db_fetch(&d, expected_path)
|
new_fetched, new_ok := db_fetch(&db, expected_path)
|
||||||
testing.expect(t, new_ok, "new path should exist in db")
|
testing.expect(t, new_ok, "new path should exist in db")
|
||||||
if new_ok {
|
if new_ok {
|
||||||
testing.expect_value(t, new_fetched.contents, "SECRET=value")
|
testing.expect_value(t, new_fetched.contents, "SECRET=value")
|
||||||
|
|||||||
@@ -4,40 +4,48 @@ import "core:c"
|
|||||||
|
|
||||||
foreign import lib "system:sqlite3"
|
foreign import lib "system:sqlite3"
|
||||||
|
|
||||||
|
Db :: distinct rawptr
|
||||||
|
Stmt :: distinct rawptr
|
||||||
|
|
||||||
|
// TODO: Use an enum?
|
||||||
OK :: 0
|
OK :: 0
|
||||||
ROW :: 100
|
ROW :: 100
|
||||||
DONE :: 101
|
DONE :: 101
|
||||||
|
|
||||||
DESERIALIZE_FREEONCLOSE :: 1
|
|
||||||
DESERIALIZE_RESIZEABLE :: 2
|
DESERIALIZE_FLAGS :: bit_set[DESERIALIZE_FLAG]
|
||||||
|
DESERIALIZE_FLAG :: enum u32 {
|
||||||
|
FREEONCLOSE = 1,
|
||||||
|
RESIZEABLE = 2,
|
||||||
|
}
|
||||||
|
|
||||||
foreign lib {
|
foreign lib {
|
||||||
@(link_name = "sqlite3_open")
|
@(link_name = "sqlite3_open")
|
||||||
open :: proc(filename: cstring, ppDb: ^^rawptr) -> c.int ---
|
open :: proc(filename: cstring, ppDb: ^Db) -> c.int ---
|
||||||
@(link_name = "sqlite3_close")
|
@(link_name = "sqlite3_close")
|
||||||
close :: proc(db: ^rawptr) -> c.int ---
|
close :: proc(db: Db) -> c.int ---
|
||||||
@(link_name = "sqlite3_errmsg")
|
@(link_name = "sqlite3_errmsg")
|
||||||
db_errmsg :: proc(db: ^rawptr) -> cstring ---
|
db_errmsg :: proc(db: Db) -> cstring ---
|
||||||
@(link_name = "sqlite3_exec")
|
@(link_name = "sqlite3_exec")
|
||||||
db_exec :: proc(db: ^rawptr, sql: cstring, callback: rawptr, callback_arg: rawptr, errmsg: ^cstring) -> c.int ---
|
db_exec :: proc(db: Db, sql: cstring, callback: rawptr, callback_arg: rawptr, errmsg: ^cstring) -> c.int ---
|
||||||
@(link_name = "sqlite3_prepare_v2")
|
@(link_name = "sqlite3_prepare_v2")
|
||||||
prepare_v2 :: proc(db: ^rawptr, sql: cstring, nByte: c.int, ppStmt: ^^rawptr, pzTail: ^cstring) -> c.int ---
|
prepare_v2 :: proc(db: Db, sql: cstring, nByte: c.int, ppStmt: ^Stmt, pzTail: ^cstring) -> c.int ---
|
||||||
@(link_name = "sqlite3_step")
|
@(link_name = "sqlite3_step")
|
||||||
step :: proc(stmt: ^rawptr) -> c.int ---
|
step :: proc(stmt: Stmt) -> c.int ---
|
||||||
@(link_name = "sqlite3_finalize")
|
@(link_name = "sqlite3_finalize")
|
||||||
finalize :: proc(stmt: ^rawptr) -> c.int ---
|
finalize :: proc(stmt: Stmt) -> c.int ---
|
||||||
@(link_name = "sqlite3_column_text")
|
@(link_name = "sqlite3_column_text")
|
||||||
column_text :: proc(stmt: ^rawptr, iCol: c.int) -> cstring ---
|
column_text :: proc(stmt: Stmt, iCol: c.int) -> cstring ---
|
||||||
@(link_name = "sqlite3_column_bytes")
|
@(link_name = "sqlite3_column_bytes")
|
||||||
column_bytes :: proc(stmt: ^rawptr, iCol: c.int) -> c.int ---
|
column_bytes :: proc(stmt: Stmt, iCol: c.int) -> c.int ---
|
||||||
@(link_name = "sqlite3_bind_text")
|
@(link_name = "sqlite3_bind_text")
|
||||||
bind_text :: proc(stmt: ^rawptr, idx: c.int, val: cstring, n: c.int, destructor: rawptr) -> c.int ---
|
bind_text :: proc(stmt: Stmt, idx: c.int, val: cstring, n: c.int, destructor: rawptr) -> c.int ---
|
||||||
@(link_name = "sqlite3_changes")
|
@(link_name = "sqlite3_changes")
|
||||||
changes :: proc(db: ^rawptr) -> c.int ---
|
changes :: proc(db: Db) -> c.int ---
|
||||||
@(link_name = "sqlite3_serialize")
|
@(link_name = "sqlite3_serialize")
|
||||||
serialize :: proc(db: ^rawptr, zSchema: cstring, piSize: ^i64, mFlags: u32) -> [^]u8 ---
|
serialize :: proc(db: Db, zSchema: cstring, piSize: ^i64, mFlags: u32) -> [^]u8 ---
|
||||||
@(link_name = "sqlite3_deserialize")
|
@(link_name = "sqlite3_deserialize")
|
||||||
deserialize :: proc(db: ^rawptr, zSchema: cstring, pData: [^]u8, szDb: i64, szBuf: i64, mFlags: u32) -> c.int ---
|
deserialize :: proc(db: Db, zSchema: cstring, pData: [^]u8, szDb: i64, szBuf: i64, mFlags: DESERIALIZE_FLAGS) -> c.int ---
|
||||||
@(link_name = "sqlite3_malloc64")
|
@(link_name = "sqlite3_malloc64")
|
||||||
malloc64 :: proc(n: i64) -> [^]u8 ---
|
malloc64 :: proc(n: i64) -> [^]u8 ---
|
||||||
@(link_name = "sqlite3_free")
|
@(link_name = "sqlite3_free")
|
||||||
|
|||||||
Reference in New Issue
Block a user