-
Notifications
You must be signed in to change notification settings - Fork 2
/
main.zig
231 lines (203 loc) · 7.32 KB
/
main.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
const std = @import("std");
const sqlite = @import("sqlite");
const HELPTEXT =
\\ awtfdb-manage: main program for awtfdb file management
\\
\\ usage:
\\ awtfdb-manage [global options..] <action> [action options...]
\\
\\ global options:
\\ -h prints this help and exits
\\ -V prints version and exits
\\ -v turns on verbosity (debug logging)
\\
\\ creating an awtfdb file:
\\ awtfdb-manage create
\\
\\ getting statistics:
\\ awtfdb-manage stats
\\
\\ current running jobs:
\\ awtfdb-manage jobs
;
const MIGRATIONS = .{
.{
1,
\\ -- uniquely identifies a tag in the ENTIRE UNIVERSE!!!
\\ -- since this uses random data for core_data, and core_hash is blake3
\\ --
\\ -- this is not randomly generated UUIDs, of which anyone can cook up 128-bit
\\ -- numbers out of thin air. using a cryptographic hash function lets us be
\\ -- sure that we have an universal tag for 'new york' or 'tree', while also
\\ -- enabling different language representations of the same tag
\\ -- (since they all reference the core!)
\\ create table tag_cores (
\\ core_hash text primary key,
\\ core_data blob not null
\\ );
\\
\\ -- files that are imported by bimport/badd are here
\\ -- this is how we learn that a certain path means a certain hash without
\\ -- having to recalculate the hash over and over.
\\ create table files (
\\ file_hash text primary key not null,
\\ local_path text not null,
\\ );
\\
\\ -- this is the main tag<->file mapping. to find out which tags a file has,
\\ -- execute your SELECT here.
\\ create table tag_files (
\\ file_hash text not null,
\\ core_hash text not null,
\\ constraint tag_files_core_fk foreign key (core_hash)
\\ references tag_cores (core_hash) on delete cascade,
\\ constraint tag_files_file_fk foreign key (file_hash)
\\ references files (file_hash) on delete cascade,
\\ constraint tag_files_pk primary key (file_hash, core_hash)
\\ );
\\
\\ -- this is the main name<->tag mapping. to find out the
\\ -- UNIVERSALLY RECOGNIZABLE id of a tag name, execute your SELECT here.
\\ create table tag_names (
\\ tag_text text not null,
\\ tag_language text not null,
\\ core_hash text not null,
\\ constraint tag_names_core_fk foreign key (core_hash) references tag_cores on delete cascade,
\\ constraint tag_names_pk primary key (tag_text, tag_language, core_hash)
\\ );
},
};
const MIGRATION_LOG_TABLE =
\\ create table if not exists migration_logs (
\\ version int primary key,
\\ applied_at int,
\\ description text
\\ );
;
const Context = struct {
args_it: *std.process.ArgIterator,
stdout: std.fs.File,
/// Always call loadDatabase before using this attribute.
db: ?sqlite.Db = null,
const Self = @This();
pub fn loadDatabase(self: *Self) !void {
// try to create the file always. this is done because
// i give up. tried a lot of things to make sqlite create the db file
// itself but it just hates me (SQLITE_CANTOPEN my beloathed).
// TODO other people do exist! (use HOME env var)
const path = "/home/luna/boorufs.db";
var file = try std.fs.cwd().createFile(path, .{});
defer file.close();
var diags: sqlite.Diagnostics = undefined;
self.db = try sqlite.Db.init(.{
.mode = sqlite.Db.Mode{ .File = path },
.open_flags = .{
.write = true,
.create = true,
},
.threading_mode = .MultiThread,
.diags = &diags,
});
// ensure our database functions work
var result = try self.fetchValue(i32, "select 123;");
if (result == null or result.? != 123) {
std.log.err("error on test statement: expected 123, got {d}", .{result});
return error.TestStatementFailed;
}
}
fn executeOnce(self: *Self, comptime statement: []const u8) !void {
var stmt = try self.db.?.prepare(statement);
defer stmt.deinit();
try stmt.exec(.{}, .{});
}
fn fetchValue(self: *Self, comptime T: type, comptime statement: []const u8) !?T {
var stmt = try self.db.?.prepare(statement);
defer stmt.deinit();
return try stmt.one(T, .{}, .{});
}
pub fn deinit(self: *Self) void {
if (self.db != null) {
self.db.?.deinit();
}
}
pub fn createCommand(self: *Self) !void {
try self.loadDatabase();
try self.migrateCommand();
}
pub fn migrateCommand(self: *Self) !void {
try self.loadDatabase();
// migration log table is forever
try self.executeOnce(MIGRATION_LOG_TABLE);
const last_ran_migration = try self.fetchValue(i32, "select max(version) from migration_logs");
std.log.debug("last migration: {d}", .{last_ran_migration});
}
pub fn statsCommand(self: *Self) !void {
try self.loadDatabase();
}
pub fn jobsCommand(self: *Self) !void {
try self.loadDatabase();
}
};
export fn sqliteLog(_: ?*anyopaque, level: c_int, message: ?[*:0]const u8) callconv(.C) void {
std.log.info("sqlite log {d} {s}", .{ level, message });
}
pub fn main() anyerror!void {
const rc = sqlite.c.sqlite3_config(sqlite.c.SQLITE_CONFIG_LOG, sqliteLog, @as(?*anyopaque, null));
if (rc != sqlite.c.SQLITE_OK) {
std.log.err("failed to configure: {d} '{s}'", .{
rc, sqlite.c.sqlite3_errstr(rc),
});
return error.ConfigFail;
}
var args_it = std.process.args();
_ = args_it.skip();
const stdout = std.io.getStdOut();
const Args = struct {
help: bool = false,
verbose: bool = false,
version: bool = false,
maybe_action: ?[]const u8 = null,
};
var given_args = Args{};
while (args_it.next()) |arg| {
if (std.mem.eql(u8, arg, "-h")) {
given_args.help = true;
} else if (std.mem.eql(u8, arg, "-v")) {
given_args.verbose = true;
} else if (std.mem.eql(u8, arg, "-V")) {
given_args.version = true;
} else {
given_args.maybe_action = arg;
}
}
if (given_args.help) {
try stdout.writer().print(HELPTEXT, .{});
return;
} else if (given_args.version) {
try stdout.writer().print("awtfdb-manage 0.0.1\n", .{});
return;
}
if (given_args.verbose) {
std.debug.todo("lmao help");
}
if (given_args.maybe_action == null) {
std.log.err("action argument is required", .{});
return error.MissingActionArgument;
}
var ctx = Context{
.args_it = &args_it,
.stdout = stdout,
.db = undefined,
};
defer ctx.deinit();
const action = given_args.maybe_action.?;
if (std.mem.eql(u8, action, "create")) {
try ctx.createCommand();
} else {
std.log.err("unknown action {s}", .{action});
return error.UnknownAction;
}
}
test "basic test" {
try std.testing.expectEqual(10, 3 + 7);
}