this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 286 lines 7.0 kB view raw
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 &param 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}