mirror of
https://github.com/oven-sh/bun
synced 2026-02-15 05:12:29 +00:00
fix(postgres) regression (#21466)
### What does this PR do? Fix: https://github.com/oven-sh/bun/issues/21351 Relevant changes: Fix advance to properly cleanup success and failed queries that could be still be in the queue Always ref before executing Use stronger atomics for ref/deref and hasPendingActivity Fallback when thisValue is freed/null/zero and check if vm is being shutdown The bug in --hot in `resolveRopeIfNeeded` Issue is not meant to be fixed in this PR this is a fix for the postgres regression Added assertions so this bug is easier to catch on CI ### How did you verify your code works? Test added --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
committed by
Jarred Sumner
parent
fd45273b5a
commit
8bc2449d62
@@ -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| {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
155
test/js/sql/issue-21351.fixture.sql
Normal file
155
test/js/sql/issue-21351.fixture.sql
Normal file
@@ -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', '[]');
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user