Sql refactor
This commit is contained in:
parent
c42039c559
commit
c6d80fd4d3
8 changed files with 967 additions and 327 deletions
|
@ -5,6 +5,56 @@ const Uuid = util.Uuid;
|
|||
const DateTime = util.DateTime;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const UnexpectedError = error{Unexpected};
|
||||
const ConstraintError = error{
|
||||
NotNullViolation,
|
||||
ForeignKeyViolation,
|
||||
UniqueViolation,
|
||||
CheckViolation,
|
||||
|
||||
/// Catchall for miscellaneous types of constraints
|
||||
ConstraintViolation,
|
||||
};
|
||||
|
||||
pub const OpenError = error{BadConnection} || UnexpectedError;
|
||||
|
||||
pub const ExecError = error{
|
||||
Cancelled,
|
||||
ConnectionLost,
|
||||
InternalException,
|
||||
DatabaseBusy,
|
||||
PermissionDenied,
|
||||
SqlException,
|
||||
|
||||
/// Argument could not be marshalled for query
|
||||
InvalidArgument,
|
||||
|
||||
/// An argument was not used by the query (not checked in all DB engines)
|
||||
UndefinedParameter,
|
||||
|
||||
/// Memory error when marshalling argument for query
|
||||
OutOfMemory,
|
||||
AllocatorRequired,
|
||||
} || ConstraintError || UnexpectedError;
|
||||
|
||||
pub const RowError = error{
|
||||
Cancelled,
|
||||
ConnectionLost,
|
||||
InternalException,
|
||||
DatabaseBusy,
|
||||
PermissionDenied,
|
||||
SqlException,
|
||||
} || ConstraintError || UnexpectedError;
|
||||
|
||||
pub const GetError = error{
|
||||
OutOfMemory,
|
||||
AllocatorRequired,
|
||||
TypeMismatch,
|
||||
} || UnexpectedError;
|
||||
|
||||
pub const ColumnCountError = error{OutOfRange};
|
||||
pub const ColumnIndexError = error{ NotFound, OutOfRange };
|
||||
|
||||
pub const QueryOptions = struct {
|
||||
// If true, then it will not return an error on the SQLite backend
|
||||
// if an argument passed does not map to a parameter in the query.
|
||||
|
@ -32,7 +82,7 @@ pub fn prepareParamText(arena: *std.heap.ArenaAllocator, val: anytype) !?[:0]con
|
|||
|
||||
return switch (@TypeOf(val)) {
|
||||
[:0]u8, [:0]const u8 => val,
|
||||
[]const u8, []u8 => try std.cstr.addNullByte(val),
|
||||
[]const u8, []u8 => try std.cstr.addNullByte(arena.allocator(), val),
|
||||
DateTime, Uuid => try std.fmt.allocPrintZ(arena.allocator(), "{}", .{val}),
|
||||
|
||||
else => |T| switch (@typeInfo(T)) {
|
||||
|
@ -44,10 +94,6 @@ pub fn prepareParamText(arena: *std.heap.ArenaAllocator, val: anytype) !?[:0]con
|
|||
};
|
||||
}
|
||||
|
||||
fn parseEnum(comptime T: type, _: []const u8) !T {
|
||||
@panic("not implemented");
|
||||
}
|
||||
|
||||
// Parse a (not-null) value from a string
|
||||
pub fn parseValueNotNull(alloc: ?Allocator, comptime T: type, str: []const u8) !T {
|
||||
return switch (T) {
|
220
src/sql/engines/postgres.zig
Normal file
220
src/sql/engines/postgres.zig
Normal file
|
@ -0,0 +1,220 @@
|
|||
const std = @import("std");
|
||||
const util = @import("util");
|
||||
const common = @import("./common.zig");
|
||||
const c = @import("./postgres/c.zig");
|
||||
const errors = @import("./postgres/errors.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const Results = struct {
|
||||
result: *c.PGresult,
|
||||
next_row_index: c_int = 0,
|
||||
|
||||
pub fn row(self: *Results) common.RowError!?Row {
|
||||
if (self.next_row_index >= self.rowCount()) return null;
|
||||
const idx = self.next_row_index;
|
||||
self.next_row_index += 1;
|
||||
|
||||
return Row{
|
||||
.result = self.result,
|
||||
.row_index = idx,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn columnCount(self: Results) common.ColumnCountError!u15 {
|
||||
return std.math.cast(u15, c.PQnfields(self.result)) orelse error.OutOfRange;
|
||||
}
|
||||
|
||||
pub fn columnIndex(self: Results, name: []const u8) common.ColumnIndexError!u15 {
|
||||
const idx = c.PQfnumber(self.result, name.ptr);
|
||||
if (idx == -1) return error.NotFound;
|
||||
return std.math.cast(u15, idx) orelse error.OutOfRange;
|
||||
}
|
||||
|
||||
pub fn finish(self: Results) void {
|
||||
c.PQclear(self.result);
|
||||
}
|
||||
};
|
||||
|
||||
fn handleError(result: *c.PQresult) common.RowError {
|
||||
const error_code = c.PQresultErrorField(result, c.PG_DIAG_SQLSTATE);
|
||||
const state = errors.SqlState.parse(error_code) catch {
|
||||
std.log.err("Database returned invalid error code {?s}", .{error_code});
|
||||
return error.Unexpected;
|
||||
};
|
||||
const class = state.errorClass();
|
||||
|
||||
// TODO: This will crash if a value not defined in Postgres 14 is returned.
|
||||
// See https://github.com/ziglang/zig/issues/12845
|
||||
// If this issue does not get accepted we should redo this to use a comptime
|
||||
// string map or something
|
||||
std.log.err(
|
||||
"Database returned error code {s}: Class {s} error {s}",
|
||||
.{ error_code, @tagName(class), @tagName(state) },
|
||||
);
|
||||
|
||||
return switch (class) {
|
||||
.triggered_action_exception,
|
||||
.feature_not_supported,
|
||||
.invalid_transaction_initiation,
|
||||
.locator_exception,
|
||||
.cardinality_violation,
|
||||
.data_exception,
|
||||
.invalid_transaction_state,
|
||||
.invalid_sql_statement_name,
|
||||
.triggered_data_change_violation,
|
||||
.dependent_privilege_descriptors_still_exist,
|
||||
.invalid_transaction_termination,
|
||||
.sql_routine_exception,
|
||||
.invalid_cursor_name,
|
||||
.external_routine_exception,
|
||||
.external_routine_invocation_exception,
|
||||
.savepoint_exception,
|
||||
.invalid_catalog_name,
|
||||
.invalid_schema_name,
|
||||
.transaction_rollback, // TODO: consider deadlock avoidance/retry strategy
|
||||
.snapshot_too_old,
|
||||
.plpgsql_error,
|
||||
=> error.SqlException,
|
||||
|
||||
.invalid_authorization_specification,
|
||||
.invalid_grantor,
|
||||
=> error.PermissionDenied,
|
||||
|
||||
.insufficient_resources,
|
||||
.connection_exception,
|
||||
.system_error,
|
||||
.config_file_error,
|
||||
.internal_error,
|
||||
=> error.InternalException,
|
||||
|
||||
.operator_intervention => switch (state) {
|
||||
.query_canceled => error.Cancelled,
|
||||
else => error.InternalException,
|
||||
},
|
||||
|
||||
.object_not_in_prerequisite_state => switch (state) {
|
||||
.lock_not_available => error.DatabaseBusy,
|
||||
else => error.SqlException,
|
||||
},
|
||||
|
||||
.syntax_error_or_access_rule_violation => switch (state) {
|
||||
.insufficient_privilege => error.PermissionDenied,
|
||||
else => error.SqlException,
|
||||
},
|
||||
|
||||
.with_check_option_violation,
|
||||
.integrity_constraint_violation,
|
||||
=> switch (state) {
|
||||
.not_null_violation => error.NotNullViolation,
|
||||
.foreign_key_violation => error.ForeignKeyViolation,
|
||||
.unique_violation => error.UniqueViolation,
|
||||
.check_violation => error.CheckViolation,
|
||||
|
||||
else => error.ConstraintViolation,
|
||||
},
|
||||
|
||||
else => error.Unexpected,
|
||||
};
|
||||
}
|
||||
|
||||
pub const Row = struct {
|
||||
result: *c.PGresult,
|
||||
row_index: c_int,
|
||||
|
||||
pub fn get(self: Row, comptime T: type, idx: u16, alloc: ?Allocator) common.GetError!T {
|
||||
const val = c.PQgetvalue(self.result, self.row_index, idx);
|
||||
const is_null = (c.PQgetisnull(self.result, self.row_index, idx) != 0);
|
||||
if (is_null) {
|
||||
return if (@typeInfo(T) == .Optional) null else error.TypeMismatch;
|
||||
}
|
||||
|
||||
if (val == null) return error.Unexpected;
|
||||
|
||||
const len = std.math.cast(
|
||||
usize,
|
||||
c.PQgetlength(self.result, self.row_index, idx),
|
||||
) orelse return error.Unexpected;
|
||||
|
||||
return try common.parseValueNotNull(alloc, T, val[0..len]);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Db = struct {
|
||||
conn: *c.PGconn,
|
||||
|
||||
pub fn open(conn_str: [:0]const u8) common.OpenError!Db {
|
||||
const conn = c.PQconnectdb(conn_str.ptr) orelse {
|
||||
std.log.err("Unable to connect to database", .{});
|
||||
return error.Unexpected;
|
||||
};
|
||||
errdefer c.PQfinish(conn);
|
||||
|
||||
std.log.info("Connecting to database using provided connection string...", .{});
|
||||
switch (c.PQstatus(conn)) {
|
||||
c.CONNECTION_OK => {},
|
||||
|
||||
c.CONNECTION_BAD => {
|
||||
std.log.err("Error connecting to database: {?s}", .{c.PQerrorMessage(conn)});
|
||||
return error.BadConnection;
|
||||
},
|
||||
|
||||
else => |status| {
|
||||
std.log.err("Unexpected PQstatus {}: {?s}", .{ status, c.PQerrorMessage(conn) });
|
||||
return error.Unexpected;
|
||||
},
|
||||
}
|
||||
std.log.info("DB connection established", .{});
|
||||
|
||||
return Db{ .conn = conn };
|
||||
}
|
||||
|
||||
pub fn close(self: Db) void {
|
||||
c.PQfinish(self.conn);
|
||||
}
|
||||
|
||||
const format_text = 0;
|
||||
const format_binary = 1;
|
||||
pub fn exec(self: Db, sql: [:0]const u8, args: anytype, alloc: ?Allocator) !Results {
|
||||
const result = blk: {
|
||||
if (comptime args.len > 0) {
|
||||
var arena = std.heap.ArenaAllocator.init(alloc orelse return error.AllocatorRequired);
|
||||
defer arena.deinit();
|
||||
const params = try arena.allocator().alloc(?[*:0]const u8, args.len);
|
||||
inline for (args) |arg, i| params[i] = if (try common.prepareParamText(&arena, arg)) |slice| slice.ptr else null;
|
||||
|
||||
break :blk c.PQexecParams(self.conn, sql.ptr, @intCast(c_int, params.len), null, params.ptr, null, null, format_text);
|
||||
} else {
|
||||
break :blk c.PQexecParams(self.conn, sql.ptr, 0, null, null, null, null, format_text);
|
||||
}
|
||||
} orelse {
|
||||
std.log.err("Error occurred in sql query: {?s}", .{c.PQerrorMessage(self.conn)});
|
||||
return error.Unexpected;
|
||||
};
|
||||
errdefer c.PQclear(result);
|
||||
|
||||
const status = c.PQresultStatus(result);
|
||||
switch (status) {
|
||||
c.PGRES_COMMAND_OK,
|
||||
c.PGRES_TUPLES_OK,
|
||||
=> return Results{ .result = result },
|
||||
|
||||
c.PGRES_EMPTY_QUERY => return error.InvalidSql,
|
||||
|
||||
c.PGRES_BAD_RESPONSE => {
|
||||
std.log.err("Database returned invalid response: {?s}", .{c.PQresultErrorMessage(result)});
|
||||
return error.Database;
|
||||
},
|
||||
|
||||
c.PGRES_FATAL_ERROR => return handleError(result),
|
||||
|
||||
else => |err| {
|
||||
std.log.err(
|
||||
"Unexpected PQresultStatus {?s} ({}): {?s}",
|
||||
.{ c.PQresStatus(err), err, c.PQresultErrorMessage(result) },
|
||||
);
|
||||
return error.Unexpected;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
3
src/sql/engines/postgres/c.zig
Normal file
3
src/sql/engines/postgres/c.zig
Normal file
|
@ -0,0 +1,3 @@
|
|||
usingnamespace @cImport({
|
||||
@cInclude("libpq-fe.h");
|
||||
});
|
341
src/sql/engines/postgres/errors.zig
Normal file
341
src/sql/engines/postgres/errors.zig
Normal file
|
@ -0,0 +1,341 @@
|
|||
const std = @import("std");
|
||||
const c = @import("./c.zig");
|
||||
const readIntBig = std.mem.readIntBig;
|
||||
|
||||
const Code = u40; // 8 * 5 = 40
|
||||
const code_class_mask: Code = 0xFFFF_000000;
|
||||
|
||||
pub const SqlStateClass = blk: {
|
||||
const info = @typeInfo(SqlState).Enum;
|
||||
var fields = &[0]std.builtin.Type.EnumField{};
|
||||
for (info.fields) |field| {
|
||||
if (field.value & ~code_class_mask == 0) fields = fields ++ &.{field};
|
||||
}
|
||||
break :blk @Type(.{ .Enum = .{
|
||||
.layout = info.layout,
|
||||
.tag_type = Code,
|
||||
.fields = fields,
|
||||
.decls = &.{},
|
||||
.is_exhaustive = false,
|
||||
} });
|
||||
};
|
||||
|
||||
// SqlState values for Postgres 14
|
||||
pub const SqlState = enum(Code) {
|
||||
pub const ParseError = error{ InvalidSize, NullPointer };
|
||||
pub fn parse(code_str: [*c]const u8) ParseError!SqlState {
|
||||
if (code_str == null) return error.NullPointer;
|
||||
const slice = std.mem.span(code_str);
|
||||
if (slice.len != @sizeOf(Code)) return error.InvalidSize;
|
||||
return @intToEnum(SqlState, std.mem.readIntSliceBig(Code, slice));
|
||||
}
|
||||
|
||||
pub fn errorClass(code: SqlState) SqlStateClass {
|
||||
return @intToEnum(SqlStateClass, @enumToInt(code) & code_class_mask);
|
||||
}
|
||||
|
||||
// Class 00 — Successful Completion
|
||||
successful_completion = readIntBig(Code, "00000"),
|
||||
// Class 01 — Warning
|
||||
warning = readIntBig(Code, "01000"),
|
||||
dynamic_result_sets_returned = readIntBig(Code, "0100C"),
|
||||
implicit_zero_bit_padding = readIntBig(Code, "01008"),
|
||||
null_value_eliminated_in_set_function = readIntBig(Code, "01003"),
|
||||
privilege_not_granted = readIntBig(Code, "01007"),
|
||||
privilege_not_revoked = readIntBig(Code, "01006"),
|
||||
string_data_right_truncation = readIntBig(Code, "01004"),
|
||||
deprecated_feature = readIntBig(Code, "01P01"),
|
||||
// Class 02 — No Data (this is also a warning class per the SQL standard)
|
||||
no_data = readIntBig(Code, "02000"),
|
||||
no_additional_dynamic_result_sets_returned = readIntBig(Code, "02001"),
|
||||
// Class 03 — SQL Statement Not Yet Complete
|
||||
sql_statement_not_yet_complete = readIntBig(Code, "03000"),
|
||||
// Class 08 — Connection Exception
|
||||
connection_exception = readIntBig(Code, "08000"),
|
||||
connection_does_not_exist = readIntBig(Code, "08003"),
|
||||
connection_failure = readIntBig(Code, "08006"),
|
||||
sqlclient_unable_to_establish_sqlconnection = readIntBig(Code, "08001"),
|
||||
sqlserver_rejected_establishment_of_sqlconnection = readIntBig(Code, "08004"),
|
||||
transaction_resolution_unknown = readIntBig(Code, "08007"),
|
||||
protocol_violation = readIntBig(Code, "08P01"),
|
||||
// Class 09 — Triggered Action Exception
|
||||
triggered_action_exception = readIntBig(Code, "09000"),
|
||||
// Class 0A — Feature Not Supported
|
||||
feature_not_supported = readIntBig(Code, "0A000"),
|
||||
// Class 0B — Invalid Transaction Initiation
|
||||
invalid_transaction_initiation = readIntBig(Code, "0B000"),
|
||||
// Class 0F — Locator Exception
|
||||
locator_exception = readIntBig(Code, "0F000"),
|
||||
invalid_locator_specification = readIntBig(Code, "0F001"),
|
||||
// Class 0L — Invalid Grantor
|
||||
invalid_grantor = readIntBig(Code, "0L000"),
|
||||
invalid_grant_operation = readIntBig(Code, "0LP01"),
|
||||
// Class 0P — Invalid Role Specification
|
||||
invalid_role_specification = readIntBig(Code, "0P000"),
|
||||
// Class 0Z — Diagnostics Exception
|
||||
diagnostics_exception = readIntBig(Code, "0Z000"),
|
||||
stacked_diagnostics_accessed_without_active_handler = readIntBig(Code, "0Z002"),
|
||||
// Class 20 — Case Not Found
|
||||
case_not_found = readIntBig(Code, "20000"),
|
||||
// Class 21 — Cardinality Violation
|
||||
cardinality_violation = readIntBig(Code, "21000"),
|
||||
// Class 22 — Data Exception
|
||||
data_exception = readIntBig(Code, "22000"),
|
||||
array_subscript_error = readIntBig(Code, "2202E"),
|
||||
character_not_in_repertoire = readIntBig(Code, "22021"),
|
||||
datetime_field_overflow = readIntBig(Code, "22008"),
|
||||
division_by_zero = readIntBig(Code, "22012"),
|
||||
error_in_assignment = readIntBig(Code, "22005"),
|
||||
escape_character_conflict = readIntBig(Code, "2200B"),
|
||||
indicator_overflow = readIntBig(Code, "22022"),
|
||||
interval_field_overflow = readIntBig(Code, "22015"),
|
||||
invalid_argument_for_logarithm = readIntBig(Code, "2201E"),
|
||||
invalid_argument_for_ntile_function = readIntBig(Code, "22014"),
|
||||
invalid_argument_for_nth_value_function = readIntBig(Code, "22016"),
|
||||
invalid_argument_for_power_function = readIntBig(Code, "2201F"),
|
||||
invalid_argument_for_width_bucket_function = readIntBig(Code, "2201G"),
|
||||
invalid_character_value_for_cast = readIntBig(Code, "22018"),
|
||||
invalid_datetime_format = readIntBig(Code, "22007"),
|
||||
invalid_escape_character = readIntBig(Code, "22019"),
|
||||
invalid_escape_octet = readIntBig(Code, "2200D"),
|
||||
invalid_escape_sequence = readIntBig(Code, "22025"),
|
||||
nonstandard_use_of_escape_character = readIntBig(Code, "22P06"),
|
||||
invalid_indicator_parameter_value = readIntBig(Code, "22010"),
|
||||
invalid_parameter_value = readIntBig(Code, "22023"),
|
||||
invalid_preceding_or_following_size = readIntBig(Code, "22013"),
|
||||
invalid_regular_expression = readIntBig(Code, "2201B"),
|
||||
invalid_row_count_in_limit_clause = readIntBig(Code, "2201W"),
|
||||
invalid_row_count_in_result_offset_clause = readIntBig(Code, "2201X"),
|
||||
invalid_tablesample_argument = readIntBig(Code, "2202H"),
|
||||
invalid_tablesample_repeat = readIntBig(Code, "2202G"),
|
||||
invalid_time_zone_displacement_value = readIntBig(Code, "22009"),
|
||||
invalid_use_of_escape_character = readIntBig(Code, "2200C"),
|
||||
most_specific_type_mismatch = readIntBig(Code, "2200G"),
|
||||
null_value_not_allowed = readIntBig(Code, "22004"),
|
||||
null_value_no_indicator_parameter = readIntBig(Code, "22002"),
|
||||
numeric_value_out_of_range = readIntBig(Code, "22003"),
|
||||
sequence_generator_limit_exceeded = readIntBig(Code, "2200H"),
|
||||
string_data_length_mismatch = readIntBig(Code, "22026"),
|
||||
string_data_right_truncation = readIntBig(Code, "22001"),
|
||||
substring_error = readIntBig(Code, "22011"),
|
||||
trim_error = readIntBig(Code, "22027"),
|
||||
unterminated_c_string = readIntBig(Code, "22024"),
|
||||
zero_length_character_string = readIntBig(Code, "2200F"),
|
||||
floating_point_exception = readIntBig(Code, "22P01"),
|
||||
invalid_text_representation = readIntBig(Code, "22P02"),
|
||||
invalid_binary_representation = readIntBig(Code, "22P03"),
|
||||
bad_copy_file_format = readIntBig(Code, "22P04"),
|
||||
untranslatable_character = readIntBig(Code, "22P05"),
|
||||
not_an_xml_document = readIntBig(Code, "2200L"),
|
||||
invalid_xml_document = readIntBig(Code, "2200M"),
|
||||
invalid_xml_content = readIntBig(Code, "2200N"),
|
||||
invalid_xml_comment = readIntBig(Code, "2200S"),
|
||||
invalid_xml_processing_instruction = readIntBig(Code, "2200T"),
|
||||
duplicate_json_object_key_value = readIntBig(Code, "22030"),
|
||||
invalid_argument_for_sql_json_datetime_function = readIntBig(Code, "22031"),
|
||||
invalid_json_text = readIntBig(Code, "22032"),
|
||||
invalid_sql_json_subscript = readIntBig(Code, "22033"),
|
||||
more_than_one_sql_json_item = readIntBig(Code, "22034"),
|
||||
no_sql_json_item = readIntBig(Code, "22035"),
|
||||
non_numeric_sql_json_item = readIntBig(Code, "22036"),
|
||||
non_unique_keys_in_a_json_object = readIntBig(Code, "22037"),
|
||||
singleton_sql_json_item_required = readIntBig(Code, "22038"),
|
||||
sql_json_array_not_found = readIntBig(Code, "22039"),
|
||||
sql_json_member_not_found = readIntBig(Code, "2203A"),
|
||||
sql_json_number_not_found = readIntBig(Code, "2203B"),
|
||||
sql_json_object_not_found = readIntBig(Code, "2203C"),
|
||||
too_many_json_array_elements = readIntBig(Code, "2203D"),
|
||||
too_many_json_object_members = readIntBig(Code, "2203E"),
|
||||
sql_json_scalar_required = readIntBig(Code, "2203F"),
|
||||
// Class 23 — Integrity Constraint Violation
|
||||
integrity_constraint_violation = readIntBig(Code, "23000"),
|
||||
restrict_violation = readIntBig(Code, "23001"),
|
||||
not_null_violation = readIntBig(Code, "23502"),
|
||||
foreign_key_violation = readIntBig(Code, "23503"),
|
||||
unique_violation = readIntBig(Code, "23505"),
|
||||
check_violation = readIntBig(Code, "23514"),
|
||||
exclusion_violation = readIntBig(Code, "23P01"),
|
||||
// Class 24 — Invalid Cursor State
|
||||
invalid_cursor_state = readIntBig(Code, "24000"),
|
||||
// Class 25 — Invalid Transaction State
|
||||
invalid_transaction_state = readIntBig(Code, "25000"),
|
||||
active_sql_transaction = readIntBig(Code, "25001"),
|
||||
branch_transaction_already_active = readIntBig(Code, "25002"),
|
||||
held_cursor_requires_same_isolation_level = readIntBig(Code, "25008"),
|
||||
inappropriate_access_mode_for_branch_transaction = readIntBig(Code, "25003"),
|
||||
inappropriate_isolation_level_for_branch_transaction = readIntBig(Code, "25004"),
|
||||
no_active_sql_transaction_for_branch_transaction = readIntBig(Code, "25005"),
|
||||
read_only_sql_transaction = readIntBig(Code, "25006"),
|
||||
schema_and_data_statement_mixing_not_supported = readIntBig(Code, "25007"),
|
||||
no_active_sql_transaction = readIntBig(Code, "25P01"),
|
||||
in_failed_sql_transaction = readIntBig(Code, "25P02"),
|
||||
idle_in_transaction_session_timeout = readIntBig(Code, "25P03"),
|
||||
// Class 26 — Invalid SQL Statement Name
|
||||
invalid_sql_statement_name = readIntBig(Code, "26000"),
|
||||
// Class 27 — Triggered Data Change Violation
|
||||
triggered_data_change_violation = readIntBig(Code, "27000"),
|
||||
// Class 28 — Invalid Authorization Specification
|
||||
invalid_authorization_specification = readIntBig(Code, "28000"),
|
||||
invalid_password = readIntBig(Code, "28P01"),
|
||||
// Class 2B — Dependent Privilege Descriptors Still Exist
|
||||
dependent_privilege_descriptors_still_exist = readIntBig(Code, "2B000"),
|
||||
dependent_objects_still_exist = readIntBig(Code, "2BP01"),
|
||||
// Class 2D — Invalid Transaction Termination
|
||||
invalid_transaction_termination = readIntBig(Code, "2D000"),
|
||||
// Class 2F — SQL Routine Exception
|
||||
sql_routine_exception = readIntBig(Code, "2F000"),
|
||||
function_executed_no_return_statement = readIntBig(Code, "2F005"),
|
||||
modifying_sql_data_not_permitted = readIntBig(Code, "2F002"),
|
||||
prohibited_sql_statement_attempted = readIntBig(Code, "2F003"),
|
||||
reading_sql_data_not_permitted = readIntBig(Code, "2F004"),
|
||||
// Class 34 — Invalid Cursor Name
|
||||
invalid_cursor_name = readIntBig(Code, "34000"),
|
||||
// Class 38 — External Routine Exception
|
||||
external_routine_exception = readIntBig(Code, "38000"),
|
||||
containing_sql_not_permitted = readIntBig(Code, "38001"),
|
||||
modifying_sql_data_not_permitted = readIntBig(Code, "38002"),
|
||||
prohibited_sql_statement_attempted = readIntBig(Code, "38003"),
|
||||
reading_sql_data_not_permitted = readIntBig(Code, "38004"),
|
||||
// Class 39 — External Routine Invocation Exception
|
||||
external_routine_invocation_exception = readIntBig(Code, "39000"),
|
||||
invalid_sqlstate_returned = readIntBig(Code, "39001"),
|
||||
null_value_not_allowed = readIntBig(Code, "39004"),
|
||||
trigger_protocol_violated = readIntBig(Code, "39P01"),
|
||||
srf_protocol_violated = readIntBig(Code, "39P02"),
|
||||
event_trigger_protocol_violated = readIntBig(Code, "39P03"),
|
||||
// Class 3B — Savepoint Exception
|
||||
savepoint_exception = readIntBig(Code, "3B000"),
|
||||
invalid_savepoint_specification = readIntBig(Code, "3B001"),
|
||||
// Class 3D — Invalid Catalog Name
|
||||
invalid_catalog_name = readIntBig(Code, "3D000"),
|
||||
// Class 3F — Invalid Schema Name
|
||||
invalid_schema_name = readIntBig(Code, "3F000"),
|
||||
// Class 40 — Transaction Rollback
|
||||
transaction_rollback = readIntBig(Code, "40000"),
|
||||
transaction_integrity_constraint_violation = readIntBig(Code, "40002"),
|
||||
serialization_failure = readIntBig(Code, "40001"),
|
||||
statement_completion_unknown = readIntBig(Code, "40003"),
|
||||
deadlock_detected = readIntBig(Code, "40P01"),
|
||||
// Class 42 — Syntax Error or Access Rule Violation
|
||||
syntax_error_or_access_rule_violation = readIntBig(Code, "42000"),
|
||||
syntax_error = readIntBig(Code, "42601"),
|
||||
insufficient_privilege = readIntBig(Code, "42501"),
|
||||
cannot_coerce = readIntBig(Code, "42846"),
|
||||
grouping_error = readIntBig(Code, "42803"),
|
||||
windowing_error = readIntBig(Code, "42P20"),
|
||||
invalid_recursion = readIntBig(Code, "42P19"),
|
||||
invalid_foreign_key = readIntBig(Code, "42830"),
|
||||
invalid_name = readIntBig(Code, "42602"),
|
||||
name_too_long = readIntBig(Code, "42622"),
|
||||
reserved_name = readIntBig(Code, "42939"),
|
||||
datatype_mismatch = readIntBig(Code, "42804"),
|
||||
indeterminate_datatype = readIntBig(Code, "42P18"),
|
||||
collation_mismatch = readIntBig(Code, "42P21"),
|
||||
indeterminate_collation = readIntBig(Code, "42P22"),
|
||||
wrong_object_type = readIntBig(Code, "42809"),
|
||||
generated_always = readIntBig(Code, "428C9"),
|
||||
undefined_column = readIntBig(Code, "42703"),
|
||||
undefined_function = readIntBig(Code, "42883"),
|
||||
undefined_table = readIntBig(Code, "42P01"),
|
||||
undefined_parameter = readIntBig(Code, "42P02"),
|
||||
undefined_object = readIntBig(Code, "42704"),
|
||||
duplicate_column = readIntBig(Code, "42701"),
|
||||
duplicate_cursor = readIntBig(Code, "42P03"),
|
||||
duplicate_database = readIntBig(Code, "42P04"),
|
||||
duplicate_function = readIntBig(Code, "42723"),
|
||||
duplicate_prepared_statement = readIntBig(Code, "42P05"),
|
||||
duplicate_schema = readIntBig(Code, "42P06"),
|
||||
duplicate_table = readIntBig(Code, "42P07"),
|
||||
duplicate_alias = readIntBig(Code, "42712"),
|
||||
duplicate_object = readIntBig(Code, "42710"),
|
||||
ambiguous_column = readIntBig(Code, "42702"),
|
||||
ambiguous_function = readIntBig(Code, "42725"),
|
||||
ambiguous_parameter = readIntBig(Code, "42P08"),
|
||||
ambiguous_alias = readIntBig(Code, "42P09"),
|
||||
invalid_column_reference = readIntBig(Code, "42P10"),
|
||||
invalid_column_definition = readIntBig(Code, "42611"),
|
||||
invalid_cursor_definition = readIntBig(Code, "42P11"),
|
||||
invalid_database_definition = readIntBig(Code, "42P12"),
|
||||
invalid_function_definition = readIntBig(Code, "42P13"),
|
||||
invalid_prepared_statement_definition = readIntBig(Code, "42P14"),
|
||||
invalid_schema_definition = readIntBig(Code, "42P15"),
|
||||
invalid_table_definition = readIntBig(Code, "42P16"),
|
||||
invalid_object_definition = readIntBig(Code, "42P17"),
|
||||
// Class 44 — WITH CHECK OPTION Violation
|
||||
with_check_option_violation = readIntBig(Code, "44000"),
|
||||
// Class 53 — Insufficient Resources
|
||||
insufficient_resources = readIntBig(Code, "53000"),
|
||||
disk_full = readIntBig(Code, "53100"),
|
||||
out_of_memory = readIntBig(Code, "53200"),
|
||||
too_many_connections = readIntBig(Code, "53300"),
|
||||
configuration_limit_exceeded = readIntBig(Code, "53400"),
|
||||
// Class 54 — Program Limit Exceeded
|
||||
program_limit_exceeded = readIntBig(Code, "54000"),
|
||||
statement_too_complex = readIntBig(Code, "54001"),
|
||||
too_many_columns = readIntBig(Code, "54011"),
|
||||
too_many_arguments = readIntBig(Code, "54023"),
|
||||
// Class 55 — Object Not In Prerequisite State
|
||||
object_not_in_prerequisite_state = readIntBig(Code, "55000"),
|
||||
object_in_use = readIntBig(Code, "55006"),
|
||||
cant_change_runtime_param = readIntBig(Code, "55P02"),
|
||||
lock_not_available = readIntBig(Code, "55P03"),
|
||||
unsafe_new_enum_value_usage = readIntBig(Code, "55P04"),
|
||||
// Class 57 — Operator Intervention
|
||||
operator_intervention = readIntBig(Code, "57000"),
|
||||
query_canceled = readIntBig(Code, "57014"),
|
||||
admin_shutdown = readIntBig(Code, "57P01"),
|
||||
crash_shutdown = readIntBig(Code, "57P02"),
|
||||
cannot_connect_now = readIntBig(Code, "57P03"),
|
||||
database_dropped = readIntBig(Code, "57P04"),
|
||||
idle_session_timeout = readIntBig(Code, "57P05"),
|
||||
// Class 58 — System Error (errors external to PostgreSQL itself)
|
||||
system_error = readIntBig(Code, "58000"),
|
||||
io_error = readIntBig(Code, "58030"),
|
||||
undefined_file = readIntBig(Code, "58P01"),
|
||||
duplicate_file = readIntBig(Code, "58P02"),
|
||||
// Class 72 — Snapshot Failure
|
||||
snapshot_too_old = readIntBig(Code, "72000"),
|
||||
// Class F0 — Configuration File Error
|
||||
config_file_error = readIntBig(Code, "F0000"),
|
||||
lock_file_exists = readIntBig(Code, "F0001"),
|
||||
// Class HV — Foreign Data Wrapper Error (SQL/MED)
|
||||
fdw_error = readIntBig(Code, "HV000"),
|
||||
fdw_column_name_not_found = readIntBig(Code, "HV005"),
|
||||
fdw_dynamic_parameter_value_needed = readIntBig(Code, "HV002"),
|
||||
fdw_function_sequence_error = readIntBig(Code, "HV010"),
|
||||
fdw_inconsistent_descriptor_information = readIntBig(Code, "HV021"),
|
||||
fdw_invalid_attribute_value = readIntBig(Code, "HV024"),
|
||||
fdw_invalid_column_name = readIntBig(Code, "HV007"),
|
||||
fdw_invalid_column_number = readIntBig(Code, "HV008"),
|
||||
fdw_invalid_data_type = readIntBig(Code, "HV004"),
|
||||
fdw_invalid_data_type_descriptors = readIntBig(Code, "HV006"),
|
||||
fdw_invalid_descriptor_field_identifier = readIntBig(Code, "HV091"),
|
||||
fdw_invalid_handle = readIntBig(Code, "HV00B"),
|
||||
fdw_invalid_option_index = readIntBig(Code, "HV00C"),
|
||||
fdw_invalid_option_name = readIntBig(Code, "HV00D"),
|
||||
fdw_invalid_string_length_or_buffer_length = readIntBig(Code, "HV090"),
|
||||
fdw_invalid_string_format = readIntBig(Code, "HV00A"),
|
||||
fdw_invalid_use_of_null_pointer = readIntBig(Code, "HV009"),
|
||||
fdw_too_many_handles = readIntBig(Code, "HV014"),
|
||||
fdw_out_of_memory = readIntBig(Code, "HV001"),
|
||||
fdw_no_schemas = readIntBig(Code, "HV00P"),
|
||||
fdw_option_name_not_found = readIntBig(Code, "HV00J"),
|
||||
fdw_reply_handle = readIntBig(Code, "HV00K"),
|
||||
fdw_schema_not_found = readIntBig(Code, "HV00Q"),
|
||||
fdw_table_not_found = readIntBig(Code, "HV00R"),
|
||||
fdw_unable_to_create_execution = readIntBig(Code, "HV00L"),
|
||||
fdw_unable_to_create_reply = readIntBig(Code, "HV00M"),
|
||||
fdw_unable_to_establish_connection = readIntBig(Code, "HV00N"),
|
||||
// Class P0 — PL/pgSQL Error
|
||||
plpgsql_error = readIntBig(Code, "P0000"),
|
||||
raise_exception = readIntBig(Code, "P0001"),
|
||||
no_data_found = readIntBig(Code, "P0002"),
|
||||
too_many_rows = readIntBig(Code, "P0003"),
|
||||
assert_failure = readIntBig(Code, "P0004"),
|
||||
// Class XX — Internal Error
|
||||
internal_error = readIntBig(Code, "XX000"),
|
||||
data_corrupted = readIntBig(Code, "XX001"),
|
||||
index_corrupted = readIntBig(Code, "XX002"),
|
||||
|
||||
_,
|
||||
};
|
|
@ -9,19 +9,6 @@ const Uuid = util.Uuid;
|
|||
const DateTime = util.DateTime;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const UnexpectedError = error{Unexpected};
|
||||
|
||||
pub const OpenError = error{
|
||||
DbCorrupt,
|
||||
BadPathName,
|
||||
IsDir,
|
||||
InputOutput,
|
||||
NoSpaceLeft,
|
||||
} || UnexpectedError;
|
||||
pub const PrepareError = UnexpectedError;
|
||||
pub const RowGetError = error{ InvalidData, StreamTooLong, OutOfMemory } || UnexpectedError;
|
||||
pub const BindError = UnexpectedError;
|
||||
|
||||
fn getCharPos(text: []const u8, offset: c_int) struct { row: usize, col: usize } {
|
||||
var row: usize = 0;
|
||||
var col: usize = 0;
|
||||
|
@ -41,7 +28,7 @@ fn getCharPos(text: []const u8, offset: c_int) struct { row: usize, col: usize }
|
|||
return .{ .row = row, .col = col };
|
||||
}
|
||||
|
||||
fn handleUnexpectedError(db: *c.sqlite3, code: c_int, sql_text: ?[]const u8) UnexpectedError {
|
||||
fn handleUnexpectedError(db: *c.sqlite3, code: c_int, sql_text: ?[]const u8) anyerror {
|
||||
std.log.err("Unexpected error in SQLite engine: {s} ({})", .{ c.sqlite3_errstr(code), code });
|
||||
|
||||
std.log.debug("Additional details:", .{});
|
||||
|
@ -61,30 +48,36 @@ fn handleUnexpectedError(db: *c.sqlite3, code: c_int, sql_text: ?[]const u8) Une
|
|||
pub const Db = struct {
|
||||
db: *c.sqlite3,
|
||||
|
||||
pub fn open(path: [:0]const u8) OpenError!Db {
|
||||
pub fn open(path: [:0]const u8) common.OpenError!Db {
|
||||
const flags = c.SQLITE_OPEN_READWRITE | c.SQLITE_OPEN_CREATE | c.SQLITE_OPEN_EXRESCODE;
|
||||
|
||||
var db: ?*c.sqlite3 = undefined;
|
||||
var db: [*c]c.sqlite3 = null;
|
||||
switch (c.sqlite3_open_v2(@ptrCast([*c]const u8, path), &db, flags, null)) {
|
||||
c.SQLITE_OK => {},
|
||||
c.SQLITE_NOTADB, c.SQLITE_CORRUPT => return error.DbCorrupt,
|
||||
c.SQLITE_IOERR_WRITE, c.SQLITE_IOERR_READ => return error.InputOutput,
|
||||
c.SQLITE_CANTOPEN_ISDIR => return error.IsDir,
|
||||
c.SQLITE_CANTOPEN_FULLPATH => return error.BadPathName,
|
||||
c.SQLITE_FULL => return error.NoSpaceLeft,
|
||||
|
||||
else => |err| {
|
||||
else => |code| {
|
||||
if (db == null) {
|
||||
// this path should only be hit if out of memory, but log it anyways
|
||||
std.log.err(
|
||||
\\Unable to open SQLite DB "{s}"
|
||||
\\Error: {s} ({})
|
||||
, .{ path, c.sqlite3_errstr(err), err });
|
||||
"Unable to open SQLite DB \"{s}\". Error: {?s} ({})",
|
||||
.{ path, c.sqlite3_errstr(code), code },
|
||||
);
|
||||
return error.InternalException;
|
||||
}
|
||||
|
||||
const ext_code = c.sqlite3_extended_errcode(db);
|
||||
std.log.err(
|
||||
\\Unable to open SQLite DB "{s}". Error: {?s} ({})
|
||||
\\Details: {?s}
|
||||
,
|
||||
.{ path, c.sqlite3_errstr(ext_code), ext_code, c.sqlite3_errmsg(db) },
|
||||
);
|
||||
|
||||
return error.Unexpected;
|
||||
},
|
||||
}
|
||||
|
||||
return Db{
|
||||
.db = db.?,
|
||||
.db = db,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -103,7 +96,7 @@ pub const Db = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn exec(self: Db, sql: []const u8, args: anytype, opts: common.QueryOptions) !Results {
|
||||
pub fn exec(self: Db, sql: []const u8, args: anytype, opts: common.QueryOptions) common.ExecError!Results {
|
||||
var stmt: ?*c.sqlite3_stmt = undefined;
|
||||
switch (c.sqlite3_prepare_v2(self.db, sql.ptr, @intCast(c_int, sql.len), &stmt, null)) {
|
||||
c.SQLITE_OK => {},
|
||||
|
@ -134,7 +127,7 @@ pub const Db = struct {
|
|||
return handleUnexpectedError(self.db, err, sql);
|
||||
},
|
||||
}
|
||||
} else if (!opts.ignore_unknown_parameters) return error.UnknownParameter;
|
||||
} else if (!opts.ignore_unknown_parameters) return error.UndefinedParameter;
|
||||
}
|
||||
|
||||
return Results{ .stmt = stmt.?, .db = self.db };
|
||||
|
@ -179,8 +172,7 @@ pub const Results = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub const StepError = UnexpectedError;
|
||||
pub fn row(self: Results) !?Row {
|
||||
pub fn row(self: Results) common.RowError!?Row {
|
||||
return switch (c.sqlite3_step(self.stmt)) {
|
||||
c.SQLITE_ROW => Row{ .stmt = self.stmt, .db = self.db },
|
||||
c.SQLITE_DONE => null,
|
||||
|
@ -193,18 +185,18 @@ pub const Results = struct {
|
|||
return ptr[0..std.mem.len(ptr)];
|
||||
}
|
||||
|
||||
pub fn columnCount(self: Results) u15 {
|
||||
pub fn columnCount(self: Results) common.ColumnCountError!u15 {
|
||||
return @intCast(u15, c.sqlite3_column_count(self.stmt));
|
||||
}
|
||||
|
||||
pub fn columnName(self: Results, idx: u15) ![]const u8 {
|
||||
fn columnName(self: Results, idx: u15) ![]const u8 {
|
||||
return if (c.sqlite3_column_name(self.stmt, idx)) |ptr|
|
||||
ptr[0..std.mem.len(ptr)]
|
||||
else
|
||||
return error.OutOfMemory;
|
||||
return error.Unexpected;
|
||||
}
|
||||
|
||||
pub fn columnNameToIndex(self: Results, name: []const u8) !u15 {
|
||||
pub fn columnIndex(self: Results, name: []const u8) common.ColumnIndexError!u15 {
|
||||
var i: u15 = 0;
|
||||
const count = self.columnCount();
|
||||
while (i < count) : (i += 1) {
|
||||
|
@ -212,7 +204,7 @@ pub const Results = struct {
|
|||
if (std.mem.eql(u8, name, column)) return i;
|
||||
}
|
||||
|
||||
return error.ColumnNotFound;
|
||||
return error.NotFound;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -220,15 +212,15 @@ pub const Row = struct {
|
|||
stmt: *c.sqlite3_stmt,
|
||||
db: *c.sqlite3,
|
||||
|
||||
pub fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) !T {
|
||||
pub fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) common.GetError!T {
|
||||
if (c.sqlite3_column_type(self.stmt, idx) == c.SQLITE_NULL) {
|
||||
return if (@typeInfo(T) == .Optional) null else error.NullValue;
|
||||
return if (@typeInfo(T) == .Optional) null else error.TypeMismatch;
|
||||
}
|
||||
|
||||
return self.getNotNull(T, idx, alloc);
|
||||
}
|
||||
|
||||
fn getNotNull(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) !T {
|
||||
fn getNotNull(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) error.GetError!T {
|
||||
return switch (T) {
|
||||
f32, f64 => @floatCast(T, c.sqlite3_column_double(self.stmt, idx)),
|
||||
|
||||
|
@ -243,7 +235,7 @@ pub const Row = struct {
|
|||
};
|
||||
}
|
||||
|
||||
fn getFromString(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) !T {
|
||||
fn getFromString(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) error.GetError!T {
|
||||
const ptr = c.sqlite3_column_text(self.stmt, idx);
|
||||
const size = @intCast(usize, c.sqlite3_column_bytes(self.stmt, idx));
|
||||
const str = ptr[0..size];
|
105
src/sql/errors.zig
Normal file
105
src/sql/errors.zig
Normal file
|
@ -0,0 +1,105 @@
|
|||
const std = @import("std");
|
||||
|
||||
// Error for any miscellaneous or unexpected error codes
|
||||
const UnexpectedError = error{Unexpected};
|
||||
|
||||
// Errors related to database connections, common to (most) api calls
|
||||
const ConnectionError = error{
|
||||
// The operation was cancelled by the user
|
||||
Cancelled,
|
||||
|
||||
// The database connection could not be created or was lost
|
||||
BadConnection,
|
||||
|
||||
// An internal error occurred in the engine.
|
||||
// Examples include:
|
||||
// - Out of Memory
|
||||
// - Filesystem full
|
||||
// - Unknown crash
|
||||
// - Filesystem permissions denied
|
||||
InternalError,
|
||||
};
|
||||
|
||||
// Errors related to constraint validation
|
||||
const ConstraintError = error{
|
||||
// A `NOT NULL` constraint was violated
|
||||
NotNullViolation,
|
||||
|
||||
// A `FOREIGN KEY` constraint was violated
|
||||
ForeignKeyViolation,
|
||||
|
||||
// A `UNIQUE` constraint was violated
|
||||
UniqueViolation,
|
||||
|
||||
// A `CHECK` constraint was violated
|
||||
CheckViolation,
|
||||
|
||||
// A unknown constraint type was violated
|
||||
ConstraintViolation,
|
||||
};
|
||||
|
||||
// Errors related to argument binding
|
||||
const ArgumentError = error{
|
||||
// One of the arguments passed could not be marshalled to pass to the SQL engine
|
||||
InvalidArgument,
|
||||
|
||||
// The set of arguments passed did not map to query parameters
|
||||
UndefinedParameter,
|
||||
|
||||
// The allocator used for staging the query ran out of memory
|
||||
OutOfMemory,
|
||||
};
|
||||
|
||||
// Errors related to retrieving query result columns
|
||||
const ResultColumnError = error{
|
||||
// The allocator used for retrieving the results ran out of memory
|
||||
OutOfMemory,
|
||||
|
||||
// A type error occurred when parsing results (means invalid data is in the DB)
|
||||
ResultTypeMismatch,
|
||||
};
|
||||
|
||||
// Errors related to executing SQL queries
|
||||
const StartQueryError = error{
|
||||
// The database is locked by another query and the timeout was exceeded
|
||||
DatabaseBusy,
|
||||
|
||||
// Access to one or more resources was denied
|
||||
PermissionDenied,
|
||||
|
||||
// The SQL query had invalid syntax or used an invalid identifier
|
||||
InvalidSql,
|
||||
|
||||
// A type error occurred during the query (means query is written wrong)
|
||||
QueryTypeMismatch,
|
||||
|
||||
// The set of columns to parse did not match the columns returned by the query
|
||||
ColumnMismatch,
|
||||
|
||||
// Either an explicit transaction was open and a query method was called on the DB directly,
|
||||
// or no explicit transaction was open and a query method was called on a transaction;
|
||||
BadTransactionState,
|
||||
};
|
||||
|
||||
const RowCountError = error{
|
||||
NoRows,
|
||||
TooManyRows,
|
||||
};
|
||||
|
||||
pub const OpenError = error{
|
||||
BadConnection,
|
||||
InternalError,
|
||||
};
|
||||
|
||||
pub const library_errors = struct {
|
||||
const BaseError = ConnectionError || UnexpectedError;
|
||||
|
||||
// TODO: clean this up
|
||||
pub const OpenError = BaseError;
|
||||
pub const QueryError = BaseError || ArgumentError || ConstraintError || StartQueryError;
|
||||
pub const RowError = BaseError || ResultColumnError || ConstraintError || StartQueryError;
|
||||
pub const QueryRowError = QueryError || RowError || RowCountError;
|
||||
pub const ExecError = QueryError || RowCountError;
|
||||
pub const BeginError = BaseError || StartQueryError;
|
||||
pub const CommitError = BaseError || StartQueryError || ConstraintError;
|
||||
};
|
365
src/sql/lib.zig
365
src/sql/lib.zig
|
@ -1,11 +1,20 @@
|
|||
const std = @import("std");
|
||||
const util = @import("util");
|
||||
|
||||
const postgres = @import("./postgres.zig");
|
||||
const sqlite = @import("./sqlite.zig");
|
||||
const common = @import("./common.zig");
|
||||
const postgres = @import("./engines/postgres.zig");
|
||||
const sqlite = @import("./engines/sqlite.zig");
|
||||
const common = @import("./engines/common.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const errors = @import("./errors.zig").library_errors;
|
||||
|
||||
pub const OpenError = errors.OpenError;
|
||||
pub const QueryError = errors.QueryError;
|
||||
pub const RowError = errors.RowError;
|
||||
pub const QueryRowError = errors.QueryRowError;
|
||||
pub const BeginError = errors.BeginError;
|
||||
pub const CommitError = errors.CommitError;
|
||||
|
||||
pub const QueryOptions = common.QueryOptions;
|
||||
|
||||
pub const Engine = enum {
|
||||
|
@ -22,11 +31,6 @@ pub const Config = union(Engine) {
|
|||
},
|
||||
};
|
||||
|
||||
pub const QueryError = error{
|
||||
OutOfMemory,
|
||||
ConnectionLost,
|
||||
};
|
||||
|
||||
pub fn fieldList(comptime RowType: type) []const u8 {
|
||||
comptime {
|
||||
const fields = std.meta.fieldNames(RowType);
|
||||
|
@ -76,14 +80,14 @@ const RawResults = union(Engine) {
|
|||
};
|
||||
}
|
||||
|
||||
fn columnNameToIndex(self: RawResults, name: []const u8) !u15 {
|
||||
fn columnIndex(self: RawResults, name: []const u8) QueryError!u15 {
|
||||
return try switch (self) {
|
||||
.postgres => |pg| pg.columnNameToIndex(name),
|
||||
.sqlite => |lite| lite.columnNameToIndex(name),
|
||||
.postgres => |pg| pg.columnIndex(name),
|
||||
.sqlite => |lite| lite.columnIndex(name),
|
||||
};
|
||||
}
|
||||
|
||||
fn row(self: *RawResults) !?Row {
|
||||
fn row(self: *RawResults) RowError!?Row {
|
||||
return switch (self.*) {
|
||||
.postgres => |*pg| if (try pg.row()) |r| Row{ .postgres = r } else null,
|
||||
.sqlite => |*lite| if (try lite.row()) |r| Row{ .sqlite = r } else null,
|
||||
|
@ -103,11 +107,16 @@ pub fn Results(comptime T: type) type {
|
|||
underlying: RawResults,
|
||||
column_indices: [fields.len]u15,
|
||||
|
||||
fn from(underlying: RawResults) !Self {
|
||||
fn from(underlying: RawResults) QueryError!Self {
|
||||
if (std.debug.runtime_safety and std.meta.trait.isTuple(T) and fields.len != underlying.columnCount()) {
|
||||
std.log.err("Expected {} columns in result, got {}", .{ fields.len, underlying.columnCount() });
|
||||
return error.ColumnMismatch;
|
||||
}
|
||||
|
||||
return Self{ .underlying = underlying, .column_indices = blk: {
|
||||
var indices: [fields.len]u15 = undefined;
|
||||
inline for (fields) |f, i| {
|
||||
indices[i] = if (!std.meta.trait.isTuple(T)) try underlying.columnNameToIndex(f.name) else i;
|
||||
indices[i] = if (!std.meta.trait.isTuple(T)) try underlying.columnIndex(f.name) else i;
|
||||
}
|
||||
break :blk indices;
|
||||
} };
|
||||
|
@ -126,7 +135,7 @@ pub fn Results(comptime T: type) type {
|
|||
// Returns the next row of results, or null if there are no more rows.
|
||||
// Caller owns all memory allocated. The entire object can be deallocated with a
|
||||
// call to util.deepFree
|
||||
pub fn row(self: *Self, alloc: ?Allocator) !?T {
|
||||
pub fn row(self: *Self, alloc: ?Allocator) RowError!?T {
|
||||
if (try self.underlying.row()) |row_val| {
|
||||
var result: T = undefined;
|
||||
var fields_allocated: usize = 0;
|
||||
|
@ -157,7 +166,7 @@ const Row = union(Engine) {
|
|||
// Not all types require an allocator to be present. If an allocator is needed but
|
||||
// not required, it will return error.AllocatorRequired.
|
||||
// The caller is responsible for deallocating T, if relevant.
|
||||
fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) anyerror!T {
|
||||
fn get(self: Row, comptime T: type, idx: u15, alloc: ?Allocator) !T {
|
||||
return switch (self) {
|
||||
.postgres => |pg| pg.get(T, idx, alloc),
|
||||
.sqlite => |lite| lite.get(T, idx, alloc),
|
||||
|
@ -165,170 +174,64 @@ const Row = union(Engine) {
|
|||
}
|
||||
};
|
||||
|
||||
const DbUnion = union(Engine) {
|
||||
const QueryHelper = union(Engine) {
|
||||
postgres: postgres.Db,
|
||||
sqlite: sqlite.Db,
|
||||
};
|
||||
|
||||
pub const ConstraintMode = enum {
|
||||
deferred,
|
||||
immediate,
|
||||
};
|
||||
|
||||
pub const Db = struct {
|
||||
tx_open: bool = false,
|
||||
underlying: DbUnion,
|
||||
|
||||
pub fn open(cfg: Config) !Db {
|
||||
return switch (cfg) {
|
||||
.postgres => |postgres_cfg| Db{
|
||||
.underlying = .{
|
||||
.postgres = try postgres.Db.open(postgres_cfg.pg_conn_str),
|
||||
},
|
||||
},
|
||||
.sqlite => |lite_cfg| Db{
|
||||
.underlying = .{
|
||||
.sqlite = try sqlite.Db.open(lite_cfg.sqlite_file_path),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: *Db) void {
|
||||
switch (self.underlying) {
|
||||
.postgres => |pg| pg.close(),
|
||||
.sqlite => |lite| lite.close(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queryWithOptions(
|
||||
self: *Db,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
opt: QueryOptions,
|
||||
) !Results(RowType) {
|
||||
if (self.tx_open) return error.TransactionOpen;
|
||||
// Create fake transaction to use its functions
|
||||
return (Tx{ .db = self }).queryWithOptions(RowType, sql, args, opt);
|
||||
}
|
||||
|
||||
pub fn query(
|
||||
self: *Db,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) !Results(RowType) {
|
||||
if (self.tx_open) return error.TransactionOpen;
|
||||
// Create fake transaction to use its functions
|
||||
return (Tx{ .db = self }).query(RowType, sql, args, alloc);
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
self: *Db,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) !void {
|
||||
if (self.tx_open) return error.TransactionOpen;
|
||||
// Create fake transaction to use its functions
|
||||
return (Tx{ .db = self }).exec(sql, args, alloc);
|
||||
}
|
||||
|
||||
pub fn queryRow(
|
||||
self: *Db,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) !?RowType {
|
||||
if (self.tx_open) return error.TransactionOpen;
|
||||
// Create fake transaction to use its functions
|
||||
return (Tx{ .db = self }).queryRow(RowType, sql, args, alloc);
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
self: *Db,
|
||||
comptime table: []const u8,
|
||||
value: anytype,
|
||||
) !void {
|
||||
if (self.tx_open) return error.TransactionOpen;
|
||||
// Create fake transaction to use its functions
|
||||
return (Tx{ .db = self }).insert(table, value);
|
||||
}
|
||||
|
||||
pub fn sqlEngine(self: *Db) Engine {
|
||||
return self.underlying;
|
||||
}
|
||||
|
||||
// Begins a transaction
|
||||
pub fn begin(self: *Db) !Tx {
|
||||
const tx = Tx{ .db = self };
|
||||
try tx.exec("BEGIN", .{}, null);
|
||||
|
||||
return tx;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Tx = struct {
|
||||
db: *Db,
|
||||
|
||||
// internal helper fn
|
||||
fn queryInternal(
|
||||
self: Tx,
|
||||
self: QueryHelper,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
opt: QueryOptions,
|
||||
) !RawResults {
|
||||
return switch (self.db.underlying) {
|
||||
return switch (self) {
|
||||
.postgres => |pg| RawResults{ .postgres = try pg.exec(sql, args, opt.prep_allocator) },
|
||||
.sqlite => |lite| RawResults{ .sqlite = try lite.exec(sql, args, opt) },
|
||||
};
|
||||
}
|
||||
|
||||
pub fn queryWithOptions(
|
||||
self: Tx,
|
||||
fn queryWithOptions(
|
||||
self: QueryHelper,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
options: QueryOptions,
|
||||
) !Results(RowType) {
|
||||
) QueryError!Results(RowType) {
|
||||
return Results(RowType).from(try self.queryInternal(sql, args, options));
|
||||
}
|
||||
|
||||
// Executes a query and returns the result set
|
||||
pub fn query(
|
||||
self: Tx,
|
||||
fn query(
|
||||
self: QueryHelper,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) !Results(RowType) {
|
||||
) QueryError!Results(RowType) {
|
||||
return self.queryWithOptions(RowType, sql, args, .{ .prep_allocator = alloc });
|
||||
}
|
||||
|
||||
// Executes a query without returning results
|
||||
pub fn exec(
|
||||
self: Tx,
|
||||
fn exec(
|
||||
self: QueryHelper,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) !void {
|
||||
_ = try self.queryRow(std.meta.Tuple(&.{}), sql, args, alloc);
|
||||
) QueryError!void {
|
||||
try self.queryRow(void, sql, args, alloc);
|
||||
}
|
||||
|
||||
// Runs a query and returns a single row
|
||||
pub fn queryRow(
|
||||
self: Tx,
|
||||
fn queryRow(
|
||||
self: QueryHelper,
|
||||
comptime RowType: type,
|
||||
q: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) !?RowType {
|
||||
) QueryRowError!?RowType {
|
||||
var results = try self.query(RowType, q, args, alloc);
|
||||
defer results.finish();
|
||||
@compileLog(args);
|
||||
|
||||
const row = (try results.row(alloc)) orelse return null;
|
||||
errdefer util.deepFree(alloc, row);
|
||||
|
@ -344,8 +247,8 @@ pub const Tx = struct {
|
|||
}
|
||||
|
||||
// Inserts a single value into a table
|
||||
pub fn insert(
|
||||
self: Tx,
|
||||
fn insert(
|
||||
self: QueryHelper,
|
||||
comptime table: []const u8,
|
||||
value: anytype,
|
||||
) !void {
|
||||
|
@ -373,19 +276,179 @@ pub const Tx = struct {
|
|||
}
|
||||
try self.exec(q, args_tuple, null);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn sqlEngine(self: Tx) Engine {
|
||||
return self.db.underlying;
|
||||
pub const ConstraintMode = enum {
|
||||
deferred,
|
||||
immediate,
|
||||
};
|
||||
|
||||
pub const Db = struct {
|
||||
tx_open: bool = false,
|
||||
engine: QueryHelper,
|
||||
|
||||
pub fn open(cfg: Config) OpenError!Db {
|
||||
return switch (cfg) {
|
||||
.postgres => |postgres_cfg| Db{
|
||||
.engine = .{
|
||||
.postgres = try postgres.Db.open(postgres_cfg.pg_conn_str),
|
||||
},
|
||||
},
|
||||
.sqlite => |lite_cfg| Db{
|
||||
.engine = .{
|
||||
.sqlite = try sqlite.Db.open(lite_cfg.sqlite_file_path),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setConstraintMode(self: Tx, mode: ConstraintMode) !void {
|
||||
switch (self.db.underlying) {
|
||||
pub fn close(self: *Db) void {
|
||||
switch (self.engine) {
|
||||
.postgres => |pg| pg.close(),
|
||||
.sqlite => |lite| lite.close(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn queryWithOptions(
|
||||
self: *Db,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
opt: QueryOptions,
|
||||
) QueryError!Results(RowType) {
|
||||
if (self.tx_open) return error.BadTransactionState;
|
||||
return self.engine.queryWithOptions(RowType, sql, args, opt);
|
||||
}
|
||||
|
||||
pub fn query(
|
||||
self: *Db,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) QueryError!Results(RowType) {
|
||||
if (self.tx_open) return error.BadTransactionState;
|
||||
return self.engine.query(RowType, sql, args, alloc);
|
||||
}
|
||||
|
||||
pub fn exec(
|
||||
self: *Db,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) QueryError!void {
|
||||
if (self.tx_open) return error.BadTransactionState;
|
||||
return self.engine.exec(sql, args, alloc);
|
||||
}
|
||||
|
||||
pub fn queryRow(
|
||||
self: *Db,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) QueryRowError!?RowType {
|
||||
if (self.tx_open) return error.BadTransactionState;
|
||||
return self.engine.queryRow(RowType, sql, args, alloc);
|
||||
}
|
||||
|
||||
pub fn insert(
|
||||
self: *Db,
|
||||
comptime table: []const u8,
|
||||
value: anytype,
|
||||
) !void {
|
||||
if (self.tx_open) return error.BadTransactionState;
|
||||
return self.engine.insert(table, value);
|
||||
}
|
||||
|
||||
pub fn sqlEngine(self: *Db) Engine {
|
||||
return self.engine;
|
||||
}
|
||||
|
||||
// Begins a transaction
|
||||
pub fn begin(self: *Db) !Tx {
|
||||
if (self.tx_open) return error.BadTransactionState;
|
||||
|
||||
const tx = Tx{ .db = self };
|
||||
try tx.exec("BEGIN", {}, null);
|
||||
self.tx_open = true;
|
||||
|
||||
return tx;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Tx = struct {
|
||||
db: *Db,
|
||||
|
||||
pub fn queryWithOptions(
|
||||
self: Tx,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
options: QueryOptions,
|
||||
) QueryError!Results(RowType) {
|
||||
if (!self.db.tx_open) return error.BadTransactionState;
|
||||
return self.db.engine.queryWithOptions(RowType, sql, args, options);
|
||||
}
|
||||
|
||||
// Executes a query and returns the result set
|
||||
pub fn query(
|
||||
self: Tx,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) QueryError!Results(RowType) {
|
||||
if (!self.db.tx_open) return error.BadTransactionState;
|
||||
return self.db.engine.query(RowType, sql, args, alloc);
|
||||
}
|
||||
|
||||
// Executes a query without returning results
|
||||
pub fn exec(
|
||||
self: Tx,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) QueryError!void {
|
||||
if (!self.db.tx_open) return error.BadTransactionState;
|
||||
return self.db.engine.exec(sql, args, alloc);
|
||||
}
|
||||
|
||||
// Runs a query and returns a single row
|
||||
pub fn queryRow(
|
||||
self: Tx,
|
||||
comptime RowType: type,
|
||||
sql: [:0]const u8,
|
||||
args: anytype,
|
||||
alloc: ?Allocator,
|
||||
) QueryRowError!?RowType {
|
||||
if (!self.db.tx_open) return error.BadTransactionState;
|
||||
return self.db.engine.queryRow(RowType, sql, args, alloc);
|
||||
}
|
||||
|
||||
// Inserts a single value into a table
|
||||
pub fn insert(
|
||||
self: Tx,
|
||||
comptime table: []const u8,
|
||||
value: anytype,
|
||||
) !void {
|
||||
if (!self.db.tx_open) return error.BadTransactionState;
|
||||
return self.db.engine.insert(table, value);
|
||||
}
|
||||
|
||||
pub fn sqlEngine(self: Tx) Engine {
|
||||
return self.db.engine;
|
||||
}
|
||||
|
||||
pub fn setConstraintMode(self: Tx, mode: ConstraintMode) QueryError!void {
|
||||
if (!self.db.tx_open) return error.BadTransactionState;
|
||||
switch (self.db.engine) {
|
||||
.sqlite => try self.exec(
|
||||
switch (mode) {
|
||||
.immediate => "PRAGMA defer_foreign_keys = FALSE",
|
||||
.deferred => "PRAGMA defer_foreign_keys = TRUE",
|
||||
},
|
||||
.{},
|
||||
{},
|
||||
null,
|
||||
),
|
||||
.postgres => try self.exec(
|
||||
|
@ -393,19 +456,23 @@ pub const Tx = struct {
|
|||
.immediate => "SET CONSTRAINTS ALL IMMEDIATE",
|
||||
.deferred => "SET CONSTRAINTS ALL DEFERRED",
|
||||
},
|
||||
.{},
|
||||
{},
|
||||
null,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rollback(self: Tx) void {
|
||||
self.exec("ROLLBACK", .{}, null) catch |err| {
|
||||
if (!self.db.tx_open) @panic("Transaction not open");
|
||||
self.exec("ROLLBACK", {}, null) catch |err| {
|
||||
std.log.err("Error occured during rollback operation: {}", .{err});
|
||||
};
|
||||
self.db.tx_open = false;
|
||||
}
|
||||
|
||||
pub fn commit(self: Tx) !void {
|
||||
try self.exec("COMMIT", .{}, null);
|
||||
pub fn commit(self: Tx) CommitError!void {
|
||||
if (!self.db.tx_open) return error.BadTransactionState;
|
||||
try self.exec("COMMIT", {}, null);
|
||||
self.db.tx_open = false;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,134 +0,0 @@
|
|||
const std = @import("std");
|
||||
const util = @import("util");
|
||||
const common = @import("./common.zig");
|
||||
const c = @cImport({
|
||||
@cInclude("libpq-fe.h");
|
||||
});
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
pub const Results = struct {
|
||||
result: *c.PGresult,
|
||||
next_row_index: c_int = 0,
|
||||
|
||||
pub fn rowCount(self: Results) usize {
|
||||
return @intCast(usize, c.PQntuples(self.result));
|
||||
}
|
||||
|
||||
pub fn row(self: *Results) !?Row {
|
||||
if (self.next_row_index >= self.rowCount()) return null;
|
||||
const idx = self.next_row_index;
|
||||
self.next_row_index += 1;
|
||||
|
||||
return Row{
|
||||
.result = self.result,
|
||||
.row_index = idx,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn columnCount(self: Results) u15 {
|
||||
return @intCast(u15, c.PQnfields(self.result));
|
||||
}
|
||||
|
||||
pub fn columnNameToIndex(self: Results, name: []const u8) !u15 {
|
||||
const idx = c.PQfnumber(self.result, name.ptr);
|
||||
if (idx == -1) return error.ColumnNotFound;
|
||||
return @intCast(u15, idx);
|
||||
}
|
||||
|
||||
pub fn finish(self: Results) void {
|
||||
c.PQclear(self.result);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Row = struct {
|
||||
result: *c.PGresult,
|
||||
row_index: c_int,
|
||||
|
||||
pub fn get(self: Row, comptime T: type, idx: u16, alloc: ?Allocator) !T {
|
||||
const val = c.PQgetvalue(self.result, self.row_index, idx);
|
||||
const is_null = (c.PQgetisnull(self.result, self.row_index, idx) != 0);
|
||||
if (is_null) {
|
||||
return if (@typeInfo(T) == .Optional) null else error.NullValue;
|
||||
}
|
||||
|
||||
if (val == null) return error.Unexpected;
|
||||
|
||||
const len = @intCast(usize, c.PQgetlength(self.result, self.row_index, idx));
|
||||
|
||||
return try common.parseValueNotNull(alloc, T, val[0..len]);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Db = struct {
|
||||
conn: *c.PGconn,
|
||||
|
||||
pub fn open(conn_str: [:0]const u8) !Db {
|
||||
const conn = c.PQconnectdb(conn_str.ptr) orelse {
|
||||
std.log.err("Unable to connect to database", .{});
|
||||
return error.UnknownError;
|
||||
};
|
||||
errdefer c.PQfinish(conn);
|
||||
|
||||
std.log.info("Connecting to database using provided connection string...", .{});
|
||||
loop: while (true) {
|
||||
switch (c.PQstatus(conn)) {
|
||||
c.CONNECTION_OK => break :loop,
|
||||
|
||||
c.CONNECTION_BAD => {
|
||||
std.log.err("Error connecting to database", .{});
|
||||
return error.BadConnection;
|
||||
},
|
||||
|
||||
else => std.os.nanosleep(0, 100 * 1000 * 1000), // 100 ms
|
||||
}
|
||||
}
|
||||
std.log.info("DB connection established", .{});
|
||||
|
||||
return Db{ .conn = conn };
|
||||
}
|
||||
|
||||
pub fn close(self: Db) void {
|
||||
c.PQfinish(self.conn);
|
||||
}
|
||||
|
||||
const format_text = 0;
|
||||
const format_binary = 1;
|
||||
pub fn exec(self: Db, sql: [:0]const u8, args: anytype, alloc: ?Allocator) !Results {
|
||||
const result = blk: {
|
||||
if (comptime args.len > 0) {
|
||||
var arena = std.heap.ArenaAllocator.init(alloc orelse return error.AllocatorRequired);
|
||||
defer arena.deinit();
|
||||
const params = try arena.allocator().alloc(?[*:0]const u8, args.len);
|
||||
inline for (args) |arg, i| params[i] = if (try common.prepareParamText(&arena, arg)) |slice| slice.ptr else null;
|
||||
|
||||
break :blk c.PQexecParams(self.conn, sql.ptr, @intCast(c_int, params.len), null, params.ptr, null, null, format_text);
|
||||
} else {
|
||||
break :blk c.PQexecParams(self.conn, sql.ptr, 0, null, null, null, null, format_text);
|
||||
}
|
||||
} orelse {
|
||||
std.log.err("Error occurred in sql query: {?s}", .{c.PQerrorMessage(self.conn)});
|
||||
return error.UnknownError;
|
||||
};
|
||||
errdefer c.PQclear(result);
|
||||
|
||||
const status = c.PQresultStatus(result);
|
||||
return switch (status) {
|
||||
c.PGRES_EMPTY_QUERY => error.EmptyQuery,
|
||||
c.PGRES_FATAL_ERROR => blk: {
|
||||
std.log.err("Error occurred in sql query: {?s}", .{c.PQresultErrorMessage(result)});
|
||||
break :blk error.UnknownError;
|
||||
},
|
||||
c.PGRES_BAD_RESPONSE => blk: {
|
||||
std.log.err("Error occurred in sql query: {?s}", .{c.PQresultErrorMessage(result)});
|
||||
break :blk error.BadResponse;
|
||||
},
|
||||
|
||||
c.PGRES_COMMAND_OK, c.PGRES_TUPLES_OK, c.PGRES_COPY_OUT, c.PGRES_COPY_IN, c.PGRES_COPY_BOTH => return Results{ .result = result }, //TODO
|
||||
|
||||
c.PGRES_SINGLE_TUPLE => unreachable, // Not yet supported
|
||||
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
};
|
Loading…
Reference in a new issue