From 8d0ebfbc95eb43932edc985a44e0fa3b5d7df0f8 Mon Sep 17 00:00:00 2001 From: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> Date: Wed, 18 Oct 2023 23:50:11 -0700 Subject: [PATCH] doesnt fix it --- src/bun.js/bindings/bindings.cpp | 9 +- src/bun.js/bindings/bindings.zig | 7 ++ src/bun.js/bindings/structure.cpp | 116 +++++++++++++++++++++++ src/sql/postgres.zig | 149 +++++++++++++++++++++++++++--- 4 files changed, 268 insertions(+), 13 deletions(-) diff --git a/src/bun.js/bindings/bindings.cpp b/src/bun.js/bindings/bindings.cpp index f3acb56dc3..33e3883e22 100644 --- a/src/bun.js/bindings/bindings.cpp +++ b/src/bun.js/bindings/bindings.cpp @@ -676,8 +676,8 @@ bool Bun__deepEquals(JSC__JSGlobalObject* globalObject, JSValue v1, JSValue v2, if ((url2 == nullptr) != (url1 == nullptr)) { return false; } - } - + } + if (url2 && url1) { // toEqual or toStrictEqual should return false when the URLs' href is not equal // But you could have added additional properties onto the @@ -4690,6 +4690,11 @@ CPP_DECL double JSC__JSValue__getUnixTimestamp(JSC__JSValue timeValue) return date->internalNumber(); } +extern "C" double WTF__parseDateFromNullTerminatedCharacters(const char* nullTerminatedChars) +{ + return WTF::parseDateFromNullTerminatedCharacters(nullTerminatedChars); +} + extern "C" EncodedJSValue JSC__JSValue__dateInstanceFromNullTerminatedString(JSC::JSGlobalObject* globalObject, const char* nullTerminatedChars) { double dateSeconds = WTF::parseDateFromNullTerminatedCharacters(nullTerminatedChars); diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 58fcb65916..e2202f0324 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -5102,6 +5102,13 @@ pub const VM = extern struct { cppFn("holdAPILock", .{ this, ctx, callback }); } + extern fn JSC__runInDeferralContext(this: *VM, ctx: ?*anyopaque, callback: *const fn (ctx: ?*anyopaque) callconv(.C) void) void; + + pub fn runInDeferralContext(this: *VM, ctx: ?*anyopaque, callback: *const fn (ctx: ?*anyopaque) callconv(.C) void) void { + JSC.markBinding(@src()); + JSC__runInDeferralContext(this, ctx, callback); + } + pub fn deferGC(this: *VM, ctx: ?*anyopaque, callback: *const fn (ctx: ?*anyopaque) callconv(.C) void) void { cppFn("deferGC", .{ this, ctx, callback }); } diff --git a/src/bun.js/bindings/structure.cpp b/src/bun.js/bindings/structure.cpp index 3d09db9378..d4be66f133 100644 --- a/src/bun.js/bindings/structure.cpp +++ b/src/bun.js/bindings/structure.cpp @@ -4,9 +4,124 @@ #include "headers-handwritten.h" #include #include +#include +#include +#include +#include "ZigGlobalObject.h" +#include +#include +#include "GCDefferalContext.h" namespace Bun { using namespace JSC; + +typedef union DataCellValue { + uint8_t null_value; + WTF::StringImpl* string; + double number; + int32_t integer; + int64_t bigint; + bool boolean; + double date; + size_t bytea[2]; + WTF::StringImpl* json; +} DataCellValue; + +enum class DataCellTag : uint8_t { + Null = 0, + String = 1, + Integer = 2, + Bigint = 3, + Boolean = 4, + Date = 5, + Bytea = 6, + Json = 7, +}; + +typedef struct DataCell { + DataCellTag tag; + DataCellValue value; +} DataCell; + +static JSC::JSValue toJS(JSC::Structure* structure, DataCell* cells, unsigned count, JSC::JSGlobalObject* globalObject) +{ + auto& vm = globalObject->vm(); + auto* object = JSC::constructEmptyObject(vm, structure); + + for (unsigned i = 0; i < count; i++) { + auto& cell = cells[i]; + switch (cell.tag) { + case DataCellTag::Null: + object->putDirectOffset(vm, i, jsNull()); + break; + case DataCellTag::String: { + object->putDirectOffset(vm, i, jsString(vm, WTF::String(cell.value.string))); + cell.value.string->deref(); + break; + } + case DataCellTag::Integer: + object->putDirectOffset(vm, i, jsNumber(cell.value.integer)); + break; + case DataCellTag::Bigint: + object->putDirectOffset(vm, i, JSC::JSBigInt::createFrom(globalObject, cell.value.bigint)); + break; + case DataCellTag::Boolean: + object->putDirectOffset(vm, i, jsBoolean(cell.value.boolean)); + break; + case DataCellTag::Date: + object->putDirectOffset(vm, i, JSC::DateInstance::create(vm, globalObject->dateStructure(), cell.value.date)); + break; + case DataCellTag::Bytea: { + Zig::GlobalObject* zigGlobal = jsCast(globalObject); + auto* subclassStructure = zigGlobal->JSBufferSubclassStructure(); + auto* uint8Array = JSC::JSUint8Array::createUninitialized(globalObject, subclassStructure, cell.value.bytea[1]); + memcpy(uint8Array->vector(), reinterpret_cast(cell.value.bytea[0]), cell.value.bytea[1]); + object->putDirectOffset(vm, i, uint8Array); + break; + } + case DataCellTag::Json: { + auto str = WTF::String(cell.value.string); + JSC::JSValue json = JSC::JSONParse(globalObject, str); + cell.value.string->deref(); + + object->putDirectOffset(vm, i, json); + break; + } + default: { + RELEASE_ASSERT_NOT_REACHED(); + } + } + } + + return object; +} + +static JSC::JSValue toJS(JSC::JSArray* array, JSC::Structure* structure, DataCell* cells, unsigned count, JSC::JSGlobalObject* globalObject) +{ + auto& vm = globalObject->vm(); + + if (array) { + array->push(globalObject, toJS(structure, cells, count, globalObject)); + return array; + } + + auto* newArray = JSC::constructEmptyArray(globalObject, nullptr); + + newArray->putDirectIndex(globalObject, 0, toJS(structure, cells, count, globalObject)); + return newArray; +} + +extern "C" EncodedJSValue JSC__constructObjectFromDataCell( + JSC::JSGlobalObject* globalObject, + EncodedJSValue arrayValue, + EncodedJSValue structureValue, DataCell* cells, unsigned count) +{ + auto* array = arrayValue ? jsDynamicCast(JSC::JSValue::decode(arrayValue)) : nullptr; + auto* structure = jsDynamicCast(JSC::JSValue::decode(structureValue)); + + return JSValue::encode(toJS(array, structure, cells, count, globalObject)); +} + extern "C" EncodedJSValue JSC__createStructure(JSC::JSGlobalObject* globalObject, JSC::JSCell* owner, unsigned int inlineCapacity, BunString* names) { auto& vm = globalObject->vm(); @@ -45,6 +160,7 @@ extern "C" EncodedJSValue JSC__createEmptyObjectWithStructure(JSC::JSGlobalObjec extern "C" void JSC__runInDeferralContext(JSC::VM* vm, void* ptr, void (*callback)(void*)) { GCDeferralContext context(*vm); + JSC::DisallowGC disallowGC; callback(ptr); } diff --git a/src/sql/postgres.zig b/src/sql/postgres.zig index 5fa2430f0d..3e5c48fd38 100644 --- a/src/sql/postgres.zig +++ b/src/sql/postgres.zig @@ -1600,9 +1600,12 @@ pub const PostgresSQLQuery = struct { } this.query.deref(); this.cursor_name.deref(); + + bun.default_allocator.destroy(this); } pub fn finalize(this: *@This()) callconv(.C) void { + debug("PostgresSQLQuery finalize", .{}); this.thisValue = .zero; this.target = .zero; this.deref(); @@ -1614,7 +1617,6 @@ pub const PostgresSQLQuery = struct { if (ref_count == 1) { this.deinit(); - bun.default_allocator.destroy(this); } } @@ -2169,6 +2171,7 @@ pub const PostgresSQLConnection = struct { } pub fn finalize(this: *PostgresSQLConnection) callconv(.C) void { + debug("PostgresSQLConnection finalize", .{}); this.js_value = .zero; this.deref(); } @@ -2236,6 +2239,19 @@ pub const PostgresSQLConnection = struct { } pub fn onData(this: *PostgresSQLConnection, data: []const u8) void { + const Deferrer = struct { + data: []const u8, + this: *PostgresSQLConnection, + + pub fn run(d: *@This()) callconv(.C) void { + _onData(d.this, d.data); + } + }; + var deferrer = Deferrer{ .data = data, .this = this }; + this.globalObject.vm().runInDeferralContext(&deferrer, @ptrCast(&Deferrer.run)); + } + + fn _onData(this: *PostgresSQLConnection, data: []const u8) void { var vm = this.globalObject.bunVM(); defer vm.drainMicrotasks(); if (this.read_buffer.remaining().len == 0) { @@ -2633,6 +2649,106 @@ pub const PostgresSQLConnection = struct { } }; + pub const DataCell = extern struct { + tag: Tag, + + value: Value, + + pub const Tag = enum(u8) { + null = 0, + string = 1, + double = 2, + int32 = 3, + int64 = 4, + boolean = 5, + date = 6, + bytea = 7, + json = 8, + }; + + pub const Value = extern union { + null: u8, + string: bun.WTF.StringImpl, + double: f64, + int32: i32, + int64: i64, + boolean: bool, + date: f64, + bytea: [2]usize, + json: bun.WTF.StringImpl, + }; + + pub fn deinit(this: *DataCell) void { + switch (this.tag) { + .string => { + this.value.string.deref(); + }, + .json => { + this.value.json.deref(); + }, + else => {}, + } + } + + extern fn WTF__parseDateFromNullTerminatedCharacters([*:0]const u8) f64; + + pub const Putter = struct { + list: []DataCell, + fields: []const protocol.FieldDescription, + + extern fn JSC__constructObjectFromDataCell(*JSC.JSGlobalObject, JSC.JSValue, JSC.JSValue, [*]DataCell, u32) JSC.JSValue; + pub fn toJS(this: *Putter, globalObject: *JSC.JSGlobalObject, array: JSC.JSValue, structure: JSC.JSValue) JSC.JSValue { + return JSC__constructObjectFromDataCell(globalObject, array, structure, this.list.ptr, @truncate(this.fields.len)); + } + + pub fn put(this: *Putter, index: u32, optional_bytes: ?*Data) anyerror!bool { + var bytes_ = optional_bytes orelse { + this.list[index] = DataCell{ .tag = .null, .value = .{ .null = 0 } }; + return true; + }; + const bytes = bytes_.slice(); + + switch (@as(types.Tag, @enumFromInt(this.fields[index].type_oid))) { + .number => { + switch (bytes.len) { + 0 => { + this.list[index] = DataCell{ .tag = .null, .value = .{ .null = 0 } }; + }, + 2 => { + this.list[index] = DataCell{ .tag = .int32, .value = .{ .int32 = @as(i32, @as(short, @bitCast(bytes[0..2].*))) } }; + }, + 4 => { + this.list[index] = DataCell{ .tag = .int32, .value = .{ .int32 = @as(i32, @bitCast(bytes[0..4].*)) } }; + }, + else => { + var eight: i64 = 0; + @memcpy(@as(*[8]u8, @ptrCast(&eight))[0..bytes.len], bytes[0..@min(8, bytes.len)]); + eight = @byteSwap(eight); + this.list[index] = DataCell{ .tag = .int64, .value = .{ .int64 = eight } }; + }, + } + }, + .json => { + this.list[index] = DataCell{ .tag = .json, .value = .{ .json = bun.String.create(bytes).value.WTFStringImpl } }; + }, + .boolean => { + this.list[index] = DataCell{ .tag = .boolean, .value = .{ .boolean = bytes.len > 0 and bytes[0] == 't' } }; + }, + .time, .datetime, .date => { + this.list[index] = DataCell{ .tag = .date, .value = .{ .date = WTF__parseDateFromNullTerminatedCharacters(bytes_.sliceZ().ptr) } }; + }, + .bytea => { + this.list[index] = DataCell{ .tag = .bytea, .value = .{ .bytea = .{ @intFromPtr(bytes_.slice().ptr), bytes.len } } }; + }, + else => { + this.list[index] = DataCell{ .tag = .string, .value = .{ .string = bun.String.create(bytes).value.WTFStringImpl } }; + }, + } + return true; + } + }; + }; + pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.EnumLiteral), comptime Context: type, reader: protocol.NewReader(Context)) !void { debug("on({s})", .{@tagName(MessageType)}); if (comptime MessageType != .ReadyForQuery) { @@ -2644,18 +2760,21 @@ pub const PostgresSQLConnection = struct { var request = this.current() orelse return error.ExpectedRequest; var statement = request.statement orelse return error.ExpectedStatement; - statement.cached_structure.clear(); - var structure = statement.structure(this.js_value, this.globalObject); std.debug.assert(!structure.isEmptyOrUndefinedOrNull()); - var row = JSC.JSObject.uninitialized(this.globalObject, structure); - row.ensureStillAlive(); + var stack_buf: [64]DataCell = undefined; + var cells: []DataCell = stack_buf[0..@min(statement.fields.len, stack_buf.len)]; - var putter = CellPutter{ - .object = row, - .vm = this.globalObject.vm(), - .globalObject = this.globalObject, + var free_cells = false; + defer if (free_cells) bun.default_allocator.free(cells); + if (statement.fields.len >= 64) { + cells = try bun.default_allocator.alloc(DataCell, statement.fields.len); + free_cells = true; + } + + var putter = DataCell.Putter{ + .list = cells, .fields = statement.fields, }; @@ -2663,9 +2782,15 @@ pub const PostgresSQLConnection = struct { &putter, Context, reader, - CellPutter.put, + DataCell.Putter.put, ); - request.push(this.globalObject, row); + + const pending_value = PostgresSQLQuery.pendingValueGetCached(request.thisValue) orelse .zero; + const result = putter.toJS(this.globalObject, pending_value, structure); + + if (pending_value == .zero) { + PostgresSQLQuery.pendingValueSetCached(request.thisValue, this.globalObject, result); + } }, .CopyData => { var copy_data: protocol.CopyData = undefined; @@ -2855,6 +2980,8 @@ pub const PostgresSQLStatement = struct { } pub fn deinit(this: *PostgresSQLStatement) void { + debug("PostgresSQLStatement deinit", .{}); + std.debug.assert(this.ref_count == 0); for (this.fields) |*field| {