From e99d034e337c232b2cda324989ac3aec89b56e36 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 28 Aug 2025 07:05:47 +0000 Subject: [PATCH] refactor: extract SQL performance tracking into shared logger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Create SQLPerformanceEntryLogger struct to eliminate code duplication between MySQL and PostgreSQL query implementations. This shared logger handles performance timing and reporting for SQL operations across different database adapters. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- cmake/sources/ZigSources.txt | 1 + src/sql/SQLPerformanceEntryLogger.zig | 91 +++++++++++++++++++++++++++ src/sql/mysql/MySQLQuery.zig | 66 +++---------------- src/sql/postgres/PostgresSQLQuery.zig | 64 ++----------------- 4 files changed, 104 insertions(+), 118 deletions(-) create mode 100644 src/sql/SQLPerformanceEntryLogger.zig diff --git a/cmake/sources/ZigSources.txt b/cmake/sources/ZigSources.txt index e6b5484f8a..df45d6be65 100644 --- a/cmake/sources/ZigSources.txt +++ b/cmake/sources/ZigSources.txt @@ -1002,6 +1002,7 @@ src/sql/shared/ObjectIterator.zig src/sql/shared/QueryBindingIterator.zig src/sql/shared/SQLDataCell.zig src/sql/shared/SQLQueryResultMode.zig +src/sql/SQLPerformanceEntryLogger.zig src/StandaloneModuleGraph.zig src/StaticHashMap.zig src/string.zig diff --git a/src/sql/SQLPerformanceEntryLogger.zig b/src/sql/SQLPerformanceEntryLogger.zig new file mode 100644 index 0000000000..ee318403a1 --- /dev/null +++ b/src/sql/SQLPerformanceEntryLogger.zig @@ -0,0 +1,91 @@ +const std = @import("std"); +const bun = @import("bun"); +const jsc = bun.jsc; + +extern "C" fn JSC__addSQLQueryPerformanceEntry(globalObject: *jsc.JSGlobalObject, name: [*:0]const u8, description: [*:0]const u8, startTime: f64, endTime: f64) void; + +/// Shared SQL performance entry logger for tracking query performance across different SQL adapters +pub const SQLPerformanceEntryLogger = struct { + /// Start time for performance tracking (in nanoseconds) + start_time_ns: u64 = 0, + + const Self = @This(); + + /// Extract the SQL command from the query string (e.g., "SELECT", "INSERT", etc.) + fn extractSQLCommand(query: []const u8) []const u8 { + if (query.len == 0) return "UNKNOWN"; + + var i: usize = 0; + // Skip leading whitespace + while (i < query.len and std.ascii.isWhitespace(query[i])) { + i += 1; + } + + const start_pos = i; + // Find the end of the first word + while (i < query.len and !std.ascii.isWhitespace(query[i])) { + i += 1; + } + + if (i <= start_pos) return "UNKNOWN"; + + const command = query[start_pos..i]; + + // Convert common commands to uppercase + if (std.ascii.eqlIgnoreCase(command, "select")) return "SELECT"; + if (std.ascii.eqlIgnoreCase(command, "insert")) return "INSERT"; + if (std.ascii.eqlIgnoreCase(command, "update")) return "UPDATE"; + if (std.ascii.eqlIgnoreCase(command, "delete")) return "DELETE"; + if (std.ascii.eqlIgnoreCase(command, "create")) return "CREATE"; + if (std.ascii.eqlIgnoreCase(command, "drop")) return "DROP"; + if (std.ascii.eqlIgnoreCase(command, "alter")) return "ALTER"; + if (std.ascii.eqlIgnoreCase(command, "show")) return "SHOW"; + if (std.ascii.eqlIgnoreCase(command, "describe") or std.ascii.eqlIgnoreCase(command, "desc")) return "DESCRIBE"; + if (std.ascii.eqlIgnoreCase(command, "explain")) return "EXPLAIN"; + if (std.ascii.eqlIgnoreCase(command, "truncate")) return "TRUNCATE"; + if (std.ascii.eqlIgnoreCase(command, "grant")) return "GRANT"; + if (std.ascii.eqlIgnoreCase(command, "revoke")) return "REVOKE"; + + return "UNKNOWN"; + } + + /// Start performance tracking for a query + pub fn start(self: *Self) void { + self.start_time_ns = @as(u64, @intCast(@max(0, std.time.nanoTimestamp()))); + } + + /// End performance tracking and report to the performance API + pub fn end(self: *Self, performance_entries_enabled: bool, query: bun.String, globalObject: *jsc.JSGlobalObject) void { + if (!performance_entries_enabled or self.start_time_ns == 0) return; + + const end_time_ns = @as(u64, @intCast(@max(0, std.time.nanoTimestamp()))); + const start_time_ms = @as(f64, @floatFromInt(self.start_time_ns)) / 1_000_000.0; + const end_time_ms = @as(f64, @floatFromInt(end_time_ns)) / 1_000_000.0; + + // Get the SQL command and query string + var query_utf8 = query.toUTF8(bun.default_allocator); + defer query_utf8.deinit(); + + const command = extractSQLCommand(query_utf8.slice()); + + // Create null-terminated strings for the C function + const command_cstr = bun.default_allocator.dupeZ(u8, command) catch return; + defer bun.default_allocator.free(command_cstr); + + const query_cstr = bun.default_allocator.dupeZ(u8, query_utf8.slice()) catch return; + defer bun.default_allocator.free(query_cstr); + + // Call the C++ binding to add the performance entry + JSC__addSQLQueryPerformanceEntry(globalObject, command_cstr.ptr, query_cstr.ptr, start_time_ms, end_time_ms); + } + + /// Initialize a new logger instance + pub fn init() Self { + return Self{}; + } + + /// Reset the logger (clear start time) + pub fn reset(self: *Self) void { + self.start_time_ns = 0; + } +}; \ No newline at end of file diff --git a/src/sql/mysql/MySQLQuery.zig b/src/sql/mysql/MySQLQuery.zig index b5efe1ca73..0bdeeb9bcf 100644 --- a/src/sql/mysql/MySQLQuery.zig +++ b/src/sql/mysql/MySQLQuery.zig @@ -1,6 +1,9 @@ const MySQLQuery = @This(); const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{}); +const std = @import("std"); +const SQLPerformanceEntryLogger = @import("../SQLPerformanceEntryLogger.zig").SQLPerformanceEntryLogger; + extern "C" fn JSC__addSQLQueryPerformanceEntry(globalObject: *jsc.JSGlobalObject, name: [*:0]const u8, description: [*:0]const u8, startTime: f64, endTime: f64) void; statement: ?*MySQLStatement = null, @@ -12,8 +15,8 @@ status: Status = Status.pending, ref_count: RefCount = RefCount.init(), -/// Start time for performance tracking (in nanoseconds) -start_time_ns: u64 = 0, +/// Performance tracking logger +performance_logger: SQLPerformanceEntryLogger = SQLPerformanceEntryLogger.init(), flags: packed struct(u8) { is_done: bool = false, @@ -51,69 +54,14 @@ pub fn hasPendingActivity(this: *@This()) bool { return this.ref_count.load(.monotonic) > 1; } -/// Extract the SQL command from the query string (e.g., "SELECT", "INSERT", etc.) -fn extractSQLCommand(query: []const u8) []const u8 { - if (query.len == 0) return "UNKNOWN"; - - var i: usize = 0; - // Skip leading whitespace - while (i < query.len and std.ascii.isWhitespace(query[i])) { - i += 1; - } - - const start = i; - // Find the end of the first word - while (i < query.len and !std.ascii.isWhitespace(query[i])) { - i += 1; - } - - if (i <= start) return "UNKNOWN"; - - const command = query[start..i]; - - // Convert common commands to uppercase - if (std.ascii.eqlIgnoreCase(command, "select")) return "SELECT"; - if (std.ascii.eqlIgnoreCase(command, "insert")) return "INSERT"; - if (std.ascii.eqlIgnoreCase(command, "update")) return "UPDATE"; - if (std.ascii.eqlIgnoreCase(command, "delete")) return "DELETE"; - if (std.ascii.eqlIgnoreCase(command, "create")) return "CREATE"; - if (std.ascii.eqlIgnoreCase(command, "drop")) return "DROP"; - if (std.ascii.eqlIgnoreCase(command, "alter")) return "ALTER"; - if (std.ascii.eqlIgnoreCase(command, "show")) return "SHOW"; - if (std.ascii.eqlIgnoreCase(command, "describe") or std.ascii.eqlIgnoreCase(command, "desc")) return "DESCRIBE"; - if (std.ascii.eqlIgnoreCase(command, "explain")) return "EXPLAIN"; - - return "UNKNOWN"; -} - /// Start performance tracking for this query pub fn startPerformanceTracking(this: *@This()) void { - this.start_time_ns = @as(u64, @intCast(@max(0, std.time.nanoTimestamp()))); + this.performance_logger.start(); } /// End performance tracking and report to the performance API pub fn endPerformanceTracking(this: *@This(), connection: anytype, globalObject: *jsc.JSGlobalObject) void { - if (!connection.performance_entries_enabled or this.start_time_ns == 0) return; - - const end_time_ns = @as(u64, @intCast(@max(0, std.time.nanoTimestamp()))); - const start_time_ms = @as(f64, @floatFromInt(this.start_time_ns)) / 1_000_000.0; - const end_time_ms = @as(f64, @floatFromInt(end_time_ns)) / 1_000_000.0; - - // Get the SQL command and query string - var query_utf8 = this.query.toUTF8(bun.default_allocator); - defer query_utf8.deinit(); - - const command = extractSQLCommand(query_utf8.slice()); - - // Create null-terminated strings for the C function - const command_cstr = bun.default_allocator.dupeZ(u8, command) catch return; - defer bun.default_allocator.free(command_cstr); - - const query_cstr = bun.default_allocator.dupeZ(u8, query_utf8.slice()) catch return; - defer bun.default_allocator.free(query_cstr); - - // Call the C++ binding to add the performance entry - JSC__addSQLQueryPerformanceEntry(globalObject, command_cstr.ptr, query_cstr.ptr, start_time_ms, end_time_ms); + this.performance_logger.end(connection.performance_entries_enabled, this.query, globalObject); } pub fn deinit(this: *@This()) void { diff --git a/src/sql/postgres/PostgresSQLQuery.zig b/src/sql/postgres/PostgresSQLQuery.zig index cfa1b8c7eb..e90d9414b0 100644 --- a/src/sql/postgres/PostgresSQLQuery.zig +++ b/src/sql/postgres/PostgresSQLQuery.zig @@ -1,6 +1,8 @@ const PostgresSQLQuery = @This(); const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{}); +const std = @import("std"); +const SQLPerformanceEntryLogger = @import("../SQLPerformanceEntryLogger.zig").SQLPerformanceEntryLogger; extern "C" fn JSC__addSQLQueryPerformanceEntry(globalObject: *jsc.JSGlobalObject, name: [*:0]const u8, description: [*:0]const u8, startTime: f64, endTime: f64) void; statement: ?*PostgresSQLStatement = null, query: bun.String = bun.String.empty, @@ -12,8 +14,7 @@ status: Status = Status.pending, ref_count: RefCount = RefCount.init(), -/// Start time for performance tracking (in nanoseconds) -start_time_ns: u64 = 0, +performance_logger: SQLPerformanceEntryLogger = SQLPerformanceEntryLogger.init(), flags: packed struct(u8) { is_done: bool = false, @@ -60,69 +61,14 @@ pub fn hasPendingActivity(this: *@This()) bool { return this.ref_count.get() > 1; } -/// Extract the SQL command from the query string (e.g., "SELECT", "INSERT", etc.) -fn extractSQLCommand(query: []const u8) []const u8 { - if (query.len == 0) return "UNKNOWN"; - - var i: usize = 0; - // Skip leading whitespace - while (i < query.len and std.ascii.isWhitespace(query[i])) { - i += 1; - } - - const start = i; - // Find the end of the first word - while (i < query.len and !std.ascii.isWhitespace(query[i])) { - i += 1; - } - - if (i <= start) return "UNKNOWN"; - - const command = query[start..i]; - - // Convert common commands to uppercase - if (std.ascii.eqlIgnoreCase(command, "select")) return "SELECT"; - if (std.ascii.eqlIgnoreCase(command, "insert")) return "INSERT"; - if (std.ascii.eqlIgnoreCase(command, "update")) return "UPDATE"; - if (std.ascii.eqlIgnoreCase(command, "delete")) return "DELETE"; - if (std.ascii.eqlIgnoreCase(command, "create")) return "CREATE"; - if (std.ascii.eqlIgnoreCase(command, "drop")) return "DROP"; - if (std.ascii.eqlIgnoreCase(command, "alter")) return "ALTER"; - if (std.ascii.eqlIgnoreCase(command, "show")) return "SHOW"; - if (std.ascii.eqlIgnoreCase(command, "describe") or std.ascii.eqlIgnoreCase(command, "desc")) return "DESCRIBE"; - if (std.ascii.eqlIgnoreCase(command, "explain")) return "EXPLAIN"; - - return "UNKNOWN"; -} - /// Start performance tracking for this query pub fn startPerformanceTracking(this: *@This()) void { - this.start_time_ns = @as(u64, @intCast(@max(0, std.time.nanoTimestamp()))); + this.performance_logger.start(); } /// End performance tracking and report to the performance API pub fn endPerformanceTracking(this: *@This(), connection: anytype, globalObject: *jsc.JSGlobalObject) void { - if (!connection.performance_entries_enabled or this.start_time_ns == 0) return; - - const end_time_ns = @as(u64, @intCast(@max(0, std.time.nanoTimestamp()))); - const start_time_ms = @as(f64, @floatFromInt(this.start_time_ns)) / 1_000_000.0; - const end_time_ms = @as(f64, @floatFromInt(end_time_ns)) / 1_000_000.0; - - // Get the SQL command and query string - var query_utf8 = this.query.toUTF8(bun.default_allocator); - defer query_utf8.deinit(); - - const command = extractSQLCommand(query_utf8.slice()); - - // Create null-terminated strings for the C function - const command_cstr = bun.default_allocator.dupeZ(u8, command) catch return; - defer bun.default_allocator.free(command_cstr); - - const query_cstr = bun.default_allocator.dupeZ(u8, query_utf8.slice()) catch return; - defer bun.default_allocator.free(query_cstr); - - // Call the C++ binding to add the performance entry - JSC__addSQLQueryPerformanceEntry(globalObject, command_cstr.ptr, query_cstr.ptr, start_time_ms, end_time_ms); + this.performance_logger.end(connection.performance_entries_enabled, this.query, globalObject); } pub fn deinit(this: *@This()) void {