Skip to content

fix: switch from prepared statements to text protocol to prevent stat…#250

Open
charlypa wants to merge 3 commits intomainfrom
fix/prepared-statement-leak
Open

fix: switch from prepared statements to text protocol to prevent stat…#250
charlypa wants to merge 3 commits intomainfrom
fix/prepared-statement-leak

Conversation

@charlypa
Copy link
Copy Markdown
Member

@charlypa charlypa commented Feb 11, 2026

Fix: Revert to execute() with safe maxPreparedStatements limit

Summary

  • Reverts the execute() to query() migration and the JSON.stringify() workaround — both were unnecessary
  • Fixes the prepared statement leak by setting maxPreparedStatements = 200 per connection, leveraging mysql2's built-in LRU cache for automatic deallocation

Problem

The prepared statement leak was caused by mysql2's default maxPreparedStatements of 16,000 per connection. With a pool of 10 connections, that allows up to 160,000 server-side prepared statements — far exceeding MySQL's global max_prepared_stmt_count (16,382).

The previous fix switched all execute() calls to query() (text protocol), which avoided prepared statements entirely but introduced a side-effect: object parameters were no longer auto-serialized, requiring JSON.stringify() workarounds in put() and update().

Solution

mysql2 already has a built-in LRU cache that automatically calls statement.close() (sends COM_STMT_CLOSE) when the cache is full. So execute() is safe — we just need a conservative maxPreparedStatements per connection.

With connectionLimit = 10 and maxPreparedStatements = 200, the max server-side total is 2,000 — well under the 16,382 limit.

Changes

File Change
src/utils/db.js Reverted all 18 query() calls back to execute()
src/utils/db.js Removed JSON.stringify() workarounds from put() and update()
src/utils/db.js Kept maxPreparedStatements = 200 in pool config
test/unit/setup-mocks.js Reverted mock from query back to execute
test/unit/utils/db-test.spec.js Reverted test mocks back to execute
test/unit/utils/db-security-test.spec.js Reverted test mocks back to execute
test/unit/utils/list-apis-test.spec.js Reverted test mocks back to execute

Test plan

  • All 206 libmysql unit tests pass
  • All 178 cocodb unit tests pass
  • Integration test with MySQL to verify prepared statements stay under limit

…ement leak

Replace all CONNECTION.execute() calls with CONNECTION.query() in db.js
to use MySQL text protocol instead of prepared statements. This prevents
the server from accumulating prepared statements that exceed
max_prepared_stmt_count (16382), especially for dynamic queries with
varying field names. Also adds maxPreparedStatements safety net in pool
config.
query() escapes JS objects as SQL SET-clauses instead of JSON strings,
breaking INSERT/UPDATE. Wrap document with JSON.stringify() in put() and
update() to serialize correctly.
@charlypa charlypa force-pushed the fix/prepared-statement-leak branch from 377e05a to 0e630b0 Compare February 11, 2026 14:30
Revert the execute→query migration (473d82e) and the JSON.stringify
workaround (0e630b0). The prepared statement leak is properly fixed by
setting maxPreparedStatements=200 per connection, which keeps the
server-side total well under max_prepared_stmt_count (16382) while
leveraging mysql2's built-in LRU cache for automatic deallocation.
@charlypa charlypa force-pushed the fix/prepared-statement-leak branch from 853e267 to 5f0b6a6 Compare February 11, 2026 14:47
@sonarqubecloud
Copy link
Copy Markdown

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.

1 participant