this repo has no description
1package addons
2
3import sqlite3 ".."
4import "base:runtime"
5import "core:c"
6import "core:fmt"
7import "core:log"
8import "core:mem"
9import "core:reflect"
10import "core:slice"
11import "core:strconv"
12import "core:strings"
13import "core:text/regex"
14
15Runtime_Config :: struct {
16 extra_runtime_checks: bool,
17 log_level: Maybe(log.Level),
18}
19
20config: Runtime_Config
21
22Query_Error :: Maybe(string)
23
24Query_Param_Value :: union {
25 i32,
26 i64,
27 f64,
28 []byte,
29 bool,
30 string,
31}
32
33Query_Param :: struct {
34 index: int,
35 value: Query_Param_Value,
36}
37
38query :: proc(
39 db: ^sqlite3.Connection,
40 out: ^[dynamic]$T,
41 sql: string,
42 params: []Query_Param = {},
43 location := #caller_location,
44) -> sqlite3.Result_Code {
45 stmt: ^sqlite3.Statement
46
47 prepare(db, &stmt, sql, params, location) or_return
48 return read_all_rows(stmt, out)
49}
50
51// Allocates. Make sure to free results even when the return value is not .Ok
52@(require_results)
53read_all_rows :: proc(stmt: ^sqlite3.Statement, out: ^[dynamic]$T) -> sqlite3.Result_Code {
54 fields, err := get_type_fields(T)
55 if err != nil {
56 log.error(err)
57 return .Internal
58 }
59
60 defer delete_field_types(fields)
61
62 field_map: map[string]^Field_Type
63 defer delete(field_map)
64 for &field in fields {
65 field_map[field.tag] = &field
66 }
67
68 defer sqlite3.finalize(stmt)
69 for sqlite3.step(stmt) == .Row {
70 item: T
71 cols := sqlite3.column_count(stmt)
72 for i in 0 ..< cols {
73 column := strings.clone_from(sqlite3.column_name(stmt, i))
74 defer delete(column)
75
76 field_type, ok := field_map[column]
77 if !ok {
78 log.errorf("could not find tag {} in {}", column, typeid_of(T))
79 return .Internal
80 }
81
82 if err := write_struct_field_from_statement(&item, field_type, stmt, c.int(i));
83 err != nil {
84 log.error(err)
85 free_query_error(err)
86 return .Internal
87 }
88 }
89
90 append(out, item)
91 }
92
93 return .Ok
94}
95
96@(require_results)
97execute :: proc(
98 db: ^sqlite3.Connection,
99 sql: string,
100 params: []Query_Param = {},
101 location := #caller_location,
102) -> sqlite3.Result_Code {
103 stmt: ^sqlite3.Statement
104 prepare(db, &stmt, sql, params, location) or_return
105 defer sqlite3.finalize(stmt)
106 result := sqlite3.Result_Code.Done
107 for {
108 result = sqlite3.step(stmt)
109 if result != .Row do break
110 }
111
112 if result == .Done do return .Ok
113 return result
114}
115
116@(require_results)
117prepare :: proc(
118 db: ^sqlite3.Connection,
119 stmt: ^^sqlite3.Statement,
120 sql: string,
121 params: []Query_Param = {},
122 location := #caller_location,
123) -> sqlite3.Result_Code {
124 c_sql := strings.clone_to_cstring(sql)
125 defer delete(c_sql)
126
127 sqlite3.prepare_v2(db, c_sql, c.int(len(c_sql)), stmt, nil) or_return
128
129 for ¶m in params {
130 idx := c.int(param.index)
131
132 if param.value == nil {
133 sqlite3.bind_null(stmt^, idx) or_return
134 } else if v, ok := param.value.(i32); ok {
135 sqlite3.bind_int(stmt^, idx, c.int(v)) or_return
136 } else if v, ok := param.value.(i64); ok {
137 sqlite3.bind_int64(stmt^, idx, c.int64_t(v)) or_return
138 } else if v, ok := param.value.([]byte); ok {
139 sqlite3.bind_blob64(
140 stmt^,
141 idx,
142 slice.as_ptr(v),
143 c.int64_t(len(v)),
144 {behaviour = .Static},
145 ) or_return
146 } else if v, ok := param.value.(bool); ok {
147 sqlite3.bind_int(stmt^, idx, c.int(v ? 1 : 0)) or_return
148 } else if v, ok := param.value.(string); ok {
149 // Sqlite treats our parameter as a "cstring" if we pass a negative length.
150 // Explicitly it's just a slice.
151 // https://sqlite.org/c3ref/bind_blob.html.
152 cstr := strings.unsafe_string_to_cstring(v)
153 sqlite3.bind_text(stmt^, idx, cstr, c.int(len(v)), {behaviour = .Static}) or_return
154 } else {
155 log.errorf("unhandled parameter type {}", param.value)
156 return .Internal
157 }
158
159 }
160
161 exp_statement := sqlite3.expanded_sql(stmt^)
162 // `expanded_sql` can return NULL(nil) as explained here: https://www.sqlite.org/c3ref/expanded_sql.html
163 if exp_statement != nil {
164 defer sqlite3.free(cast(rawptr)exp_statement)
165 do_log("SQL: {}", exp_statement, location = location)
166 } else {
167 // Not going to return an error here because everything else worked fine,
168 // but it should be logged regardless.
169 log.errorf("Unable to allocate memory while expanding the sql statement")
170 }
171 return .Ok
172}
173
174@(private)
175do_log :: #force_inline proc(format_str: string, args: ..any, location := #caller_location) {
176 level, ok := config.log_level.?
177 if !ok do return
178
179 if context.logger.procedure != log.nil_logger_proc {
180 log.logf(level, format_str, ..args, location = location)
181 return
182 }
183
184 logger := log.create_console_logger()
185 defer log.destroy_console_logger(logger)
186 {
187 context.logger = logger
188 log.logf(level, format_str, ..args, location = location)
189 }
190}
191
192@(private)
193free_query_error :: #force_inline proc(err: Query_Error) {
194 delete(err.(string), context.temp_allocator)
195}
196
197@(private)
198@(require_results)
199write_struct_field_from_statement :: proc(
200 obj: ^$T,
201 field: ^Field_Type,
202 stmt: ^sqlite3.Statement,
203 col_idx: c.int,
204) -> Query_Error {
205 switch field.type.id {
206 case typeid_of(string):
207 value := strings.clone_from(sqlite3.column_text(stmt, col_idx))
208 write_struct_field(obj, field^, value) or_return
209
210 case typeid_of(bool):
211 value := sqlite3.column_int(stmt, col_idx) != 0
212 write_struct_field(obj, field^, value) or_return
213
214 case typeid_of(int):
215 value := int(sqlite3.column_int(stmt, col_idx))
216 write_struct_field(obj, field^, value) or_return
217
218 case typeid_of(uint):
219 value := uint(sqlite3.column_int(stmt, col_idx))
220 write_struct_field(obj, field^, value) or_return
221
222 case typeid_of(i8):
223 value := i8(sqlite3.column_int(stmt, col_idx))
224 write_struct_field(obj, field^, value) or_return
225
226 case typeid_of(u8):
227 value := u8(sqlite3.column_int(stmt, col_idx))
228 write_struct_field(obj, field^, value) or_return
229
230 case typeid_of(i16):
231 value := i16(sqlite3.column_int(stmt, col_idx))
232 write_struct_field(obj, field^, value) or_return
233
234 case typeid_of(u16):
235 value := u16(sqlite3.column_int(stmt, col_idx))
236 write_struct_field(obj, field^, value) or_return
237
238 case typeid_of(i32):
239 value := i32(sqlite3.column_int(stmt, col_idx))
240 write_struct_field(obj, field^, value) or_return
241
242 case typeid_of(u32):
243 value := u32(sqlite3.column_int(stmt, col_idx))
244 write_struct_field(obj, field^, value) or_return
245
246 case typeid_of(i64):
247 value := i64(sqlite3.column_int64(stmt, col_idx))
248 write_struct_field(obj, field^, value) or_return
249
250 case typeid_of(u64):
251 value := u64(sqlite3.column_int64(stmt, col_idx))
252 write_struct_field(obj, field^, value) or_return
253
254 case:
255 if reflect.is_enum(field.type) {
256 enum_values := reflect.enum_field_values(field.type.id)
257
258 value := i64(sqlite3.column_int64(stmt, col_idx))
259
260 if config.extra_runtime_checks {
261 found := false
262
263 for it in enum_values {
264 if i64(it) == value {
265 found = true
266 break
267 }
268 }
269
270 if !found {
271 return fmt.tprintf(
272 "expected to find enum value {} in {}",
273 value,
274 field.type.id,
275 )
276 }
277 }
278
279 write_struct_field(obj, field^, value) or_return
280 } else {
281 return fmt.tprintf("unhandled data type {}", field.type.id)
282 }
283 }
284
285 return nil
286}