Files
bun.sh/src/sql/mysql/MySQLStatement.zig
Jarred Sumner a00c0d5232 try this
2025-09-11 00:09:35 -07:00

179 lines
5.5 KiB
Zig

const MySQLStatement = @This();
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
cached_structure: CachedStructure = .{},
ref_count: RefCount = RefCount.init(),
statement_id: u32 = 0,
params: []Param = &[_]Param{},
params_received: u32 = 0,
columns: []ColumnDefinition41 = &[_]ColumnDefinition41{},
columns_received: u32 = 0,
signature: Signature,
status: Status = Status.parsing,
error_response: ErrorPacket = .{ .error_code = 0 },
execution_flags: ExecutionFlags = .{},
fields_flags: SQLDataCell.Flags = .{},
result_count: u64 = 0,
pub const ExecutionFlags = packed struct(u8) {
header_received: bool = false,
needs_duplicate_check: bool = true,
need_to_send_params: bool = true,
_: u5 = 0,
};
pub const Status = enum {
pending,
parsing,
prepared,
failed,
};
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
pub fn reset(this: *MySQLStatement) void {
this.result_count = 0;
this.columns_received = 0;
this.execution_flags = .{};
}
pub fn deinit(this: *MySQLStatement) void {
debug("MySQLStatement deinit", .{});
for (this.columns) |*column| {
column.deinit();
}
if (this.columns.len > 0) {
bun.default_allocator.free(this.columns);
}
if (this.params.len > 0) {
bun.default_allocator.free(this.params);
}
this.cached_structure.deinit();
this.error_response.deinit();
this.signature.deinit();
bun.default_allocator.destroy(this);
}
pub fn checkForDuplicateFields(this: *@This()) void {
if (!this.execution_flags.needs_duplicate_check) return;
this.execution_flags.needs_duplicate_check = false;
var seen_numbers = std.ArrayList(u32).init(bun.default_allocator);
defer seen_numbers.deinit();
var seen_fields = bun.StringHashMap(void).init(bun.default_allocator);
bun.handleOom(seen_fields.ensureUnusedCapacity(@intCast(this.columns.len)));
defer seen_fields.deinit();
// iterate backwards
var remaining = this.columns.len;
var flags: SQLDataCell.Flags = .{};
while (remaining > 0) {
remaining -= 1;
const field: *ColumnDefinition41 = &this.columns[remaining];
switch (field.name_or_index) {
.name => |*name| {
const seen = seen_fields.getOrPut(name.slice()) catch unreachable;
if (seen.found_existing) {
field.name_or_index = .duplicate;
flags.has_duplicate_columns = true;
}
flags.has_named_columns = true;
},
.index => |index| {
if (std.mem.indexOfScalar(u32, seen_numbers.items, index) != null) {
field.name_or_index = .duplicate;
flags.has_duplicate_columns = true;
} else {
bun.handleOom(seen_numbers.append(index));
}
flags.has_indexed_columns = true;
},
.duplicate => {
flags.has_duplicate_columns = true;
},
}
}
this.fields_flags = flags;
}
pub fn structure(this: *MySQLStatement, owner: JSValue, globalObject: *jsc.JSGlobalObject) *CachedStructure {
if (this.cached_structure.has()) {
return &this.cached_structure;
}
this.checkForDuplicateFields();
// lets avoid most allocations
var stack_ids: [70]jsc.JSObject.ExternColumnIdentifier = [_]jsc.JSObject.ExternColumnIdentifier{.{ .tag = 0, .value = .{ .index = 0 } }} ** 70;
// lets de duplicate the fields early
var nonDuplicatedCount = this.columns.len;
for (this.columns) |*column| {
if (column.name_or_index == .duplicate) {
nonDuplicatedCount -= 1;
}
}
const ids = if (nonDuplicatedCount <= jsc.JSObject.maxInlineCapacity()) stack_ids[0..nonDuplicatedCount] else bun.handleOom(bun.default_allocator.alloc(jsc.JSObject.ExternColumnIdentifier, nonDuplicatedCount));
var i: usize = 0;
for (this.columns) |*column| {
if (column.name_or_index == .duplicate) continue;
var id: *jsc.JSObject.ExternColumnIdentifier = &ids[i];
switch (column.name_or_index) {
.name => |name| {
id.value.name = String.createAtomIfPossible(name.slice());
},
.index => |index| {
id.value.index = index;
},
.duplicate => unreachable,
}
id.tag = switch (column.name_or_index) {
.name => 2,
.index => 1,
.duplicate => 0,
};
i += 1;
}
if (nonDuplicatedCount > jsc.JSObject.maxInlineCapacity()) {
this.cached_structure.set(globalObject, null, ids);
} else {
this.cached_structure.set(globalObject, jsc.JSObject.createStructure(
globalObject,
owner,
@truncate(ids.len),
ids.ptr,
), null);
}
return &this.cached_structure;
}
pub const Param = struct {
type: types.FieldType,
flags: ColumnDefinition41.ColumnFlags,
};
const debug = bun.Output.scoped(.MySQLStatement, .hidden);
const CachedStructure = @import("../shared/CachedStructure.zig");
const ColumnDefinition41 = @import("./protocol/ColumnDefinition41.zig");
const ErrorPacket = @import("./protocol/ErrorPacket.zig");
const Signature = @import("./protocol/Signature.zig");
const std = @import("std");
const types = @import("./MySQLTypes.zig");
const SQLDataCell = @import("../shared/SQLDataCell.zig").SQLDataCell;
const bun = @import("bun");
const String = bun.String;
const jsc = bun.jsc;
const JSValue = jsc.JSValue;