Skip to content

fix(Bun.SQL) fix "this" context handling in onclose function - #22507

Closed
cirospaciari wants to merge 64 commits into
mainfrom
ciro/fix-onclose
Closed

cirospaciari wants to merge 64 commits into
mainfrom
ciro/fix-onclose

Conversation

@cirospaciari

@cirospaciari cirospaciari commented Sep 8, 2025 •

Copy link
Copy Markdown
Member

What does this PR do?

Fix memories issues, exports in internals and handling of hasPendingActivity

How did you verify your code works?

CI

@robobun

robobun commented Sep 8, 2025 •

Copy link
Copy Markdown
Collaborator
Updated 5:58 PM PT - Sep 11th, 2025

❌ @cirospaciari, your commit c226a94 has 4 failures in Build #25814:


🧪   To try this PR locally:

bunx bun-pr 22507

That installs a local version of the PR into your bun-22507 executable, so you can run:

bun-22507 --bun

@cirospaciari cirospaciari changed the title dont override this let it use binded value fix(Bun.SQL) fix "this" context handling Sep 8, 2025
@cirospaciari cirospaciari changed the title fix(Bun.SQL) fix "this" context handling fix(Bun.SQL) fix "this" context handling in onclose function Sep 8, 2025
@cirospaciari
cirospaciari marked this pull request as ready for review September 8, 2025 23:04
@coderabbitai

coderabbitai Bot commented Sep 8, 2025 •

Copy link
Copy Markdown
Contributor

Walkthrough

Replace per-object JSValue fields with a jsc.JSRef API (tryGet/upgrade/downgrade/finalize), remove per-object pending_activity counters, tighten lifecycle/refcount flows across MySQL/Postgres queries and connections, adjust error/on_close binding and microtask queuing, and update JS exports, tests, and codegen guards.

Changes

Cohort / File(s) Summary of Changes
MySQL connection & query core
src/sql/mysql/MySQLConnection.zig, src/sql/mysql/MySQLQuery.zig
Replace JSValue with jsc.JSRef; remove pending_activity_count/hasPendingActivity; centralize ref lifecycle via updateReferenceType (ref/deref/finalize); use tryGet() guards for JS access; adjust pendingValue/query callbacks to accept maybe-undefined JS refs; change RefCount to non-thread-safe.
MySQL protocol & error mapping
src/sql/mysql/protocol/ErrorPacket.zig, src/sql/mysql/protocol/AnyMySQLError.zig
CreateMySQLError initial capacity changed; add UnknownError and InvalidState error variants and map them to JS error codes.
Postgres connection & query
src/sql/postgres/PostgresSQLConnection.zig, src/sql/postgres/PostgresSQLQuery.zig, src/sql/postgres/CommandTag.zig
Guard JS access with js_value.tryGet(); compute/ensure-alive js_error; call on_close with undefined this and (js_error, queries_array) payload; getTarget returns ?jsc.JSValue; remove hasPendingActivity; switch RefCount to non-thread-safe; toJSTag now returns bun.JSError!jsc.JSValue.
JSRef runtime API
src/bun.js/bindings/JSRef.zig
Replace get() with tryGet(): ?jsc.JSValue; add downgrade(), isEmpty()/isNotEmpty(), finalize(); validate inputs in init/setStrong/setWeak; weak state defaults to .js_undefined; change deinit/finalize semantics.
Server / Request JS lifecycle
src/bun.js/api/server.zig, src/bun.js/api/server/RequestContext.zig
Replace .get() with .tryGet() orelse .js_undefined; jsValueAssertAlive uses tryGet; stop uses js_value.downgrade(); finalize() uses js_value.finalize(); guard callback args via tryGet.
Bindings microtask scheduling (C++)
src/bun.js/bindings/ZigGlobalObject.cpp, src/bun.js/bindings/bindings.cpp
Cache microtask/reject functions into locals, normalize empty async context to jsUndefined(), use cached function for queueing, and add ASSERT-only validations before queuing microtasks.
Codegen & bundling
src/codegen/bundle-modules.ts
Update scanner usage to t.scan(input) → iterate scannedImports.imports; add guard that throws BunError when a builtin module mixes export default with named exports.
Internal JS SQL modules & exports
src/js/internal/sql/mysql.ts, src/js/internal/sql/postgres.ts, src/js/internal/sql/sqlite.ts, src/js/bun/sql.ts
Several export class → local class (exposed via default export); PooledMySQLConnection.#onConnected now stores connection; #startConnection no longer awaits; unify connectionClosedError() usage to pool.connectionClosedError().
Query wrappers & target/pending APIs
src/sql/mysql/MySQLQuery.zig, src/sql/postgres/PostgresSQLQuery.zig
getTarget now returns optional ?jsc.JSValue; callers use tryGet/early-return; setPendingValue signatures changed to obtain this from callframe; onResult/onWriteFail/onJSError updated to build/keep-alive JS errors and call JS callbacks with guarded values.
Shared iterator, tests & harness
src/sql/shared/ObjectIterator.zig, test/harness.ts, test/js/sql/sql-mysql.test.ts, test/js/sql/sql.test.ts
Fast-fail guard in ObjectIterator.next for missing arrays/columns; relax Docker/platform gating in test harness; add tests for async password throw and sql.file; update expected timeout message to "Connection timeout".

Possibly related PRs

Suggested reviewers

  • Jarred-Sumner
  • pfgithub
  • markovejnovic

Pre-merge checks (2 passed, 1 inconclusive)

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Description Check ❓ Inconclusive The description includes the required template headings but is too terse and contains a typo ("memories" vs "memory"); it lacks a clear summary of the main changes, affected files or public API impacts, concrete verification steps beyond "CI", and notes about current CI failures referenced in PR comments. As written it is insufficient for a reviewer to understand risk, breaking changes, or how to reproduce/verify. Expand the description to correct typos, list the key behavioral changes and important files, state any public API or compatibility impacts, provide concrete verification steps (local commands and failing test names), and mention current CI failures or reproduction instructions so reviewers can assess risk and test locally.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title refers to a real change in the patch (fixing the "this" context in onclose callbacks) but is narrower than the PR's broader work (JSRef lifecycle/memory fixes, export visibility changes, hasPendingActivity refactor) and redundantly repeats "fix". This may undercommunicate the full scope to reviewers scanning history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ciro/fix-onclose

Comment @coderabbitai help to get the list of available commands and usage tips.

@cirospaciari
cirospaciari marked this pull request as draft September 8, 2025 23:09
@cirospaciari
cirospaciari marked this pull request as ready for review September 9, 2025 02:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/sql/mysql/protocol/ErrorPacket.zig (1)

22-31: Optional: pre-size to expected property count to avoid a tiny growth cost.

If this path is hot, preallocating for the worst-case 4 properties can shave a minor resize.

-    const opts_obj = JSValue.createEmptyObject(globalObject, 0);
+    const opts_obj = JSValue.createEmptyObject(globalObject, 4); // code, errno?, sqlState?, message
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4654a8a and 3be8796.

📒 Files selected for processing (1)
  • src/sql/mysql/protocol/ErrorPacket.zig (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/mysql/protocol/ErrorPacket.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/mysql/protocol/ErrorPacket.zig
🔇 Additional comments (1)
src/sql/mysql/protocol/ErrorPacket.zig (1)

22-22: Capacity 0 for createEmptyObject is reasonable here.

Fewer props are set (≤4), so reducing from 18 avoids unnecessary preallocation. Behavior is unchanged.

@cirospaciari
cirospaciari marked this pull request as draft September 9, 2025 02:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/bun.js/bindings/bindings.cpp (1)

3467-3493: Fix inverted ASSERTs and use sanitized values when enqueuing the microtask.

ASSERTs for asyncContext/value are inverted and checked before normalization; queueMicrotask currently passes the raw internalField(0) instead of the sanitized asyncContext — this can enqueue empty JSValues and cause crashes.

File: src/bun.js/bindings/bindings.cpp (≈lines 3467-3493)

-        auto* globalObject = jsCast<Zig::GlobalObject*>(promise->globalObject());
-        auto microtaskFunction = globalObject->performMicrotaskFunction();
-        auto rejectPromiseFunction = globalObject->rejectPromiseFunction();
-
-        auto asyncContext = globalObject->m_asyncContextData.get()->getInternalField(0);
-#if ASSERT_ENABLED
-        ASSERT_WITH_MESSAGE(microtaskFunction, "Invalid microtask function");
-        ASSERT_WITH_MESSAGE(asyncContext.isEmpty(), "Invalid microtask context");
-        ASSERT_WITH_MESSAGE(rejectPromiseFunction, "Invalid microtask callback");
-        ASSERT_WITH_MESSAGE(value.isEmpty(), "Invalid microtask value");
-#endif
-
-        if (asyncContext.isEmpty()) {
-            asyncContext = jsUndefined();
-        }
-
-        if (value.isEmpty()) {
-            value = jsUndefined();
-        }
-
-        globalObject->queueMicrotask(
-            microtaskFunction,
-            rejectPromiseFunction,
-            globalObject->m_asyncContextData.get()->getInternalField(0),
-            promise,
-            value);
+        auto* globalObject = jsCast<Zig::GlobalObject*>(promise->globalObject());
+        auto* microtaskFunction = globalObject->performMicrotaskFunction();
+        auto* rejectPromiseFunction = globalObject->rejectPromiseFunction();
+        auto asyncContext = globalObject->m_asyncContextData.get()->getInternalField(0);
+#if ASSERT_ENABLED
+        ASSERT_WITH_MESSAGE(microtaskFunction, "Missing microtask function");
+        ASSERT_WITH_MESSAGE(rejectPromiseFunction, "Missing rejectPromise function");
+#endif
+        if (asyncContext.isEmpty())
+            asyncContext = jsUndefined();
+        if (value.isEmpty())
+            value = jsUndefined();
+#if ASSERT_ENABLED
+        ASSERT_WITH_MESSAGE(!asyncContext.isEmpty(), "Invalid microtask context (empty)");
+        ASSERT_WITH_MESSAGE(!value.isEmpty(), "Invalid microtask value (empty)");
+#endif
+        globalObject->queueMicrotask(
+            microtaskFunction,
+            rejectPromiseFunction,
+            asyncContext,
+            promise,
+            value);
🧹 Nitpick comments (1)
src/bun.js/bindings/bindings.cpp (1)

6145-6168: Consistent naming and const correctness for cached microtask function.

Minor consistency/readability: use the same identifier style as elsewhere and make the cached pointer const.

-    auto microTaskFunction = globalObject->performMicrotaskFunction();
+    const auto* microtaskFunction = globalObject->performMicrotaskFunction();
-    ASSERT_WITH_MESSAGE(microTaskFunction, "Invalid microtask function");
+    ASSERT_WITH_MESSAGE(microtaskFunction, "Invalid microtask function");
-    globalObject->queueMicrotask(
-        microTaskFunction,
+    globalObject->queueMicrotask(
+        microtaskFunction,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 61bd3e6 and 07631e0.

📒 Files selected for processing (2)
  • src/bun.js/bindings/ZigGlobalObject.cpp (2 hunks)
  • src/bun.js/bindings/bindings.cpp (4 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{cpp,h}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.{cpp,h}: When exposing a JS class with public Constructor and Prototype, define three C++ types: class Foo : public JSC::DestructibleObject (if it has C++ fields), class FooPrototype : public JSC::JSNonFinalObject, and class FooConstructor : public JSC::InternalFunction
If the class has C++ data members, inherit from JSC::DestructibleObject and provide proper destruction; if it has no C++ fields (only JS properties), avoid a class and use JSC::constructEmptyObject(vm, structure) with putDirectOffset
Prefer placing the subspaceFor implementation in the .cpp file rather than the header when possible

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
  • src/bun.js/bindings/bindings.cpp
**/*.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.cpp: Include "root.h" at the top of C++ binding files to satisfy lints
Define prototype properties using a const HashTableValue array and declare accessors/functions with JSC_DECLARE_* macros
Prototype classes should subclass JSC::JSNonFinalObject, provide create/createStructure, DECLARE_INFO, finishCreation that reifies static properties, and set mayBePrototype on the Structure
Custom getters should use JSC_DEFINE_CUSTOM_GETTER, jsDynamicCast to validate this, and throwThisTypeError on mismatch
Custom setters should use JSC_DEFINE_CUSTOM_SETTER, validate this via jsDynamicCast, and store via WriteBarrier/set semantics
Prototype functions should use JSC_DEFINE_HOST_FUNCTION, validate this with jsDynamicCast, and return encoded JSValue
Constructors should subclass JSC::InternalFunction, return internalFunctionSpace in subspaceFor, set the prototype property as non-configurable/non-writable, and provide create/createStructure
Provide a setup function that builds the Prototype, Constructor, and Structure, and assigns them to the LazyClassStructure initializer
Use the cached Structure via globalObject->m_.get(globalObject) when constructing instances
Expose constructors to Zig via an extern "C" function that returns the constructor from the LazyClassStructure
Provide an extern "C" Bun____toJS function that creates an instance using the cached Structure and returns an EncodedJSValue

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
  • src/bun.js/bindings/bindings.cpp
src/bun.js/bindings/ZigGlobalObject.cpp

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

Initialize the LazyClassStructure in GlobalObject::finishCreation and visit it in GlobalObject::visitChildrenImpl

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
src/bun.js/bindings/ZigGlobalObject.{h,cpp}

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
**/*.{cpp,cc,cxx,h,hpp,hxx}

📄 CodeRabbit inference engine (CLAUDE.md)

Format C/C++ sources/headers with clang-format (bun run clang-format)

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
  • src/bun.js/bindings/bindings.cpp
src/bun.js/bindings/**/*.cpp

📄 CodeRabbit inference engine (CLAUDE.md)

src/bun.js/bindings/**/*.cpp: For JS classes with a public constructor, implement Foo, FooPrototype, and FooConstructor (JSC patterns)
Define class properties using HashTableValue arrays in JSC bindings
Add iso subspaces for classes with C++ fields in JSC bindings
Cache structures in ZigGlobalObject for JSC-bound classes

Files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
  • src/bun.js/bindings/bindings.cpp
🧠 Learnings (15)
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JSC-bound classes

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.cpp : Initialize the LazyClassStructure in GlobalObject::finishCreation and visit it in GlobalObject::visitChildrenImpl

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/napi/napi.zig : Add new V8 API method mangled symbols to the V8API struct in src/napi/napi.zig for both GCC/Clang and MSVC

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/src/symbols.txt : Add new V8 API mangled symbols (without leading underscore) to src/symbols.txt

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/ZigGlobalObject.cpp
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Add iso subspaces for classes with C++ fields in JSC bindings

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/**/*.{h,cpp} : Use localToJSValue() when converting V8 handles to JSC values before performing JSC operations

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-08T00:41:12.052Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/bun.js/bindings/v8/CLAUDE.md:0-0
Timestamp: 2025-09-08T00:41:12.052Z
Learning: Applies to src/bun.js/bindings/v8/**/*.{h,cpp} : Use JSC::WriteBarrier for heap-allocated references and implement visitChildren() for custom heap objects

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : For JS classes with a public constructor, implement Foo, FooPrototype, and FooConstructor (JSC patterns)

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-07T05:41:52.563Z
Learnt from: CR
PR: oven-sh/bun#0
File: src/js/CLAUDE.md:0-0
Timestamp: 2025-09-07T05:41:52.563Z
Learning: Applies to src/js/{builtins,node,bun,thirdparty,internal}/**/*.{js,ts} : Prefer JSC intrinsics and `$`-prefixed private APIs for performance and safety (e.g., `$Array`, `$newArrayWithSize`, `$putByIdDirectPrivate`, `$assert`, `$debug`)

Applied to files:

  • src/bun.js/bindings/bindings.cpp
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Define class properties using HashTableValue arrays in JSC bindings

Applied to files:

  • src/bun.js/bindings/bindings.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (2)
src/bun.js/bindings/ZigGlobalObject.cpp (2)

4156-4161: LGTM on added debug guards in JSC__JSGlobalObject__queueMicrotaskCallback.

Asserting non-null trampoline, context pointer, and callback improves crash diagnostics without changing release behavior.


1551-1561: Fix inverted debug assertions in queueMicrotask (callback/context checks).

callback.isEmpty() and asyncContext.isEmpty() are inverted — callback must be non-empty (callable) and asyncContext is expected to be undefined. File: src/bun.js/bindings/ZigGlobalObject.cpp (≈ lines 1551–1561).

Apply:

-    ASSERT_WITH_MESSAGE(function, "Invalid microtask function");
-    ASSERT_WITH_MESSAGE(asyncContext.isEmpty(), "Invalid microtask context");
-    ASSERT_WITH_MESSAGE(callback.isEmpty(), "Invalid microtask callback");
+    ASSERT_WITH_MESSAGE(function, "Invalid microtask function");
+    ASSERT_WITH_MESSAGE(asyncContext.isUndefined(), "Invalid microtask context (expected undefined)");
+    ASSERT_WITH_MESSAGE(!callback.isEmpty(), "Invalid microtask callback");
+#if 0 // optional, stronger debug check (redundant with V::validateFunction)
+    ASSERT_WITH_MESSAGE(JSC::getCallData(callback).type != JSC::CallData::Type::None, "callback must be callable");
+#endif

Re-run failing job locally to verify (the provided CI command failed in this environment due to missing bunx):

bunx bun-pr 22507
bun-22507 --bun test/js/sql/sql-mysql.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/sql/mysql/MySQLConnection.zig (2)

934-934: User-facing error string mentions “postgresql” in MySQL connector

Incorrect message can mislead debugging.

-                    return globalObject.throwError(err, "failed to connect to postgresql");
+                    return globalObject.throwError(err, "failed to connect to mysql");

1313-1326: Inconsistent onconnect microtask args — standardize the callback contract

  • MySQL enqueues [null, connection] (src/sql/mysql/MySQLConnection.zig — queueMicrotask(on_connect, &[_]JSValue{ JSValue.jsNull(), js_value })).
  • Valkey enqueues [connection, hello_value] (src/valkey/js_valkey.zig — queueMicrotask(on_connect, &[_]JSValue{ js_value, hello_value })).
    Action: choose the canonical signature (e.g., (err, conn) or (conn, info)), update all call sites and tests, and verify Postgres (src/sql/postgres/PostgresSQLConnection.zig) follows the same contract.
♻️ Duplicate comments (1)
src/sql/mysql/MySQLConnection.zig (1)

1901-1903: Fix unsafe optional unwrap of cached_structure (can panic at runtime)

cached_structure may be null when js_value.tryGet() fails. Guard before calling jsValue() to avoid a trap.

Apply:

-                    .objects => {
-                        cached_structure = if (this.js_value.tryGet()) |value| statement.structure(value, this.globalObject) else null;
-                        structure = cached_structure.?.jsValue() orelse .js_undefined;
-                    },
+                    .objects => {
+                        cached_structure = if (this.js_value.tryGet()) |value|
+                            statement.structure(value, this.globalObject)
+                        else
+                            null;
+                        structure = if (cached_structure) |cs|
+                            cs.jsValue() orelse .js_undefined
+                        else
+                            .js_undefined;
+                    },
🧹 Nitpick comments (2)
src/sql/mysql/MySQLConnection.zig (2)

124-131: Reference gating policy is reasonable; consider idling-downgrade tweak

Optional: downgrade when fully idle (no requests and no pending writes), but keep strong during connect/handshake/auth or when write_buffer has data. This avoids keeping a strong JS ref for indefinitely idle connections.

Apply:

 fn updateReferenceType(this: *MySQLConnection) void {
-    if (this.js_value.isNotEmpty()) {
-        if (this.requests.readableLength() > 0 or (this.status != .disconnected and this.status != .failed)) {
-            this.js_value.upgrade(this.globalObject);
-            return;
-        }
-        this.js_value.downgrade();
-    }
+    if (this.js_value.isNotEmpty()) {
+        const active =
+            this.requests.readableLength() > 0 or
+            this.write_buffer.len() > 0 or
+            (this.status == .connecting or
+             this.status == .handshaking or
+             this.status == .authenticating or
+             this.status == .authentication_awaiting_pk);
+        if (active) {
+            this.js_value.upgrade(this.globalObject);
+        } else {
+            this.js_value.downgrade();
+        }
+    }
 }

268-274: Unregister AutoFlusher in finalize to avoid stray microtasks

If finalize runs while a flusher is scheduled, a latent microtask could reference a soon-to-be-derefed connection. Unregistering is cheap and safer.

 pub fn finalize(this: *MySQLConnection) void {
     this.stopTimers();
     debug("MySQLConnection finalize", .{});
+    this.unregisterAutoFlusher();
 
     this.js_value.deinit();
     this.deref();
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e5cc5d5 and 019ccc3.

📒 Files selected for processing (1)
  • src/sql/mysql/MySQLConnection.zig (19 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/mysql/MySQLConnection.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/mysql/MySQLConnection.zig
🔇 Additional comments (11)
src/sql/mysql/MySQLConnection.zig (11)

24-24: JSRef migration + setStrong on creation — LGTM

Switching to jsc.JSRef and setting a strong ref at creation is correct and aligns with the new lifecycle model.

Also applies to: 955-957


351-355: Guarded access to cached queries — LGTM

Correct use of tryGet() with undefined fallback.


400-410: onEnd/onClose ordering — LGTM

onClose defers deref and delegates to onEnd → fail path first, then refAndClose. Matches intent.


428-430: Disconnect gating + status transition — LGTM

Defer updateReferenceType() around status change is correct.


728-736: Socket handlers unify close/error -> onEnd — LGTM

Consistent terminal path simplifies lifecycle and reduces duplication.

Also applies to: 738-741


761-762: Timeout path unified wording — LGTM

Error code and message align; behavior is unchanged.


1015-1020: TCP onOpen state + ref symmetry — LGTM

Setting .handshaking and holding a ref only for TCP (not post-upgrade TLS) is correct.


1158-1163: Packet length handling — LGTM

Computing packet_length = header_length + PacketHeader.size and deferring setOffsetFromStart is correct for short-read recovery and padding.


1284-1295: Safe consumption of cached callbacks — LGTM

tryGet(), ensureStillAlive(), and cache-clearing semantics look correct.

Also applies to: 1298-1309


1333-1339: updateRef ties strongness to poll_ref — LGTM

Keeps process liveness aligned with JSRef strength. Good.


1792-1793: Passing connection JS value into onResult — LGTM

Using tryGet() with undefined fallback is correct.

Comment thread src/sql/mysql/MySQLConnection.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sql/mysql/MySQLQuery.zig (1)

68-95: Protect object during enqueue and clear caches on failure

Mirror Postgres: hold a temporary ref to this, clear caches, then downgrade. Prevents leaks and premature destruction while scheduling the callback.

Apply:

 pub fn onWriteFail(
     this: *@This(),
     err: AnyMySQLError.Error,
     globalObject: *jsc.JSGlobalObject,
     queries_array: JSValue,
 ) void {
-    this.status = .fail;
-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
-    const targetValue = this.getTarget(globalObject, true) orelse return;
+    this.ref();
+    defer this.deref();
+    this.status = .fail;
+    const thisValue = this.thisValue.tryGet() orelse return;
+    const targetValue = this.getTarget(globalObject, true) orelse return;
+    defer {
+        allowGC(thisValue, globalObject);
+        this.thisValue.downgrade();
+    }
♻️ Duplicate comments (6)
src/bun.js/bindings/JSRef.zig (1)

61-69: Guard downgrade() when strong.get() is null to avoid calling ensureStillAlive() on .zero

If get() returns null, calling ensureStillAlive() on .zero is unsafe. Guard and only keep the weak value when non-null.

Apply:

-            .strong => |*strong| {
-                const value = strong.get() orelse .zero;
-                value.ensureStillAlive();
-                strong.deinit();
-                this.* = .{ .weak = value };
-            },
+            .strong => |*strong| {
+                const maybe = strong.get();
+                strong.deinit();
+                if (maybe) {
+                    const value = maybe.?;
+                    value.ensureStillAlive();
+                    this.* = .{ .weak = value };
+                } else {
+                    this.* = .{ .weak = .zero };
+                }
+            },
src/sql/postgres/PostgresSQLQuery.zig (2)

78-81: Clear all caches on failure to avoid leaks

After grabbing target, call allowGC(thisValue, ...) and then downgrade(). This mirrors success path cleanup and prevents stale cached refs.

Apply:

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
-    const targetValue = this.getTarget(globalObject, true) orelse return;
+    const thisValue = this.thisValue.tryGet() orelse return;
+    const targetValue = this.getTarget(globalObject, true) orelse return;
+    defer {
+        allowGC(thisValue, globalObject);
+        this.thisValue.downgrade();
+    }

96-99: Do the same cleanup in onJSError()

Symmetric failure cleanup prevents retained arrays/bindings.

Apply:

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
-    const targetValue = this.getTarget(globalObject, true) orelse return;
+    const thisValue = this.thisValue.tryGet() orelse return;
+    const targetValue = this.getTarget(globalObject, true) orelse return;
+    defer {
+        allowGC(thisValue, globalObject);
+        this.thisValue.downgrade();
+    }
src/sql/mysql/MySQLQuery.zig (1)

161-178: Also keep JS “this” alive during error-callback scheduling and clear caches

Create a temporary Strong on thisValue (JS object) while enqueuing; also clear caches + downgrade like onWriteFail.

Apply:

     this.status = .fail;
-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
-    const targetValue = this.getTarget(globalObject, true) orelse return;
+    const thisValue = this.thisValue.tryGet() orelse return;
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
+    const targetValue = this.getTarget(globalObject, true) orelse return;
+    defer {
+        allowGC(thisValue, globalObject);
+        this.thisValue.downgrade();
+    }
src/sql/mysql/MySQLConnection.zig (2)

364-392: Fix on_close microtask invocation to use proper this-binding


1901-1902: Fix unsafe optional unwrap — guard cached_structure before calling .jsValue()

🧹 Nitpick comments (1)
src/bun.js/bindings/JSRef.zig (1)

10-10: Nit: prefer parameter name globalObject for consistency

Most bindings use globalObject; consider renaming for uniformity.

Also applies to: 39-39

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 019ccc3 and 324c72c.

📒 Files selected for processing (5)
  • src/bun.js/api/server.zig (4 hunks)
  • src/bun.js/bindings/JSRef.zig (4 hunks)
  • src/sql/mysql/MySQLConnection.zig (19 hunks)
  • src/sql/mysql/MySQLQuery.zig (7 hunks)
  • src/sql/postgres/PostgresSQLQuery.zig (6 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/bun.js/api/server.zig
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/mysql/MySQLConnection.zig
  • src/sql/postgres/PostgresSQLQuery.zig
  • src/sql/mysql/MySQLQuery.zig
  • src/bun.js/bindings/JSRef.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/mysql/MySQLConnection.zig
  • src/sql/postgres/PostgresSQLQuery.zig
  • src/sql/mysql/MySQLQuery.zig
  • src/bun.js/bindings/JSRef.zig
src/bun.js/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/zig-javascriptcore-classes.mdc)

src/bun.js/**/*.zig: In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS and re-export toJS/fromJS/fromJSDirect
Constructors and prototype methods should return bun.JSError!JSC.JSValue to integrate Zig error handling with JS exceptions
Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors
Implement getters as get(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface
Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern
Access JS call data via CallFrame (argument(i), argumentCount(), thisValue()) and throw errors with globalObject.throw(...)
For properties marked cache: true, use the generated Zig accessors (NameSetCached/GetCached) to work with GC-owned values
In finalize() for objects holding JS references, release them using .deref() before destroy

Files:

  • src/bun.js/bindings/JSRef.zig
🧠 Learnings (12)
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to **/*.zig : In Zig, manage memory carefully with allocators and use defer for cleanup

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Always implement proper cleanup in deinit() and finalize() for JS-exposed types

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In finalize() for objects holding JS references, release them using .deref() before destroy

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Implement getters as get<PropertyName>(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface

Applied to files:

  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/bindings/generated_classes_list.zig : Update src/bun.js/bindings/generated_classes_list.zig to include new classes

Applied to files:

  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-09-05T19:49:26.188Z
Learnt from: markovejnovic
PR: oven-sh/bun#21728
File: src/valkey/js_valkey_functions.zig:852-867
Timestamp: 2025-09-05T19:49:26.188Z
Learning: In Bun’s Zig code, `.js_undefined` is a valid and preferred JSValue literal for “undefined” (e.g., resolving JSPromise). Do not refactor usages to `jsc.JSValue.jsUndefined()`, especially in src/valkey/js_valkey_functions.zig unsubscribe().

Applied to files:

  • src/bun.js/bindings/JSRef.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Provide deinit() for resource cleanup and finalize() that calls deinit(); use bun.destroy(this) or appropriate destroy pattern

Applied to files:

  • src/bun.js/bindings/JSRef.zig
🔇 Additional comments (33)
src/bun.js/bindings/JSRef.zig (5)

11-13: Asserting non-zero strong refs is good

Ensures we never create a strong ref to .zero. Matches intended API constraints.


19-25: tryGet(): API shape looks correct

Optional return across weak/strong/finalized states is clear and safe.


39-46: setStrong(): non-zero assertion is appropriate

Prevents accidental strong refs to .zero.


76-90: isEmpty()/isNotEmpty(): semantics are consistent

Matches weak!=.zero and strong.has() expectations.


104-107: finalize(): simple and correct

Deinits and marks finalized; aligns with lifecycle expectations.

src/sql/postgres/PostgresSQLQuery.zig (3)

26-33: getTarget(): optional return + clean reset is correct

Early bailouts on missing thisValue/target reduce crash surface; cache clear on demand is good.


139-144: Success-path cleanup looks good

allowGC() + downgrade() when is_last ensures caches are cleared and ref downgraded at the right time.

Also applies to: 141-142


231-235: Stray this.pending_value usage likely dead or broken

Struct has no pending_value field; this won’t compile if referenced. Either remove or switch to pendingValueGetCached(thisValue).

Suggested rewrite:

 pub fn push(this: *PostgresSQLQuery, globalThis: *jsc.JSGlobalObject, value: JSValue) void {
-    var pending_value = this.pending_value.get() orelse return;
-    pending_value.push(globalThis, value);
+    const thisValue = this.thisValue.tryGet() orelse return;
+    const arr = js.pendingValueGetCached(thisValue) orelse return;
+    arr.push(globalThis, value);
 }
src/sql/mysql/MySQLQuery.zig (4)

64-66: finalize(): correct lifecycle

Releases statement, finalizes JS ref, then deref — matches intended pattern.


117-121: bind(): guarded cache reads are good

Using .js_undefined defaults avoids .zero misuse in iterators.


179-186: getTarget(): optional return + selective cache clear is correct

Matches the new JSRef API and avoids touching other caches.


221-228: Success-path cleanup is correct

allowGC() + downgrade() when is_last mirrors Postgres.

src/sql/mysql/MySQLConnection.zig (21)

24-24: LGTM!

The change from JSValue to jsc.JSRef = jsc.JSRef.empty() aligns with the new reference lifecycle management system described in the AI summary.


124-132: LGTM!

The updateReferenceType function properly implements the new reference management by checking connection state and request queue length to determine when to upgrade/downgrade the JS reference.


272-272: LGTM!

The finalize method correctly uses js_value.finalize() instead of directly zeroing the value, which aligns with the new JSRef API.


278-278: LGTM!

Calling updateReferenceType() in doRef ensures the reference state is properly synchronized.


284-284: LGTM!

Calling updateReferenceType() in doUnref ensures the reference state is properly synchronized.


351-355: LGTM!

The getQueriesArray function properly uses js_value.tryGet() to safely access the JS value and returns .js_undefined when the value is not available.


400-409: LGTM!

The new onEnd and onClose lifecycle methods provide clear separation between socket end events and close events, with onClose properly dereferencing the connection.


414-416: LGTM!

The refAndClose method correctly calls cleanUpRequests with the JS reason parameter to handle cleanup consistently.


428-429: LGTM!

Calling updateReferenceType() in the disconnect flow ensures reference state is properly managed during connection lifecycle changes.


735-740: LGTM!

The socket handlers correctly call the new onEnd() method for both end and connect error events.


761-761: Timeout message uses "Connection timeout" consistently

The timeout message now consistently uses "Connection timeout" which aligns with the timeout message updates mentioned in the AI summary.


956-956: LGTM!

Using js_value.setStrong(js_value, globalObject) correctly establishes a strong reference to the JS value during connection creation.


1015-1019: LGTM!

The socket open handler properly updates the connection status, manages references, and calls updateReferenceType() to maintain proper reference counting.


1157-1162: LGTM!

The packet processing correctly calculates packet_length as header_length + PacketHeader.size and uses it for capacity checks and offset updates.


1285-1309: LGTM!

Both callback consumption methods properly use js_value.tryGet() to safely access the JS value and handle cases where the value might not be available.


1313-1313: LGTM!

Calling updateReferenceType() in setStatus ensures reference state is updated when connection status changes.


1322-1325: LGTM!

The connection success flow properly uses js_value.tryGet() to get the current JS value and falls back to .js_undefined if unavailable.


1333-1339: LGTM!

The updateRef method correctly manages the poll reference based on the JS value state (strong vs weak reference).


1792-1792: LGTM!

Using js_value.tryGet() orelse .js_undefined safely accesses the JS value for result handling.


1911-1911: LGTM!

Safely accessing the pending value using request.thisValue.tryGet() with proper fallback.


1929-1933: LGTM!

The pending value handling correctly checks if the value is empty/undefined/null and safely accesses the request's JS value using tryGet().

Comment thread src/sql/postgres/PostgresSQLQuery.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/sql/mysql/MySQLQuery.zig (2)

75-93: Fix CI: replace .isCallable() (compile error) and keep this alive during callback enqueue

Build is failing at Line 85 due to .isCallable() on function. Also, take a temporary strong on this while the callback is queued to avoid GC races (same ask as the earlier review).

     const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    // Keep JS `this` alive while the callback is enqueued.
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
+    defer this.thisValue.downgrade();

     const targetValue = this.getTarget(globalObject, true) orelse return;

     const instance = AnyMySQLError.mysqlErrorToJS(globalObject, "Failed to bind query", err);
     const js_err = instance.toError() orelse instance;
     bun.assertf(js_err != .zero, "js_err is zero", .{});
     js_err.ensureStillAlive();
     const vm = jsc.VirtualMachine.get();
-    const function = vm.rareData().mysql_context.onQueryRejectFn.get() orelse return;
-    bun.assertf(function.isCallable(), "onQueryRejectFn is not callable", .{});
+    const function = vm.rareData().mysql_context.onQueryRejectFn.get() orelse return;
+    // Guard: skip if hook isn't callable to avoid native crash.
+    if (!jsc.JSValueIsCallable(globalObject, function)) return;

     const event_loop = vm.eventLoop();
-    const js_array = if (queries_array == .zero) .js_undefined else queries_array;
-    js_array.ensureStillAlive();
+    const js_array = if (queries_array == .zero) .js_undefined else queries_array;
+    if (js_array != .js_undefined) js_array.ensureStillAlive();

     event_loop.runCallback(function, globalObject, thisValue, &.{
         targetValue,
         js_err,
         js_array,
     });

Confirm the correct callable-check helper and presence across the tree:

#!/bin/bash
# Find canonical callable checks in bun/jsc wrappers
rg -nP '\bisCallable\s*\(' --type=zig -C2
rg -nP 'JSValueIsCallable' --type=zig -C2
rg -nP 'onQueryRejectFn' --type=zig -C3

Expected: one of the above exists (prefer the same symbol used elsewhere in SQL/Postgres paths). If neither exists, drop the guard and rely on invariants (field is engine-internal).


161-177: Mirror fixes in onJSError: temporary strong + callable guard

Same lifecycle and build issues as in onWriteFail. Protect this and avoid .isCallable() usage.

     this.status = .fail;
     const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
+    defer this.thisValue.downgrade();

     const targetValue = this.getTarget(globalObject, true) orelse return;

     var vm = jsc.VirtualMachine.get();
-    const function = vm.rareData().mysql_context.onQueryRejectFn.get() orelse return;
-    bun.assertf(function.isCallable(), "onQueryRejectFn is not callable", .{});
+    const function = vm.rareData().mysql_context.onQueryRejectFn.get() orelse return;
+    if (!jsc.JSValueIsCallable(globalObject, function)) return;

     const event_loop = vm.eventLoop();
     var js_error = err.toError() orelse err;
     if (js_error == .zero) {
         js_error = AnyMySQLError.mysqlErrorToJS(globalObject, "Query failed", error.UnknownError);
     }
     js_error.ensureStillAlive();
🧹 Nitpick comments (3)
src/sql/mysql/MySQLQuery.zig (3)

223-229: queries_array handling is robust; micro-nit on ensure

The extra .zero check then ensureStillAlive() is fine. Minor nit: you can fold into one branch, but not necessary.

-    var queries_array = if (connection == .zero) .js_undefined else MySQLConnection.js.queriesGetCached(connection) orelse .js_undefined;
-    if (queries_array == .zero) {
-        queries_array = .js_undefined;
-    } else {
-        queries_array.ensureStillAlive();
-    }
+    var queries_array = if (connection == .zero)
+        .js_undefined
+    else
+        (MySQLConnection.js.queriesGetCached(connection) orelse .js_undefined);
+    if (queries_array != .js_undefined)
+        queries_array.ensureStillAlive();

Also applies to: 235-235


523-523: Align debug logger with guidelines

Guidelines recommend false for the scoped logger’s second arg to keep logs off by default.

-const debug = bun.Output.scoped(.MySQLQuery, .visible);
+const debug = bun.Output.scoped(.MySQLQuery, false);

64-66: Finalize ordering: clear this.thisValue after finalize to avoid accidental reuse

rg shows finalize() only at this location; there are other downgrade()/upgrade() uses in the file — null the ref after finalize to prevent re-use.

-    this.thisValue.finalize();
+    this.thisValue.finalize();
+    this.thisValue = JSRef.empty();
     this.deref();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 324c72c and a39179a.

📒 Files selected for processing (1)
  • src/sql/mysql/MySQLQuery.zig (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/mysql/MySQLQuery.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/mysql/MySQLQuery.zig
🔇 Additional comments (6)
src/sql/mysql/MySQLQuery.zig (6)

118-121: Good: safer defaults for cached bindings/columns

Returning .js_undefined when caches miss simplifies downstream checks. LGTM.


179-186: getTarget optionality and cleanup look correct

Returning ?jsc.JSValue and clearing the cached target behind clean_target matches the new lifecycle. LGTM.


210-216: Degrade thisValue only on last result — correct

The conditional downgrade on is_last preserves reachability across partial responses. Good.


2-2: RefCount switched to non-thread-safe variant — confirm single-threaded ownership

Repo scan shows MySQLQuery is only referenced under src/sql/mysql and is managed by MySQLConnection; no Thread.spawn/ThreadPool hits found in src/sql. Verify MySQLQuery is never handed to worker threads or thread pools (it’s exported to JS in bun.js/bindings/generated_classes_list.zig); if it can be accessed cross-thread, restore a thread-safe RefCount.


355-356: Confirm updateRef is safe as used

I inspected the repository search output you ran. MySQLConnection.updateRef (src/sql/mysql/MySQLConnection.zig:1332) calls updateReferenceType() then either poll_ref.ref(this.vm) or poll_ref.unref(this.vm) based on this.js_value, matching the pattern used across the codebase (Postgres and other readers/writers use the same defer/updateRef pattern). That implementation is not a paired increment/decrement counter — it recomputes desired state and applies ref/unref to match it, so calling it on early returns or via defer is safe and idempotent.

Action: keep defer connection.updateRef(); as-is.


218-220: Guard onQueryResolveFn instead of asserting

Replace the assert at src/sql/mysql/MySQLQuery.zig:218–219 with an early-return callable check — e.g.:
if (!function.isCell() or !function.isCallable()) return;
The suggested jsc.JSValueIsCallable(...) helper is not present in the repo.

Likely an incorrect or invalid review comment.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/sql/postgres/PostgresSQLQuery.zig (2)

78-81: Clear all cached refs on failure to avoid leaks (repeat)

Failure path only clears the target via clean_target=true; pending/binding caches remain. Clear them with allowGC(...) before downgrading.

Apply:

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    const thisValue = this.thisValue.tryGet() orelse return;
+    defer this.thisValue.downgrade();
+    defer allowGC(thisValue, globalObject);

96-99: Mirror failure cleanup in onJSError

Same leak risk here; clear all caches before downgrade.

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    const thisValue = this.thisValue.tryGet() orelse return;
+    defer this.thisValue.downgrade();
+    defer allowGC(thisValue, globalObject);
🧹 Nitpick comments (2)
src/sql/postgres/PostgresSQLQuery.zig (2)

26-33: getTarget now returns optional and conditionally clears only the target cache

Semantics look good. Minor note: clean_target clears just the target cache; pending/binding remain. Ensure callers invoke allowGC(...) when fully disposing to clear all caches.


492-492: Align debug logger with repo guideline

Use a non-verbose scoped logger by default.

-const debug = bun.Output.scoped(.Postgres, .visible);
+const debug = bun.Output.scoped(.Postgres, false);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a39179a and 2b66c6f.

📒 Files selected for processing (1)
  • src/sql/postgres/PostgresSQLQuery.zig (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/postgres/PostgresSQLQuery.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/postgres/PostgresSQLQuery.zig
🧠 Learnings (5)
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to **/*.zig : In Zig, manage memory carefully with allocators and use defer for cleanup

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Always implement proper cleanup in deinit() and finalize() for JS-exposed types

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In finalize() for objects holding JS references, release them using .deref() before destroy

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (3)
src/sql/postgres/PostgresSQLQuery.zig (3)

2-2: Verify thread-safety after switching to non-thread-safe RefCount

Changing to bun.ptr.RefCount (non-thread-safe) is fine only if PostgresSQLQuery never crosses threads. Please confirm this object stays on one VM thread; otherwise revert to a thread-safe refcount.


63-67: Finalize recursion removed — LGTM

finalize() no longer calls itself and correctly unlinks the JSRef before deref().


138-144: Resolve path cleanup ordering — LGTM

Using defer to allowGC(...) and downgrade() only on is_last while fetching targetValue first is correct.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/sql/postgres/PostgresSQLQuery.zig (4)

96-107: Same fixes as onWriteFail: guard callback, clear caches, strong ref, keep error alive

Prevents traps and leaks; preserves this context until enqueue completes.

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
-    const targetValue = this.getTarget(globalObject, true) orelse return;
+    const thisValue = this.thisValue.tryGet() orelse return;
+    defer this.thisValue.downgrade();
+    const targetValue = this.getTarget(globalObject, true) orelse return;
+    allowGC(thisValue, globalObject);
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
@@
-    const function = vm.rareData().postgresql_context.onQueryRejectFn.get().?;
+    const function = vm.rareData().postgresql_context.onQueryRejectFn.get() orelse return;
+    bun.assertf(function.isCallable(), "onQueryRejectFn is not callable", .{});
@@
-    event_loop.runCallback(function, globalObject, thisValue, &.{
+    var js_error = err.toError() orelse err;
+    js_error.ensureStillAlive();
+    event_loop.runCallback(function, globalObject, thisValue, &.{
         targetValue,
-        err.toError() orelse err,
+        js_error,
     });

138-157: Guard resolve callback and keep queries array alive

Avoid forced unwrap; ensure callable; keep queries_array alive.

-    const vm = jsc.VirtualMachine.get();
-    const function = vm.rareData().postgresql_context.onQueryResolveFn.get().?;
+    const vm = jsc.VirtualMachine.get();
+    const function = vm.rareData().postgresql_context.onQueryResolveFn.get() orelse return;
+    bun.assertf(function.isCallable(), "onQueryResolveFn is not callable", .{});
@@
-        if (connection == .zero) .js_undefined else PostgresSQLConnection.js.queriesGetCached(connection) orelse .js_undefined,
+        if (connection == .zero) .js_undefined else blk: {
+            var qa = PostgresSQLConnection.js.queriesGetCached(connection) orelse .js_undefined;
+            if (qa != .js_undefined) qa.ensureStillAlive();
+            break :blk qa;
+        },

231-234: Compile-time error: this.pending_value is not a field

Use the cached pending array from thisValue.

 pub fn push(this: *PostgresSQLQuery, globalThis: *jsc.JSGlobalObject, value: JSValue) void {
-    var pending_value = this.pending_value.get() orelse return;
-    pending_value.push(globalThis, value);
+    const thisValue = this.thisValue.tryGet() orelse return;
+    const arr = js.pendingValueGetCached(thisValue) orelse return;
+    arr.ensureStillAlive();
+    arr.push(globalThis, value);
 }

260-269: Balance poll_ref.ref in PostgresSQLQuery.doRun — doRun calls connection.poll_ref.ref(globalObject.bunVM()) (src/sql/postgres/PostgresSQLQuery.zig:266) but has early-return/error paths that won't unref; add defer connection.updateRef(); immediately after acquiring connection (mirror MySQLQuery.doRun) to guarantee the ref is paired.

♻️ Duplicate comments (4)
src/sql/postgres/PostgresSQLQuery.zig (2)

78-91: Avoid trap on missing callback; clear all caches on failure; keep a temporary strong ref; keep args alive

  • Use orelse return instead of forcing .?.
  • Clear binding/pending caches (target is already cleared via clean_target).
  • Take a temporary Strong to protect thisValue until the callback is queued.
  • Keep js_err and queries_array alive.
-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
-    const targetValue = this.getTarget(globalObject, true) orelse return;
+    const thisValue = this.thisValue.tryGet() orelse return;
+    defer this.thisValue.downgrade();
+    const targetValue = this.getTarget(globalObject, true) orelse return;
+    // Clear other caches to avoid leaks/stale refs
+    allowGC(thisValue, globalObject);
+    // Protect `this` while enqueueing callback
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
@@
-    const function = vm.rareData().postgresql_context.onQueryRejectFn.get().?;
+    const function = vm.rareData().postgresql_context.onQueryRejectFn.get() orelse return;
+    bun.assertf(function.isCallable(), "onQueryRejectFn is not callable", .{});
@@
-    const js_err = postgresErrorToJS(globalObject, null, err);
+    const js_err = postgresErrorToJS(globalObject, null, err);
+    js_err.ensureStillAlive();
-    event_loop.runCallback(function, globalObject, thisValue, &.{
+    const js_array = if (queries_array == .zero) .js_undefined else queries_array;
+    js_array.ensureStillAlive();
+    event_loop.runCallback(function, globalObject, thisValue, &.{
         targetValue,
-        js_err.toError() orelse js_err,
-        queries_array,
+        js_err.toError() orelse js_err,
+        js_array,
     });

63-67: Break native cycles in finalize before deref

Mirror MySQL: release any statement to avoid ref cycles preventing deinit.

 pub fn finalize(this: *@This()) void {
     debug("PostgresSQLQuery finalize", .{});
-    this.thisValue.finalize();
+    if (this.statement) |statement| {
+        statement.deref();
+        this.statement = null;
+    }
+    this.thisValue.finalize();
     this.deref();
 }
src/sql/mysql/MySQLQuery.zig (2)

161-178: Mirror failure-path cleanup and strong ref in onJSError

Same reasoning as onWriteFail.

     this.status = .fail;
     const thisValue = this.thisValue.tryGet() orelse return;
     defer this.thisValue.downgrade();
     const targetValue = this.getTarget(globalObject, true) orelse return;
+    js.bindingSetCached(thisValue, globalObject, .zero);
+    js.pendingValueSetCached(thisValue, globalObject, .zero);
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
@@
     var js_error = err.toError() orelse err;
     if (js_error == .zero) {
         js_error = AnyMySQLError.mysqlErrorToJS(globalObject, "Query failed", error.UnknownError);
     }
     js_error.ensureStillAlive();

75-94: Also clear binding/pending caches and hold a temporary strong ref

Target cache is cleared via clean_target=true, but binding/pending remain. Clear them and keep thisValue strong until enqueue completes.

     this.status = .fail;
     const thisValue = this.thisValue.tryGet() orelse return;
     defer this.thisValue.downgrade();
     const targetValue = this.getTarget(globalObject, true) orelse return;
+    js.bindingSetCached(thisValue, globalObject, .zero);
+    js.pendingValueSetCached(thisValue, globalObject, .zero);
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
@@
     const instance = AnyMySQLError.mysqlErrorToJS(globalObject, "Failed to bind query", err);
     const js_err = instance.toError() orelse instance;
     bun.assertf(js_err != .zero, "js_err is zero", .{});
     js_err.ensureStillAlive();
@@
     const event_loop = vm.eventLoop();
-    const js_array = if (queries_array == .zero) .js_undefined else queries_array;
+    const js_array = if (queries_array == .zero) .js_undefined else queries_array;
     js_array.ensureStillAlive();
🧹 Nitpick comments (2)
src/sql/postgres/PostgresSQLQuery.zig (1)

492-494: Align logging with repo guidelines

Use const log = bun.Output.scoped(.Postgres, false); and call log("...", .{}).

-const debug = bun.Output.scoped(.Postgres, .visible);
+const log = bun.Output.scoped(.Postgres, false);

Note: update call sites accordingly.

src/sql/mysql/MySQLQuery.zig (1)

523-524: Align logging with repo guidelines

Use const log = bun.Output.scoped(.MySQLQuery, false); and log("...", .{}).

-const debug = bun.Output.scoped(.MySQLQuery, .visible);
+const log = bun.Output.scoped(.MySQLQuery, false);

Note: update call sites accordingly.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ca80e07 and b4a9f5f.

📒 Files selected for processing (2)
  • src/sql/mysql/MySQLQuery.zig (8 hunks)
  • src/sql/postgres/PostgresSQLQuery.zig (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/mysql/MySQLQuery.zig
  • src/sql/postgres/PostgresSQLQuery.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/mysql/MySQLQuery.zig
  • src/sql/postgres/PostgresSQLQuery.zig
🧠 Learnings (5)
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to **/*.zig : In Zig, manage memory carefully with allocators and use defer for cleanup

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Always implement proper cleanup in deinit() and finalize() for JS-exposed types

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In finalize() for objects holding JS references, release them using .deref() before destroy

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
🔇 Additional comments (9)
src/sql/postgres/PostgresSQLQuery.zig (3)

2-2: RefCount swap to bun.ptr.RefCount looks correct

Matches the new pattern across SQL query types.


26-33: Optional getTarget return is a good safety improvement

Returning null instead of trapping is preferable. Call sites handle it.


241-246: setPendingValue via callframe.this is fine

Matches the new pattern used across SQL queries.

src/sql/mysql/MySQLQuery.zig (6)

2-2: RefCount swap to bun.ptr.RefCount looks correct

Consistent with Postgres and broader refactor.


64-66: Finalize path looks good

JSRef finalize then native deref; statement is released above. Matches guidance.


118-121: Safe access to cached bindings/columns

Using tryGet + js_undefined defaults is correct.


179-186: getTarget optional-return LGTM

Clears target on demand and avoids traps.


315-320: setPendingValue via callframe.this is correct

Matches the new JSRef-driven pattern.


355-356: Connection ref handling LGTM

defer connection.updateRef(); clearly pairs lifecycle with doRun scope.

Comment thread src/sql/mysql/MySQLQuery.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sql/mysql/MySQLConnection.zig (1)

1314-1327: Fix on_connect “this” binding to avoid shifting args

Same issue as on_close: pass .js_undefined as this, then (null, js_value) as args.

Apply:

-            this.globalObject.queueMicrotask(on_connect, &[_]JSValue{ JSValue.jsNull(), js_value });
+            this.globalObject.queueMicrotask(on_connect, &[_]JSValue{ .js_undefined, JSValue.jsNull(), js_value });
♻️ Duplicate comments (1)
src/sql/mysql/MySQLConnection.zig (1)

1901-1906: Guard optional cached_structure before .jsValue()

Same issue previously reported: cached_structure can be null; calling cached_structure.?.jsValue() can trap. Guard it.

Apply:

-                        cached_structure = if (this.js_value.tryGet()) |value| statement.structure(value, this.globalObject) else null;
-                        structure = cached_structure.?.jsValue() orelse .js_undefined;
+                        cached_structure = if (this.js_value.tryGet()) |value|
+                            statement.structure(value, this.globalObject)
+                        else
+                            null;
+                        structure = if (cached_structure) |cs|
+                            cs.jsValue() orelse .js_undefined
+                        else
+                            .js_undefined;
🧹 Nitpick comments (1)
src/sql/mysql/MySQLConnection.zig (1)

369-373: Comment contradicts execution order

The defer runs before the queued microtask, so requests are cleaned up before on_close runs. Either update the comment to reflect reality or move cleanup to run after on_close (e.g., schedule a subsequent microtask).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b4a9f5f and b616fe1.

📒 Files selected for processing (1)
  • src/sql/mysql/MySQLConnection.zig (19 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/mysql/MySQLConnection.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/mysql/MySQLConnection.zig
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code

Applied to files:

  • src/sql/mysql/MySQLConnection.zig
🔇 Additional comments (14)
src/sql/mysql/MySQLConnection.zig (14)

24-24: JSRef field initialization looks correct

Using jsc.JSRef.empty() is appropriate for default-initializing the JS handle.


124-131: Reference upgrade/downgrade policy — verify all mutation sites call this

Logic to upgrade when requests exist or not-disconnected, and downgrade otherwise, is sound. Ensure every place mutating status/queue calls updateReferenceType() (you already do in setStatus, disconnect, doRef/doUnref).


268-274: Finalize JSRef before releasing native ref

Calling this.js_value.finalize() then deref() is the right order to let GC drop JS wrappers safely.


276-286: doRef/doUnref now delegate to updateReferenceType

Good: poll_ref ref/unref paired with JSRef upgrade/downgrade centralization reduces lifecycle drift.


351-355: Guarded access to queries array

Using this.js_value.tryGet() avoids dereferencing an empty/weak ref. LGTM.


401-410: onClose defers deref; onEnd calls fail — confirm single path

onClose -> onEnd -> failWithJSValue is fine and idempotent via status==.failed guard. Verify onEnd isn’t also invoked by the transport for the same connection to avoid double work.


427-431: Defer updateReferenceType on disconnect

Good: status change to .disconnected followed by ref-type recalculation prevents dangling strong refs.


1016-1021: Keep native ref while socket is open; recalc ref-type

Ref on open and updateReferenceType() are appropriate to keep the process alive during handshake.


1158-1164: Packet length handling — small API consistency check

Using header_length + PacketHeader.size and ensuring capacity before deferring skip is correct. Confirm NewReader(Context).ensureCapacity(packet_length) is an error-union in both StackReader and Reader; if it returns bool in any context, prefer:

if (!reader.ensureCapacity(packet_length)) return AnyMySQLError.Error.ShortRead;

1284-1296: consumeOnConnect now uses JSRef.tryGet

Safer callback retrieval/caching. LGTM.


1298-1310: consumeOnClose now uses JSRef.tryGet

Matches the onConnect pattern and avoids null traps. LGTM.


1789-1794: Passing possibly-undefined connection value to onResult

Using this.js_value.tryGet() orelse .js_undefined is correct under the new JSRef lifecycle.


1912-1912: pending_value retrieval via tryGet is correct

Avoids reaching into a dead JS object; good defensive pattern.


1930-1934: Only set pendingValue when empty

The isEmptyOrUndefinedOrNull() check prevents unnecessary overwrites. LGTM.

Comment thread src/sql/mysql/MySQLConnection.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/sql/mysql/MySQLQuery.zig (3)

161-178: Also take a temporary strong in onJSError to protect this during enqueue

Same lifecycle concern as above/onWriteFail. Take a short-lived strong while scheduling.

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    const thisValue = this.thisValue.tryGet() orelse return;
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
+    defer this.thisValue.downgrade();

210-216: Free caches on last result before downgrade (parity with PG)

Clear binding/pending caches when is_last to avoid leaks/stale state.

-    defer if (is_last) {
-        this.thisValue.downgrade();
-    };
+    defer if (is_last) {
+        js.bindingSetCached(thisValue, globalObject, .zero);
+        js.pendingValueSetCached(thisValue, globalObject, .zero);
+        this.thisValue.downgrade();
+    };

75-93: Protect this during reject enqueue; add a temporary strong and clear caches

Guard the JS this while enqueuing the callback, and clear per-query caches since this is a terminal failure.

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    const thisValue = this.thisValue.tryGet() orelse return;
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
+    defer this.thisValue.downgrade();

@@
-    const js_array = if (queries_array == .zero) .js_undefined else queries_array;
+    const js_array = if (queries_array == .zero) .js_undefined else queries_array;
     js_array.ensureStillAlive();
+    // terminal: free caches
+    js.bindingSetCached(thisValue, globalObject, .zero);
+    js.pendingValueSetCached(thisValue, globalObject, .zero);
🧹 Nitpick comments (2)
src/sql/postgres/CommandTag.zig (1)

46-51: Watch for precision loss converting u64 → JS Number

JSValue.jsNumber(val) will lose integer precision for values > Number.MAX_SAFE_INTEGER (2^53−1 = 9,007,199,254,740,991). toJSNumber is invoked at src/sql/postgres/PostgresSQLQuery.zig:154 and src/sql/mysql/MySQLQuery.zig:228; the counts come from CommandTag.init (parses u64) and MySQL OKPacket affected_rows (src/sql/mysql/protocol/OKPacket.zig:24-26). Choose one: return a JS BigInt/string for values > 2^53−1 or document/truncation as intended.

src/sql/mysql/MySQLQuery.zig (1)

224-230: Optionally keep queries_array alive before scheduling

Mirror the error path: retain queries_array to avoid GC races during enqueue.

-    event_loop.runCallback(function, globalObject, thisValue, &.{
+    queries_array.ensureStillAlive();
+    event_loop.runCallback(function, globalObject, thisValue, &.{
@@
-        queries_array,
+        queries_array,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b616fe1 and 4400f39.

📒 Files selected for processing (3)
  • src/sql/mysql/MySQLConnection.zig (19 hunks)
  • src/sql/mysql/MySQLQuery.zig (9 hunks)
  • src/sql/postgres/CommandTag.zig (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/sql/mysql/MySQLConnection.zig
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/postgres/CommandTag.zig
  • src/sql/mysql/MySQLQuery.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/postgres/CommandTag.zig
  • src/sql/mysql/MySQLQuery.zig
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Implement getters as get<PropertyName>(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JSC-bound classes

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/bun.js/api/BunObject.zig : Create a getter function in BunObject.zig (define const Getter with toJSGetter and export it in exportAll)

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-09-05T19:49:26.188Z
Learnt from: markovejnovic
PR: oven-sh/bun#21728
File: src/valkey/js_valkey_functions.zig:852-867
Timestamp: 2025-09-05T19:49:26.188Z
Learning: In Bun’s Zig code, `.js_undefined` is a valid and preferred JSValue literal for “undefined” (e.g., resolving JSPromise). Do not refactor usages to `jsc.JSValue.jsUndefined()`, especially in src/valkey/js_valkey_functions.zig unsubscribe().

Applied to files:

  • src/sql/postgres/CommandTag.zig
🔇 Additional comments (9)
src/sql/postgres/CommandTag.zig (1)

42-42: LGTM: safer JS string bridging for “other” tags

Switching to bun.String.createUTF8ForJS(globalObject, tag) avoids the temporary ZigString wrapper and aligns with current interop patterns in this PR. Looks good.

src/sql/mysql/MySQLQuery.zig (8)

2-2: RefCount migration LGTM

Switch to bun.ptr.RefCount with unchanged public API looks good.


64-66: Finalize ordering LGTM

Finalizing JSRef before deref’ing the Zig refcount is correct.


118-121: Binding cache access defaults LGTM

Using .js_undefined fallbacks for binding/columns is consistent with iterator semantics.


179-186: getTarget API LGTM

Nullable return and optional cache clean-up read clearly.


201-201: onResult signature LGTM

Parameters/order align with resolve path usage.


218-220: Callable guard LGTM

Validating onQueryResolveFn is callable is good defensive practice.


309-314: Fix for this context in setPendingValue LGTM

Using callframe.this() resolves the “this” binding pitfall.


349-349: Deferred connection.updateRef LGTM

Matches the new ref-driven lifecycle.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (5)
src/sql/postgres/PostgresSQLQuery.zig (2)

79-81: Clear all caches on failure to avoid leaks

On failure you only clear target via getTarget(clean_target=true). Also clear binding/pending (parity with allowGC on success).

-    defer this.thisValue.downgrade();
+    defer {
+        allowGC(thisValue, globalObject);
+        this.thisValue.downgrade();
+    }

96-99: Also clear caches on JS error path

Same reasoning as onWriteFail; free binding/pending caches before downgrading.

-    defer this.thisValue.downgrade();
+    defer {
+        allowGC(thisValue, globalObject);
+        this.thisValue.downgrade();
+    }
src/sql/mysql/MySQLQuery.zig (3)

75-89: Protect thisArg during callback enqueue and clear caches on failure

Take a temporary strong ref to thisValue while scheduling; also clear binding/pending caches before downgrade.

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    const thisValue = this.thisValue.tryGet() orelse return;
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
+    defer {
+        js.bindingSetCached(thisValue, globalObject, .zero);
+        js.pendingValueSetCached(thisValue, globalObject, .zero);
+        this.thisValue.downgrade();
+    }

161-177: Mirror failure-path protections in onJSError

Apply the same strong-ref and cache clearing here.

-    const thisValue = this.thisValue.tryGet() orelse return;
-    defer this.thisValue.downgrade();
+    const thisValue = this.thisValue.tryGet() orelse return;
+    var strong_this = jsc.Strong.Optional.create(thisValue, globalObject);
+    defer strong_this.deinit();
+    defer {
+        js.bindingSetCached(thisValue, globalObject, .zero);
+        js.pendingValueSetCached(thisValue, globalObject, .zero);
+        this.thisValue.downgrade();
+    }

215-221: Free caches on last result for parity and memory hygiene

Clear binding/pending when is_last before downgrade.

-    defer if (is_last) {
-        this.thisValue.downgrade();
-    };
+    defer if (is_last) {
+        js.bindingSetCached(thisValue, globalObject, .zero);
+        js.pendingValueSetCached(thisValue, globalObject, .zero);
+        this.thisValue.downgrade();
+    };
🧹 Nitpick comments (2)
src/sql/postgres/PostgresSQLQuery.zig (1)

26-33: Keep returned target alive when clearing cache

When clean_target is true, the cached slot is nulled; keep a temporary alive handle to avoid GC before enqueue.

-    const target = js.targetGetCached(thisValue) orelse return null;
+    const target = js.targetGetCached(thisValue) orelse return null;
+    target.ensureStillAlive();
src/sql/mysql/MySQLQuery.zig (1)

231-234: Normalize queries_array to undefined when zero

Prevents passing a zero handle into JS.

-        queries_array,
+        (if (queries_array == .zero) .js_undefined else queries_array),
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4400f39 and a6d7492.

📒 Files selected for processing (3)
  • src/sql/mysql/MySQLQuery.zig (8 hunks)
  • src/sql/postgres/CommandTag.zig (2 hunks)
  • src/sql/postgres/PostgresSQLQuery.zig (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/postgres/CommandTag.zig
  • src/sql/postgres/PostgresSQLQuery.zig
  • src/sql/mysql/MySQLQuery.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/postgres/CommandTag.zig
  • src/sql/postgres/PostgresSQLQuery.zig
  • src/sql/mysql/MySQLQuery.zig
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/{**/js_*.zig,bun.js/api/**/*.zig} : Use bun.JSError!JSValue for proper error propagation in JS-exposed Zig functions

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to **/*.zig : Wrap the Bun__<Type>__toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.{h,cpp} : If only a class (no public constructor/prototype), use JSC::LazyProperty<JSGlobalObject, Structure>, initialize it in finishCreation, and visit it in visitChildren

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Use parameter name globalObject (not ctx) and accept (*JSC.JSGlobalObject, *JSC.CallFrame) in binding methods/constructors

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : Implement getters as get<PropertyName>(this, globalObject) returning JSC.JSValue and matching the .classes.ts interface

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to src/bun.js/bindings/**/*.cpp : Cache structures in ZigGlobalObject for JSC-bound classes

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/bun.js/api/BunObject.zig : Create a getter function in BunObject.zig (define const Getter with toJSGetter and export it in exportAll)

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In Zig binding structs, expose generated bindings via pub const js = JSC.Codegen.JS<ClassName> and re-export toJS/fromJS/fromJSDirect

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:00.890Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/javascriptcore-class.mdc:0-0
Timestamp: 2025-08-30T00:11:00.890Z
Learning: Applies to src/bun.js/bindings/ZigGlobalObject.h : When there is a class, prototype, and constructor, add a JSC::LazyClassStructure field for the class to ZigGlobalObject.h

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-09-05T19:49:26.188Z
Learnt from: markovejnovic
PR: oven-sh/bun#21728
File: src/valkey/js_valkey_functions.zig:852-867
Timestamp: 2025-09-05T19:49:26.188Z
Learning: In Bun’s Zig code, `.js_undefined` is a valid and preferred JSValue literal for “undefined” (e.g., resolving JSPromise). Do not refactor usages to `jsc.JSValue.jsUndefined()`, especially in src/valkey/js_valkey_functions.zig unsubscribe().

Applied to files:

  • src/sql/postgres/CommandTag.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-09-08T04:44:59.101Z
Learnt from: CR
PR: oven-sh/bun#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-08T04:44:59.101Z
Learning: Applies to **/*.zig : In Zig, manage memory carefully with allocators and use defer for cleanup

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:11:57.076Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Always implement proper cleanup in deinit() and finalize() for JS-exposed types

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : In finalize() for objects holding JS references, release them using .deref() before destroy

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
📚 Learning: 2025-08-30T00:13:36.815Z
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/zig-javascriptcore-classes.mdc:0-0
Timestamp: 2025-08-30T00:13:36.815Z
Learning: Applies to src/bun.js/**/*.zig : For properties marked cache: true, use the generated Zig accessors (<Prop>NameSetCached/GetCached) to work with GC-owned values

Applied to files:

  • src/sql/postgres/PostgresSQLQuery.zig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Format
🔇 Additional comments (6)
src/sql/postgres/CommandTag.zig (1)

32-32: All callers updated — no action required

rg found usages in src/sql/mysql/MySQLQuery.zig and src/sql/postgres/PostgresSQLQuery.zig; both handle the error union using catch |e| return this.onJSError(...).

src/sql/postgres/PostgresSQLQuery.zig (1)

138-157: LGTM: safer tag conversion and error propagation

Using CommandTag.toJSTag with error handling and keeping js_tag alive is correct.

src/sql/mysql/MySQLQuery.zig (4)

118-121: Good: defaulting missing caches to js_undefined

Avoids .zero propagation during binding.


179-186: LGTM: getTarget optional return with cache clearing

Optional target with clean_target flag is a good simplification.


353-354: Connection ref mgmt switch to updateRef looks good

Defer-based updateRef aligns with new lifecycle.


313-318: setPendingValue: ignore this and use callframe.this is correct

Matches the new public API surface.

Comment thread src/sql/postgres/CommandTag.zig

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/sql/mysql/MySQLConnection.zig (1)

1310-1323: Fix on_connect this-binding/args to match public API

Same microtask calling convention issue as on_close: include explicit this first, then (null, js_value).

-            this.globalObject.queueMicrotask(on_connect, &[_]JSValue{ JSValue.jsNull(), js_value });
+            this.globalObject.queueMicrotask(on_connect, &[_]JSValue{ .js_undefined, JSValue.jsNull(), js_value });
♻️ Duplicate comments (2)
src/sql/mysql/MySQLConnection.zig (2)

364-393: Fix on_close this-binding and argument order passed to queueMicrotask

Currently js_error becomes the this value and shifts args. Pass an explicit this first, then (error, queries); this likely contributes to the reported segfault/timeout.

Apply:

-    this.globalObject.queueMicrotask(on_close, &[_]JSValue{ js_error, queries_array });
+    this.globalObject.queueMicrotask(on_close, &[_]JSValue{ .js_undefined, js_error, queries_array });

Optional: mirror Postgres by using on_close.call(this.globalObject, .js_undefined, &[_]JSValue{ js_error, queries_array }) and surface exceptions, if desired.


1898-1901: Guard optional before calling jsValue() to prevent trap/segfault

cached_structure can be null; cached_structure.?.jsValue() may trap. Guard and return .js_undefined when absent. This likely explains the Linux segfault noted by CI.

-                        cached_structure = if (this.js_value.tryGet()) |value| statement.structure(value, this.globalObject) else null;
-                        structure = cached_structure.?.jsValue() orelse .js_undefined;
+                        cached_structure = if (this.js_value.tryGet()) |value|
+                            statement.structure(value, this.globalObject)
+                        else
+                            null;
+                        structure = if (cached_structure) |cs|
+                            cs.jsValue() orelse .js_undefined
+                        else
+                            .js_undefined;
🧹 Nitpick comments (1)
src/sql/mysql/MySQLConnection.zig (1)

1329-1336: Minor: prefer an intent-revealing check for JSRef strength

If JSRef exposes a helper (e.g., isStrong()), use it over == .strong for readability. If not, ignore.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a6d7492 and c226a94.

📒 Files selected for processing (1)
  • src/sql/mysql/MySQLConnection.zig (20 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/building-bun.mdc)

Implement debug logs in Zig using const log = bun.Output.scoped(.${SCOPE}, false); and invoking log("...", .{})

Files:

  • src/sql/mysql/MySQLConnection.zig
**/*.zig

📄 CodeRabbit inference engine (.cursor/rules/javascriptcore-class.mdc)

**/*.zig: Declare the extern C symbol in Zig and export a Zig-friendly alias for use
Wrap the Bun____toJS extern in a Zig method that takes a JSGlobalObject and returns JSC.JSValue

**/*.zig: Format Zig files with zig-format (bun run zig-format)
In Zig, manage memory carefully with allocators and use defer for cleanup

Files:

  • src/sql/mysql/MySQLConnection.zig
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
PR: oven-sh/bun#0
File: .cursor/rules/registering-bun-modules.mdc:0-0
Timestamp: 2025-08-30T00:11:57.076Z
Learning: Applies to src/**/js_*.zig : Handle reference counting correctly with ref()/deref() in JS-facing Zig code
🔇 Additional comments (9)
src/sql/mysql/MySQLConnection.zig (9)

24-24: JSRef lifecycle upgrade/downgrade — looks good

Adopting jsc.JSRef and the upgrade/downgrade gating in updateReferenceType() is sane and aligns with request/state-driven liveness.

Also applies to: 124-132


227-237: Timeout messaging clarity — OK

Reworded messages for connecting/auth are clearer and preserve error codes.

Also applies to: 762-762


268-286: Finalize/ref/unref flow — OK

js_value.finalize() plus updating reference type in doRef/doUnref is correct given the new JSRef model.


351-355: Guarded access to queries array — OK

Using tryGet() and returning .js_undefined is safe; propagation to callbacks is consistent.

Also applies to: 1789-1790


429-430: Disconnect and deinit cleanups — OK

Deferred updateReferenceType() on disconnect and explicit request.deref() during deinit are correct.

Also applies to: 975-976


1012-1017: onOpen handshake transition + ref — OK

Setting .handshaking only for TCP and holding a ref for the socket aligns with upgrade semantics; updating ref type is appropriate.


1154-1160: Packet-boundary handling — OK

Using packet_length = header_length + PacketHeader.size, ensuring capacity, and deferring setOffsetFromStart(packet_length) avoids partial reads.


1282-1292: consumeOnConnect/consumeOnClose — OK

tryGet() + cache clearing + zero checks prevent stale callbacks and accidental re-entry.

Also applies to: 1296-1306


1908-1909: Pending value caching via tryGet — OK

Safe retrieval and conditional set avoid leaking intermediate row state.

Also applies to: 1926-1929

Comment thread src/sql/mysql/MySQLConnection.zig
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants