diff --git a/src/ast/E.zig b/src/ast/E.zig index 0f7c94089a..2e134aea62 100644 --- a/src/ast/E.zig +++ b/src/ast/E.zig @@ -925,7 +925,6 @@ pub const String = struct { pub fn resolveRopeIfNeeded(this: *String, allocator: std.mem.Allocator) void { if (this.next == null or !this.isUTF8()) return; var bytes = std.ArrayList(u8).initCapacity(allocator, this.rope_len) catch bun.outOfMemory(); - bytes.appendSliceAssumeCapacity(this.data); var str = this.next; while (str) |part| { diff --git a/src/ptr/ref_count.zig b/src/ptr/ref_count.zig index a0a5acbf4c..0a2c0213f0 100644 --- a/src/ptr/ref_count.zig +++ b/src/ptr/ref_count.zig @@ -275,6 +275,10 @@ pub fn ThreadSafeRefCount(T: type, field_name: []const u8, destructor: fn (*T) v return counter.active_counts.load(.seq_cst) == 1; } + pub fn getCount(counter: *const @This()) u32 { + return counter.active_counts.load(.seq_cst); + } + pub fn dumpActiveRefs(count: *@This()) void { if (enable_debug) { const ptr: *T = @alignCast(@fieldParentPtr(field_name, count)); diff --git a/src/sql/postgres/PostgresSQLConnection.zig b/src/sql/postgres/PostgresSQLConnection.zig index 351fbebb51..8af36bbf2d 100644 --- a/src/sql/postgres/PostgresSQLConnection.zig +++ b/src/sql/postgres/PostgresSQLConnection.zig @@ -1,8 +1,8 @@ const PostgresSQLConnection = @This(); - +const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); socket: Socket, status: Status = Status.connecting, -ref_count: u32 = 1, +ref_count: RefCount = RefCount.init(), write_buffer: bun.OffsetByteList = .{}, read_buffer: bun.OffsetByteList = .{}, @@ -15,7 +15,7 @@ nonpipelinable_requests: u32 = 0, poll_ref: bun.Async.KeepAlive = .{}, globalObject: *jsc.JSGlobalObject, - +vm: *jsc.VirtualMachine, statements: PreparedStatementsMap, prepared_statement_id: u64 = 0, pending_activity_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), @@ -66,6 +66,9 @@ max_lifetime_timer: bun.api.Timer.EventLoopTimer = .{ }, auto_flusher: AutoFlusher = .{}, +pub const ref = RefCount.ref; +pub const deref = RefCount.deref; + pub fn onAutoFlush(this: *@This()) bool { if (this.flags.has_backpressure) { debug("onAutoFlush: has backpressure", .{}); @@ -95,7 +98,7 @@ fn registerAutoFlusher(this: *PostgresSQLConnection) void { data_to_send > 0 and // we need data to send this.status == .connected //and we need to be connected ) { - AutoFlusher.registerDeferredMicrotaskWithTypeUnchecked(@This(), this, this.globalObject.bunVM()); + AutoFlusher.registerDeferredMicrotaskWithTypeUnchecked(@This(), this, this.vm); this.auto_flusher.registered = true; } } @@ -103,7 +106,7 @@ fn registerAutoFlusher(this: *PostgresSQLConnection) void { fn unregisterAutoFlusher(this: *PostgresSQLConnection) void { debug("unregisterAutoFlusher registered: {}", .{this.auto_flusher.registered}); if (this.auto_flusher.registered) { - AutoFlusher.unregisterDeferredMicrotaskWithType(@This(), this, this.globalObject.bunVM()); + AutoFlusher.unregisterDeferredMicrotaskWithType(@This(), this, this.vm); this.auto_flusher.registered = false; } } @@ -117,7 +120,7 @@ fn getTimeoutInterval(this: *const PostgresSQLConnection) u32 { } pub fn disableConnectionTimeout(this: *PostgresSQLConnection) void { if (this.timer.state == .ACTIVE) { - this.globalObject.bunVM().timer.remove(&this.timer); + this.vm.timer.remove(&this.timer); } this.timer.state = .CANCELLED; } @@ -126,14 +129,14 @@ pub fn resetConnectionTimeout(this: *PostgresSQLConnection) void { if (this.flags.is_processing_data) return; const interval = this.getTimeoutInterval(); if (this.timer.state == .ACTIVE) { - this.globalObject.bunVM().timer.remove(&this.timer); + this.vm.timer.remove(&this.timer); } if (interval == 0) { return; } this.timer.next = bun.timespec.msFromNow(@intCast(interval)); - this.globalObject.bunVM().timer.insert(&this.timer); + this.vm.timer.insert(&this.timer); } pub fn getQueries(_: *PostgresSQLConnection, thisValue: jsc.JSValue, globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue { @@ -192,7 +195,7 @@ fn setupMaxLifetimeTimerIfNecessary(this: *PostgresSQLConnection) void { if (this.max_lifetime_timer.state == .ACTIVE) return; this.max_lifetime_timer.next = bun.timespec.msFromNow(@intCast(this.max_lifetime_interval_ms)); - this.globalObject.bunVM().timer.insert(&this.max_lifetime_timer); + this.vm.timer.insert(&this.max_lifetime_timer); } pub fn onConnectionTimeout(this: *PostgresSQLConnection) bun.api.Timer.EventLoopTimer.Arm { @@ -254,6 +257,7 @@ pub fn setStatus(this: *PostgresSQLConnection, status: Status) void { this.status = status; this.resetConnectionTimeout(); + if (this.vm.isShuttingDown()) return; switch (status) { .connected => { @@ -261,7 +265,7 @@ pub fn setStatus(this: *PostgresSQLConnection, status: Status) void { const js_value = this.js_value; js_value.ensureStillAlive(); this.globalObject.queueMicrotask(on_connect, &[_]JSValue{ JSValue.jsNull(), js_value }); - this.poll_ref.unref(this.globalObject.bunVM()); + this.poll_ref.unref(this.vm); }, else => {}, } @@ -315,7 +319,7 @@ pub fn failWithJSValue(this: *PostgresSQLConnection, value: JSValue) void { defer this.refAndClose(value); const on_close = this.consumeOnCloseCallback(this.globalObject) orelse return; - const loop = this.globalObject.bunVM().eventLoop(); + const loop = this.vm.eventLoop(); loop.enter(); defer loop.exit(); _ = on_close.call( @@ -343,13 +347,21 @@ pub fn fail(this: *PostgresSQLConnection, message: []const u8, err: AnyPostgresE pub fn onClose(this: *PostgresSQLConnection) void { this.unregisterAutoFlusher(); - var vm = this.globalObject.bunVM(); - const loop = vm.eventLoop(); - loop.enter(); - defer loop.exit(); - this.poll_ref.unref(this.globalObject.bunVM()); + if (this.vm.isShuttingDown()) { + defer this.updateHasPendingActivity(); + this.stopTimers(); + if (this.status == .failed) return; - this.fail("Connection closed", error.ConnectionClosed); + this.status = .failed; + this.cleanUpRequests(null); + } else { + const loop = this.vm.eventLoop(); + loop.enter(); + defer loop.exit(); + this.poll_ref.unref(this.vm); + + this.fail("Connection closed", error.ConnectionClosed); + } } fn sendStartupMessage(this: *PostgresSQLConnection) void { @@ -392,7 +404,7 @@ fn startTLS(this: *PostgresSQLConnection, socket: uws.AnySocket) void { pub fn onOpen(this: *PostgresSQLConnection, socket: uws.AnySocket) void { this.socket = socket; - this.poll_ref.ref(this.globalObject.bunVM()); + this.poll_ref.ref(this.vm); this.updateHasPendingActivity(); if (this.tls_status == .message_sent or this.tls_status == .pending) { @@ -460,7 +472,9 @@ pub fn onDrain(this: *PostgresSQLConnection) void { fn drainInternal(this: *PostgresSQLConnection) void { debug("drainInternal", .{}); - const event_loop = this.globalObject.bunVM().eventLoop(); + if (this.vm.isShuttingDown()) return this.close(); + + const event_loop = this.vm.eventLoop(); event_loop.enter(); defer event_loop.exit(); @@ -476,7 +490,7 @@ fn drainInternal(this: *PostgresSQLConnection) void { pub fn onData(this: *PostgresSQLConnection, data: []const u8) void { this.ref(); this.flags.is_processing_data = true; - const vm = this.globalObject.bunVM(); + const vm = this.vm; this.disableConnectionTimeout(); defer { @@ -681,7 +695,7 @@ pub fn call(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JS ptr.* = PostgresSQLConnection{ .globalObject = globalObject, - + .vm = globalObject.bunVM(), .database = database, .user = username, .password = password, @@ -764,10 +778,20 @@ fn SocketHandler(comptime ssl: bool) type { return Socket{ .SocketTCP = s }; } pub fn onOpen(this: *PostgresSQLConnection, socket: SocketType) void { + if (this.vm.isShuttingDown()) { + @branchHint(.unlikely); + this.close(); + return; + } this.onOpen(_socket(socket)); } fn onHandshake_(this: *PostgresSQLConnection, _: anytype, success: i32, ssl_error: uws.us_bun_verify_error_t) void { + if (this.vm.isShuttingDown()) { + @branchHint(.unlikely); + this.close(); + return; + } this.onHandshake(success, ssl_error); } @@ -785,39 +809,54 @@ fn SocketHandler(comptime ssl: bool) type { pub fn onConnectError(this: *PostgresSQLConnection, socket: SocketType, _: i32) void { _ = socket; + if (this.vm.isShuttingDown()) { + @branchHint(.unlikely); + this.close(); + return; + } this.onClose(); } pub fn onTimeout(this: *PostgresSQLConnection, socket: SocketType) void { _ = socket; + if (this.vm.isShuttingDown()) { + @branchHint(.unlikely); + this.close(); + return; + } this.onTimeout(); } pub fn onData(this: *PostgresSQLConnection, socket: SocketType, data: []const u8) void { _ = socket; + if (this.vm.isShuttingDown()) { + @branchHint(.unlikely); + this.close(); + return; + } this.onData(data); } pub fn onWritable(this: *PostgresSQLConnection, socket: SocketType) void { _ = socket; + if (this.vm.isShuttingDown()) { + @branchHint(.unlikely); + this.close(); + return; + } this.onDrain(); } }; } -pub fn ref(this: *@This()) void { - bun.assert(this.ref_count > 0); - this.ref_count += 1; -} - pub fn doRef(this: *@This(), _: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue { - this.poll_ref.ref(this.globalObject.bunVM()); + this.poll_ref.ref(this.vm); this.updateHasPendingActivity(); return .js_undefined; } pub fn doUnref(this: *@This(), _: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue { - this.poll_ref.unref(this.globalObject.bunVM()); + this.poll_ref.unref(this.vm); this.updateHasPendingActivity(); return .js_undefined; } @@ -826,35 +865,29 @@ pub fn doFlush(this: *PostgresSQLConnection, _: *jsc.JSGlobalObject, _: *jsc.Cal return .js_undefined; } -pub fn deref(this: *@This()) void { - const ref_count = this.ref_count; - this.ref_count -= 1; - - if (ref_count == 1) { - this.disconnect(); - this.deinit(); - } +fn close(this: *@This()) void { + this.disconnect(); + this.unregisterAutoFlusher(); + this.write_buffer.deinit(bun.default_allocator); } pub fn doClose(this: *@This(), globalObject: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue { _ = globalObject; - this.disconnect(); - this.unregisterAutoFlusher(); - this.write_buffer.deinit(bun.default_allocator); - + this.close(); return .js_undefined; } pub fn stopTimers(this: *PostgresSQLConnection) void { if (this.timer.state == .ACTIVE) { - this.globalObject.bunVM().timer.remove(&this.timer); + this.vm.timer.remove(&this.timer); } if (this.max_lifetime_timer.state == .ACTIVE) { - this.globalObject.bunVM().timer.remove(&this.max_lifetime_timer); + this.vm.timer.remove(&this.max_lifetime_timer); } } pub fn deinit(this: *@This()) void { + this.disconnect(); this.stopTimers(); var iter = this.statements.valueIterator(); while (iter.next()) |stmt_ptr| { @@ -872,17 +905,7 @@ pub fn deinit(this: *@This()) void { bun.default_allocator.destroy(this); } -fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void { - // refAndClose is always called when we wanna to disconnect or when we are closed - - if (!this.socket.isClosed()) { - // event loop need to be alive to close the socket - this.poll_ref.ref(this.globalObject.bunVM()); - // will unref on socket close - this.socket.close(); - } - - // cleanup requests +fn cleanUpRequests(this: *@This(), js_reason: ?jsc.JSValue) void { while (this.current()) |request| { switch (request.status) { // pending we will fail the request and the stmt will be marked as error ConnectionClosed too @@ -890,10 +913,12 @@ fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void { const stmt = request.statement orelse continue; stmt.error_response = .{ .postgres_error = AnyPostgresError.ConnectionClosed }; stmt.status = .failed; - if (js_reason) |reason| { - request.onJSError(reason, this.globalObject); - } else { - request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject); + if (!this.vm.isShuttingDown()) { + if (js_reason) |reason| { + request.onJSError(reason, this.globalObject); + } else { + request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject); + } } }, // in the middle of running @@ -901,10 +926,12 @@ fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void { .running, .partial_response, => { - if (js_reason) |reason| { - request.onJSError(reason, this.globalObject); - } else { - request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject); + if (!this.vm.isShuttingDown()) { + if (js_reason) |reason| { + request.onJSError(reason, this.globalObject); + } else { + request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject); + } } }, // just ignore success and fail cases @@ -914,6 +941,19 @@ fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void { this.requests.discard(1); } } +fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void { + // refAndClose is always called when we wanna to disconnect or when we are closed + + if (!this.socket.isClosed()) { + // event loop need to be alive to close the socket + this.poll_ref.ref(this.vm); + // will unref on socket close + this.socket.close(); + } + + // cleanup requests + this.cleanUpRequests(js_reason); +} pub fn disconnect(this: *@This()) void { this.stopTimers(); @@ -928,6 +968,7 @@ fn current(this: *PostgresSQLConnection) ?*PostgresSQLQuery { if (this.requests.readableLength() == 0) { return null; } + return this.requests.peekItem(0); } @@ -1022,10 +1063,42 @@ pub fn bufferedReader(this: *PostgresSQLConnection) protocol.NewReader(Reader) { }; } +fn cleanupSuccessQuery(this: *PostgresSQLConnection, item: *PostgresSQLQuery) void { + if (item.flags.simple) { + this.nonpipelinable_requests -= 1; + } else if (item.flags.pipelined) { + this.pipelined_requests -= 1; + } else if (this.flags.waiting_to_prepare) { + this.flags.waiting_to_prepare = false; + } +} fn advance(this: *PostgresSQLConnection) void { var offset: usize = 0; debug("advance", .{}); + defer { + while (this.requests.readableLength() > 0) { + const result = this.requests.peekItem(0); + // An item may be in the success or failed state and still be inside the queue (see deinit later comments) + // so we do the cleanup her + switch (result.status) { + .success => { + this.cleanupSuccessQuery(result); + result.deref(); + this.requests.discard(1); + continue; + }, + .fail => { + result.deref(); + this.requests.discard(1); + continue; + }, + else => break, // trully current item + } + } + } while (this.requests.readableLength() > offset and !this.flags.has_backpressure) { + if (this.vm.isShuttingDown()) return this.close(); + var req: *PostgresSQLQuery = this.requests.peekItem(offset); switch (req.status) { .pending => { @@ -1084,8 +1157,18 @@ fn advance(this: *PostgresSQLConnection) void { continue; }, .prepared => { - const thisValue = req.thisValue.get(); - bun.assert(thisValue != .zero); + const thisValue = req.thisValue.tryGet() orelse { + bun.assertf(false, "query value was freed earlier than expected", .{}); + if (offset == 0) { + req.deref(); + this.requests.discard(1); + } else { + // deinit later + req.status = .fail; + offset += 1; + } + continue; + }; const binding_value = PostgresSQLQuery.js.bindingGetCached(thisValue) orelse .zero; const columns_value = PostgresSQLQuery.js.columnsGetCached(thisValue) orelse .zero; req.flags.binary = stmt.fields.len > 0; @@ -1129,8 +1212,18 @@ fn advance(this: *PostgresSQLConnection) void { const has_params = stmt.signature.fields.len > 0; // If it does not have params, we can write and execute immediately in one go if (!has_params) { - const thisValue = req.thisValue.get(); - bun.assert(thisValue != .zero); + const thisValue = req.thisValue.tryGet() orelse { + bun.assertf(false, "query value was freed earlier than expected", .{}); + if (offset == 0) { + req.deref(); + this.requests.discard(1); + } else { + // deinit later + req.status = .fail; + offset += 1; + } + continue; + }; // prepareAndQueryWithSignature will write + bind + execute, it will change to running after binding is complete const binding_value = PostgresSQLQuery.js.bindingGetCached(thisValue) orelse .zero; debug("prepareAndQueryWithSignature", .{}); @@ -1201,13 +1294,7 @@ fn advance(this: *PostgresSQLConnection) void { return; }, .success => { - if (req.flags.simple) { - this.nonpipelinable_requests -= 1; - } else if (req.flags.pipelined) { - this.pipelined_requests -= 1; - } else if (this.flags.waiting_to_prepare) { - this.flags.waiting_to_prepare = false; - } + this.cleanupSuccessQuery(req); if (offset > 0) { // deinit later req.status = .fail; @@ -1242,6 +1329,7 @@ pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.enum_litera switch (comptime MessageType) { .DataRow => { const request = this.current() orelse return error.ExpectedRequest; + var statement = request.statement orelse return error.ExpectedStatement; var structure: JSValue = .js_undefined; var cached_structure: ?PostgresCachedStructure = null; @@ -1297,8 +1385,10 @@ pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.enum_litera DataCell.Putter.put, ); } - const thisValue = request.thisValue.get(); - bun.assert(thisValue != .zero); + const thisValue = request.thisValue.tryGet() orelse return { + bun.assertf(false, "query value was freed earlier than expected", .{}); + return error.ExpectedRequest; + }; const pending_value = PostgresSQLQuery.js.pendingValueGetCached(thisValue) orelse .zero; pending_value.ensureStillAlive(); const result = putter.toJS(this.globalObject, pending_value, structure, statement.fields_flags, request.flags.result_mode, cached_structure); @@ -1682,9 +1772,9 @@ pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.enum_litera pub fn updateRef(this: *PostgresSQLConnection) void { this.updateHasPendingActivity(); if (this.pending_activity_count.raw > 0) { - this.poll_ref.ref(this.globalObject.bunVM()); + this.poll_ref.ref(this.vm); } else { - this.poll_ref.unref(this.globalObject.bunVM()); + this.poll_ref.unref(this.vm); } } diff --git a/src/sql/postgres/PostgresSQLQuery.zig b/src/sql/postgres/PostgresSQLQuery.zig index 79b3f67311..7d51065e41 100644 --- a/src/sql/postgres/PostgresSQLQuery.zig +++ b/src/sql/postgres/PostgresSQLQuery.zig @@ -1,5 +1,5 @@ const PostgresSQLQuery = @This(); - +const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{}); statement: ?*PostgresSQLStatement = null, query: bun.String = bun.String.empty, cursor_name: bun.String = bun.String.empty, @@ -8,7 +8,7 @@ thisValue: JSRef = JSRef.empty(), status: Status = Status.pending, -ref_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(1), +ref_count: RefCount = RefCount.init(), flags: packed struct(u8) { is_done: bool = false, @@ -20,11 +20,11 @@ flags: packed struct(u8) { _padding: u1 = 0, } = .{}, +pub const ref = RefCount.ref; +pub const deref = RefCount.deref; + pub fn getTarget(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, clean_target: bool) jsc.JSValue { - const thisValue = this.thisValue.get(); - if (thisValue == .zero) { - return .zero; - } + const thisValue = this.thisValue.tryGet() orelse return .zero; const target = js.targetGetCached(thisValue) orelse return .zero; if (clean_target) { js.targetSetCached(thisValue, globalObject, .zero); @@ -52,7 +52,7 @@ pub const Status = enum(u8) { }; pub fn hasPendingActivity(this: *@This()) bool { - return this.ref_count.load(.monotonic) > 1; + return this.ref_count.getCount() > 1; } pub fn deinit(this: *@This()) void { @@ -75,24 +75,14 @@ pub fn finalize(this: *@This()) void { this.deref(); } -pub fn deref(this: *@This()) void { - const ref_count = this.ref_count.fetchSub(1, .monotonic); - - if (ref_count == 1) { - this.deinit(); - } -} - -pub fn ref(this: *@This()) void { - bun.assert(this.ref_count.fetchAdd(1, .monotonic) > 0); -} - pub fn onWriteFail( this: *@This(), err: AnyPostgresError, globalObject: *jsc.JSGlobalObject, queries_array: JSValue, ) void { + this.ref(); + defer this.deref(); this.status = .fail; const thisValue = this.thisValue.get(); defer this.thisValue.deinit(); @@ -111,10 +101,9 @@ pub fn onWriteFail( }); } pub fn onJSError(this: *@This(), err: jsc.JSValue, globalObject: *jsc.JSGlobalObject) void { - this.status = .fail; this.ref(); defer this.deref(); - + this.status = .fail; const thisValue = this.thisValue.get(); defer this.thisValue.deinit(); const targetValue = this.getTarget(globalObject, true); @@ -268,7 +257,8 @@ pub fn doDone(this: *@This(), globalObject: *jsc.JSGlobalObject, _: *jsc.CallFra } pub fn setPendingValue(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue { const result = callframe.argument(0); - js.pendingValueSetCached(this.thisValue.get(), globalObject, result); + const thisValue = this.thisValue.tryGet() orelse return .js_undefined; + js.pendingValueSetCached(thisValue, globalObject, result); return .js_undefined; } pub fn setMode(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue { @@ -303,13 +293,30 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra var query_str = this.query.toUTF8(bun.default_allocator); defer query_str.deinit(); var writer = connection.writer(); - + // We need a strong reference to the query so that it doesn't get GC'd + this.ref(); if (this.flags.simple) { debug("executeQuery", .{}); + const stmt = bun.default_allocator.create(PostgresSQLStatement) catch { + this.deref(); + return globalObject.throwOutOfMemory(); + }; + // Query is simple and it's the only owner of the statement + stmt.* = .{ + .signature = Signature.empty(), + .status = .parsing, + }; + this.statement = stmt; + const can_execute = !connection.hasQueryRunning(); if (can_execute) { PostgresRequest.executeQuery(query_str.slice(), PostgresSQLConnection.Writer, writer) catch |err| { + // fail to run do cleanup + this.statement = null; + bun.default_allocator.destroy(stmt); + this.deref(); + if (!globalObject.hasException()) return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to execute query", err)); return error.JSError; @@ -320,21 +327,16 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra } else { this.status = .pending; } - const stmt = bun.default_allocator.create(PostgresSQLStatement) catch { + connection.requests.writeItem(this) catch { + // fail to run do cleanup + this.statement = null; + bun.default_allocator.destroy(stmt); + this.deref(); + return globalObject.throwOutOfMemory(); }; - // Query is simple and it's the only owner of the statement - stmt.* = .{ - .signature = Signature.empty(), - .ref_count = 1, - .status = .parsing, - }; - this.statement = stmt; - // We need a strong reference to the query so that it doesn't get GC'd - connection.requests.writeItem(this) catch return globalObject.throwOutOfMemory(); - this.ref(); - this.thisValue.upgrade(globalObject); + this.thisValue.upgrade(globalObject); js.targetSetCached(this_value, globalObject, query); if (this.status == .running) { connection.flushDataAndResetTimeout(); @@ -347,6 +349,7 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra const columns_value: JSValue = js.columnsGetCached(this_value) orelse .js_undefined; var signature = Signature.generate(globalObject, query_str.slice(), binding_value, columns_value, connection.prepared_statement_id, connection.flags.use_unnamed_prepared_statements) catch |err| { + this.deref(); if (!globalObject.hasException()) return globalObject.throwError(err, "failed to generate signature"); return error.JSError; @@ -363,12 +366,16 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra }; connection_entry_value = entry.value_ptr; if (entry.found_existing) { - this.statement = connection_entry_value.?.*; - this.statement.?.ref(); + const stmt = connection_entry_value.?.*; + this.statement = stmt; + stmt.ref(); signature.deinit(); - switch (this.statement.?.status) { + switch (stmt.status) { .failed => { + this.statement = null; + stmt.deref(); + this.deref(); // If the statement failed, we need to throw the error return globalObject.throwValue(this.statement.?.error_response.?.toJS(globalObject)); }, @@ -379,6 +386,11 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra // bindAndExecute will bind + execute, it will change to running after binding is complete PostgresRequest.bindAndExecute(globalObject, this.statement.?, binding_value, columns_value, PostgresSQLConnection.Writer, writer) catch |err| { + // fail to run do cleanup + this.statement = null; + stmt.deref(); + this.deref(); + if (!globalObject.hasException()) return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to bind and execute query", err)); return error.JSError; @@ -406,6 +418,11 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra // prepareAndQueryWithSignature will write + bind + execute, it will change to running after binding is complete PostgresRequest.prepareAndQueryWithSignature(globalObject, query_str.slice(), binding_value, PostgresSQLConnection.Writer, writer, &signature) catch |err| { signature.deinit(); + if (this.statement) |stmt| { + this.statement = null; + stmt.deref(); + } + this.deref(); if (!globalObject.hasException()) return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to prepare and query", err)); return error.JSError; @@ -419,6 +436,11 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra PostgresRequest.writeQuery(query_str.slice(), signature.prepared_statement_name, signature.fields, PostgresSQLConnection.Writer, writer) catch |err| { signature.deinit(); + if (this.statement) |stmt| { + this.statement = null; + stmt.deref(); + } + this.deref(); if (!globalObject.hasException()) return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to write query", err)); return error.JSError; @@ -436,24 +458,31 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra } { const stmt = bun.default_allocator.create(PostgresSQLStatement) catch { + this.deref(); return globalObject.throwOutOfMemory(); }; // we only have connection_entry_value if we are using named prepared statements if (connection_entry_value) |entry_value| { connection.prepared_statement_id += 1; - stmt.* = .{ .signature = signature, .ref_count = 2, .status = if (can_execute) .parsing else .pending }; + stmt.* = .{ + .signature = signature, + .ref_count = .initExactRefs(2), + .status = if (can_execute) .parsing else .pending, + }; this.statement = stmt; entry_value.* = stmt; } else { - stmt.* = .{ .signature = signature, .ref_count = 1, .status = if (can_execute) .parsing else .pending }; + stmt.* = .{ + .signature = signature, + .status = if (can_execute) .parsing else .pending, + }; this.statement = stmt; } } } - // We need a strong reference to the query so that it doesn't get GC'd + connection.requests.writeItem(this) catch return globalObject.throwOutOfMemory(); - this.ref(); this.thisValue.upgrade(globalObject); js.targetSetCached(this_value, globalObject, query); diff --git a/src/sql/postgres/PostgresSQLStatement.zig b/src/sql/postgres/PostgresSQLStatement.zig index c238a19676..644ea5800b 100644 --- a/src/sql/postgres/PostgresSQLStatement.zig +++ b/src/sql/postgres/PostgresSQLStatement.zig @@ -1,7 +1,7 @@ const PostgresSQLStatement = @This(); - +const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{}); cached_structure: PostgresCachedStructure = .{}, -ref_count: u32 = 1, +ref_count: RefCount = RefCount.init(), fields: []protocol.FieldDescription = &[_]protocol.FieldDescription{}, parameters: []const int4 = &[_]int4{}, signature: Signature, @@ -9,6 +9,8 @@ status: Status = Status.pending, error_response: ?Error = null, needs_duplicate_check: bool = true, fields_flags: DataCell.Flags = .{}, +pub const ref = RefCount.ref; +pub const deref = RefCount.deref; pub const Error = union(enum) { protocol: protocol.ErrorResponse, @@ -38,19 +40,6 @@ pub const Status = enum { return this == .parsing; } }; -pub fn ref(this: *@This()) void { - bun.assert(this.ref_count > 0); - this.ref_count += 1; -} - -pub fn deref(this: *@This()) void { - const ref_count = this.ref_count; - this.ref_count -= 1; - - if (ref_count == 1) { - this.deinit(); - } -} pub fn checkForDuplicateFields(this: *PostgresSQLStatement) void { if (!this.needs_duplicate_check) return; @@ -100,7 +89,7 @@ pub fn checkForDuplicateFields(this: *PostgresSQLStatement) void { pub fn deinit(this: *PostgresSQLStatement) void { debug("PostgresSQLStatement deinit", .{}); - bun.assert(this.ref_count == 0); + this.ref_count.assertNoRefs(); for (this.fields) |*field| { field.deinit(); diff --git a/src/sql/postgres/protocol/NewReader.zig b/src/sql/postgres/protocol/NewReader.zig index 87a1e1f1cb..5832f65953 100644 --- a/src/sql/postgres/protocol/NewReader.zig +++ b/src/sql/postgres/protocol/NewReader.zig @@ -57,10 +57,14 @@ pub fn NewReaderWrap( pub fn int(this: @This(), comptime Int: type) !Int { var data = try this.read(@sizeOf((Int))); defer data.deinit(); - if (comptime Int == u8) { - return @as(Int, data.slice()[0]); + const slice = data.slice(); + if (slice.len < @sizeOf(Int)) { + return error.ShortRead; } - return @byteSwap(@as(Int, @bitCast(data.slice()[0..@sizeOf(Int)].*))); + if (comptime Int == u8) { + return @as(Int, slice[0]); + } + return @byteSwap(@as(Int, @bitCast(slice[0..@sizeOf(Int)].*))); } pub fn peekInt(this: @This(), comptime Int: type) ?Int { diff --git a/test/js/sql/issue-21351.fixture.sql b/test/js/sql/issue-21351.fixture.sql new file mode 100644 index 0000000000..bd0cf171c4 --- /dev/null +++ b/test/js/sql/issue-21351.fixture.sql @@ -0,0 +1,155 @@ +-- Insert 50 Users +INSERT INTO users (id, name, email, identifier, role, phone, bio, skills, privacy, linkedin_url, github_url, facebook_url, twitter_url, picture) VALUES +('alice01', 'Alice Anderson', 'alice01@example.com', 'alice01', 'CUSTOMER', '+1-555-111-0001', '{"about": "Data Scientist", "location": "Los Angeles"}', '["Figma", "Sketch", "UI/UX"]', 'PRIVATE', NULL, 'https://github.com/alice01', NULL, NULL, '{"url": "https://pics.example.com/alice01.jpg"}'), +('bob02', 'Bob Moore', 'bob02@example.com', 'bob02', 'CUSTOMER', '+1-555-111-0002', '{"about": "Cloud Engineer", "location": "Seattle"}', '["Figma", "Sketch", "UI/UX"]', 'PRIVATE', 'https://linkedin.com/in/bob02', 'https://github.com/bob02', NULL, 'https://twitter.com/bob02', '{"url": "https://pics.example.com/bob02.jpg"}'), +('charlie03', 'Charlie Adams', 'charlie03@example.com', 'charlie03', 'CUSTOMER', '+1-555-111-0003', '{"about": "Digital Marketer", "location": "Boston"}', '["Java", "Spring Boot", "Microservices"]', 'PUBLIC', NULL, 'https://github.com/charlie03', NULL, 'https://twitter.com/charlie03', '{"url": "https://pics.example.com/charlie03.jpg"}'), +('diana04', 'Diana Johnson', 'diana04@example.com', 'diana04', 'CUSTOMER', '+1-555-111-0004', '{"about": "Digital Marketer", "location": "New York"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, 'https://github.com/diana04', NULL, 'https://twitter.com/diana04', '{"url": "https://pics.example.com/diana04.jpg"}'), +('ethan05', 'Ethan Brown', 'ethan05@example.com', 'ethan05', 'CUSTOMER', '+1-555-111-0005', '{"about": "Digital Marketer", "location": "Seattle"}', '["Flutter", "Dart", "Firebase"]', 'PRIVATE', 'https://linkedin.com/in/ethan05', 'https://github.com/ethan05', NULL, 'https://twitter.com/ethan05', '{"url": "https://pics.example.com/ethan05.jpg"}'), +('fiona06', 'Fiona Adams', 'fiona06@example.com', 'fiona06', 'CUSTOMER', '+1-555-111-0006', '{"about": "Digital Marketer", "location": "Los Angeles"}', '["Kubernetes", "Docker", "CI/CD"]', 'PUBLIC', 'https://linkedin.com/in/fiona06', 'https://github.com/fiona06', NULL, 'https://twitter.com/fiona06', '{"url": "https://pics.example.com/fiona06.jpg"}'), +('george07', 'George Wilson', 'george07@example.com', 'george07', 'CUSTOMER', '+1-555-111-0007', '{"about": "Data Scientist", "location": "Los Angeles"}', '["Python", "Pandas", "Machine Learning"]', 'PRIVATE', 'https://linkedin.com/in/george07', NULL, NULL, 'https://twitter.com/george07', '{"url": "https://pics.example.com/george07.jpg"}'), +('hannah08', 'Hannah Moore', 'hannah08@example.com', 'hannah08', 'CUSTOMER', '+1-555-111-0008', '{"about": "UX Designer", "location": "Chicago"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/hannah08', 'https://github.com/hannah08', NULL, NULL, '{"url": "https://pics.example.com/hannah08.jpg"}'), +('ian09', 'Ian Adams', 'ian09@example.com', 'ian09', 'CUSTOMER', '+1-555-111-0009', '{"about": "Cloud Engineer", "location": "Boston"}', '["Deep Learning", "NLP", "PyTorch"]', 'PRIVATE', 'https://linkedin.com/in/ian09', 'https://github.com/ian09', NULL, 'https://twitter.com/ian09', '{"url": "https://pics.example.com/ian09.jpg"}'), +('julia10', 'Julia Thomas', 'julia10@example.com', 'julia10', 'CUSTOMER', '+1-555-111-0010', '{"about": "Cloud Engineer", "location": "New York"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/julia10', NULL, NULL, 'https://twitter.com/julia10', '{"url": "https://pics.example.com/julia10.jpg"}'), +('kevin11', 'Kevin Wilson', 'kevin11@example.com', 'kevin11', 'CUSTOMER', '+1-555-111-0011', '{"about": "Full stack developer", "location": "New York"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, NULL, NULL, 'https://twitter.com/kevin11', '{"url": "https://pics.example.com/kevin11.jpg"}'), +('laura12', 'Laura Lee', 'laura12@example.com', 'laura12', 'CUSTOMER', '+1-555-111-0012', '{"about": "Full stack developer", "location": "Chicago"}', '["Kubernetes", "Docker", "CI/CD"]', 'PUBLIC', 'https://linkedin.com/in/laura12', NULL, NULL, NULL, '{"url": "https://pics.example.com/laura12.jpg"}'), +('mike13', 'Mike Wilson', 'mike13@example.com', 'mike13', 'CUSTOMER', '+1-555-111-0013', '{"about": "Full stack developer", "location": "San Francisco"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/mike13', 'https://github.com/mike13', NULL, NULL, '{"url": "https://pics.example.com/mike13.jpg"}'), +('nina14', 'Nina Wilson', 'nina14@example.com', 'nina14', 'CUSTOMER', '+1-555-111-0014', '{"about": "UX Designer", "location": "Seattle"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/nina14', 'https://github.com/nina14', NULL, NULL, '{"url": "https://pics.example.com/nina14.jpg"}'), +('oscar15', 'Oscar Johnson', 'oscar15@example.com', 'oscar15', 'CUSTOMER', '+1-555-111-0015', '{"about": "Cloud Engineer", "location": "Boston"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, 'https://github.com/oscar15', NULL, NULL, '{"url": "https://pics.example.com/oscar15.jpg"}'), +('paula16', 'Paula Taylor', 'paula16@example.com', 'paula16', 'CUSTOMER', '+1-555-111-0016', '{"about": "Cloud Engineer", "location": "New York"}', '["Deep Learning", "NLP", "PyTorch"]', 'PRIVATE', 'https://linkedin.com/in/paula16', NULL, NULL, 'https://twitter.com/paula16', '{"url": "https://pics.example.com/paula16.jpg"}'), +('quinn17', 'Quinn Thomas', 'quinn17@example.com', 'quinn17', 'CUSTOMER', '+1-555-111-0017', '{"about": "Full stack developer", "location": "New York"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/quinn17', NULL, NULL, NULL, '{"url": "https://pics.example.com/quinn17.jpg"}'), +('ryan18', 'Ryan Wilson', 'ryan18@example.com', 'ryan18', 'CUSTOMER', '+1-555-111-0018', '{"about": "UX Designer", "location": "Los Angeles"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/ryan18', 'https://github.com/ryan18', NULL, NULL, '{"url": "https://pics.example.com/ryan18.jpg"}'), +('sophia19', 'Sophia Lee', 'sophia19@example.com', 'sophia19', 'CUSTOMER', '+1-555-111-0019', '{"about": "Mobile App Developer", "location": "San Francisco"}', '["Flutter", "Dart", "Firebase"]', 'PUBLIC', 'https://linkedin.com/in/sophia19', 'https://github.com/sophia19', NULL, NULL, '{"url": "https://pics.example.com/sophia19.jpg"}'), +('tom20', 'Tom Thomas', 'tom20@example.com', 'tom20', 'CUSTOMER', '+1-555-111-0020', '{"about": "Digital Marketer", "location": "Chicago"}', '["AWS", "Terraform", "DevOps"]', 'PRIVATE', 'https://linkedin.com/in/tom20', 'https://github.com/tom20', NULL, 'https://twitter.com/tom20', '{"url": "https://pics.example.com/tom20.jpg"}'), +('uma21', 'Uma Johnson', 'uma21@example.com', 'uma21', 'CUSTOMER', '+1-555-111-0021', '{"about": "Data Scientist", "location": "Los Angeles"}', '["Java", "Spring Boot", "Microservices"]', 'PRIVATE', 'https://linkedin.com/in/uma21', NULL, NULL, NULL, '{"url": "https://pics.example.com/uma21.jpg"}'), +('victor22', 'Victor Brown', 'victor22@example.com', 'victor22', 'CUSTOMER', '+1-555-111-0022', '{"about": "Cloud Engineer", "location": "New York"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/victor22', 'https://github.com/victor22', NULL, 'https://twitter.com/victor22', '{"url": "https://pics.example.com/victor22.jpg"}'), +('wendy23', 'Wendy Anderson', 'wendy23@example.com', 'wendy23', 'CUSTOMER', '+1-555-111-0023', '{"about": "Mobile App Developer", "location": "Chicago"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/wendy23', 'https://github.com/wendy23', NULL, 'https://twitter.com/wendy23', '{"url": "https://pics.example.com/wendy23.jpg"}'), +('xavier24', 'Xavier Taylor', 'xavier24@example.com', 'xavier24', 'CUSTOMER', '+1-555-111-0024', '{"about": "Digital Marketer", "location": "Boston"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/xavier24', 'https://github.com/xavier24', NULL, 'https://twitter.com/xavier24', '{"url": "https://pics.example.com/xavier24.jpg"}'), +('yara25', 'Yara Brown', 'yara25@example.com', 'yara25', 'CUSTOMER', '+1-555-111-0025', '{"about": "UX Designer", "location": "New York"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, NULL, NULL, 'https://twitter.com/yara25', '{"url": "https://pics.example.com/yara25.jpg"}'), +('zane26', 'Zane Taylor', 'zane26@example.com', 'zane26', 'CUSTOMER', '+1-555-111-0026', '{"about": "Cloud Engineer", "location": "Seattle"}', '["JavaScript", "React", "Node.js"]', 'PUBLIC', 'https://linkedin.com/in/zane26', 'https://github.com/zane26', NULL, 'https://twitter.com/zane26', '{"url": "https://pics.example.com/zane26.jpg"}'), +('amber27', 'Amber Wilson', 'amber27@example.com', 'amber27', 'CUSTOMER', '+1-555-111-0027', '{"about": "Digital Marketer", "location": "New York"}', '["Kubernetes", "Docker", "CI/CD"]', 'PUBLIC', NULL, 'https://github.com/amber27', NULL, 'https://twitter.com/amber27', '{"url": "https://pics.example.com/amber27.jpg"}'), +('brian28', 'Brian Wilson', 'brian28@example.com', 'brian28', 'CUSTOMER', '+1-555-111-0028', '{"about": "Digital Marketer", "location": "Chicago"}', '["Figma", "Sketch", "UI/UX"]', 'PRIVATE', 'https://linkedin.com/in/brian28', NULL, NULL, 'https://twitter.com/brian28', '{"url": "https://pics.example.com/brian28.jpg"}'), +('carmen29', 'Carmen Moore', 'carmen29@example.com', 'carmen29', 'CUSTOMER', '+1-555-111-0029', '{"about": "Digital Marketer", "location": "Boston"}', '["Kubernetes", "Docker", "CI/CD"]', 'PRIVATE', 'https://linkedin.com/in/carmen29', NULL, NULL, 'https://twitter.com/carmen29', '{"url": "https://pics.example.com/carmen29.jpg"}'), +('daniel30', 'Daniel Smith', 'daniel30@example.com', 'daniel30', 'CUSTOMER', '+1-555-111-0030', '{"about": "Full stack developer", "location": "New York"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', 'https://linkedin.com/in/daniel30', 'https://github.com/daniel30', NULL, NULL, '{"url": "https://pics.example.com/daniel30.jpg"}'), +('elena31', 'Elena Lee', 'elena31@example.com', 'elena31', 'CUSTOMER', '+1-555-111-0031', '{"about": "Mobile App Developer", "location": "San Francisco"}', '["SEO", "Content Marketing"]', 'PRIVATE', NULL, 'https://github.com/elena31', NULL, NULL, '{"url": "https://pics.example.com/elena31.jpg"}'), +('frank32', 'Frank Johnson', 'frank32@example.com', 'frank32', 'CUSTOMER', '+1-555-111-0032', '{"about": "Digital Marketer", "location": "San Francisco"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', 'https://linkedin.com/in/frank32', 'https://github.com/frank32', NULL, 'https://twitter.com/frank32', '{"url": "https://pics.example.com/frank32.jpg"}'), +('grace33', 'Grace Adams', 'grace33@example.com', 'grace33', 'CUSTOMER', '+1-555-111-0033', '{"about": "Mobile App Developer", "location": "Seattle"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', NULL, 'https://github.com/grace33', NULL, 'https://twitter.com/grace33', '{"url": "https://pics.example.com/grace33.jpg"}'), +('henry34', 'Henry Adams', 'henry34@example.com', 'henry34', 'CUSTOMER', '+1-555-111-0034', '{"about": "AI Researcher", "location": "San Francisco"}', '["JavaScript", "React", "Node.js"]', 'PUBLIC', 'https://linkedin.com/in/henry34', 'https://github.com/henry34', NULL, 'https://twitter.com/henry34', '{"url": "https://pics.example.com/henry34.jpg"}'), +('isla35', 'Isla Adams', 'isla35@example.com', 'isla35', 'CUSTOMER', '+1-555-111-0035', '{"about": "Cloud Engineer", "location": "Los Angeles"}', '["Flutter", "Dart", "Firebase"]', 'PRIVATE', NULL, 'https://github.com/isla35', NULL, 'https://twitter.com/isla35', '{"url": "https://pics.example.com/isla35.jpg"}'), +('jack36', 'Jack Johnson', 'jack36@example.com', 'jack36', 'CUSTOMER', '+1-555-111-0036', '{"about": "UX Designer", "location": "Seattle"}', '["Kubernetes", "Docker", "CI/CD"]', 'PRIVATE', 'https://linkedin.com/in/jack36', NULL, NULL, NULL, '{"url": "https://pics.example.com/jack36.jpg"}'), +('kara37', 'Kara Moore', 'kara37@example.com', 'kara37', 'CUSTOMER', '+1-555-111-0037', '{"about": "Data Scientist", "location": "Chicago"}', '["JavaScript", "React", "Node.js"]', 'PRIVATE', 'https://linkedin.com/in/kara37', 'https://github.com/kara37', NULL, 'https://twitter.com/kara37', '{"url": "https://pics.example.com/kara37.jpg"}'), +('liam38', 'Liam Adams', 'liam38@example.com', 'liam38', 'CUSTOMER', '+1-555-111-0038', '{"about": "Cloud Engineer", "location": "Seattle"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/liam38', 'https://github.com/liam38', NULL, 'https://twitter.com/liam38', '{"url": "https://pics.example.com/liam38.jpg"}'), +('maya39', 'Maya Brown', 'maya39@example.com', 'maya39', 'CUSTOMER', '+1-555-111-0039', '{"about": "UX Designer", "location": "Los Angeles"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, 'https://github.com/maya39', NULL, 'https://twitter.com/maya39', '{"url": "https://pics.example.com/maya39.jpg"}'), +('noah40', 'Noah Johnson', 'noah40@example.com', 'noah40', 'CUSTOMER', '+1-555-111-0040', '{"about": "Cloud Engineer", "location": "Chicago"}', '["Kubernetes", "Docker", "CI/CD"]', 'PRIVATE', 'https://linkedin.com/in/noah40', 'https://github.com/noah40', NULL, 'https://twitter.com/noah40', '{"url": "https://pics.example.com/noah40.jpg"}'), +('olivia41', 'Olivia Thomas', 'olivia41@example.com', 'olivia41', 'CUSTOMER', '+1-555-111-0041', '{"about": "AI Researcher", "location": "Boston"}', '["Java", "Spring Boot", "Microservices"]', 'PRIVATE', 'https://linkedin.com/in/olivia41', 'https://github.com/olivia41', NULL, 'https://twitter.com/olivia41', '{"url": "https://pics.example.com/olivia41.jpg"}'), +('peter42', 'Peter Adams', 'peter42@example.com', 'peter42', 'CUSTOMER', '+1-555-111-0042', '{"about": "Cloud Engineer", "location": "New York"}', '["JavaScript", "React", "Node.js"]', 'PRIVATE', 'https://linkedin.com/in/peter42', 'https://github.com/peter42', NULL, NULL, '{"url": "https://pics.example.com/peter42.jpg"}'), +('queen43', 'Queen Brown', 'queen43@example.com', 'queen43', 'CUSTOMER', '+1-555-111-0043', '{"about": "UX Designer", "location": "Seattle"}', '["SEO", "Content Marketing"]', 'PRIVATE', NULL, NULL, NULL, 'https://twitter.com/queen43', '{"url": "https://pics.example.com/queen43.jpg"}'), +('rita44', 'Rita Moore', 'rita44@example.com', 'rita44', 'CUSTOMER', '+1-555-111-0044', '{"about": "Mobile App Developer", "location": "Los Angeles"}', '["Deep Learning", "NLP", "PyTorch"]', 'PRIVATE', NULL, 'https://github.com/rita44', NULL, 'https://twitter.com/rita44', '{"url": "https://pics.example.com/rita44.jpg"}'), +('samuel45', 'Samuel Moore', 'samuel45@example.com', 'samuel45', 'CUSTOMER', '+1-555-111-0045', '{"about": "Full stack developer", "location": "Boston"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/samuel45', NULL, NULL, 'https://twitter.com/samuel45', '{"url": "https://pics.example.com/samuel45.jpg"}'), +('tina46', 'Tina Wilson', 'tina46@example.com', 'tina46', 'CUSTOMER', '+1-555-111-0046', '{"about": "Mobile App Developer", "location": "Los Angeles"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/tina46', 'https://github.com/tina46', NULL, 'https://twitter.com/tina46', '{"url": "https://pics.example.com/tina46.jpg"}'), +('ursula47', 'Ursula Wilson', 'ursula47@example.com', 'ursula47', 'CUSTOMER', '+1-555-111-0047', '{"about": "Digital Marketer", "location": "Seattle"}', '["Flutter", "Dart", "Firebase"]', 'PUBLIC', NULL, 'https://github.com/ursula47', NULL, 'https://twitter.com/ursula47', '{"url": "https://pics.example.com/ursula47.jpg"}'), +('vera48', 'Vera Anderson', 'vera48@example.com', 'vera48', 'CUSTOMER', '+1-555-111-0048', '{"about": "Cloud Engineer", "location": "San Francisco"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', 'https://linkedin.com/in/vera48', 'https://github.com/vera48', NULL, 'https://twitter.com/vera48', '{"url": "https://pics.example.com/vera48.jpg"}'), +('william49', 'William Lee', 'william49@example.com', 'william49', 'CUSTOMER', '+1-555-111-0049', '{"about": "AI Researcher", "location": "Los Angeles"}', '["Go", "gRPC", "PostgreSQL"]', 'PUBLIC', 'https://linkedin.com/in/william49', 'https://github.com/william49', NULL, NULL, '{"url": "https://pics.example.com/william49.jpg"}'), +('zoe50', 'Zoe Thomas', 'zoe50@example.com', 'zoe50', 'CUSTOMER', '+1-555-111-0050', '{"about": "Digital Marketer", "location": "Los Angeles"}', '["Flutter", "Dart", "Firebase"]', 'PRIVATE', NULL, 'https://github.com/zoe50', NULL, NULL, '{"url": "https://pics.example.com/zoe50.jpg"}'); + +-- Insert 100+ Posts (2 per user) +INSERT INTO posts (user_id, title, content, tags, type, attachments) VALUES +('alice01', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'), +('alice01', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'), +('bob02', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["NLP", "AI"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('bob02', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Python", "Pandas"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'), +('charlie03', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Python", "Pandas"]', 'published', '[{"url": "https://files.example.com/data-cleaning-with-pandas.pdf"}]'), +('charlie03', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["DevOps", "Cloud"]', 'draft', '[]'), +('diana04', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["DevOps", "Cloud"]', 'draft', '[]'), +('diana04', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Python", "Pandas"]', 'draft', '[]'), +('ethan05', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Deep Learning", "Python"]', 'draft', '[]'), +('ethan05', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Backend", "Node.js"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('fiona06', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'draft', '[]'), +('fiona06', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["React", "JavaScript"]', 'draft', '[]'), +('george07', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["ML", "AI"]', 'published', '[]'), +('george07', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'), +('hannah08', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'), +('hannah08', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Kubernetes", "Docker"]', 'published', '[]'), +('ian09', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('ian09', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/data-cleaning-with-pandas.pdf"}]'), +('julia10', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["ML", "AI"]', 'draft', '[]'), +('julia10', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Deep Learning", "Python"]', 'published', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'), +('kevin11', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["NLP", "AI"]', 'published', '[]'), +('kevin11', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["Python", "Pandas"]', 'published', '[]'), +('laura12', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'draft', '[]'), +('laura12', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["NLP", "AI"]', 'draft', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'), +('mike13', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'draft', '[]'), +('mike13', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Kubernetes", "Docker"]', 'draft', '[]'), +('nina14', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["UI", "Design"]', 'draft', '[]'), +('nina14', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'), +('oscar15', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["NLP", "AI"]', 'published', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'), +('oscar15', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["NLP", "AI"]', 'published', '[]'), +('paula16', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Python", "Pandas"]', 'draft', '[]'), +('paula16', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Kubernetes", "Docker"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'), +('quinn17', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["ML", "AI"]', 'published', '[]'), +('quinn17', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Deep Learning", "Python"]', 'published', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'), +('ryan18', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["React", "JavaScript"]', 'published', '[]'), +('ryan18', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'draft', '[]'), +('sophia19', 'Deep Learning for Beginners', '{"text": "This is a detailed guide about Deep Learning for Beginners."}', '["NLP", "AI"]', 'published', '[]'), +('sophia19', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Mobile", "Flutter"]', 'draft', '[]'), +('tom20', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Python", "Pandas"]', 'draft', '[]'), +('tom20', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["NLP", "AI"]', 'published', '[]'), +('uma21', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'), +('uma21', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Mobile", "Flutter"]', 'draft', '[]'), +('victor22', 'Deep Learning for Beginners', '{"text": "This is a detailed guide about Deep Learning for Beginners."}', '["Python", "Pandas"]', 'draft', '[]'), +('victor22', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'), +('wendy23', 'Deep Learning for Beginners', '{"text": "This is a detailed guide about Deep Learning for Beginners."}', '["NLP", "AI"]', 'draft', '[{"url": "https://files.example.com/deep-learning-for-beginners.pdf"}]'), +('wendy23', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'), +('xavier24', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["UI", "Design"]', 'published', '[]'), +('xavier24', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'draft', '[]'), +('yara25', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Deep Learning", "Python"]', 'published', '[]'), +('yara25', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["NLP", "AI"]', 'published', '[]'), +('zane26', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('zane26', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["NLP", "AI"]', 'draft', '[]'), +('amber27', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["DevOps", "Cloud"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'), +('amber27', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Mobile", "Flutter"]', 'draft', '[]'), +('brian28', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'), +('brian28', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('carmen29', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'), +('carmen29', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["NLP", "AI"]', 'published', '[]'), +('daniel30', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["ML", "AI"]', 'draft', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'), +('daniel30', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["NLP", "AI"]', 'draft', '[]'), +('elena31', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'), +('elena31', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'draft', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'), +('frank32', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["ML", "AI"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'), +('frank32', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'), +('grace33', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'), +('grace33', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Mobile", "Flutter"]', 'draft', '[]'), +('henry34', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["ML", "AI"]', 'published', '[]'), +('henry34', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Mobile", "Flutter"]', 'draft', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'), +('isla35', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'), +('isla35', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'), +('jack36', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'), +('jack36', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Deep Learning", "Python"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('kara37', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["NLP", "AI"]', 'published', '[]'), +('kara37', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'), +('liam38', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["React", "JavaScript"]', 'published', '[]'), +('liam38', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('maya39', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["React", "JavaScript"]', 'draft', '[]'), +('maya39', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Deep Learning", "Python"]', 'published', '[]'), +('noah40', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Deep Learning", "Python"]', 'published', '[]'), +('noah40', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Deep Learning", "Python"]', 'draft', '[]'), +('olivia41', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Deep Learning", "Python"]', 'published', '[]'), +('olivia41', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Deep Learning", "Python"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('peter42', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["NLP", "AI"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'), +('peter42', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Mobile", "Flutter"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'), +('queen43', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'), +('queen43', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Python", "Pandas"]', 'draft', '[]'), +('rita44', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Deep Learning", "Python"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'), +('rita44', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'), +('samuel45', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'), +('samuel45', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'), +('tina46', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Kubernetes", "Docker"]', 'draft', '[]'), +('tina46', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["UI", "Design"]', 'published', '[]'), +('ursula47', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'), +('ursula47', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Deep Learning", "Python"]', 'published', '[]'), +('vera48', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'published', '[]'), +('vera48', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'published', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'), +('william49', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Kubernetes", "Docker"]', 'published', '[{"url": "https://files.example.com/data-cleaning-with-pandas.pdf"}]'), +('william49', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'), +('zoe50', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'published', '[]'), +('zoe50', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["Deep Learning", "Python"]', 'published', '[]'); \ No newline at end of file diff --git a/test/js/sql/local-sql.test.ts b/test/js/sql/local-sql.test.ts index 0cf3df97c8..21d95453ab 100644 --- a/test/js/sql/local-sql.test.ts +++ b/test/js/sql/local-sql.test.ts @@ -1,6 +1,6 @@ import { SQL } from "bun"; import { afterAll, expect, test } from "bun:test"; -import { isLinux } from "harness"; +import { bunEnv, bunExe, isLinux, tempDirWithFiles } from "harness"; import path from "path"; const postgres = (...args) => new SQL(...args); @@ -26,8 +26,9 @@ async function waitForPostgres(port) { for (let i = 0; i < 3; i++) { try { const sql = new SQL(`postgres://bun_sql_test@localhost:${port}/bun_sql_test`, { - idle_timeout: 20, - max_lifetime: 60 * 30, + idleTimeout: 1, + connectionTimeout: 1, + maxLifetime: 1, tls: { ca: Bun.file(path.join(import.meta.dir, "docker-tls", "server.crt")), }, @@ -64,7 +65,6 @@ async function startContainer(): Promise<{ port: number; containerName: string } // Start the container await execAsync(`${dockerCLI} run -d --name ${containerName} -p ${port}:5432 custom-postgres-tls`); - // Wait for PostgreSQL to be ready await waitForPostgres(port); return { @@ -148,4 +148,196 @@ if (isDockerEnabled()) { const result = (await sql`select 1 as x`)[0].x; expect(result).toBe(1); }); + + test("should not segfault under pressure #21351", async () => { + // we need at least the usename and port + await using sql = postgres(connectionString, { + max: 1, + idleTimeout: 1, + connectionTimeout: 1, + tls: { + rejectUnauthorized: false, + }, + }); + await sql`create table users ( + id text not null, + created_at timestamp with time zone not null default now(), + name text null, + email text null, + identifier text not null default '-'::text, + role text null default 'CUSTOMER'::text, + phone text null, + bio jsonb null, + skills jsonb null default '[]'::jsonb, + privacy text null default 'PUBLIC'::text, + linkedin_url text null, + github_url text null, + facebook_url text null, + twitter_url text null, + picture jsonb null, + constraint users_pkey primary key (id), + constraint users_identifier_key unique (identifier) + ) TABLESPACE pg_default; + create table posts ( + id uuid not null default gen_random_uuid (), + created_at timestamp with time zone not null default now(), + user_id text null, + title text null, + content jsonb null, + tags jsonb null, + type text null default 'draft'::text, + attachments jsonb null default '[]'::jsonb, + updated_at timestamp with time zone null, + constraint posts_pkey primary key (id), + constraint posts_user_id_fkey foreign KEY (user_id) references users (id) on update CASCADE on delete CASCADE + ) TABLESPACE pg_default;`.simple(); + await sql.file(path.join(import.meta.dirname, "issue-21351.fixture.sql")); + + const dir = tempDirWithFiles("import-meta-no-inline", { + "index.ts": ` + import { SQL } from "bun"; + + const db = new SQL({ + url: process.env.DATABASE_URL, + max: 1, + idleTimeout: 60 * 5, + maxLifetime: 60 * 15, + tls: { + ca: Bun.file(process.env.DATABASE_CA as string), + }, + }); + await db.connect(); + const server = Bun.serve({ + port: 0, + fetch: async (req) => { + try{ + await Bun.sleep(100); + let fragment = db\`\`; + + const searchs = await db\` + WITH cte AS ( + SELECT + post.id, + post."content", + post.created_at AS "createdAt", + users."name" AS "userName", + users.id AS "userId", + users.identifier AS "userIdentifier", + users.picture AS "userPicture", + '{}'::json AS "group" + FROM posts post + INNER JOIN users + ON users.id = post.user_id + \${fragment} + ORDER BY post.created_at DESC + ) + SELECT + * + FROM cte + -- LIMIT 5 + \`; + return Response.json(searchs); + } catch { + return new Response(null, { status: 500 }); + } + }, + }); + + console.log(server.url.href); + `, + }); + sql.end({ timeout: 0 }); + async function bombardier(url, batchSize = 100, abortSignal) { + let batch = []; + for (let i = 0; i < 100_000 && !abortSignal.aborted; i++) { + //@ts-ignore + batch.push(fetch(url, { signal: abortSignal }).catch(() => {})); + if (batch.length > batchSize) { + await Promise.all(batch); + batch = []; + } + } + await Promise.all(batch); + } + let failed = false; + function spawnServer(controller) { + return new Promise(async (resolve, reject) => { + const server = Bun.spawn([bunExe(), "index.ts"], { + stdin: "ignore", + stdout: "pipe", + stderr: "pipe", + cwd: dir, + env: { + ...bunEnv, + BUN_DEBUG_QUIET_LOGS: "1", + DATABASE_URL: connectionString, + DATABASE_CA: path.join(import.meta.dir, "docker-tls", "server.crt"), + }, + onExit(proc, exitCode, signalCode, error) { + // exit handler + if (exitCode !== 0) { + failed = true; + controller.abort(); + } + }, + }); + + const reader = server.stdout.getReader(); + const errorReader = server.stderr.getReader(); + + const decoder = new TextDecoder(); + async function outputData(reader, type = "log") { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (value) { + if (type === "error") { + console.error(decoder.decode(value)); + } else { + console.log(decoder.decode(value)); + } + } + } + } + + const url = decoder.decode((await reader.read()).value); + resolve({ url, kill: () => server.kill() }); + outputData(reader); + errorReader.read().then(({ value }) => { + if (value) { + console.error(decoder.decode(value)); + failed = true; + } + outputData(errorReader, "error"); + }); + }); + } + async function spawnRestarts(controller) { + for (let i = 0; i < 20 && !controller.signal.aborted; i++) { + await Bun.$`${dockerCLI} restart ${container.containerName}`.nothrow().quiet(); + await Bun.sleep(500); + } + + try { + controller.abort(); + } catch {} + } + + const controller = new AbortController(); + + const { promise, resolve, reject } = Promise.withResolvers(); + const server = (await spawnServer(controller)) as { url: string; kill: () => void }; + + controller.signal.addEventListener("abort", () => { + if (!failed) resolve(); + else reject(new Error("Server crashed")); + server.kill(); + }); + + bombardier(server.url, 100, controller.signal); + + await Bun.sleep(1000); + spawnRestarts(controller); + await promise; + }, 30_000); }