From b79bbfe2894041a6914a1ec817b87de2290ddae3 Mon Sep 17 00:00:00 2001 From: Ciro Spaciari Date: Wed, 3 Sep 2025 18:59:15 -0700 Subject: [PATCH] fix(Bun.SQL) fix SSLRequest (#22378) ### What does this PR do? Fixes https://github.com/oven-sh/bun/issues/22312 Fixes https://github.com/oven-sh/bun/issues/22313 The correct flow for TLS handshaking is: Server sending [Protocol::Handshake](https://dev.mysql.com/doc/dev/mysql-server/8.4.5/page_protocol_connection_phase_packets_protocol_handshake.html) Client replying with [Protocol::SSLRequest:](https://dev.mysql.com/doc/dev/mysql-server/8.4.5/page_protocol_connection_phase_packets_protocol_ssl_request.html) The usual SSL exchange leading to establishing SSL connection Client sends [Protocol::HandshakeResponse:](https://dev.mysql.com/doc/dev/mysql-server/8.4.5/page_protocol_connection_phase_packets_protocol_handshake_response.html) Screenshot 2025-09-03 at 15 02 25 Source: https://dev.mysql.com/doc/dev/mysql-server/8.4.5/page_protocol_connection_phase.html ### How did you verify your code works? Tests --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- cmake/sources/ZigSources.txt | 2137 +++++++++-------- src/bun.js/event_loop.zig | 2 +- src/sql/mysql/MySQLConnection.zig | 90 +- src/sql/mysql/TLSStatus.zig | 3 +- .../mysql/protocol/HandshakeResponse41.zig | 5 +- src/sql/mysql/protocol/SSLRequest.zig | 42 + src/sql/postgres/PostgresSQLConnection.zig | 5 +- test/harness.ts | 19 + test/js/sql/mysql-tls/Dockerfile | 22 + test/js/sql/mysql-tls/conf.d/ssl.cnf | 7 + test/js/sql/mysql-tls/ssl/ca-key.pem | 52 + test/js/sql/mysql-tls/ssl/ca.pem | 30 + test/js/sql/mysql-tls/ssl/ca.srl | 1 + test/js/sql/mysql-tls/ssl/server-cert.pem | 31 + test/js/sql/mysql-tls/ssl/server-ext.cnf | 4 + test/js/sql/mysql-tls/ssl/server-key.pem | 52 + test/js/sql/mysql-tls/ssl/server.csr | 26 + test/js/sql/sql-mysql.test.ts | 1660 ++++++------- test/js/sql/sql.test.ts | 21 +- 19 files changed, 2288 insertions(+), 1921 deletions(-) create mode 100644 src/sql/mysql/protocol/SSLRequest.zig create mode 100644 test/js/sql/mysql-tls/Dockerfile create mode 100644 test/js/sql/mysql-tls/conf.d/ssl.cnf create mode 100644 test/js/sql/mysql-tls/ssl/ca-key.pem create mode 100644 test/js/sql/mysql-tls/ssl/ca.pem create mode 100644 test/js/sql/mysql-tls/ssl/ca.srl create mode 100644 test/js/sql/mysql-tls/ssl/server-cert.pem create mode 100644 test/js/sql/mysql-tls/ssl/server-ext.cnf create mode 100644 test/js/sql/mysql-tls/ssl/server-key.pem create mode 100644 test/js/sql/mysql-tls/ssl/server.csr diff --git a/cmake/sources/ZigSources.txt b/cmake/sources/ZigSources.txt index 25f6e46937..f27dae7fbf 100644 --- a/cmake/sources/ZigSources.txt +++ b/cmake/sources/ZigSources.txt @@ -1,1068 +1,1069 @@ -src/allocators.zig -src/allocators/allocation_scope.zig -src/allocators/basic.zig -src/allocators/fallback.zig -src/allocators/fallback/z.zig -src/allocators/LinuxMemFdAllocator.zig -src/allocators/MaxHeapAllocator.zig -src/allocators/maybe_owned.zig -src/allocators/MemoryReportingAllocator.zig -src/allocators/mimalloc.zig -src/allocators/MimallocArena.zig -src/allocators/NullableAllocator.zig -src/analytics.zig -src/analytics/schema.zig -src/api/schema.zig -src/asan.zig -src/ast.zig -src/ast/Ast.zig -src/ast/ASTMemoryAllocator.zig -src/ast/B.zig -src/ast/base.zig -src/ast/Binding.zig -src/ast/BundledAst.zig -src/ast/CharFreq.zig -src/ast/ConvertESMExportsForHmr.zig -src/ast/E.zig -src/ast/Expr.zig -src/ast/foldStringAddition.zig -src/ast/G.zig -src/ast/ImportScanner.zig -src/ast/KnownGlobal.zig -src/ast/Macro.zig -src/ast/maybe.zig -src/ast/NewStore.zig -src/ast/Op.zig -src/ast/P.zig -src/ast/parse.zig -src/ast/parseFn.zig -src/ast/parseImportExport.zig -src/ast/parseJSXElement.zig -src/ast/parsePrefix.zig -src/ast/parseProperty.zig -src/ast/Parser.zig -src/ast/parseStmt.zig -src/ast/parseSuffix.zig -src/ast/parseTypescript.zig -src/ast/S.zig -src/ast/Scope.zig -src/ast/ServerComponentBoundary.zig -src/ast/SideEffects.zig -src/ast/skipTypescript.zig -src/ast/Stmt.zig -src/ast/Symbol.zig -src/ast/symbols.zig -src/ast/TS.zig -src/ast/TypeScript.zig -src/ast/UseDirective.zig -src/ast/visit.zig -src/ast/visitBinaryExpression.zig -src/ast/visitExpr.zig -src/ast/visitStmt.zig -src/async/posix_event_loop.zig -src/async/stub_event_loop.zig -src/async/windows_event_loop.zig -src/bake.zig -src/bake/DevServer.zig -src/bake/DevServer/Assets.zig -src/bake/DevServer/DirectoryWatchStore.zig -src/bake/DevServer/ErrorReportRequest.zig -src/bake/DevServer/HmrSocket.zig -src/bake/DevServer/HotReloadEvent.zig -src/bake/DevServer/IncrementalGraph.zig -src/bake/DevServer/memory_cost.zig -src/bake/DevServer/PackedMap.zig -src/bake/DevServer/RouteBundle.zig -src/bake/DevServer/SerializedFailure.zig -src/bake/DevServer/SourceMapStore.zig -src/bake/DevServer/WatcherAtomics.zig -src/bake/FrameworkRouter.zig -src/bake/production.zig -src/base64/base64.zig -src/bits.zig -src/boringssl.zig -src/brotli.zig -src/btjs.zig -src/bun.js.zig -src/bun.js/api.zig -src/bun.js/api/bun/dns.zig -src/bun.js/api/bun/h2_frame_parser.zig -src/bun.js/api/bun/lshpack.zig -src/bun.js/api/bun/process.zig -src/bun.js/api/bun/socket.zig -src/bun.js/api/bun/socket/Handlers.zig -src/bun.js/api/bun/socket/Listener.zig -src/bun.js/api/bun/socket/SocketAddress.zig -src/bun.js/api/bun/socket/tls_socket_functions.zig -src/bun.js/api/bun/socket/WindowsNamedPipeContext.zig -src/bun.js/api/bun/spawn.zig -src/bun.js/api/bun/spawn/stdio.zig -src/bun.js/api/bun/ssl_wrapper.zig -src/bun.js/api/bun/subprocess.zig -src/bun.js/api/bun/subprocess/Readable.zig -src/bun.js/api/bun/subprocess/ResourceUsage.zig -src/bun.js/api/bun/subprocess/StaticPipeWriter.zig -src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig -src/bun.js/api/bun/subprocess/Writable.zig -src/bun.js/api/bun/udp_socket.zig -src/bun.js/api/bun/x509.zig -src/bun.js/api/BunObject.zig -src/bun.js/api/crypto.zig -src/bun.js/api/crypto/CryptoHasher.zig -src/bun.js/api/crypto/EVP.zig -src/bun.js/api/crypto/HMAC.zig -src/bun.js/api/crypto/PasswordObject.zig -src/bun.js/api/crypto/PBKDF2.zig -src/bun.js/api/ffi.zig -src/bun.js/api/FFIObject.zig -src/bun.js/api/filesystem_router.zig -src/bun.js/api/glob.zig -src/bun.js/api/HashObject.zig -src/bun.js/api/html_rewriter.zig -src/bun.js/api/JSBundler.zig -src/bun.js/api/JSTranspiler.zig -src/bun.js/api/server.zig -src/bun.js/api/server/AnyRequestContext.zig -src/bun.js/api/server/FileRoute.zig -src/bun.js/api/server/HTMLBundle.zig -src/bun.js/api/server/HTTPStatusText.zig -src/bun.js/api/server/InspectorBunFrontendDevServerAgent.zig -src/bun.js/api/server/NodeHTTPResponse.zig -src/bun.js/api/server/RequestContext.zig -src/bun.js/api/server/ServerConfig.zig -src/bun.js/api/server/ServerWebSocket.zig -src/bun.js/api/server/SSLConfig.zig -src/bun.js/api/server/StaticRoute.zig -src/bun.js/api/server/WebSocketServerContext.zig -src/bun.js/api/streams.classes.zig -src/bun.js/api/Timer.zig -src/bun.js/api/Timer/DateHeaderTimer.zig -src/bun.js/api/Timer/EventLoopTimer.zig -src/bun.js/api/Timer/ImmediateObject.zig -src/bun.js/api/Timer/TimeoutObject.zig -src/bun.js/api/Timer/TimerObjectInternals.zig -src/bun.js/api/Timer/WTFTimer.zig -src/bun.js/api/TOMLObject.zig -src/bun.js/api/UnsafeObject.zig -src/bun.js/api/YAMLObject.zig -src/bun.js/bindgen_test.zig -src/bun.js/bindings/AbortSignal.zig -src/bun.js/bindings/AnyPromise.zig -src/bun.js/bindings/bun-simdutf.zig -src/bun.js/bindings/CachedBytecode.zig -src/bun.js/bindings/CallFrame.zig -src/bun.js/bindings/CatchScope.zig -src/bun.js/bindings/codegen.zig -src/bun.js/bindings/CommonAbortReason.zig -src/bun.js/bindings/CommonStrings.zig -src/bun.js/bindings/CPUFeatures.zig -src/bun.js/bindings/CustomGetterSetter.zig -src/bun.js/bindings/DeferredError.zig -src/bun.js/bindings/DOMFormData.zig -src/bun.js/bindings/DOMURL.zig -src/bun.js/bindings/EncodedJSValue.zig -src/bun.js/bindings/Errorable.zig -src/bun.js/bindings/ErrorCode.zig -src/bun.js/bindings/EventType.zig -src/bun.js/bindings/Exception.zig -src/bun.js/bindings/FetchHeaders.zig -src/bun.js/bindings/FFI.zig -src/bun.js/bindings/generated_classes_list.zig -src/bun.js/bindings/GetterSetter.zig -src/bun.js/bindings/HTTPServerAgent.zig -src/bun.js/bindings/JSArray.zig -src/bun.js/bindings/JSArrayIterator.zig -src/bun.js/bindings/JSBigInt.zig -src/bun.js/bindings/JSCell.zig -src/bun.js/bindings/JSErrorCode.zig -src/bun.js/bindings/JSFunction.zig -src/bun.js/bindings/JSGlobalObject.zig -src/bun.js/bindings/JSInternalPromise.zig -src/bun.js/bindings/JSMap.zig -src/bun.js/bindings/JSModuleLoader.zig -src/bun.js/bindings/JSObject.zig -src/bun.js/bindings/JSPromise.zig -src/bun.js/bindings/JSPromiseRejectionOperation.zig -src/bun.js/bindings/JSPropertyIterator.zig -src/bun.js/bindings/JSRef.zig -src/bun.js/bindings/JSRuntimeType.zig -src/bun.js/bindings/JSSecrets.zig -src/bun.js/bindings/JSString.zig -src/bun.js/bindings/JSType.zig -src/bun.js/bindings/JSUint8Array.zig -src/bun.js/bindings/JSValue.zig -src/bun.js/bindings/MarkedArgumentBuffer.zig -src/bun.js/bindings/NodeModuleModule.zig -src/bun.js/bindings/RegularExpression.zig -src/bun.js/bindings/ResolvedSource.zig -src/bun.js/bindings/ScriptExecutionStatus.zig -src/bun.js/bindings/sizes.zig -src/bun.js/bindings/SourceProvider.zig -src/bun.js/bindings/SourceType.zig -src/bun.js/bindings/static_export.zig -src/bun.js/bindings/StringBuilder.zig -src/bun.js/bindings/SystemError.zig -src/bun.js/bindings/TextCodec.zig -src/bun.js/bindings/URL.zig -src/bun.js/bindings/URLSearchParams.zig -src/bun.js/bindings/VM.zig -src/bun.js/bindings/WTF.zig -src/bun.js/bindings/ZigErrorType.zig -src/bun.js/bindings/ZigException.zig -src/bun.js/bindings/ZigStackFrame.zig -src/bun.js/bindings/ZigStackFrameCode.zig -src/bun.js/bindings/ZigStackFramePosition.zig -src/bun.js/bindings/ZigStackTrace.zig -src/bun.js/bindings/ZigString.zig -src/bun.js/BuildMessage.zig -src/bun.js/config.zig -src/bun.js/ConsoleObject.zig -src/bun.js/Counters.zig -src/bun.js/Debugger.zig -src/bun.js/event_loop.zig -src/bun.js/event_loop/AnyEventLoop.zig -src/bun.js/event_loop/AnyTask.zig -src/bun.js/event_loop/AnyTaskWithExtraContext.zig -src/bun.js/event_loop/ConcurrentPromiseTask.zig -src/bun.js/event_loop/ConcurrentTask.zig -src/bun.js/event_loop/CppTask.zig -src/bun.js/event_loop/DeferredTaskQueue.zig -src/bun.js/event_loop/EventLoopHandle.zig -src/bun.js/event_loop/GarbageCollectionController.zig -src/bun.js/event_loop/JSCScheduler.zig -src/bun.js/event_loop/ManagedTask.zig -src/bun.js/event_loop/MiniEventLoop.zig -src/bun.js/event_loop/PosixSignalHandle.zig -src/bun.js/event_loop/Task.zig -src/bun.js/event_loop/WorkTask.zig -src/bun.js/hot_reloader.zig -src/bun.js/ipc.zig -src/bun.js/javascript_core_c_api.zig -src/bun.js/jsc.zig -src/bun.js/jsc/array_buffer.zig -src/bun.js/jsc/dom_call.zig -src/bun.js/jsc/host_fn.zig -src/bun.js/jsc/RefString.zig -src/bun.js/ModuleLoader.zig -src/bun.js/node.zig -src/bun.js/node/assert/myers_diff.zig -src/bun.js/node/buffer.zig -src/bun.js/node/dir_iterator.zig -src/bun.js/node/fs_events.zig -src/bun.js/node/net/BlockList.zig -src/bun.js/node/node_assert_binding.zig -src/bun.js/node/node_assert.zig -src/bun.js/node/node_cluster_binding.zig -src/bun.js/node/node_crypto_binding.zig -src/bun.js/node/node_error_binding.zig -src/bun.js/node/node_fs_binding.zig -src/bun.js/node/node_fs_constant.zig -src/bun.js/node/node_fs_stat_watcher.zig -src/bun.js/node/node_fs_watcher.zig -src/bun.js/node/node_fs.zig -src/bun.js/node/node_http_binding.zig -src/bun.js/node/node_net_binding.zig -src/bun.js/node/node_os.zig -src/bun.js/node/node_process.zig -src/bun.js/node/node_util_binding.zig -src/bun.js/node/node_zlib_binding.zig -src/bun.js/node/nodejs_error_code.zig -src/bun.js/node/os/constants.zig -src/bun.js/node/path_watcher.zig -src/bun.js/node/path.zig -src/bun.js/node/Stat.zig -src/bun.js/node/StatFS.zig -src/bun.js/node/time_like.zig -src/bun.js/node/types.zig -src/bun.js/node/util/parse_args_utils.zig -src/bun.js/node/util/parse_args.zig -src/bun.js/node/util/validators.zig -src/bun.js/node/win_watcher.zig -src/bun.js/node/zlib/NativeBrotli.zig -src/bun.js/node/zlib/NativeZlib.zig -src/bun.js/node/zlib/NativeZstd.zig -src/bun.js/ProcessAutoKiller.zig -src/bun.js/rare_data.zig -src/bun.js/ResolveMessage.zig -src/bun.js/RuntimeTranspilerCache.zig -src/bun.js/SavedSourceMap.zig -src/bun.js/Strong.zig -src/bun.js/test/diff_format.zig -src/bun.js/test/diff/diff_match_patch.zig -src/bun.js/test/diff/printDiff.zig -src/bun.js/test/expect.zig -src/bun.js/test/expect/toBe.zig -src/bun.js/test/expect/toBeArray.zig -src/bun.js/test/expect/toBeArrayOfSize.zig -src/bun.js/test/expect/toBeBoolean.zig -src/bun.js/test/expect/toBeCloseTo.zig -src/bun.js/test/expect/toBeDate.zig -src/bun.js/test/expect/toBeDefined.zig -src/bun.js/test/expect/toBeEmpty.zig -src/bun.js/test/expect/toBeEmptyObject.zig -src/bun.js/test/expect/toBeEven.zig -src/bun.js/test/expect/toBeFalse.zig -src/bun.js/test/expect/toBeFalsy.zig -src/bun.js/test/expect/toBeFinite.zig -src/bun.js/test/expect/toBeFunction.zig -src/bun.js/test/expect/toBeGreaterThan.zig -src/bun.js/test/expect/toBeGreaterThanOrEqual.zig -src/bun.js/test/expect/toBeInstanceOf.zig -src/bun.js/test/expect/toBeInteger.zig -src/bun.js/test/expect/toBeLessThan.zig -src/bun.js/test/expect/toBeLessThanOrEqual.zig -src/bun.js/test/expect/toBeNaN.zig -src/bun.js/test/expect/toBeNegative.zig -src/bun.js/test/expect/toBeNil.zig -src/bun.js/test/expect/toBeNull.zig -src/bun.js/test/expect/toBeNumber.zig -src/bun.js/test/expect/toBeObject.zig -src/bun.js/test/expect/toBeOdd.zig -src/bun.js/test/expect/toBeOneOf.zig -src/bun.js/test/expect/toBePositive.zig -src/bun.js/test/expect/toBeString.zig -src/bun.js/test/expect/toBeSymbol.zig -src/bun.js/test/expect/toBeTrue.zig -src/bun.js/test/expect/toBeTruthy.zig -src/bun.js/test/expect/toBeTypeOf.zig -src/bun.js/test/expect/toBeUndefined.zig -src/bun.js/test/expect/toBeValidDate.zig -src/bun.js/test/expect/toBeWithin.zig -src/bun.js/test/expect/toContain.zig -src/bun.js/test/expect/toContainAllKeys.zig -src/bun.js/test/expect/toContainAllValues.zig -src/bun.js/test/expect/toContainAnyKeys.zig -src/bun.js/test/expect/toContainAnyValues.zig -src/bun.js/test/expect/toContainEqual.zig -src/bun.js/test/expect/toContainKey.zig -src/bun.js/test/expect/toContainKeys.zig -src/bun.js/test/expect/toContainValue.zig -src/bun.js/test/expect/toContainValues.zig -src/bun.js/test/expect/toEndWith.zig -src/bun.js/test/expect/toEqual.zig -src/bun.js/test/expect/toEqualIgnoringWhitespace.zig -src/bun.js/test/expect/toHaveBeenCalled.zig -src/bun.js/test/expect/toHaveBeenCalledOnce.zig -src/bun.js/test/expect/toHaveBeenCalledTimes.zig -src/bun.js/test/expect/toHaveBeenCalledWith.zig -src/bun.js/test/expect/toHaveBeenLastCalledWith.zig -src/bun.js/test/expect/toHaveBeenNthCalledWith.zig -src/bun.js/test/expect/toHaveLastReturnedWith.zig -src/bun.js/test/expect/toHaveLength.zig -src/bun.js/test/expect/toHaveNthReturnedWith.zig -src/bun.js/test/expect/toHaveProperty.zig -src/bun.js/test/expect/toHaveReturned.zig -src/bun.js/test/expect/toHaveReturnedTimes.zig -src/bun.js/test/expect/toHaveReturnedWith.zig -src/bun.js/test/expect/toInclude.zig -src/bun.js/test/expect/toIncludeRepeated.zig -src/bun.js/test/expect/toMatch.zig -src/bun.js/test/expect/toMatchInlineSnapshot.zig -src/bun.js/test/expect/toMatchObject.zig -src/bun.js/test/expect/toMatchSnapshot.zig -src/bun.js/test/expect/toSatisfy.zig -src/bun.js/test/expect/toStartWith.zig -src/bun.js/test/expect/toStrictEqual.zig -src/bun.js/test/expect/toThrow.zig -src/bun.js/test/expect/toThrowErrorMatchingInlineSnapshot.zig -src/bun.js/test/expect/toThrowErrorMatchingSnapshot.zig -src/bun.js/test/jest.zig -src/bun.js/test/pretty_format.zig -src/bun.js/test/snapshot.zig -src/bun.js/test/test.zig -src/bun.js/uuid.zig -src/bun.js/virtual_machine_exports.zig -src/bun.js/VirtualMachine.zig -src/bun.js/Weak.zig -src/bun.js/web_worker.zig -src/bun.js/webcore.zig -src/bun.js/webcore/ArrayBufferSink.zig -src/bun.js/webcore/AutoFlusher.zig -src/bun.js/webcore/Blob.zig -src/bun.js/webcore/blob/copy_file.zig -src/bun.js/webcore/blob/read_file.zig -src/bun.js/webcore/blob/Store.zig -src/bun.js/webcore/blob/write_file.zig -src/bun.js/webcore/Body.zig -src/bun.js/webcore/ByteBlobLoader.zig -src/bun.js/webcore/ByteStream.zig -src/bun.js/webcore/CookieMap.zig -src/bun.js/webcore/Crypto.zig -src/bun.js/webcore/encoding.zig -src/bun.js/webcore/EncodingLabel.zig -src/bun.js/webcore/fetch.zig -src/bun.js/webcore/FileReader.zig -src/bun.js/webcore/FileSink.zig -src/bun.js/webcore/ObjectURLRegistry.zig -src/bun.js/webcore/prompt.zig -src/bun.js/webcore/ReadableStream.zig -src/bun.js/webcore/Request.zig -src/bun.js/webcore/Response.zig -src/bun.js/webcore/ResumableSink.zig -src/bun.js/webcore/S3Client.zig -src/bun.js/webcore/S3File.zig -src/bun.js/webcore/S3Stat.zig -src/bun.js/webcore/ScriptExecutionContext.zig -src/bun.js/webcore/Sink.zig -src/bun.js/webcore/streams.zig -src/bun.js/webcore/TextDecoder.zig -src/bun.js/webcore/TextEncoder.zig -src/bun.js/webcore/TextEncoderStreamEncoder.zig -src/bun.zig -src/bundler/AstBuilder.zig -src/bundler/bundle_v2.zig -src/bundler/BundleThread.zig -src/bundler/Chunk.zig -src/bundler/DeferredBatchTask.zig -src/bundler/entry_points.zig -src/bundler/Graph.zig -src/bundler/HTMLImportManifest.zig -src/bundler/IndexStringMap.zig -src/bundler/linker_context/computeChunks.zig -src/bundler/linker_context/computeCrossChunkDependencies.zig -src/bundler/linker_context/convertStmtsForChunk.zig -src/bundler/linker_context/convertStmtsForChunkForDevServer.zig -src/bundler/linker_context/doStep5.zig -src/bundler/linker_context/findAllImportedPartsInJSOrder.zig -src/bundler/linker_context/findImportedCSSFilesInJSOrder.zig -src/bundler/linker_context/findImportedFilesInCSSOrder.zig -src/bundler/linker_context/generateChunksInParallel.zig -src/bundler/linker_context/generateCodeForFileInChunkJS.zig -src/bundler/linker_context/generateCodeForLazyExport.zig -src/bundler/linker_context/generateCompileResultForCssChunk.zig -src/bundler/linker_context/generateCompileResultForHtmlChunk.zig -src/bundler/linker_context/generateCompileResultForJSChunk.zig -src/bundler/linker_context/OutputFileListBuilder.zig -src/bundler/linker_context/postProcessCSSChunk.zig -src/bundler/linker_context/postProcessHTMLChunk.zig -src/bundler/linker_context/postProcessJSChunk.zig -src/bundler/linker_context/prepareCssAstsForChunk.zig -src/bundler/linker_context/renameSymbolsInChunk.zig -src/bundler/linker_context/scanImportsAndExports.zig -src/bundler/linker_context/StaticRouteVisitor.zig -src/bundler/linker_context/writeOutputFilesToDisk.zig -src/bundler/LinkerContext.zig -src/bundler/LinkerGraph.zig -src/bundler/ParseTask.zig -src/bundler/PathToSourceIndexMap.zig -src/bundler/ServerComponentParseTask.zig -src/bundler/ThreadPool.zig -src/bunfig.zig -src/cache.zig -src/ci_info.zig -src/cli.zig -src/cli/add_command.zig -src/cli/add_completions.zig -src/cli/Arguments.zig -src/cli/audit_command.zig -src/cli/build_command.zig -src/cli/bunx_command.zig -src/cli/colon_list_type.zig -src/cli/create_command.zig -src/cli/discord_command.zig -src/cli/exec_command.zig -src/cli/filter_arg.zig -src/cli/filter_run.zig -src/cli/init_command.zig -src/cli/install_command.zig -src/cli/install_completions_command.zig -src/cli/link_command.zig -src/cli/list-of-yarn-commands.zig -src/cli/outdated_command.zig -src/cli/pack_command.zig -src/cli/package_manager_command.zig -src/cli/patch_command.zig -src/cli/patch_commit_command.zig -src/cli/pm_pkg_command.zig -src/cli/pm_trusted_command.zig -src/cli/pm_version_command.zig -src/cli/pm_view_command.zig -src/cli/pm_why_command.zig -src/cli/publish_command.zig -src/cli/remove_command.zig -src/cli/run_command.zig -src/cli/shell_completions.zig -src/cli/test_command.zig -src/cli/test/Scanner.zig -src/cli/unlink_command.zig -src/cli/update_command.zig -src/cli/update_interactive_command.zig -src/cli/upgrade_command.zig -src/cli/why_command.zig -src/codegen/process_windows_translate_c.zig -src/collections.zig -src/collections/baby_list.zig -src/collections/bit_set.zig -src/collections/BoundedArray.zig -src/collections/hive_array.zig -src/collections/multi_array_list.zig -src/compile_target.zig -src/comptime_string_map.zig -src/copy_file.zig -src/crash_handler.zig -src/create/SourceFileProjectGenerator.zig -src/csrf.zig -src/css/compat.zig -src/css/context.zig -src/css/css_internals.zig -src/css/css_modules.zig -src/css/css_parser.zig -src/css/declaration.zig -src/css/dependencies.zig -src/css/error.zig -src/css/generics.zig -src/css/logical.zig -src/css/media_query.zig -src/css/prefixes.zig -src/css/printer.zig -src/css/properties/align.zig -src/css/properties/animation.zig -src/css/properties/background.zig -src/css/properties/border_image.zig -src/css/properties/border_radius.zig -src/css/properties/border.zig -src/css/properties/box_shadow.zig -src/css/properties/contain.zig -src/css/properties/css_modules.zig -src/css/properties/custom.zig -src/css/properties/display.zig -src/css/properties/effects.zig -src/css/properties/flex.zig -src/css/properties/font.zig -src/css/properties/grid.zig -src/css/properties/list.zig -src/css/properties/margin_padding.zig -src/css/properties/masking.zig -src/css/properties/outline.zig -src/css/properties/overflow.zig -src/css/properties/position.zig -src/css/properties/prefix_handler.zig -src/css/properties/properties_generated.zig -src/css/properties/properties_impl.zig -src/css/properties/properties.zig -src/css/properties/shape.zig -src/css/properties/size.zig -src/css/properties/svg.zig -src/css/properties/text.zig -src/css/properties/transform.zig -src/css/properties/transition.zig -src/css/properties/ui.zig -src/css/rules/container.zig -src/css/rules/counter_style.zig -src/css/rules/custom_media.zig -src/css/rules/document.zig -src/css/rules/font_face.zig -src/css/rules/font_palette_values.zig -src/css/rules/import.zig -src/css/rules/keyframes.zig -src/css/rules/layer.zig -src/css/rules/media.zig -src/css/rules/namespace.zig -src/css/rules/nesting.zig -src/css/rules/page.zig -src/css/rules/property.zig -src/css/rules/rules.zig -src/css/rules/scope.zig -src/css/rules/starting_style.zig -src/css/rules/style.zig -src/css/rules/supports.zig -src/css/rules/tailwind.zig -src/css/rules/unknown.zig -src/css/rules/viewport.zig -src/css/selectors/builder.zig -src/css/selectors/parser.zig -src/css/selectors/selector.zig -src/css/small_list.zig -src/css/sourcemap.zig -src/css/targets.zig -src/css/values/alpha.zig -src/css/values/angle.zig -src/css/values/calc.zig -src/css/values/color_generated.zig -src/css/values/color_js.zig -src/css/values/color.zig -src/css/values/css_string.zig -src/css/values/easing.zig -src/css/values/gradient.zig -src/css/values/ident.zig -src/css/values/image.zig -src/css/values/length.zig -src/css/values/number.zig -src/css/values/percentage.zig -src/css/values/position.zig -src/css/values/ratio.zig -src/css/values/rect.zig -src/css/values/resolution.zig -src/css/values/size.zig -src/css/values/syntax.zig -src/css/values/time.zig -src/css/values/url.zig -src/css/values/values.zig -src/darwin.zig -src/defines-table.zig -src/defines.zig -src/deps/boringssl.translated.zig -src/deps/brotli_c.zig -src/deps/c_ares.zig -src/deps/libdeflate.zig -src/deps/libuv.zig -src/deps/lol-html.zig -src/deps/picohttp.zig -src/deps/picohttpparser.zig -src/deps/tcc.zig -src/deps/uws.zig -src/deps/uws/App.zig -src/deps/uws/BodyReaderMixin.zig -src/deps/uws/ConnectingSocket.zig -src/deps/uws/InternalLoopData.zig -src/deps/uws/ListenSocket.zig -src/deps/uws/Loop.zig -src/deps/uws/Request.zig -src/deps/uws/Response.zig -src/deps/uws/socket.zig -src/deps/uws/SocketContext.zig -src/deps/uws/Timer.zig -src/deps/uws/udp.zig -src/deps/uws/UpgradedDuplex.zig -src/deps/uws/us_socket_t.zig -src/deps/uws/WebSocket.zig -src/deps/uws/WindowsNamedPipe.zig -src/deps/zig-clap/clap.zig -src/deps/zig-clap/clap/args.zig -src/deps/zig-clap/clap/comptime.zig -src/deps/zig-clap/clap/streaming.zig -src/deps/zlib.posix.zig -src/deps/zlib.shared.zig -src/deps/zlib.win32.zig -src/deps/zstd.zig -src/dir.zig -src/dns.zig -src/env_loader.zig -src/env.zig -src/errno/darwin_errno.zig -src/errno/linux_errno.zig -src/errno/windows_errno.zig -src/fd.zig -src/feature_flags.zig -src/fmt.zig -src/fs.zig -src/fs/stat_hash.zig -src/generated_perf_trace_events.zig -src/generated_versions_list.zig -src/glob.zig -src/glob/GlobWalker.zig -src/glob/match.zig -src/Global.zig -src/handle_oom.zig -src/heap_breakdown.zig -src/highway.zig -src/hmac.zig -src/HTMLScanner.zig -src/http.zig -src/http/AsyncHTTP.zig -src/http/CertificateInfo.zig -src/http/Decompressor.zig -src/http/Encoding.zig -src/http/ETag.zig -src/http/FetchRedirect.zig -src/http/HeaderBuilder.zig -src/http/Headers.zig -src/http/HeaderValueIterator.zig -src/http/HTTPCertError.zig -src/http/HTTPContext.zig -src/http/HTTPRequestBody.zig -src/http/HTTPThread.zig -src/http/InitError.zig -src/http/InternalState.zig -src/http/Method.zig -src/http/mime_type_list_enum.zig -src/http/MimeType.zig -src/http/ProxyTunnel.zig -src/http/SendFile.zig -src/http/Signals.zig -src/http/ThreadSafeStreamBuffer.zig -src/http/URLPath.zig -src/http/websocket_client.zig -src/http/websocket_client/CppWebSocket.zig -src/http/websocket_client/WebSocketDeflate.zig -src/http/websocket_client/WebSocketUpgradeClient.zig -src/http/websocket_http_client.zig -src/http/websocket.zig -src/http/zlib.zig -src/identity_context.zig -src/import_record.zig -src/ini.zig -src/install/bin.zig -src/install/dependency.zig -src/install/ExternalSlice.zig -src/install/extract_tarball.zig -src/install/hoisted_install.zig -src/install/install_binding.zig -src/install/install.zig -src/install/integrity.zig -src/install/isolated_install.zig -src/install/isolated_install/FileCopier.zig -src/install/isolated_install/Hardlinker.zig -src/install/isolated_install/Installer.zig -src/install/isolated_install/Store.zig -src/install/isolated_install/Symlinker.zig -src/install/lifecycle_script_runner.zig -src/install/lockfile.zig -src/install/lockfile/Buffers.zig -src/install/lockfile/bun.lock.zig -src/install/lockfile/bun.lockb.zig -src/install/lockfile/CatalogMap.zig -src/install/lockfile/lockfile_json_stringify_for_debugging.zig -src/install/lockfile/OverrideMap.zig -src/install/lockfile/Package.zig -src/install/lockfile/Package/Meta.zig -src/install/lockfile/Package/Scripts.zig -src/install/lockfile/Package/WorkspaceMap.zig -src/install/lockfile/printer/tree_printer.zig -src/install/lockfile/printer/Yarn.zig -src/install/lockfile/Tree.zig -src/install/migration.zig -src/install/NetworkTask.zig -src/install/npm.zig -src/install/PackageInstall.zig -src/install/PackageInstaller.zig -src/install/PackageManager.zig -src/install/PackageManager/CommandLineArguments.zig -src/install/PackageManager/install_with_manager.zig -src/install/PackageManager/PackageJSONEditor.zig -src/install/PackageManager/PackageManagerDirectories.zig -src/install/PackageManager/PackageManagerEnqueue.zig -src/install/PackageManager/PackageManagerLifecycle.zig -src/install/PackageManager/PackageManagerOptions.zig -src/install/PackageManager/PackageManagerResolution.zig -src/install/PackageManager/patchPackage.zig -src/install/PackageManager/processDependencyList.zig -src/install/PackageManager/ProgressStrings.zig -src/install/PackageManager/runTasks.zig -src/install/PackageManager/security_scanner.zig -src/install/PackageManager/updatePackageJSONAndInstall.zig -src/install/PackageManager/UpdateRequest.zig -src/install/PackageManager/WorkspacePackageJSONCache.zig -src/install/PackageManagerTask.zig -src/install/PackageManifestMap.zig -src/install/padding_checker.zig -src/install/patch_install.zig -src/install/repository.zig -src/install/resolution.zig -src/install/resolvers/folder_resolver.zig -src/install/versioned_url.zig -src/install/windows-shim/BinLinkingShim.zig -src/install/windows-shim/bun_shim_impl.zig -src/install/yarn.zig -src/interchange.zig -src/interchange/json.zig -src/interchange/toml.zig -src/interchange/toml/lexer.zig -src/interchange/yaml.zig -src/io/heap.zig -src/io/io.zig -src/io/MaxBuf.zig -src/io/openForWriting.zig -src/io/PipeReader.zig -src/io/pipes.zig -src/io/PipeWriter.zig -src/io/source.zig -src/js_lexer_tables.zig -src/js_lexer.zig -src/js_lexer/identifier.zig -src/js_parser.zig -src/js_printer.zig -src/jsc_stub.zig -src/libarchive/libarchive-bindings.zig -src/libarchive/libarchive.zig -src/linear_fifo.zig -src/linker.zig -src/linux.zig -src/logger.zig -src/macho.zig -src/main_test.zig -src/main_wasm.zig -src/main.zig -src/memory.zig -src/meta.zig -src/napi/napi.zig -src/node_fallbacks.zig -src/open.zig -src/options.zig -src/output.zig -src/OutputFile.zig -src/patch.zig -src/paths.zig -src/paths/EnvPath.zig -src/paths/path_buffer_pool.zig -src/paths/Path.zig -src/pe.zig -src/perf.zig -src/pool.zig -src/Progress.zig -src/ptr.zig -src/ptr/Cow.zig -src/ptr/CowSlice.zig -src/ptr/meta.zig -src/ptr/owned.zig -src/ptr/ref_count.zig -src/ptr/shared.zig -src/ptr/tagged_pointer.zig -src/ptr/weak_ptr.zig -src/renamer.zig -src/resolver/data_url.zig -src/resolver/dir_info.zig -src/resolver/package_json.zig -src/resolver/resolve_path.zig -src/resolver/resolver.zig -src/resolver/tsconfig_json.zig -src/result.zig -src/router.zig -src/runtime.zig -src/s3/acl.zig -src/s3/client.zig -src/s3/credentials.zig -src/s3/download_stream.zig -src/s3/error.zig -src/s3/list_objects.zig -src/s3/multipart_options.zig -src/s3/multipart.zig -src/s3/simple_request.zig -src/s3/storage_class.zig -src/safety.zig -src/safety/alloc.zig -src/safety/CriticalSection.zig -src/safety/thread_id.zig -src/safety/ThreadLock.zig -src/semver.zig -src/semver/ExternalString.zig -src/semver/SemverObject.zig -src/semver/SemverQuery.zig -src/semver/SemverRange.zig -src/semver/SemverString.zig -src/semver/SlicedString.zig -src/semver/Version.zig -src/sha.zig -src/shell/AllocScope.zig -src/shell/braces.zig -src/shell/Builtin.zig -src/shell/builtin/basename.zig -src/shell/builtin/cat.zig -src/shell/builtin/cd.zig -src/shell/builtin/cp.zig -src/shell/builtin/dirname.zig -src/shell/builtin/echo.zig -src/shell/builtin/exit.zig -src/shell/builtin/export.zig -src/shell/builtin/false.zig -src/shell/builtin/ls.zig -src/shell/builtin/mkdir.zig -src/shell/builtin/mv.zig -src/shell/builtin/pwd.zig -src/shell/builtin/rm.zig -src/shell/builtin/seq.zig -src/shell/builtin/touch.zig -src/shell/builtin/true.zig -src/shell/builtin/which.zig -src/shell/builtin/yes.zig -src/shell/EnvMap.zig -src/shell/EnvStr.zig -src/shell/interpreter.zig -src/shell/IO.zig -src/shell/IOReader.zig -src/shell/IOWriter.zig -src/shell/ParsedShellScript.zig -src/shell/RefCountedStr.zig -src/shell/shell.zig -src/shell/states/Assigns.zig -src/shell/states/Async.zig -src/shell/states/Base.zig -src/shell/states/Binary.zig -src/shell/states/Cmd.zig -src/shell/states/CondExpr.zig -src/shell/states/Expansion.zig -src/shell/states/If.zig -src/shell/states/Pipeline.zig -src/shell/states/Script.zig -src/shell/states/Stmt.zig -src/shell/states/Subshell.zig -src/shell/subproc.zig -src/shell/util.zig -src/shell/Yield.zig -src/sourcemap/CodeCoverage.zig -src/sourcemap/JSSourceMap.zig -src/sourcemap/LineOffsetTable.zig -src/sourcemap/sourcemap.zig -src/sourcemap/VLQ.zig -src/sql/mysql.zig -src/sql/mysql/AuthMethod.zig -src/sql/mysql/Capabilities.zig -src/sql/mysql/ConnectionState.zig -src/sql/mysql/MySQLConnection.zig -src/sql/mysql/MySQLContext.zig -src/sql/mysql/MySQLQuery.zig -src/sql/mysql/MySQLRequest.zig -src/sql/mysql/MySQLStatement.zig -src/sql/mysql/MySQLTypes.zig -src/sql/mysql/protocol/AnyMySQLError.zig -src/sql/mysql/protocol/Auth.zig -src/sql/mysql/protocol/AuthSwitchRequest.zig -src/sql/mysql/protocol/AuthSwitchResponse.zig -src/sql/mysql/protocol/CharacterSet.zig -src/sql/mysql/protocol/ColumnDefinition41.zig -src/sql/mysql/protocol/CommandType.zig -src/sql/mysql/protocol/DecodeBinaryValue.zig -src/sql/mysql/protocol/EncodeInt.zig -src/sql/mysql/protocol/EOFPacket.zig -src/sql/mysql/protocol/ErrorPacket.zig -src/sql/mysql/protocol/HandshakeResponse41.zig -src/sql/mysql/protocol/HandshakeV10.zig -src/sql/mysql/protocol/LocalInfileRequest.zig -src/sql/mysql/protocol/NewReader.zig -src/sql/mysql/protocol/NewWriter.zig -src/sql/mysql/protocol/OKPacket.zig -src/sql/mysql/protocol/PacketHeader.zig -src/sql/mysql/protocol/PacketType.zig -src/sql/mysql/protocol/PreparedStatement.zig -src/sql/mysql/protocol/Query.zig -src/sql/mysql/protocol/ResultSet.zig -src/sql/mysql/protocol/ResultSetHeader.zig -src/sql/mysql/protocol/Signature.zig -src/sql/mysql/protocol/StackReader.zig -src/sql/mysql/protocol/StmtPrepareOKPacket.zig -src/sql/mysql/SSLMode.zig -src/sql/mysql/StatusFlags.zig -src/sql/mysql/TLSStatus.zig -src/sql/postgres.zig -src/sql/postgres/AnyPostgresError.zig -src/sql/postgres/AuthenticationState.zig -src/sql/postgres/CommandTag.zig -src/sql/postgres/DataCell.zig -src/sql/postgres/DebugSocketMonitorReader.zig -src/sql/postgres/DebugSocketMonitorWriter.zig -src/sql/postgres/PostgresProtocol.zig -src/sql/postgres/PostgresRequest.zig -src/sql/postgres/PostgresSQLConnection.zig -src/sql/postgres/PostgresSQLContext.zig -src/sql/postgres/PostgresSQLQuery.zig -src/sql/postgres/PostgresSQLStatement.zig -src/sql/postgres/PostgresTypes.zig -src/sql/postgres/protocol/ArrayList.zig -src/sql/postgres/protocol/Authentication.zig -src/sql/postgres/protocol/BackendKeyData.zig -src/sql/postgres/protocol/Close.zig -src/sql/postgres/protocol/CommandComplete.zig -src/sql/postgres/protocol/CopyData.zig -src/sql/postgres/protocol/CopyFail.zig -src/sql/postgres/protocol/CopyInResponse.zig -src/sql/postgres/protocol/CopyOutResponse.zig -src/sql/postgres/protocol/DataRow.zig -src/sql/postgres/protocol/DecoderWrap.zig -src/sql/postgres/protocol/Describe.zig -src/sql/postgres/protocol/ErrorResponse.zig -src/sql/postgres/protocol/Execute.zig -src/sql/postgres/protocol/FieldDescription.zig -src/sql/postgres/protocol/FieldMessage.zig -src/sql/postgres/protocol/FieldType.zig -src/sql/postgres/protocol/NegotiateProtocolVersion.zig -src/sql/postgres/protocol/NewReader.zig -src/sql/postgres/protocol/NewWriter.zig -src/sql/postgres/protocol/NoticeResponse.zig -src/sql/postgres/protocol/NotificationResponse.zig -src/sql/postgres/protocol/ParameterDescription.zig -src/sql/postgres/protocol/ParameterStatus.zig -src/sql/postgres/protocol/Parse.zig -src/sql/postgres/protocol/PasswordMessage.zig -src/sql/postgres/protocol/PortalOrPreparedStatement.zig -src/sql/postgres/protocol/ReadyForQuery.zig -src/sql/postgres/protocol/RowDescription.zig -src/sql/postgres/protocol/SASLInitialResponse.zig -src/sql/postgres/protocol/SASLResponse.zig -src/sql/postgres/protocol/StackReader.zig -src/sql/postgres/protocol/StartupMessage.zig -src/sql/postgres/protocol/TransactionStatusIndicator.zig -src/sql/postgres/protocol/WriteWrap.zig -src/sql/postgres/protocol/zHelpers.zig -src/sql/postgres/SASL.zig -src/sql/postgres/Signature.zig -src/sql/postgres/SocketMonitor.zig -src/sql/postgres/SSLMode.zig -src/sql/postgres/Status.zig -src/sql/postgres/TLSStatus.zig -src/sql/postgres/types/bool.zig -src/sql/postgres/types/bytea.zig -src/sql/postgres/types/date.zig -src/sql/postgres/types/int_types.zig -src/sql/postgres/types/json.zig -src/sql/postgres/types/numeric.zig -src/sql/postgres/types/PostgresString.zig -src/sql/postgres/types/Tag.zig -src/sql/shared/CachedStructure.zig -src/sql/shared/ColumnIdentifier.zig -src/sql/shared/ConnectionFlags.zig -src/sql/shared/Data.zig -src/sql/shared/ObjectIterator.zig -src/sql/shared/QueryBindingIterator.zig -src/sql/shared/SQLDataCell.zig -src/sql/shared/SQLQueryResultMode.zig -src/StandaloneModuleGraph.zig -src/StaticHashMap.zig -src/string.zig -src/string/HashedString.zig -src/string/immutable.zig -src/string/immutable/escapeHTML.zig -src/string/immutable/exact_size_matcher.zig -src/string/immutable/grapheme.zig -src/string/immutable/paths.zig -src/string/immutable/unicode.zig -src/string/immutable/visible.zig -src/string/MutableString.zig -src/string/PathString.zig -src/string/SmolStr.zig -src/string/StringBuilder.zig -src/string/StringJoiner.zig -src/string/WTFStringImpl.zig -src/sys_uv.zig -src/sys.zig -src/sys/coreutils_error_map.zig -src/sys/Error.zig -src/sys/File.zig -src/sys/libuv_error_map.zig -src/system_timer.zig -src/test/fixtures.zig -src/test/recover.zig -src/threading.zig -src/threading/channel.zig -src/threading/Condition.zig -src/threading/Futex.zig -src/threading/guarded.zig -src/threading/Mutex.zig -src/threading/ThreadPool.zig -src/threading/unbounded_queue.zig -src/threading/WaitGroup.zig -src/tmp.zig -src/tracy.zig -src/trait.zig -src/transpiler.zig -src/unit_test.zig -src/url.zig -src/util.zig -src/valkey/index.zig -src/valkey/js_valkey_functions.zig -src/valkey/js_valkey.zig -src/valkey/valkey_protocol.zig -src/valkey/valkey.zig -src/valkey/ValkeyCommand.zig -src/valkey/ValkeyContext.zig -src/walker_skippable.zig -src/Watcher.zig -src/watcher/INotifyWatcher.zig -src/watcher/KEventWatcher.zig -src/watcher/WindowsWatcher.zig -src/which_npm_client.zig -src/which.zig -src/windows.zig -src/work_pool.zig -src/workaround_missing_symbols.zig -src/wyhash.zig -src/zlib.zig +src/allocators.zig +src/allocators/allocation_scope.zig +src/allocators/basic.zig +src/allocators/fallback.zig +src/allocators/fallback/z.zig +src/allocators/LinuxMemFdAllocator.zig +src/allocators/MaxHeapAllocator.zig +src/allocators/maybe_owned.zig +src/allocators/MemoryReportingAllocator.zig +src/allocators/mimalloc.zig +src/allocators/MimallocArena.zig +src/allocators/NullableAllocator.zig +src/analytics.zig +src/analytics/schema.zig +src/api/schema.zig +src/asan.zig +src/ast.zig +src/ast/Ast.zig +src/ast/ASTMemoryAllocator.zig +src/ast/B.zig +src/ast/base.zig +src/ast/Binding.zig +src/ast/BundledAst.zig +src/ast/CharFreq.zig +src/ast/ConvertESMExportsForHmr.zig +src/ast/E.zig +src/ast/Expr.zig +src/ast/foldStringAddition.zig +src/ast/G.zig +src/ast/ImportScanner.zig +src/ast/KnownGlobal.zig +src/ast/Macro.zig +src/ast/maybe.zig +src/ast/NewStore.zig +src/ast/Op.zig +src/ast/P.zig +src/ast/parse.zig +src/ast/parseFn.zig +src/ast/parseImportExport.zig +src/ast/parseJSXElement.zig +src/ast/parsePrefix.zig +src/ast/parseProperty.zig +src/ast/Parser.zig +src/ast/parseStmt.zig +src/ast/parseSuffix.zig +src/ast/parseTypescript.zig +src/ast/S.zig +src/ast/Scope.zig +src/ast/ServerComponentBoundary.zig +src/ast/SideEffects.zig +src/ast/skipTypescript.zig +src/ast/Stmt.zig +src/ast/Symbol.zig +src/ast/symbols.zig +src/ast/TS.zig +src/ast/TypeScript.zig +src/ast/UseDirective.zig +src/ast/visit.zig +src/ast/visitBinaryExpression.zig +src/ast/visitExpr.zig +src/ast/visitStmt.zig +src/async/posix_event_loop.zig +src/async/stub_event_loop.zig +src/async/windows_event_loop.zig +src/bake.zig +src/bake/DevServer.zig +src/bake/DevServer/Assets.zig +src/bake/DevServer/DirectoryWatchStore.zig +src/bake/DevServer/ErrorReportRequest.zig +src/bake/DevServer/HmrSocket.zig +src/bake/DevServer/HotReloadEvent.zig +src/bake/DevServer/IncrementalGraph.zig +src/bake/DevServer/memory_cost.zig +src/bake/DevServer/PackedMap.zig +src/bake/DevServer/RouteBundle.zig +src/bake/DevServer/SerializedFailure.zig +src/bake/DevServer/SourceMapStore.zig +src/bake/DevServer/WatcherAtomics.zig +src/bake/FrameworkRouter.zig +src/bake/production.zig +src/base64/base64.zig +src/bits.zig +src/boringssl.zig +src/brotli.zig +src/btjs.zig +src/bun.js.zig +src/bun.js/api.zig +src/bun.js/api/bun/dns.zig +src/bun.js/api/bun/h2_frame_parser.zig +src/bun.js/api/bun/lshpack.zig +src/bun.js/api/bun/process.zig +src/bun.js/api/bun/socket.zig +src/bun.js/api/bun/socket/Handlers.zig +src/bun.js/api/bun/socket/Listener.zig +src/bun.js/api/bun/socket/SocketAddress.zig +src/bun.js/api/bun/socket/tls_socket_functions.zig +src/bun.js/api/bun/socket/WindowsNamedPipeContext.zig +src/bun.js/api/bun/spawn.zig +src/bun.js/api/bun/spawn/stdio.zig +src/bun.js/api/bun/ssl_wrapper.zig +src/bun.js/api/bun/subprocess.zig +src/bun.js/api/bun/subprocess/Readable.zig +src/bun.js/api/bun/subprocess/ResourceUsage.zig +src/bun.js/api/bun/subprocess/StaticPipeWriter.zig +src/bun.js/api/bun/subprocess/SubprocessPipeReader.zig +src/bun.js/api/bun/subprocess/Writable.zig +src/bun.js/api/bun/udp_socket.zig +src/bun.js/api/bun/x509.zig +src/bun.js/api/BunObject.zig +src/bun.js/api/crypto.zig +src/bun.js/api/crypto/CryptoHasher.zig +src/bun.js/api/crypto/EVP.zig +src/bun.js/api/crypto/HMAC.zig +src/bun.js/api/crypto/PasswordObject.zig +src/bun.js/api/crypto/PBKDF2.zig +src/bun.js/api/ffi.zig +src/bun.js/api/FFIObject.zig +src/bun.js/api/filesystem_router.zig +src/bun.js/api/glob.zig +src/bun.js/api/HashObject.zig +src/bun.js/api/html_rewriter.zig +src/bun.js/api/JSBundler.zig +src/bun.js/api/JSTranspiler.zig +src/bun.js/api/server.zig +src/bun.js/api/server/AnyRequestContext.zig +src/bun.js/api/server/FileRoute.zig +src/bun.js/api/server/HTMLBundle.zig +src/bun.js/api/server/HTTPStatusText.zig +src/bun.js/api/server/InspectorBunFrontendDevServerAgent.zig +src/bun.js/api/server/NodeHTTPResponse.zig +src/bun.js/api/server/RequestContext.zig +src/bun.js/api/server/ServerConfig.zig +src/bun.js/api/server/ServerWebSocket.zig +src/bun.js/api/server/SSLConfig.zig +src/bun.js/api/server/StaticRoute.zig +src/bun.js/api/server/WebSocketServerContext.zig +src/bun.js/api/streams.classes.zig +src/bun.js/api/Timer.zig +src/bun.js/api/Timer/DateHeaderTimer.zig +src/bun.js/api/Timer/EventLoopTimer.zig +src/bun.js/api/Timer/ImmediateObject.zig +src/bun.js/api/Timer/TimeoutObject.zig +src/bun.js/api/Timer/TimerObjectInternals.zig +src/bun.js/api/Timer/WTFTimer.zig +src/bun.js/api/TOMLObject.zig +src/bun.js/api/UnsafeObject.zig +src/bun.js/api/YAMLObject.zig +src/bun.js/bindgen_test.zig +src/bun.js/bindings/AbortSignal.zig +src/bun.js/bindings/AnyPromise.zig +src/bun.js/bindings/bun-simdutf.zig +src/bun.js/bindings/CachedBytecode.zig +src/bun.js/bindings/CallFrame.zig +src/bun.js/bindings/CatchScope.zig +src/bun.js/bindings/codegen.zig +src/bun.js/bindings/CommonAbortReason.zig +src/bun.js/bindings/CommonStrings.zig +src/bun.js/bindings/CPUFeatures.zig +src/bun.js/bindings/CustomGetterSetter.zig +src/bun.js/bindings/DeferredError.zig +src/bun.js/bindings/DOMFormData.zig +src/bun.js/bindings/DOMURL.zig +src/bun.js/bindings/EncodedJSValue.zig +src/bun.js/bindings/Errorable.zig +src/bun.js/bindings/ErrorCode.zig +src/bun.js/bindings/EventType.zig +src/bun.js/bindings/Exception.zig +src/bun.js/bindings/FetchHeaders.zig +src/bun.js/bindings/FFI.zig +src/bun.js/bindings/generated_classes_list.zig +src/bun.js/bindings/GetterSetter.zig +src/bun.js/bindings/HTTPServerAgent.zig +src/bun.js/bindings/JSArray.zig +src/bun.js/bindings/JSArrayIterator.zig +src/bun.js/bindings/JSBigInt.zig +src/bun.js/bindings/JSCell.zig +src/bun.js/bindings/JSErrorCode.zig +src/bun.js/bindings/JSFunction.zig +src/bun.js/bindings/JSGlobalObject.zig +src/bun.js/bindings/JSInternalPromise.zig +src/bun.js/bindings/JSMap.zig +src/bun.js/bindings/JSModuleLoader.zig +src/bun.js/bindings/JSObject.zig +src/bun.js/bindings/JSPromise.zig +src/bun.js/bindings/JSPromiseRejectionOperation.zig +src/bun.js/bindings/JSPropertyIterator.zig +src/bun.js/bindings/JSRef.zig +src/bun.js/bindings/JSRuntimeType.zig +src/bun.js/bindings/JSSecrets.zig +src/bun.js/bindings/JSString.zig +src/bun.js/bindings/JSType.zig +src/bun.js/bindings/JSUint8Array.zig +src/bun.js/bindings/JSValue.zig +src/bun.js/bindings/MarkedArgumentBuffer.zig +src/bun.js/bindings/NodeModuleModule.zig +src/bun.js/bindings/RegularExpression.zig +src/bun.js/bindings/ResolvedSource.zig +src/bun.js/bindings/ScriptExecutionStatus.zig +src/bun.js/bindings/sizes.zig +src/bun.js/bindings/SourceProvider.zig +src/bun.js/bindings/SourceType.zig +src/bun.js/bindings/static_export.zig +src/bun.js/bindings/StringBuilder.zig +src/bun.js/bindings/SystemError.zig +src/bun.js/bindings/TextCodec.zig +src/bun.js/bindings/URL.zig +src/bun.js/bindings/URLSearchParams.zig +src/bun.js/bindings/VM.zig +src/bun.js/bindings/WTF.zig +src/bun.js/bindings/ZigErrorType.zig +src/bun.js/bindings/ZigException.zig +src/bun.js/bindings/ZigStackFrame.zig +src/bun.js/bindings/ZigStackFrameCode.zig +src/bun.js/bindings/ZigStackFramePosition.zig +src/bun.js/bindings/ZigStackTrace.zig +src/bun.js/bindings/ZigString.zig +src/bun.js/BuildMessage.zig +src/bun.js/config.zig +src/bun.js/ConsoleObject.zig +src/bun.js/Counters.zig +src/bun.js/Debugger.zig +src/bun.js/event_loop.zig +src/bun.js/event_loop/AnyEventLoop.zig +src/bun.js/event_loop/AnyTask.zig +src/bun.js/event_loop/AnyTaskWithExtraContext.zig +src/bun.js/event_loop/ConcurrentPromiseTask.zig +src/bun.js/event_loop/ConcurrentTask.zig +src/bun.js/event_loop/CppTask.zig +src/bun.js/event_loop/DeferredTaskQueue.zig +src/bun.js/event_loop/EventLoopHandle.zig +src/bun.js/event_loop/GarbageCollectionController.zig +src/bun.js/event_loop/JSCScheduler.zig +src/bun.js/event_loop/ManagedTask.zig +src/bun.js/event_loop/MiniEventLoop.zig +src/bun.js/event_loop/PosixSignalHandle.zig +src/bun.js/event_loop/Task.zig +src/bun.js/event_loop/WorkTask.zig +src/bun.js/hot_reloader.zig +src/bun.js/ipc.zig +src/bun.js/javascript_core_c_api.zig +src/bun.js/jsc.zig +src/bun.js/jsc/array_buffer.zig +src/bun.js/jsc/dom_call.zig +src/bun.js/jsc/host_fn.zig +src/bun.js/jsc/RefString.zig +src/bun.js/ModuleLoader.zig +src/bun.js/node.zig +src/bun.js/node/assert/myers_diff.zig +src/bun.js/node/buffer.zig +src/bun.js/node/dir_iterator.zig +src/bun.js/node/fs_events.zig +src/bun.js/node/net/BlockList.zig +src/bun.js/node/node_assert_binding.zig +src/bun.js/node/node_assert.zig +src/bun.js/node/node_cluster_binding.zig +src/bun.js/node/node_crypto_binding.zig +src/bun.js/node/node_error_binding.zig +src/bun.js/node/node_fs_binding.zig +src/bun.js/node/node_fs_constant.zig +src/bun.js/node/node_fs_stat_watcher.zig +src/bun.js/node/node_fs_watcher.zig +src/bun.js/node/node_fs.zig +src/bun.js/node/node_http_binding.zig +src/bun.js/node/node_net_binding.zig +src/bun.js/node/node_os.zig +src/bun.js/node/node_process.zig +src/bun.js/node/node_util_binding.zig +src/bun.js/node/node_zlib_binding.zig +src/bun.js/node/nodejs_error_code.zig +src/bun.js/node/os/constants.zig +src/bun.js/node/path_watcher.zig +src/bun.js/node/path.zig +src/bun.js/node/Stat.zig +src/bun.js/node/StatFS.zig +src/bun.js/node/time_like.zig +src/bun.js/node/types.zig +src/bun.js/node/util/parse_args_utils.zig +src/bun.js/node/util/parse_args.zig +src/bun.js/node/util/validators.zig +src/bun.js/node/win_watcher.zig +src/bun.js/node/zlib/NativeBrotli.zig +src/bun.js/node/zlib/NativeZlib.zig +src/bun.js/node/zlib/NativeZstd.zig +src/bun.js/ProcessAutoKiller.zig +src/bun.js/rare_data.zig +src/bun.js/ResolveMessage.zig +src/bun.js/RuntimeTranspilerCache.zig +src/bun.js/SavedSourceMap.zig +src/bun.js/Strong.zig +src/bun.js/test/diff_format.zig +src/bun.js/test/diff/diff_match_patch.zig +src/bun.js/test/diff/printDiff.zig +src/bun.js/test/expect.zig +src/bun.js/test/expect/toBe.zig +src/bun.js/test/expect/toBeArray.zig +src/bun.js/test/expect/toBeArrayOfSize.zig +src/bun.js/test/expect/toBeBoolean.zig +src/bun.js/test/expect/toBeCloseTo.zig +src/bun.js/test/expect/toBeDate.zig +src/bun.js/test/expect/toBeDefined.zig +src/bun.js/test/expect/toBeEmpty.zig +src/bun.js/test/expect/toBeEmptyObject.zig +src/bun.js/test/expect/toBeEven.zig +src/bun.js/test/expect/toBeFalse.zig +src/bun.js/test/expect/toBeFalsy.zig +src/bun.js/test/expect/toBeFinite.zig +src/bun.js/test/expect/toBeFunction.zig +src/bun.js/test/expect/toBeGreaterThan.zig +src/bun.js/test/expect/toBeGreaterThanOrEqual.zig +src/bun.js/test/expect/toBeInstanceOf.zig +src/bun.js/test/expect/toBeInteger.zig +src/bun.js/test/expect/toBeLessThan.zig +src/bun.js/test/expect/toBeLessThanOrEqual.zig +src/bun.js/test/expect/toBeNaN.zig +src/bun.js/test/expect/toBeNegative.zig +src/bun.js/test/expect/toBeNil.zig +src/bun.js/test/expect/toBeNull.zig +src/bun.js/test/expect/toBeNumber.zig +src/bun.js/test/expect/toBeObject.zig +src/bun.js/test/expect/toBeOdd.zig +src/bun.js/test/expect/toBeOneOf.zig +src/bun.js/test/expect/toBePositive.zig +src/bun.js/test/expect/toBeString.zig +src/bun.js/test/expect/toBeSymbol.zig +src/bun.js/test/expect/toBeTrue.zig +src/bun.js/test/expect/toBeTruthy.zig +src/bun.js/test/expect/toBeTypeOf.zig +src/bun.js/test/expect/toBeUndefined.zig +src/bun.js/test/expect/toBeValidDate.zig +src/bun.js/test/expect/toBeWithin.zig +src/bun.js/test/expect/toContain.zig +src/bun.js/test/expect/toContainAllKeys.zig +src/bun.js/test/expect/toContainAllValues.zig +src/bun.js/test/expect/toContainAnyKeys.zig +src/bun.js/test/expect/toContainAnyValues.zig +src/bun.js/test/expect/toContainEqual.zig +src/bun.js/test/expect/toContainKey.zig +src/bun.js/test/expect/toContainKeys.zig +src/bun.js/test/expect/toContainValue.zig +src/bun.js/test/expect/toContainValues.zig +src/bun.js/test/expect/toEndWith.zig +src/bun.js/test/expect/toEqual.zig +src/bun.js/test/expect/toEqualIgnoringWhitespace.zig +src/bun.js/test/expect/toHaveBeenCalled.zig +src/bun.js/test/expect/toHaveBeenCalledOnce.zig +src/bun.js/test/expect/toHaveBeenCalledTimes.zig +src/bun.js/test/expect/toHaveBeenCalledWith.zig +src/bun.js/test/expect/toHaveBeenLastCalledWith.zig +src/bun.js/test/expect/toHaveBeenNthCalledWith.zig +src/bun.js/test/expect/toHaveLastReturnedWith.zig +src/bun.js/test/expect/toHaveLength.zig +src/bun.js/test/expect/toHaveNthReturnedWith.zig +src/bun.js/test/expect/toHaveProperty.zig +src/bun.js/test/expect/toHaveReturned.zig +src/bun.js/test/expect/toHaveReturnedTimes.zig +src/bun.js/test/expect/toHaveReturnedWith.zig +src/bun.js/test/expect/toInclude.zig +src/bun.js/test/expect/toIncludeRepeated.zig +src/bun.js/test/expect/toMatch.zig +src/bun.js/test/expect/toMatchInlineSnapshot.zig +src/bun.js/test/expect/toMatchObject.zig +src/bun.js/test/expect/toMatchSnapshot.zig +src/bun.js/test/expect/toSatisfy.zig +src/bun.js/test/expect/toStartWith.zig +src/bun.js/test/expect/toStrictEqual.zig +src/bun.js/test/expect/toThrow.zig +src/bun.js/test/expect/toThrowErrorMatchingInlineSnapshot.zig +src/bun.js/test/expect/toThrowErrorMatchingSnapshot.zig +src/bun.js/test/jest.zig +src/bun.js/test/pretty_format.zig +src/bun.js/test/snapshot.zig +src/bun.js/test/test.zig +src/bun.js/uuid.zig +src/bun.js/virtual_machine_exports.zig +src/bun.js/VirtualMachine.zig +src/bun.js/Weak.zig +src/bun.js/web_worker.zig +src/bun.js/webcore.zig +src/bun.js/webcore/ArrayBufferSink.zig +src/bun.js/webcore/AutoFlusher.zig +src/bun.js/webcore/Blob.zig +src/bun.js/webcore/blob/copy_file.zig +src/bun.js/webcore/blob/read_file.zig +src/bun.js/webcore/blob/Store.zig +src/bun.js/webcore/blob/write_file.zig +src/bun.js/webcore/Body.zig +src/bun.js/webcore/ByteBlobLoader.zig +src/bun.js/webcore/ByteStream.zig +src/bun.js/webcore/CookieMap.zig +src/bun.js/webcore/Crypto.zig +src/bun.js/webcore/encoding.zig +src/bun.js/webcore/EncodingLabel.zig +src/bun.js/webcore/fetch.zig +src/bun.js/webcore/FileReader.zig +src/bun.js/webcore/FileSink.zig +src/bun.js/webcore/ObjectURLRegistry.zig +src/bun.js/webcore/prompt.zig +src/bun.js/webcore/ReadableStream.zig +src/bun.js/webcore/Request.zig +src/bun.js/webcore/Response.zig +src/bun.js/webcore/ResumableSink.zig +src/bun.js/webcore/S3Client.zig +src/bun.js/webcore/S3File.zig +src/bun.js/webcore/S3Stat.zig +src/bun.js/webcore/ScriptExecutionContext.zig +src/bun.js/webcore/Sink.zig +src/bun.js/webcore/streams.zig +src/bun.js/webcore/TextDecoder.zig +src/bun.js/webcore/TextEncoder.zig +src/bun.js/webcore/TextEncoderStreamEncoder.zig +src/bun.zig +src/bundler/AstBuilder.zig +src/bundler/bundle_v2.zig +src/bundler/BundleThread.zig +src/bundler/Chunk.zig +src/bundler/DeferredBatchTask.zig +src/bundler/entry_points.zig +src/bundler/Graph.zig +src/bundler/HTMLImportManifest.zig +src/bundler/IndexStringMap.zig +src/bundler/linker_context/computeChunks.zig +src/bundler/linker_context/computeCrossChunkDependencies.zig +src/bundler/linker_context/convertStmtsForChunk.zig +src/bundler/linker_context/convertStmtsForChunkForDevServer.zig +src/bundler/linker_context/doStep5.zig +src/bundler/linker_context/findAllImportedPartsInJSOrder.zig +src/bundler/linker_context/findImportedCSSFilesInJSOrder.zig +src/bundler/linker_context/findImportedFilesInCSSOrder.zig +src/bundler/linker_context/generateChunksInParallel.zig +src/bundler/linker_context/generateCodeForFileInChunkJS.zig +src/bundler/linker_context/generateCodeForLazyExport.zig +src/bundler/linker_context/generateCompileResultForCssChunk.zig +src/bundler/linker_context/generateCompileResultForHtmlChunk.zig +src/bundler/linker_context/generateCompileResultForJSChunk.zig +src/bundler/linker_context/OutputFileListBuilder.zig +src/bundler/linker_context/postProcessCSSChunk.zig +src/bundler/linker_context/postProcessHTMLChunk.zig +src/bundler/linker_context/postProcessJSChunk.zig +src/bundler/linker_context/prepareCssAstsForChunk.zig +src/bundler/linker_context/renameSymbolsInChunk.zig +src/bundler/linker_context/scanImportsAndExports.zig +src/bundler/linker_context/StaticRouteVisitor.zig +src/bundler/linker_context/writeOutputFilesToDisk.zig +src/bundler/LinkerContext.zig +src/bundler/LinkerGraph.zig +src/bundler/ParseTask.zig +src/bundler/PathToSourceIndexMap.zig +src/bundler/ServerComponentParseTask.zig +src/bundler/ThreadPool.zig +src/bunfig.zig +src/cache.zig +src/ci_info.zig +src/cli.zig +src/cli/add_command.zig +src/cli/add_completions.zig +src/cli/Arguments.zig +src/cli/audit_command.zig +src/cli/build_command.zig +src/cli/bunx_command.zig +src/cli/colon_list_type.zig +src/cli/create_command.zig +src/cli/discord_command.zig +src/cli/exec_command.zig +src/cli/filter_arg.zig +src/cli/filter_run.zig +src/cli/init_command.zig +src/cli/install_command.zig +src/cli/install_completions_command.zig +src/cli/link_command.zig +src/cli/list-of-yarn-commands.zig +src/cli/outdated_command.zig +src/cli/pack_command.zig +src/cli/package_manager_command.zig +src/cli/patch_command.zig +src/cli/patch_commit_command.zig +src/cli/pm_pkg_command.zig +src/cli/pm_trusted_command.zig +src/cli/pm_version_command.zig +src/cli/pm_view_command.zig +src/cli/pm_why_command.zig +src/cli/publish_command.zig +src/cli/remove_command.zig +src/cli/run_command.zig +src/cli/shell_completions.zig +src/cli/test_command.zig +src/cli/test/Scanner.zig +src/cli/unlink_command.zig +src/cli/update_command.zig +src/cli/update_interactive_command.zig +src/cli/upgrade_command.zig +src/cli/why_command.zig +src/codegen/process_windows_translate_c.zig +src/collections.zig +src/collections/baby_list.zig +src/collections/bit_set.zig +src/collections/BoundedArray.zig +src/collections/hive_array.zig +src/collections/multi_array_list.zig +src/compile_target.zig +src/comptime_string_map.zig +src/copy_file.zig +src/crash_handler.zig +src/create/SourceFileProjectGenerator.zig +src/csrf.zig +src/css/compat.zig +src/css/context.zig +src/css/css_internals.zig +src/css/css_modules.zig +src/css/css_parser.zig +src/css/declaration.zig +src/css/dependencies.zig +src/css/error.zig +src/css/generics.zig +src/css/logical.zig +src/css/media_query.zig +src/css/prefixes.zig +src/css/printer.zig +src/css/properties/align.zig +src/css/properties/animation.zig +src/css/properties/background.zig +src/css/properties/border_image.zig +src/css/properties/border_radius.zig +src/css/properties/border.zig +src/css/properties/box_shadow.zig +src/css/properties/contain.zig +src/css/properties/css_modules.zig +src/css/properties/custom.zig +src/css/properties/display.zig +src/css/properties/effects.zig +src/css/properties/flex.zig +src/css/properties/font.zig +src/css/properties/grid.zig +src/css/properties/list.zig +src/css/properties/margin_padding.zig +src/css/properties/masking.zig +src/css/properties/outline.zig +src/css/properties/overflow.zig +src/css/properties/position.zig +src/css/properties/prefix_handler.zig +src/css/properties/properties_generated.zig +src/css/properties/properties_impl.zig +src/css/properties/properties.zig +src/css/properties/shape.zig +src/css/properties/size.zig +src/css/properties/svg.zig +src/css/properties/text.zig +src/css/properties/transform.zig +src/css/properties/transition.zig +src/css/properties/ui.zig +src/css/rules/container.zig +src/css/rules/counter_style.zig +src/css/rules/custom_media.zig +src/css/rules/document.zig +src/css/rules/font_face.zig +src/css/rules/font_palette_values.zig +src/css/rules/import.zig +src/css/rules/keyframes.zig +src/css/rules/layer.zig +src/css/rules/media.zig +src/css/rules/namespace.zig +src/css/rules/nesting.zig +src/css/rules/page.zig +src/css/rules/property.zig +src/css/rules/rules.zig +src/css/rules/scope.zig +src/css/rules/starting_style.zig +src/css/rules/style.zig +src/css/rules/supports.zig +src/css/rules/tailwind.zig +src/css/rules/unknown.zig +src/css/rules/viewport.zig +src/css/selectors/builder.zig +src/css/selectors/parser.zig +src/css/selectors/selector.zig +src/css/small_list.zig +src/css/sourcemap.zig +src/css/targets.zig +src/css/values/alpha.zig +src/css/values/angle.zig +src/css/values/calc.zig +src/css/values/color_generated.zig +src/css/values/color_js.zig +src/css/values/color.zig +src/css/values/css_string.zig +src/css/values/easing.zig +src/css/values/gradient.zig +src/css/values/ident.zig +src/css/values/image.zig +src/css/values/length.zig +src/css/values/number.zig +src/css/values/percentage.zig +src/css/values/position.zig +src/css/values/ratio.zig +src/css/values/rect.zig +src/css/values/resolution.zig +src/css/values/size.zig +src/css/values/syntax.zig +src/css/values/time.zig +src/css/values/url.zig +src/css/values/values.zig +src/darwin.zig +src/defines-table.zig +src/defines.zig +src/deps/boringssl.translated.zig +src/deps/brotli_c.zig +src/deps/c_ares.zig +src/deps/libdeflate.zig +src/deps/libuv.zig +src/deps/lol-html.zig +src/deps/picohttp.zig +src/deps/picohttpparser.zig +src/deps/tcc.zig +src/deps/uws.zig +src/deps/uws/App.zig +src/deps/uws/BodyReaderMixin.zig +src/deps/uws/ConnectingSocket.zig +src/deps/uws/InternalLoopData.zig +src/deps/uws/ListenSocket.zig +src/deps/uws/Loop.zig +src/deps/uws/Request.zig +src/deps/uws/Response.zig +src/deps/uws/socket.zig +src/deps/uws/SocketContext.zig +src/deps/uws/Timer.zig +src/deps/uws/udp.zig +src/deps/uws/UpgradedDuplex.zig +src/deps/uws/us_socket_t.zig +src/deps/uws/WebSocket.zig +src/deps/uws/WindowsNamedPipe.zig +src/deps/zig-clap/clap.zig +src/deps/zig-clap/clap/args.zig +src/deps/zig-clap/clap/comptime.zig +src/deps/zig-clap/clap/streaming.zig +src/deps/zlib.posix.zig +src/deps/zlib.shared.zig +src/deps/zlib.win32.zig +src/deps/zstd.zig +src/dir.zig +src/dns.zig +src/env_loader.zig +src/env.zig +src/errno/darwin_errno.zig +src/errno/linux_errno.zig +src/errno/windows_errno.zig +src/fd.zig +src/feature_flags.zig +src/fmt.zig +src/fs.zig +src/fs/stat_hash.zig +src/generated_perf_trace_events.zig +src/generated_versions_list.zig +src/glob.zig +src/glob/GlobWalker.zig +src/glob/match.zig +src/Global.zig +src/handle_oom.zig +src/heap_breakdown.zig +src/highway.zig +src/hmac.zig +src/HTMLScanner.zig +src/http.zig +src/http/AsyncHTTP.zig +src/http/CertificateInfo.zig +src/http/Decompressor.zig +src/http/Encoding.zig +src/http/ETag.zig +src/http/FetchRedirect.zig +src/http/HeaderBuilder.zig +src/http/Headers.zig +src/http/HeaderValueIterator.zig +src/http/HTTPCertError.zig +src/http/HTTPContext.zig +src/http/HTTPRequestBody.zig +src/http/HTTPThread.zig +src/http/InitError.zig +src/http/InternalState.zig +src/http/Method.zig +src/http/mime_type_list_enum.zig +src/http/MimeType.zig +src/http/ProxyTunnel.zig +src/http/SendFile.zig +src/http/Signals.zig +src/http/ThreadSafeStreamBuffer.zig +src/http/URLPath.zig +src/http/websocket_client.zig +src/http/websocket_client/CppWebSocket.zig +src/http/websocket_client/WebSocketDeflate.zig +src/http/websocket_client/WebSocketUpgradeClient.zig +src/http/websocket_http_client.zig +src/http/websocket.zig +src/http/zlib.zig +src/identity_context.zig +src/import_record.zig +src/ini.zig +src/install/bin.zig +src/install/dependency.zig +src/install/ExternalSlice.zig +src/install/extract_tarball.zig +src/install/hoisted_install.zig +src/install/install_binding.zig +src/install/install.zig +src/install/integrity.zig +src/install/isolated_install.zig +src/install/isolated_install/FileCopier.zig +src/install/isolated_install/Hardlinker.zig +src/install/isolated_install/Installer.zig +src/install/isolated_install/Store.zig +src/install/isolated_install/Symlinker.zig +src/install/lifecycle_script_runner.zig +src/install/lockfile.zig +src/install/lockfile/Buffers.zig +src/install/lockfile/bun.lock.zig +src/install/lockfile/bun.lockb.zig +src/install/lockfile/CatalogMap.zig +src/install/lockfile/lockfile_json_stringify_for_debugging.zig +src/install/lockfile/OverrideMap.zig +src/install/lockfile/Package.zig +src/install/lockfile/Package/Meta.zig +src/install/lockfile/Package/Scripts.zig +src/install/lockfile/Package/WorkspaceMap.zig +src/install/lockfile/printer/tree_printer.zig +src/install/lockfile/printer/Yarn.zig +src/install/lockfile/Tree.zig +src/install/migration.zig +src/install/NetworkTask.zig +src/install/npm.zig +src/install/PackageInstall.zig +src/install/PackageInstaller.zig +src/install/PackageManager.zig +src/install/PackageManager/CommandLineArguments.zig +src/install/PackageManager/install_with_manager.zig +src/install/PackageManager/PackageJSONEditor.zig +src/install/PackageManager/PackageManagerDirectories.zig +src/install/PackageManager/PackageManagerEnqueue.zig +src/install/PackageManager/PackageManagerLifecycle.zig +src/install/PackageManager/PackageManagerOptions.zig +src/install/PackageManager/PackageManagerResolution.zig +src/install/PackageManager/patchPackage.zig +src/install/PackageManager/processDependencyList.zig +src/install/PackageManager/ProgressStrings.zig +src/install/PackageManager/runTasks.zig +src/install/PackageManager/security_scanner.zig +src/install/PackageManager/updatePackageJSONAndInstall.zig +src/install/PackageManager/UpdateRequest.zig +src/install/PackageManager/WorkspacePackageJSONCache.zig +src/install/PackageManagerTask.zig +src/install/PackageManifestMap.zig +src/install/padding_checker.zig +src/install/patch_install.zig +src/install/repository.zig +src/install/resolution.zig +src/install/resolvers/folder_resolver.zig +src/install/versioned_url.zig +src/install/windows-shim/BinLinkingShim.zig +src/install/windows-shim/bun_shim_impl.zig +src/install/yarn.zig +src/interchange.zig +src/interchange/json.zig +src/interchange/toml.zig +src/interchange/toml/lexer.zig +src/interchange/yaml.zig +src/io/heap.zig +src/io/io.zig +src/io/MaxBuf.zig +src/io/openForWriting.zig +src/io/PipeReader.zig +src/io/pipes.zig +src/io/PipeWriter.zig +src/io/source.zig +src/js_lexer_tables.zig +src/js_lexer.zig +src/js_lexer/identifier.zig +src/js_parser.zig +src/js_printer.zig +src/jsc_stub.zig +src/libarchive/libarchive-bindings.zig +src/libarchive/libarchive.zig +src/linear_fifo.zig +src/linker.zig +src/linux.zig +src/logger.zig +src/macho.zig +src/main_test.zig +src/main_wasm.zig +src/main.zig +src/memory.zig +src/meta.zig +src/napi/napi.zig +src/node_fallbacks.zig +src/open.zig +src/options.zig +src/output.zig +src/OutputFile.zig +src/patch.zig +src/paths.zig +src/paths/EnvPath.zig +src/paths/path_buffer_pool.zig +src/paths/Path.zig +src/pe.zig +src/perf.zig +src/pool.zig +src/Progress.zig +src/ptr.zig +src/ptr/Cow.zig +src/ptr/CowSlice.zig +src/ptr/meta.zig +src/ptr/owned.zig +src/ptr/ref_count.zig +src/ptr/shared.zig +src/ptr/tagged_pointer.zig +src/ptr/weak_ptr.zig +src/renamer.zig +src/resolver/data_url.zig +src/resolver/dir_info.zig +src/resolver/package_json.zig +src/resolver/resolve_path.zig +src/resolver/resolver.zig +src/resolver/tsconfig_json.zig +src/result.zig +src/router.zig +src/runtime.zig +src/s3/acl.zig +src/s3/client.zig +src/s3/credentials.zig +src/s3/download_stream.zig +src/s3/error.zig +src/s3/list_objects.zig +src/s3/multipart_options.zig +src/s3/multipart.zig +src/s3/simple_request.zig +src/s3/storage_class.zig +src/safety.zig +src/safety/alloc.zig +src/safety/CriticalSection.zig +src/safety/thread_id.zig +src/safety/ThreadLock.zig +src/semver.zig +src/semver/ExternalString.zig +src/semver/SemverObject.zig +src/semver/SemverQuery.zig +src/semver/SemverRange.zig +src/semver/SemverString.zig +src/semver/SlicedString.zig +src/semver/Version.zig +src/sha.zig +src/shell/AllocScope.zig +src/shell/braces.zig +src/shell/Builtin.zig +src/shell/builtin/basename.zig +src/shell/builtin/cat.zig +src/shell/builtin/cd.zig +src/shell/builtin/cp.zig +src/shell/builtin/dirname.zig +src/shell/builtin/echo.zig +src/shell/builtin/exit.zig +src/shell/builtin/export.zig +src/shell/builtin/false.zig +src/shell/builtin/ls.zig +src/shell/builtin/mkdir.zig +src/shell/builtin/mv.zig +src/shell/builtin/pwd.zig +src/shell/builtin/rm.zig +src/shell/builtin/seq.zig +src/shell/builtin/touch.zig +src/shell/builtin/true.zig +src/shell/builtin/which.zig +src/shell/builtin/yes.zig +src/shell/EnvMap.zig +src/shell/EnvStr.zig +src/shell/interpreter.zig +src/shell/IO.zig +src/shell/IOReader.zig +src/shell/IOWriter.zig +src/shell/ParsedShellScript.zig +src/shell/RefCountedStr.zig +src/shell/shell.zig +src/shell/states/Assigns.zig +src/shell/states/Async.zig +src/shell/states/Base.zig +src/shell/states/Binary.zig +src/shell/states/Cmd.zig +src/shell/states/CondExpr.zig +src/shell/states/Expansion.zig +src/shell/states/If.zig +src/shell/states/Pipeline.zig +src/shell/states/Script.zig +src/shell/states/Stmt.zig +src/shell/states/Subshell.zig +src/shell/subproc.zig +src/shell/util.zig +src/shell/Yield.zig +src/sourcemap/CodeCoverage.zig +src/sourcemap/JSSourceMap.zig +src/sourcemap/LineOffsetTable.zig +src/sourcemap/sourcemap.zig +src/sourcemap/VLQ.zig +src/sql/mysql.zig +src/sql/mysql/AuthMethod.zig +src/sql/mysql/Capabilities.zig +src/sql/mysql/ConnectionState.zig +src/sql/mysql/MySQLConnection.zig +src/sql/mysql/MySQLContext.zig +src/sql/mysql/MySQLQuery.zig +src/sql/mysql/MySQLRequest.zig +src/sql/mysql/MySQLStatement.zig +src/sql/mysql/MySQLTypes.zig +src/sql/mysql/protocol/AnyMySQLError.zig +src/sql/mysql/protocol/Auth.zig +src/sql/mysql/protocol/AuthSwitchRequest.zig +src/sql/mysql/protocol/AuthSwitchResponse.zig +src/sql/mysql/protocol/CharacterSet.zig +src/sql/mysql/protocol/ColumnDefinition41.zig +src/sql/mysql/protocol/CommandType.zig +src/sql/mysql/protocol/DecodeBinaryValue.zig +src/sql/mysql/protocol/EncodeInt.zig +src/sql/mysql/protocol/EOFPacket.zig +src/sql/mysql/protocol/ErrorPacket.zig +src/sql/mysql/protocol/HandshakeResponse41.zig +src/sql/mysql/protocol/HandshakeV10.zig +src/sql/mysql/protocol/LocalInfileRequest.zig +src/sql/mysql/protocol/NewReader.zig +src/sql/mysql/protocol/NewWriter.zig +src/sql/mysql/protocol/OKPacket.zig +src/sql/mysql/protocol/PacketHeader.zig +src/sql/mysql/protocol/PacketType.zig +src/sql/mysql/protocol/PreparedStatement.zig +src/sql/mysql/protocol/Query.zig +src/sql/mysql/protocol/ResultSet.zig +src/sql/mysql/protocol/ResultSetHeader.zig +src/sql/mysql/protocol/Signature.zig +src/sql/mysql/protocol/SSLRequest.zig +src/sql/mysql/protocol/StackReader.zig +src/sql/mysql/protocol/StmtPrepareOKPacket.zig +src/sql/mysql/SSLMode.zig +src/sql/mysql/StatusFlags.zig +src/sql/mysql/TLSStatus.zig +src/sql/postgres.zig +src/sql/postgres/AnyPostgresError.zig +src/sql/postgres/AuthenticationState.zig +src/sql/postgres/CommandTag.zig +src/sql/postgres/DataCell.zig +src/sql/postgres/DebugSocketMonitorReader.zig +src/sql/postgres/DebugSocketMonitorWriter.zig +src/sql/postgres/PostgresProtocol.zig +src/sql/postgres/PostgresRequest.zig +src/sql/postgres/PostgresSQLConnection.zig +src/sql/postgres/PostgresSQLContext.zig +src/sql/postgres/PostgresSQLQuery.zig +src/sql/postgres/PostgresSQLStatement.zig +src/sql/postgres/PostgresTypes.zig +src/sql/postgres/protocol/ArrayList.zig +src/sql/postgres/protocol/Authentication.zig +src/sql/postgres/protocol/BackendKeyData.zig +src/sql/postgres/protocol/Close.zig +src/sql/postgres/protocol/CommandComplete.zig +src/sql/postgres/protocol/CopyData.zig +src/sql/postgres/protocol/CopyFail.zig +src/sql/postgres/protocol/CopyInResponse.zig +src/sql/postgres/protocol/CopyOutResponse.zig +src/sql/postgres/protocol/DataRow.zig +src/sql/postgres/protocol/DecoderWrap.zig +src/sql/postgres/protocol/Describe.zig +src/sql/postgres/protocol/ErrorResponse.zig +src/sql/postgres/protocol/Execute.zig +src/sql/postgres/protocol/FieldDescription.zig +src/sql/postgres/protocol/FieldMessage.zig +src/sql/postgres/protocol/FieldType.zig +src/sql/postgres/protocol/NegotiateProtocolVersion.zig +src/sql/postgres/protocol/NewReader.zig +src/sql/postgres/protocol/NewWriter.zig +src/sql/postgres/protocol/NoticeResponse.zig +src/sql/postgres/protocol/NotificationResponse.zig +src/sql/postgres/protocol/ParameterDescription.zig +src/sql/postgres/protocol/ParameterStatus.zig +src/sql/postgres/protocol/Parse.zig +src/sql/postgres/protocol/PasswordMessage.zig +src/sql/postgres/protocol/PortalOrPreparedStatement.zig +src/sql/postgres/protocol/ReadyForQuery.zig +src/sql/postgres/protocol/RowDescription.zig +src/sql/postgres/protocol/SASLInitialResponse.zig +src/sql/postgres/protocol/SASLResponse.zig +src/sql/postgres/protocol/StackReader.zig +src/sql/postgres/protocol/StartupMessage.zig +src/sql/postgres/protocol/TransactionStatusIndicator.zig +src/sql/postgres/protocol/WriteWrap.zig +src/sql/postgres/protocol/zHelpers.zig +src/sql/postgres/SASL.zig +src/sql/postgres/Signature.zig +src/sql/postgres/SocketMonitor.zig +src/sql/postgres/SSLMode.zig +src/sql/postgres/Status.zig +src/sql/postgres/TLSStatus.zig +src/sql/postgres/types/bool.zig +src/sql/postgres/types/bytea.zig +src/sql/postgres/types/date.zig +src/sql/postgres/types/int_types.zig +src/sql/postgres/types/json.zig +src/sql/postgres/types/numeric.zig +src/sql/postgres/types/PostgresString.zig +src/sql/postgres/types/Tag.zig +src/sql/shared/CachedStructure.zig +src/sql/shared/ColumnIdentifier.zig +src/sql/shared/ConnectionFlags.zig +src/sql/shared/Data.zig +src/sql/shared/ObjectIterator.zig +src/sql/shared/QueryBindingIterator.zig +src/sql/shared/SQLDataCell.zig +src/sql/shared/SQLQueryResultMode.zig +src/StandaloneModuleGraph.zig +src/StaticHashMap.zig +src/string.zig +src/string/HashedString.zig +src/string/immutable.zig +src/string/immutable/escapeHTML.zig +src/string/immutable/exact_size_matcher.zig +src/string/immutable/grapheme.zig +src/string/immutable/paths.zig +src/string/immutable/unicode.zig +src/string/immutable/visible.zig +src/string/MutableString.zig +src/string/PathString.zig +src/string/SmolStr.zig +src/string/StringBuilder.zig +src/string/StringJoiner.zig +src/string/WTFStringImpl.zig +src/sys_uv.zig +src/sys.zig +src/sys/coreutils_error_map.zig +src/sys/Error.zig +src/sys/File.zig +src/sys/libuv_error_map.zig +src/system_timer.zig +src/test/fixtures.zig +src/test/recover.zig +src/threading.zig +src/threading/channel.zig +src/threading/Condition.zig +src/threading/Futex.zig +src/threading/guarded.zig +src/threading/Mutex.zig +src/threading/ThreadPool.zig +src/threading/unbounded_queue.zig +src/threading/WaitGroup.zig +src/tmp.zig +src/tracy.zig +src/trait.zig +src/transpiler.zig +src/unit_test.zig +src/url.zig +src/util.zig +src/valkey/index.zig +src/valkey/js_valkey_functions.zig +src/valkey/js_valkey.zig +src/valkey/valkey_protocol.zig +src/valkey/valkey.zig +src/valkey/ValkeyCommand.zig +src/valkey/ValkeyContext.zig +src/walker_skippable.zig +src/Watcher.zig +src/watcher/INotifyWatcher.zig +src/watcher/KEventWatcher.zig +src/watcher/WindowsWatcher.zig +src/which_npm_client.zig +src/which.zig +src/windows.zig +src/work_pool.zig +src/workaround_missing_symbols.zig +src/wyhash.zig +src/zlib.zig diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig index e5ea83b5d3..58b149db96 100644 --- a/src/bun.js/event_loop.zig +++ b/src/bun.js/event_loop.zig @@ -98,7 +98,7 @@ pub fn pipeReadBuffer(this: *const EventLoop) []u8 { } pub const Queue = std.fifo.LinearFifo(Task, .Dynamic); -const log = bun.Output.scoped(.EventLoop, .visible); +const log = bun.Output.scoped(.EventLoop, .hidden); pub fn tickWhilePaused(this: *EventLoop, done: *bool) void { while (!done.*) { diff --git a/src/sql/mysql/MySQLConnection.zig b/src/sql/mysql/MySQLConnection.zig index 0690a425b1..82bce2824e 100644 --- a/src/sql/mysql/MySQLConnection.zig +++ b/src/sql/mysql/MySQLConnection.zig @@ -254,11 +254,14 @@ fn drainInternal(this: *@This()) void { defer event_loop.exit(); this.flushData(); - if (!this.flags.has_backpressure) { - // no backpressure yet so pipeline more if possible and flush again - this.advance(); - this.flushData(); + if (this.tls_status == .message_sent) { + this.upgradeToTLS(); + } else { + // no backpressure yet so pipeline more if possible and flush again + this.advance(); + this.flushData(); + } } } pub fn finalize(this: *MySQLConnection) void { @@ -820,6 +823,7 @@ pub fn call(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JS return globalObject.throwValue(err.toJS(globalObject)); } + debug("configured TLS context", .{}); uws.NewSocketHandler(true).configure(tls_ctx.?, true, *@This(), SocketHandler(true)); } @@ -908,6 +912,7 @@ pub fn call(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JS }; if (path.len > 0) { + debug("connecting to mysql with path", .{}); ptr.socket = .{ .SocketTCP = uws.SocketTCP.connectUnixAnon(path, ctx, ptr, false) catch |err| { tls_config.deinit(); @@ -919,6 +924,7 @@ pub fn call(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JS }, }; } else { + debug("connecting to mysql with hostname", .{}); ptr.socket = .{ .SocketTCP = uws.SocketTCP.connectAnon(hostname.slice(), port, ctx, ptr, false) catch |err| { tls_config.deinit(); @@ -973,26 +979,49 @@ pub fn deinit(this: *MySQLConnection) void { bun.default_allocator.destroy(this); } +pub fn upgradeToTLS(this: *MySQLConnection) void { + if (this.socket == .SocketTCP) { + const new_socket = this.socket.SocketTCP.socket.connected.upgrade(this.tls_ctx.?, this.tls_config.server_name) orelse { + this.fail("Failed to upgrade to TLS", error.AuthenticationFailed); + return; + }; + this.socket = .{ + .SocketTLS = .{ + .socket = .{ + .connected = new_socket, + }, + }, + }; + } +} + pub fn onOpen(this: *MySQLConnection, socket: Socket) void { + debug("onOpen", .{}); this.setupMaxLifetimeTimerIfNecessary(); this.resetConnectionTimeout(); this.socket = socket; - this.setStatus(.handshaking); + if (socket == .SocketTCP) { + // when upgrading to TLS the onOpen callback will be called again and at this moment we dont wanna to change the status to handshaking + this.setStatus(.handshaking); + } this.poll_ref.ref(this.vm); this.updateHasPendingActivity(); } pub fn onHandshake(this: *MySQLConnection, success: i32, ssl_error: uws.us_bun_verify_error_t) void { - debug("onHandshake: {d} {d}", .{ success, ssl_error.error_no }); + debug("onHandshake: {d} {d} {s}", .{ success, ssl_error.error_no, @tagName(this.ssl_mode) }); const handshake_success = if (success == 1) true else false; + this.sequence_id = this.sequence_id +% 1; if (handshake_success) { + this.tls_status = .ssl_ok; if (this.tls_config.reject_unauthorized != 0) { + // follow the same rules as postgres + // https://github.com/porsager/postgres/blob/6ec85a432b17661ccacbdf7f765c651e88969d36/src/connection.js#L272-L279 // only reject the connection if reject_unauthorized == true switch (this.ssl_mode) { - // https://github.com/porsager/postgres/blob/6ec85a432b17661ccacbdf7f765c651e88969d36/src/connection.js#L272-L279 - .verify_ca, .verify_full => { if (ssl_error.error_no != 0) { + this.tls_status = .ssl_failed; this.failWithJSValue(ssl_error.toJS(this.globalObject)); return; } @@ -1001,16 +1030,18 @@ pub fn onHandshake(this: *MySQLConnection, success: i32, ssl_error: uws.us_bun_v if (BoringSSL.c.SSL_get_servername(ssl_ptr, 0)) |servername| { const hostname = servername[0..bun.len(servername)]; if (!BoringSSL.checkServerIdentity(ssl_ptr, hostname)) { - this.failWithJSValue(ssl_error.toJS(this.globalObject)); + this.tls_status = .ssl_failed; + return this.failWithJSValue(ssl_error.toJS(this.globalObject)); } } }, - else => { - return; - }, + // require is the same as prefer + .require, .prefer, .disable => {}, } } + this.sendHandshakeResponse() catch |err| this.failFmt(err, "Failed to send handshake response", .{}); } else { + this.tls_status = .ssl_failed; // if we are here is because server rejected us, and the error_no is the cause of this // no matter if reject_unauthorized is false because we are disconnected by the server this.failWithJSValue(ssl_error.toJS(this.globalObject)); @@ -1186,6 +1217,36 @@ pub fn handleHandshake(this: *MySQLConnection, comptime Context: type, reader: N // Update status this.setStatus(.authenticating); + // https://dev.mysql.com/doc/dev/mysql-server/8.4.6/page_protocol_connection_phase_packets_protocol_ssl_request.html + if (this.capabilities.CLIENT_SSL) { + var response = SSLRequest{ + .capability_flags = this.capabilities, + .max_packet_size = 0, //16777216, + .character_set = CharacterSet.default, + // bun always send connection attributes + .has_connection_attributes = true, + }; + defer response.deinit(); + try response.write(this.writer()); + this.capabilities = response.capability_flags; + this.tls_status = .message_sent; + this.flushData(); + if (!this.flags.has_backpressure) { + this.upgradeToTLS(); + } + return; + } + if (this.tls_status != .none) { + this.tls_status = .ssl_not_available; + + switch (this.ssl_mode) { + .verify_ca, .verify_full => { + return this.failFmt(error.AuthenticationFailed, "SSL is not available", .{}); + }, + // require is the same as prefer + .require, .prefer, .disable => {}, + } + } // Send auth response try this.sendHandshakeResponse(); } @@ -1325,7 +1386,7 @@ pub fn handleAuth(this: *MySQLConnection, comptime Context: type, reader: NewRea debug("sending password TLS enabled", .{}); // SSL mode is enabled, send password as is var packet = try this.writer().start(this.sequence_id); - try this.writer().write(this.password); + try this.writer().writeZ(this.password); try packet.end(); this.flushData(); } @@ -1427,6 +1488,7 @@ pub fn handleCommand(this: *MySQLConnection, comptime Context: type, reader: New } pub fn sendHandshakeResponse(this: *MySQLConnection) AnyMySQLError.Error!void { + debug("sendHandshakeResponse", .{}); // Only require password for caching_sha2_password when connecting for the first time if (this.auth_plugin) |plugin| { const requires_password = switch (plugin) { @@ -1458,6 +1520,7 @@ pub fn sendHandshakeResponse(this: *MySQLConnection) AnyMySQLError.Error!void { "", }, .auth_response = .{ .empty = {} }, + .sequence_id = this.sequence_id, }; defer response.deinit(); @@ -1938,6 +2001,7 @@ const PacketHeader = @import("./protocol/PacketHeader.zig"); const PreparedStatement = @import("./protocol/PreparedStatement.zig"); const ResultSet = @import("./protocol/ResultSet.zig"); const ResultSetHeader = @import("./protocol/ResultSetHeader.zig"); +const SSLRequest = @import("./protocol/SSLRequest.zig"); const SocketMonitor = @import("../postgres/SocketMonitor.zig"); const StackReader = @import("./protocol/StackReader.zig"); const StmtPrepareOKPacket = @import("./protocol/StmtPrepareOKPacket.zig"); diff --git a/src/sql/mysql/TLSStatus.zig b/src/sql/mysql/TLSStatus.zig index a711af013a..47a027f82f 100644 --- a/src/sql/mysql/TLSStatus.zig +++ b/src/sql/mysql/TLSStatus.zig @@ -4,8 +4,9 @@ pub const TLSStatus = union(enum) { /// Number of bytes sent of the 8-byte SSL request message. /// Since we may send a partial message, we need to know how many bytes were sent. - message_sent: u8, + message_sent, ssl_not_available, + ssl_failed, ssl_ok, }; diff --git a/src/sql/mysql/protocol/HandshakeResponse41.zig b/src/sql/mysql/protocol/HandshakeResponse41.zig index 5d56b3942e..be237a7780 100644 --- a/src/sql/mysql/protocol/HandshakeResponse41.zig +++ b/src/sql/mysql/protocol/HandshakeResponse41.zig @@ -8,6 +8,7 @@ auth_response: Data, database: Data, auth_plugin_name: Data, connect_attrs: bun.StringHashMapUnmanaged([]const u8) = .{}, +sequence_id: u8, pub fn deinit(this: *HandshakeResponse41) void { this.username.deinit(); @@ -24,14 +25,14 @@ pub fn deinit(this: *HandshakeResponse41) void { } pub fn writeInternal(this: *HandshakeResponse41, comptime Context: type, writer: NewWriter(Context)) !void { - var packet = try writer.start(1); + var packet = try writer.start(this.sequence_id); this.capability_flags.CLIENT_CONNECT_ATTRS = this.connect_attrs.count() > 0; // Write client capabilities flags (4 bytes) const caps = this.capability_flags.toInt(); try writer.int4(caps); - debug("Client capabilities: [{}] 0x{x:0>8}", .{ this.capability_flags, caps }); + debug("Client capabilities: [{}] 0x{x:0>8} sequence_id: {d}", .{ this.capability_flags, caps, this.sequence_id }); // Write max packet size (4 bytes) try writer.int4(this.max_packet_size); diff --git a/src/sql/mysql/protocol/SSLRequest.zig b/src/sql/mysql/protocol/SSLRequest.zig new file mode 100644 index 0000000000..5579e2f85f --- /dev/null +++ b/src/sql/mysql/protocol/SSLRequest.zig @@ -0,0 +1,42 @@ +// https://dev.mysql.com/doc/dev/mysql-server/8.4.6/page_protocol_connection_phase_packets_protocol_ssl_request.html +// SSLRequest +const SSLRequest = @This(); +capability_flags: Capabilities, +max_packet_size: u32 = 0xFFFFFF, // 16MB default +character_set: CharacterSet = CharacterSet.default, +has_connection_attributes: bool = false, + +pub fn deinit(_: *SSLRequest) void {} + +pub fn writeInternal(this: *SSLRequest, comptime Context: type, writer: NewWriter(Context)) !void { + var packet = try writer.start(1); + + this.capability_flags.CLIENT_CONNECT_ATTRS = this.has_connection_attributes; + + // Write client capabilities flags (4 bytes) + const caps = this.capability_flags.toInt(); + try writer.int4(caps); + debug("Client capabilities: [{}] 0x{x:0>8}", .{ this.capability_flags, caps }); + + // Write max packet size (4 bytes) + try writer.int4(this.max_packet_size); + + // Write character set (1 byte) + try writer.int1(@intFromEnum(this.character_set)); + + // Write 23 bytes of padding + try writer.write(&[_]u8{0} ** 23); + + try packet.end(); +} + +pub const write = writeWrap(SSLRequest, writeInternal).write; + +const debug = bun.Output.scoped(.MySQLConnection, .hidden); + +const Capabilities = @import("../Capabilities.zig"); +const bun = @import("bun"); +const CharacterSet = @import("./CharacterSet.zig").CharacterSet; + +const NewWriter = @import("./NewWriter.zig").NewWriter; +const writeWrap = @import("./NewWriter.zig").writeWrap; diff --git a/src/sql/postgres/PostgresSQLConnection.zig b/src/sql/postgres/PostgresSQLConnection.zig index f5bbeebdc0..a7422f532f 100644 --- a/src/sql/postgres/PostgresSQLConnection.zig +++ b/src/sql/postgres/PostgresSQLConnection.zig @@ -443,9 +443,8 @@ pub fn onHandshake(this: *PostgresSQLConnection, success: i32, ssl_error: uws.us } } }, - else => { - return; - }, + // require is the same as prefer + .require, .prefer, .disable => {}, } } } else { diff --git a/test/harness.ts b/test/harness.ts index 803bac0676..2183e7585d 100644 --- a/test/harness.ts +++ b/test/harness.ts @@ -13,6 +13,7 @@ import { readdir, readFile, readlink, rm, writeFile } from "fs/promises"; import fs, { closeSync, openSync, rmSync } from "node:fs"; import os from "node:os"; import { dirname, isAbsolute, join } from "path"; +import { execSync } from "child_process"; type Awaitable = T | Promise; @@ -856,6 +857,24 @@ export function dockerExe(): string | null { return which("docker") || which("podman") || null; } +export function isDockerEnabled(): boolean { + const dockerCLI = dockerExe(); + if (!dockerCLI) { + return false; + } + + // TODO: investigate why its not starting on Linux arm64 + if ((isLinux && process.arch === "arm64") || isMacOS) { + return false; + } + + try { + const info = execSync(`${dockerCLI} info`, { stdio: ["ignore", "pipe", "inherit"] }); + return info.toString().indexOf("Server Version:") !== -1; + } catch { + return false; + } +} export async function waitForPort(port: number, timeout: number = 60_000): Promise { let deadline = Date.now() + Math.max(1, timeout); let error: unknown; diff --git a/test/js/sql/mysql-tls/Dockerfile b/test/js/sql/mysql-tls/Dockerfile new file mode 100644 index 0000000000..2c9647f2ac --- /dev/null +++ b/test/js/sql/mysql-tls/Dockerfile @@ -0,0 +1,22 @@ +# Dockerfile +ARG MYSQL_VERSION=8.4 +FROM mysql:${MYSQL_VERSION} + +# Copy TLS materials + config +# Expect these in the build context: +# ssl/ca.pem +# ssl/server-cert.pem +# ssl/server-key.pem +# conf.d/ssl.cnf +COPY ssl /etc/mysql/ssl +COPY conf.d /etc/mysql/conf.d + +# Lock down permissions so mysqld accepts the key +# The official image runs mysqld as user "mysql" +RUN chown -R mysql:mysql /etc/mysql/ssl /etc/mysql/conf.d \ + && chmod 600 /etc/mysql/ssl/server-key.pem \ + && find /etc/mysql/ssl -type f -name "*.pem" -exec chmod 640 {} \; \ + && echo "require_secure_transport=ON" >> /etc/mysql/conf.d/force_tls.cnf + +# Expose MySQL +EXPOSE 3306 \ No newline at end of file diff --git a/test/js/sql/mysql-tls/conf.d/ssl.cnf b/test/js/sql/mysql-tls/conf.d/ssl.cnf new file mode 100644 index 0000000000..8de43572b2 --- /dev/null +++ b/test/js/sql/mysql-tls/conf.d/ssl.cnf @@ -0,0 +1,7 @@ +[mysqld] +require_secure_transport=ON +ssl_ca=/etc/mysql/ssl/ca.pem +ssl_cert=/etc/mysql/ssl/server-cert.pem +ssl_key=/etc/mysql/ssl/server-key.pem +tls_version=TLSv1.2,TLSv1.3 +skip_name_resolve=ON diff --git a/test/js/sql/mysql-tls/ssl/ca-key.pem b/test/js/sql/mysql-tls/ssl/ca-key.pem new file mode 100644 index 0000000000..0eeea1af4a --- /dev/null +++ b/test/js/sql/mysql-tls/ssl/ca-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +nqvCgB/RFZmsH54yrG/MBVretR2ocg== +-----END PRIVATE KEY----- diff --git a/test/js/sql/mysql-tls/ssl/ca.pem b/test/js/sql/mysql-tls/ssl/ca.pem new file mode 100644 index 0000000000..cb436e0cf7 --- /dev/null +++ b/test/js/sql/mysql-tls/ssl/ca.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFEzCCAvugAwIBAgIUN1lAPnifp5z9GpMzfITrw4sXSOswDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwObG9jYWwtbXlzcWwtQ0EwHhcNMjUwOTAzMTkwODI1WhcN +MzUwOTAxMTkwODI1WjAZMRcwFQYDVQQDDA5sb2NhbC1teXNxbC1DQTCCAiIwDQYJ +KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJlsJOVUgBz9cTNzXOAC4k/4l+lUwyRM +cbP7Qjtnjld1pzZfTRI5T77TgCrxYT5swGgae/lC+qcW+VxR5kj2LYiaVdKTMR1y +Z9SzC3OUOptZhOhJJzJ3LiscFkJyY+s/F7TP3UyuyxTo3X9jSgYZ1aT6geBK/xgR +Txyo1v+3bnL3fBLXUs2oqcqPD4kw/nF7jfowzrLuWL5skxquvc3RqDXhHhaBsocB +IXtBGFYSzE13wTJS4MtDfpMCbBldXcrC4IdlJRKzvz5F8k7/AtgmYC6yYlfB2o/s +en2hMPHqTA7mDGLByQycMgjoGHfFs/MP+YdGmk0sRZwVOp6vonFyeRNMKPTb7xgq +L8Vb42eyHbculct4pnF2auC88+pxad1PWw/z3Mt5oLSuEqlL4UC52pvEPSybvCyk +WLOooHGIbrpMJ2q1kR+U7JYXv8sW0Kq6VaoFJ4aJMha/wjh9VlhLO3AlY74bgO5k +KTQV9mqvsAu4mKrw5sZvAHboW7OG5oWTs4wnbQ2RZCdOIp4YWC/IzfV3BdKKW5gW +G7oc4vXiJH/v1pShBLVzwWWrTQDLUx9szwU3c2rNL9Wkt4gmH9IuLH+/SIvuPPFg +sG+awSlMOnIlNEDQcaqu2/bu6J5SfBBgUYl2ZuY5dduOZEMql+fxNhSRytTnjmuM +KNUgA1ynQRHhAgMBAAGjUzBRMB0GA1UdDgQWBBQ/XwM2Ps/SQvtWinFGbsp/QeNj +8DAfBgNVHSMEGDAWgBQ/XwM2Ps/SQvtWinFGbsp/QeNj8DAPBgNVHRMBAf8EBTAD +AQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAfRxuRG1eN82LPKuQL5PvKheYg3BEM+2lG +XuaQK+SIIXdu/TROgi4N94Xs/d7NxGsivBQa/lDaVXHnYBpkkGWs6apnCa9k7qwQ +wolSDL0qfkUxVSpqKtcPYTKrT6DPOUUizBT38500/mTawlQozUxsLfiOSkwgBIo8 +4/XeWAImhxszn5G3tdKO6BaTcXYuYz0sduQudDUmxWMy3ltcFurNPUYZwOBSR8l6 +Cf5sRnDQbIiJ1njdPTszhppp9negklmgwatNlYgcqwCSGft1NQvO2KkurnfExGjs +YSQy21CFwHje0FapJxCtqHAwToDtAST4aqO2ZOMwfimv0NcW8V2/wkmO5wPZ/zJM +OLctFVplJ8kIQwYbvgKWYl1b4eTVE/LYYHpw98SovoAVH3GBNay8oRLX08aTBvx9 +Bc6JUPX/z69As0yNGznP1eS6GgzE0ZtxHyRHsjPl0deFDv6oT6xvWrPCNl2kpZTu +xTieN1MUQ+zsq8oT3LpMR1n6xI44P22nc7+NFeyWNaWT/j5gIi90v0zqDh+uXIYC +Q6h+tsKRIff3bRdfRZjk2fyYNMFkUqM/16aYUhk64PAO40NAXvGpW4mkep+boZcO +2LnJ9UTa6egbkn3YtmABQjIuFuR7y/IIcMtFgVkDFM6FhgESypHBtY9bKkXWDDMz +nW+dMjhQNA== +-----END CERTIFICATE----- diff --git a/test/js/sql/mysql-tls/ssl/ca.srl b/test/js/sql/mysql-tls/ssl/ca.srl new file mode 100644 index 0000000000..d333ac319b --- /dev/null +++ b/test/js/sql/mysql-tls/ssl/ca.srl @@ -0,0 +1 @@ +4DC92406985980749D42DC174C7C9CE08A0033F4 diff --git a/test/js/sql/mysql-tls/ssl/server-cert.pem b/test/js/sql/mysql-tls/ssl/server-cert.pem new file mode 100644 index 0000000000..9164221fb0 --- /dev/null +++ b/test/js/sql/mysql-tls/ssl/server-cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFSzCCAzOgAwIBAgIUTckkBphZgHSdQtwXTHyc4IoAM/QwDQYJKoZIhvcNAQEL +BQAwGTEXMBUGA1UEAwwObG9jYWwtbXlzcWwtQ0EwHhcNMjUwOTAzMTkxMDA4WhcN +MzUwOTAxMTkxMDA4WjAQMQ4wDAYDVQQDDAVteXNxbDCCAiIwDQYJKoZIhvcNAQEB +BQADggIPADCCAgoCggIBALZNyqkceIfDZge1yitBoTYZe/PJiHhShOk66caUjKTs +0dkfTBnzOdF/VBMv33co4FwO8TA1YNdI6ZgiNL2Np4kxPpoHyPi/6TKMESyxM9Cm +ijxW661ytQeFub2h6HStXZ1xCxhHADnY22JB2MW4L0qoff0ybKnm5grX+ko11bPx +BbP+d5MsXdRub0hd270O1sWOFCk996sNx9btv1VIiTCjc0KSeFZChLyPj68/cFip +8uxIntYe6ZVx8utmlX0Ikkp5192TllWtTSefJStGYnrIUG8cZfDYVKUO/fiteTWI +q9w6AXt2/oj1nR6ea3ZKcJJ0szgv7dlfAq4FU+oCk+J/iPChXLqZFgqsnB1NxHo6 +J3XdO6W0BD5RMOgZr0OIZsPJlTxsgiTjrLVsSIdgTSL3Dd8QANu7L/MW5DuIm9yZ +NA0V72yJ4/sMtrha/9kW/ZzJDZ7RbhY85ddp+IeFz7Roc3sVmKlE/COsJQB365Pz +yHp95/c0Q/3SUU3WvKCe/0tVTaJfXi7MytSrZjyir3XE7VGsQGavU8NRU3SZ1C9r +JHChah1rlUAwY0t8dp8f7uOvi6u5I7p2PGTVqGRLZVu46uqM+hvf7Mvmy1w8PQY4 +O4wFAjY9Dx11jzCgAZRdUFpwPlfBrhucec5LgJjfSHwk25vZ1lELMuwjSThpLF3J +AgMBAAGjgZMwgZAwIQYDVR0RBBowGIIFbXlzcWyCCWxvY2FsaG9zdIcEfwAAATAJ +BgNVHRMEAjAAMAsGA1UdDwQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAdBgNV +HQ4EFgQUDE/yhvcQOQs8CjmRnF/MJihmT8wwHwYDVR0jBBgwFoAUP18DNj7P0kL7 +VopxRm7Kf0HjY/AwDQYJKoZIhvcNAQELBQADggIBAJLSxzKPflsv4uNfQNFlI1xD +dwBum/lFDnoAQOubsFgmEHDm8Th5HYw/kSZ3ooC0Hkyv6IyO/bcNQNwaT7OhVvPa +s7ZXklO1/Yk7ohVHJr1202ifqgxmsRXfYtqaImU1wMlbPrd72RayRI3zyQHbbAan +VM6zJ322SpVXVWMeFytSQoYbgMnjXdcZRI/P2Ewm5J1jo/7pgiJGrEGa9AajdKth +wThbJ3kwbQG+732ScBb99RvijwmdgX3SOgwVQK4h+5IbjV+zDtMi+3kULIW2wqEg +d0iCUnUV8y+sDNckphxyBh5sPd5yO3RgXFDk15LVRbv9t0J5rg1TAEm3AKoWXr4P +ZqMdSsaFNeI/PUxYkoO3TTZ+Ei2L0JLQIQuy+GYITwn08/IJl0/bLXehe/BG7BBU +TTq4bTO8QqO4jUYuobWQN7PYSW87WTkMpVeuPyUNfWdUr8n/CtQVULpTx5gHFdSS +yw2sLc0zABJxCJJ3e6blteDc0fXybnG6+Z+bgWt0U3uT1gu/w09AN1ked+8nrIWC +25jXA9GxvtTyj39MfBjRZmw95JnAHtbu2anwybtPk0o4NS1v4sr8409VNRshjMjV +tWkjQCA5aT/3fPdvqVApWu102kyJFzwvHnoh4YJOD+JhkGNubs86yhUx5ZBt4Kg0 +PezmVEOAP5O4hKorkQ7M +-----END CERTIFICATE----- diff --git a/test/js/sql/mysql-tls/ssl/server-ext.cnf b/test/js/sql/mysql-tls/ssl/server-ext.cnf new file mode 100644 index 0000000000..4f29d81a73 --- /dev/null +++ b/test/js/sql/mysql-tls/ssl/server-ext.cnf @@ -0,0 +1,4 @@ +subjectAltName = DNS:mysql, DNS:localhost, IP:127.0.0.1 +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +extendedKeyUsage = serverAuth diff --git a/test/js/sql/mysql-tls/ssl/server-key.pem b/test/js/sql/mysql-tls/ssl/server-key.pem new file mode 100644 index 0000000000..78e5ada613 --- /dev/null +++ b/test/js/sql/mysql-tls/ssl/server-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 ++qHJhX0QjPI1H/jbGWyoIB5pieMzEiY= +-----END PRIVATE KEY----- diff --git a/test/js/sql/mysql-tls/ssl/server.csr b/test/js/sql/mysql-tls/ssl/server.csr new file mode 100644 index 0000000000..1e053d8353 --- /dev/null +++ b/test/js/sql/mysql-tls/ssl/server.csr @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEVTCCAj0CAQAwEDEOMAwGA1UEAwwFbXlzcWwwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQC2TcqpHHiHw2YHtcorQaE2GXvzyYh4UoTpOunGlIyk7NHZ +H0wZ8znRf1QTL993KOBcDvEwNWDXSOmYIjS9jaeJMT6aB8j4v+kyjBEssTPQpoo8 +VuutcrUHhbm9oeh0rV2dcQsYRwA52NtiQdjFuC9KqH39Mmyp5uYK1/pKNdWz8QWz +/neTLF3Ubm9IXdu9DtbFjhQpPferDcfW7b9VSIkwo3NCknhWQoS8j4+vP3BYqfLs +SJ7WHumVcfLrZpV9CJJKedfdk5ZVrU0nnyUrRmJ6yFBvHGXw2FSlDv34rXk1iKvc +OgF7dv6I9Z0enmt2SnCSdLM4L+3ZXwKuBVPqApPif4jwoVy6mRYKrJwdTcR6Oid1 +3TultAQ+UTDoGa9DiGbDyZU8bIIk46y1bEiHYE0i9w3fEADbuy/zFuQ7iJvcmTQN +Fe9sieP7DLa4Wv/ZFv2cyQ2e0W4WPOXXafiHhc+0aHN7FZipRPwjrCUAd+uT88h6 +fef3NEP90lFN1rygnv9LVU2iX14uzMrUq2Y8oq91xO1RrEBmr1PDUVN0mdQvayRw +oWoda5VAMGNLfHafH+7jr4uruSO6djxk1ahkS2VbuOrqjPob3+zL5stcPD0GODuM +BQI2PQ8ddY8woAGUXVBacD5Xwa4bnHnOS4CY30h8JNub2dZRCzLsI0k4aSxdyQID +AQABoAAwDQYJKoZIhvcNAQELBQADggIBAEHh2O5u6yzgH19EXUP4ai7GuWG/C4Ap +vEDtD6G5CQmDZx6pSyL607cdRh+e7Z3GdgGJ9nq5R0wR7UWbPM4MOcoRKT1oQSBp +UykW5WOuyIxcGD6sLnnUkUX+uPcIHV7hMGdg786ygIYyvs8MoY19WSC9ACtofzKq +VEJDU/iIJ0oL3I4NHWXajfV8TnXs1zRkLwiU3nuKvdzzHYtpSNRNi6wr0zfm9mfo +rX62pFbRhWlI0I4JHtinO4bUNLGVQb1DMyJJmXyd379rOe9u8M2rLd+Va71gvF1T +9FmFwoL1l9YO893eGBGFD6qllCfIhyCV4HbH8V1H4AOCay+znjJDNAnE2T1ZqPNT ++nfLMil+EDou/Y9ZpD+VVXcAOZyaKOK0cc0GoiJNPmGPfepdMZC+fSQSeFlUaifI +1PTQLMlhmLI+OCKvt4RBy3JYGWvmOobyotoQB1fFOROEBzAbIjWgvjhsKqMaFM6o +vZtW+XMP74keP30GX3iDznwSTtJglfasDwuVmi4Ewbl9iwmiBvFybMg1t9J1SpXm +JQrNHn8gmMOJxcvoOMNCD3iby1/dCI3fydZ9ceU2+3HW7olwiUQe38CV/7ypTkqc +LBlEojYT09X1wBPZrM58C12JP1RZL6xwJsyWs8oQgi7BEWAX8QfQNHZbLP0+EPli +oDzzz5mRRQ6i +-----END CERTIFICATE REQUEST----- diff --git a/test/js/sql/sql-mysql.test.ts b/test/js/sql/sql-mysql.test.ts index 379d7df2b3..cdce289482 100644 --- a/test/js/sql/sql-mysql.test.ts +++ b/test/js/sql/sql-mysql.test.ts @@ -1,6 +1,6 @@ import { SQL, randomUUIDv7 } from "bun"; import { describe, expect, mock, test } from "bun:test"; -import { describeWithContainer, tempDirWithFiles } from "harness"; +import { describeWithContainer, dockerExe, isDockerEnabled, tempDirWithFiles } from "harness"; import net from "net"; import path from "path"; const dir = tempDirWithFiles("sql-test", { @@ -10,859 +10,891 @@ const dir = tempDirWithFiles("sql-test", { function rel(filename: string) { return path.join(dir, filename); } -describeWithContainer( - "mysql", - { - image: "mysql:8", - env: { - MYSQL_ROOT_PASSWORD: "bun", +const docker = isDockerEnabled() ? dockerExe() : null; +if (docker) { + const dockerfilePath = path.join(import.meta.dir, "mysql-tls", "."); + console.log("Building Docker image..."); + const dockerProcess = Bun.spawn([docker, "build", "-t", "mysql-tls", dockerfilePath], { + cwd: path.join(import.meta.dir, "mysql-tls"), + }); + expect(await dockerProcess.exited).toBe(0); + console.log("Docker image built"); + const images = [ + { + name: "MySQL with TLS", + image: "mysql-tls", + env: { + MYSQL_ROOT_PASSWORD: "bun", + }, }, - }, - (port: number) => { - const options = { - url: `mysql://root:bun@localhost:${port}`, - max: 1, - }; - const sql = new SQL(options); - test("should return lastInsertRowid and affectedRows", async () => { - await using db = new SQL({ ...options, max: 1, idleTimeout: 5 }); - using sql = await db.reserve(); - const random_name = "test_" + randomUUIDv7("hex").replaceAll("-", ""); + { + name: "MySQL", + image: "mysql:8", + env: { + MYSQL_ROOT_PASSWORD: "bun", + }, + }, + ]; - await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name text)`; - - const { lastInsertRowid } = await sql`INSERT INTO ${sql(random_name)} (name) VALUES (${"test"})`; - expect(lastInsertRowid).toBe(1); - const { affectedRows } = await sql`UPDATE ${sql(random_name)} SET name = "test2" WHERE id = ${lastInsertRowid}`; - expect(affectedRows).toBe(1); - }); - describe("should work with more than the max inline capacity", () => { - for (let size of [50, 60, 62, 64, 70, 100]) { - for (let duplicated of [true, false]) { - test(`${size} ${duplicated ? "+ duplicated" : "unique"} fields`, async () => { - await using sql = new SQL(options); - const longQuery = `select ${Array.from({ length: size }, (_, i) => { - if (duplicated) { - return i % 2 === 0 ? `${i + 1} as f${i}, ${i} as f${i}` : `${i} as f${i}`; - } - return `${i} as f${i}`; - }).join(",\n")}`; - const result = await sql.unsafe(longQuery); - let value = 0; - for (const column of Object.values(result[0])) { - expect(column?.toString()).toEqual(value.toString()); - value++; - } - }); - } - } - }); - - test("Connection timeout works", async () => { - const onclose = mock(); - const onconnect = mock(); - await using sql = new SQL({ - ...options, - hostname: "example.com", - connection_timeout: 4, - onconnect, - onclose, - max: 1, - }); - let error: any; - try { - await sql`select SLEEP(8)`; - } catch (e) { - error = e; - } - expect(error.code).toBe(`ERR_MYSQL_CONNECTION_TIMEOUT`); - expect(error.message).toContain("Connection timeout after 4s"); - expect(onconnect).not.toHaveBeenCalled(); - expect(onclose).toHaveBeenCalledTimes(1); - }); - - test("Idle timeout works at start", async () => { - const onclose = mock(); - const onconnect = mock(); - await using sql = new SQL({ - ...options, - idle_timeout: 1, - onconnect, - onclose, - }); - let error: any; - try { - await sql`select SLEEP(2)`; - } catch (e) { - error = e; - } - expect(error.code).toBe(`ERR_MYSQL_IDLE_TIMEOUT`); - expect(onconnect).toHaveBeenCalled(); - expect(onclose).toHaveBeenCalledTimes(1); - }); - - test("Idle timeout is reset when a query is run", async () => { - const onClosePromise = Promise.withResolvers(); - const onclose = mock(err => { - onClosePromise.resolve(err); - }); - const onconnect = mock(); - await using sql = new SQL({ - ...options, - idle_timeout: 1, - onconnect, - onclose, - }); - expect(await sql`select 123 as x`).toEqual([{ x: 123 }]); - expect(onconnect).toHaveBeenCalledTimes(1); - expect(onclose).not.toHaveBeenCalled(); - const err = await onClosePromise.promise; - expect(err.code).toBe(`ERR_MYSQL_IDLE_TIMEOUT`); - }); - - test("Max lifetime works", async () => { - const onClosePromise = Promise.withResolvers(); - const onclose = mock(err => { - onClosePromise.resolve(err); - }); - const onconnect = mock(); - const sql = new SQL({ - ...options, - max_lifetime: 1, - onconnect, - onclose, - }); - let error: any; - expect(await sql`select 1 as x`).toEqual([{ x: 1 }]); - expect(onconnect).toHaveBeenCalledTimes(1); - try { - while (true) { - for (let i = 0; i < 100; i++) { - await sql`select SLEEP(1)`; - } - } - } catch (e) { - error = e; - } - - expect(onclose).toHaveBeenCalledTimes(1); - - expect(error.code).toBe(`ERR_MYSQL_LIFETIME_TIMEOUT`); - }); - - // Last one wins. - test("Handles duplicate string column names", async () => { - const result = await sql`select 1 as x, 2 as x, 3 as x`; - expect(result).toEqual([{ x: 3 }]); - }); - - test("should not timeout in long results", async () => { - await using db = new SQL({ ...options, max: 1, idleTimeout: 5 }); - using sql = await db.reserve(); - const random_name = "test_" + randomUUIDv7("hex").replaceAll("-", ""); - - await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (id int, name text)`; - const promises: Promise[] = []; - for (let i = 0; i < 10_000; i++) { - promises.push(sql`INSERT INTO ${sql(random_name)} VALUES (${i}, ${"test" + i})`); - if (i % 50 === 0 && i > 0) { - await Promise.all(promises); - promises.length = 0; - } - } - await Promise.all(promises); - await sql`SELECT * FROM ${sql(random_name)}`; - await sql`SELECT * FROM ${sql(random_name)}`; - await sql`SELECT * FROM ${sql(random_name)}`; - - expect().pass(); - }, 10_000); - - test("Handles numeric column names", async () => { - // deliberately out of order - const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 0 as "0"`; - expect(result).toEqual([{ "1": 1, "2": 2, "3": 3, "0": 0 }]); - - expect(Object.keys(result[0])).toEqual(["0", "1", "2", "3"]); - // Sanity check: ensure iterating through the properties doesn't crash. - Bun.inspect(result); - }); - - // Last one wins. - test("Handles duplicate numeric column names", async () => { - const result = await sql`select 1 as "1", 2 as "1", 3 as "1"`; - expect(result).toEqual([{ "1": 3 }]); - // Sanity check: ensure iterating through the properties doesn't crash. - Bun.inspect(result); - }); - - test("Handles mixed column names", async () => { - const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 4 as x`; - expect(result).toEqual([{ "1": 1, "2": 2, "3": 3, x: 4 }]); - // Sanity check: ensure iterating through the properties doesn't crash. - Bun.inspect(result); - }); - - test("Handles mixed column names with duplicates", async () => { - const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 4 as "1", 1 as x, 2 as x`; - expect(result).toEqual([{ "1": 4, "2": 2, "3": 3, x: 2 }]); - // Sanity check: ensure iterating through the properties doesn't crash. - Bun.inspect(result); - - // Named columns are inserted first, but they appear from JS as last. - expect(Object.keys(result[0])).toEqual(["1", "2", "3", "x"]); - }); - - test("Handles mixed column names with duplicates at the end", async () => { - const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 4 as "1", 1 as x, 2 as x, 3 as x, 4 as "y"`; - expect(result).toEqual([{ "1": 4, "2": 2, "3": 3, x: 3, y: 4 }]); - - // Sanity check: ensure iterating through the properties doesn't crash. - Bun.inspect(result); - }); - - test("Handles mixed column names with duplicates at the start", async () => { - const result = await sql`select 1 as "1", 2 as "1", 3 as "2", 4 as "3", 1 as x, 2 as x, 3 as x`; - expect(result).toEqual([{ "1": 2, "2": 3, "3": 4, x: 3 }]); - // Sanity check: ensure iterating through the properties doesn't crash. - Bun.inspect(result); - }); - - test("Uses default database without slash", async () => { - const sql = new SQL("mysql://localhost"); - expect("mysql").toBe(sql.options.database); - }); - - test("Uses default database with slash", async () => { - const sql = new SQL("mysql://localhost/"); - expect("mysql").toBe(sql.options.database); - }); - - test("Result is array", async () => { - expect(await sql`select 1`).toBeArray(); - }); - - test("Create table", async () => { - await sql`create table test(id int)`; - await sql`drop table test`; - }); - - test("Drop table", async () => { - await sql`create table test(id int)`; - await sql`drop table test`; - // Verify that table is dropped - const result = await sql`select * from information_schema.tables where table_name = 'test'`; - expect(result).toBeArrayOfSize(0); - }); - - test("null", async () => { - expect((await sql`select ${null} as x`)[0].x).toBeNull(); - }); - - test("Unsigned Integer", async () => { - expect((await sql`select ${0x7fffffff + 2} as x`)[0].x).toBe(2147483649); - }); - - test("Signed Integer", async () => { - expect((await sql`select ${-1} as x`)[0].x).toBe(-1); - expect((await sql`select ${1} as x`)[0].x).toBe(1); - }); - - test("Double", async () => { - expect((await sql`select ${1.123456789} as x`)[0].x).toBe(1.123456789); - }); - - test("String", async () => { - expect((await sql`select ${"hello"} as x`)[0].x).toBe("hello"); - }); - - test("MediumInt/Int24", async () => { - let random_name = ("t_" + Bun.randomUUIDv7("hex").replaceAll("-", "")).toLowerCase(); - await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a mediumint unsigned)`; - await sql`INSERT INTO ${sql(random_name)} VALUES (${1})`; - const result = await sql`select * from ${sql(random_name)}`; - expect(result[0].a).toBe(1); - const result2 = await sql`select * from ${sql(random_name)}`.simple(); - expect(result2[0].a).toBe(1); - }); - - test("Boolean/TinyInt/BIT", async () => { - // Protocol will always return 0 or 1 for TRUE and FALSE when not using a table. - expect((await sql`select ${false} as x`)[0].x).toBe(0); - expect((await sql`select ${true} as x`)[0].x).toBe(1); - let random_name = ("t_" + Bun.randomUUIDv7("hex").replaceAll("-", "")).toLowerCase(); - await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a bool)`; - const values = [{ a: true }, { a: false }, { a: 8 }, { a: -1 }]; - await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; - const [[a], [b], [c], [d]] = await sql`select * from ${sql(random_name)}`.values(); - expect(a).toBe(1); - expect(b).toBe(0); - expect(c).toBe(8); - expect(d).toBe(-1); + for (const image of images) { + describeWithContainer( + image.name, { - random_name += "2"; - await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a tinyint(1) unsigned)`; - try { - const values = [{ a: -1 }]; - await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; - expect.unreachable(); - } catch (e: any) { - expect(e.code).toBe("ERR_MYSQL_SERVER_ERROR"); - expect(e.message).toContain("Out of range value for column 'a'"); - } + image: image.image, + env: image.env, + }, + (port: number) => { + const options = { + url: `mysql://root:bun@localhost:${port}`, + max: 1, + tls: + image.name === "MySQL with TLS" + ? Bun.file(path.join(import.meta.dir, "mysql-tls", "ssl", "ca.pem")) + : undefined, + }; + const sql = new SQL(options); + test("should return lastInsertRowid and affectedRows", async () => { + await using db = new SQL({ ...options, max: 1, idleTimeout: 5 }); + using sql = await db.reserve(); + const random_name = "test_" + randomUUIDv7("hex").replaceAll("-", ""); - const values = [{ a: 255 }]; - await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; - const [[a]] = await sql`select * from ${sql(random_name)}`.values(); - expect(a).toBe(255); - } + await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, name text)`; - { - random_name += "3"; - await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a bit(1), b bit(2))`; - const values = [ - { a: true, b: 1 }, - { a: false, b: 2 }, - ]; - await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; - const results = await sql`select * from ${sql(random_name)}`; - // return true or false for BIT(1) and buffer for BIT(n) - expect(results[0].a).toBe(true); - expect(results[0].b).toEqual(Buffer.from([1])); - expect(results[1].a).toBe(false); - expect(results[1].b).toEqual(Buffer.from([2])); - // text protocol should behave the same - const results2 = await sql`select * from ${sql(random_name)}`.simple(); - expect(results2[0].a).toBe(true); - expect(results2[0].b).toEqual(Buffer.from([1])); - expect(results2[1].a).toBe(false); - expect(results2[1].b).toEqual(Buffer.from([2])); - } - }); - - test("Date", async () => { - const now = new Date(); - const then = (await sql`select ${now} as x`)[0].x; - expect(then).toEqual(now); - }); - - test("Timestamp", async () => { - { - const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL -25 SECOND) as x`)[0].x; - expect(result.getTime()).toBe(-25000); - } - { - const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL 25 SECOND) as x`)[0].x; - expect(result.getSeconds()).toBe(25); - } - { - const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL 251000 MICROSECOND) as x`)[0].x; - expect(result.getMilliseconds()).toBe(251); - } - { - const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL -251000 MICROSECOND) as x`)[0].x; - expect(result.getTime()).toBe(-251); - } - }); - - test("JSON", async () => { - const x = (await sql`select CAST(${{ a: "hello", b: 42 }} AS JSON) as x`)[0].x; - expect(x).toEqual({ a: "hello", b: 42 }); - - const y = (await sql`select CAST('{"key": "value", "number": 123}' AS JSON) as x`)[0].x; - expect(y).toEqual({ key: "value", number: 123 }); - - const random_name = ("t_" + Bun.randomUUIDv7("hex").replaceAll("-", "")).toLowerCase(); - await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a json)`; - const values = [{ a: { b: 1 } }, { a: { b: 2 } }]; - await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; - const [[a], [b]] = await sql`select * from ${sql(random_name)}`.values(); - expect(a).toEqual({ b: 1 }); - expect(b).toEqual({ b: 2 }); - }); - - test("bulk insert nested sql()", async () => { - await sql`create table users (name text, age int)`; - const users = [ - { name: "Alice", age: 25 }, - { name: "Bob", age: 30 }, - ]; - try { - await sql`insert into users ${sql(users)}`; - const result = await sql`select * from users`; - expect(result).toEqual([ - { name: "Alice", age: 25 }, - { name: "Bob", age: 30 }, - ]); - } finally { - await sql`drop table users`; - } - }); - - test("Escapes", async () => { - expect(Object.keys((await sql`select 1 as ${sql('hej"hej')}`)[0])[0]).toBe('hej"hej'); - }); - - test("null for int", async () => { - const result = await sql`create table test (x int)`; - expect(result.count).toBe(0); - try { - await sql`insert into test values(${null})`; - const result2 = await sql`select * from test`; - expect(result2).toEqual([{ x: null }]); - } finally { - await sql`drop table test`; - } - }); - - test("should be able to execute different queries in the same connection #16774", async () => { - const sql = new SQL({ ...options, max: 1 }); - const random_table_name = `test_user_${Math.random().toString(36).substring(2, 15)}`; - await sql`CREATE TEMPORARY TABLE IF NOT EXISTS ${sql(random_table_name)} (id int, name text)`; - - const promises: Array> = []; - // POPULATE TABLE - for (let i = 0; i < 1_000; i++) { - promises.push(sql`insert into ${sql(random_table_name)} values (${i}, ${`test${i}`})`.execute()); - } - await Promise.all(promises); - - // QUERY TABLE using execute() to force executing the query immediately - { - for (let i = 0; i < 1_000; i++) { - // mix different parameters - switch (i % 3) { - case 0: - promises.push(sql`select id, name from ${sql(random_table_name)} where id = ${i}`.execute()); - break; - case 1: - promises.push(sql`select id from ${sql(random_table_name)} where id = ${i}`.execute()); - break; - case 2: - promises.push(sql`select 1, id, name from ${sql(random_table_name)} where id = ${i}`.execute()); - break; - } - } - await Promise.all(promises); - } - }); - - test("Prepared transaction", async () => { - await using sql = new SQL(options); - await sql`create table test (a int)`; - - try { - await sql.beginDistributed("tx1", async sql => { - await sql`insert into test values(1)`; + const { lastInsertRowid } = await sql`INSERT INTO ${sql(random_name)} (name) VALUES (${"test"})`; + expect(lastInsertRowid).toBe(1); + const { affectedRows } = + await sql`UPDATE ${sql(random_name)} SET name = "test2" WHERE id = ${lastInsertRowid}`; + expect(affectedRows).toBe(1); + }); + describe("should work with more than the max inline capacity", () => { + for (let size of [50, 60, 62, 64, 70, 100]) { + for (let duplicated of [true, false]) { + test(`${size} ${duplicated ? "+ duplicated" : "unique"} fields`, async () => { + await using sql = new SQL(options); + const longQuery = `select ${Array.from({ length: size }, (_, i) => { + if (duplicated) { + return i % 2 === 0 ? `${i + 1} as f${i}, ${i} as f${i}` : `${i} as f${i}`; + } + return `${i} as f${i}`; + }).join(",\n")}`; + const result = await sql.unsafe(longQuery); + let value = 0; + for (const column of Object.values(result[0])) { + expect(column?.toString()).toEqual(value.toString()); + value++; + } + }); + } + } }); - await sql.commitDistributed("tx1"); - expect((await sql`select count(*) from test`).count).toBe(1); - } finally { - await sql`drop table test`; - } - }); - test("Idle timeout retry works", async () => { - await using sql = new SQL({ ...options, idleTimeout: 1 }); - await sql`select 1`; - await Bun.sleep(1100); // 1.1 seconds so it should retry - await sql`select 1`; - expect().pass(); - }); + test("Connection timeout works", async () => { + const onclose = mock(); + const onconnect = mock(); + await using sql = new SQL({ + ...options, + hostname: "example.com", + connection_timeout: 4, + onconnect, + onclose, + max: 1, + }); + let error: any; + try { + await sql`select SLEEP(8)`; + } catch (e) { + error = e; + } + expect(error.code).toBe(`ERR_MYSQL_CONNECTION_TIMEOUT`); + expect(error.message).toContain("Connection timeout after 4s"); + expect(onconnect).not.toHaveBeenCalled(); + expect(onclose).toHaveBeenCalledTimes(1); + }); - test("Fragments in transactions", async () => { - const sql = new SQL({ ...options, debug: true, idle_timeout: 1, fetch_types: false }); - expect((await sql.begin(sql => sql`select 1 as x where ${sql`1=1`}`))[0].x).toBe(1); - }); + test("Idle timeout works at start", async () => { + const onclose = mock(); + const onconnect = mock(); + await using sql = new SQL({ + ...options, + idle_timeout: 1, + onconnect, + onclose, + }); + let error: any; + try { + await sql`select SLEEP(2)`; + } catch (e) { + error = e; + } + expect(error.code).toBe(`ERR_MYSQL_IDLE_TIMEOUT`); + expect(onconnect).toHaveBeenCalled(); + expect(onclose).toHaveBeenCalledTimes(1); + }); - test("Helpers in Transaction", async () => { - const result = await sql.begin(async sql => await sql`select ${sql.unsafe("1 as x")}`); - expect(result[0].x).toBe(1); - }); + test("Idle timeout is reset when a query is run", async () => { + const onClosePromise = Promise.withResolvers(); + const onclose = mock(err => { + onClosePromise.resolve(err); + }); + const onconnect = mock(); + await using sql = new SQL({ + ...options, + idle_timeout: 1, + onconnect, + onclose, + }); + expect(await sql`select 123 as x`).toEqual([{ x: 123 }]); + expect(onconnect).toHaveBeenCalledTimes(1); + expect(onclose).not.toHaveBeenCalled(); + const err = await onClosePromise.promise; + expect(err.code).toBe(`ERR_MYSQL_IDLE_TIMEOUT`); + }); - test("Undefined values throws", async () => { - const result = await sql`select ${undefined} as x`; - expect(result[0].x).toBeNull(); - }); + test("Max lifetime works", async () => { + const onClosePromise = Promise.withResolvers(); + const onclose = mock(err => { + onClosePromise.resolve(err); + }); + const onconnect = mock(); + const sql = new SQL({ + ...options, + max_lifetime: 1, + onconnect, + onclose, + }); + let error: any; + expect(await sql`select 1 as x`).toEqual([{ x: 1 }]); + expect(onconnect).toHaveBeenCalledTimes(1); + try { + while (true) { + for (let i = 0; i < 100; i++) { + await sql`select SLEEP(1)`; + } + } + } catch (e) { + error = e; + } - test("Null sets to null", async () => expect((await sql`select ${null} as x`)[0].x).toBeNull()); + expect(onclose).toHaveBeenCalledTimes(1); - // Add code property. - test("Throw syntax error", async () => { - await using sql = new SQL({ ...options, max: 1 }); - const err = await sql`wat 1`.catch(x => x); - expect(err.code).toBe("ERR_MYSQL_SYNTAX_ERROR"); - }); + expect(error.code).toBe(`ERR_MYSQL_LIFETIME_TIMEOUT`); + }); - test("should work with fragments", async () => { - await using sql = new SQL({ ...options, max: 1 }); - const random_name = sql("test_" + randomUUIDv7("hex").replaceAll("-", "")); - await sql`CREATE TEMPORARY TABLE IF NOT EXISTS ${random_name} (id int, hotel_id int, created_at timestamp)`; - await sql`INSERT INTO ${random_name} VALUES (1, 1, '2024-01-01 10:00:00')`; - // single escaped identifier - { - const results = await sql`SELECT * FROM ${random_name}`; - expect(results).toEqual([{ id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }]); - } - // multiple escaped identifiers - { - const results = await sql`SELECT ${random_name}.* FROM ${random_name}`; - expect(results).toEqual([{ id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }]); - } - // even more complex fragment - { - const results = - await sql`SELECT ${random_name}.* FROM ${random_name} WHERE ${random_name}.hotel_id = ${1} ORDER BY ${random_name}.created_at DESC`; - expect(results).toEqual([{ id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }]); - } - }); - test("should handle nested fragments", async () => { - await using sql = new SQL({ ...options, max: 1 }); - const random_name = sql("test_" + randomUUIDv7("hex").replaceAll("-", "")); + // Last one wins. + test("Handles duplicate string column names", async () => { + const result = await sql`select 1 as x, 2 as x, 3 as x`; + expect(result).toEqual([{ x: 3 }]); + }); - await sql`CREATE TEMPORARY TABLE IF NOT EXISTS ${random_name} (id int, hotel_id int, created_at timestamp)`; - await sql`INSERT INTO ${random_name} VALUES (1, 1, '2024-01-01 10:00:00')`; - await sql`INSERT INTO ${random_name} VALUES (2, 1, '2024-01-02 10:00:00')`; - await sql`INSERT INTO ${random_name} VALUES (3, 2, '2024-01-03 10:00:00')`; + test("should not timeout in long results", async () => { + await using db = new SQL({ ...options, max: 1, idleTimeout: 5 }); + using sql = await db.reserve(); + const random_name = "test_" + randomUUIDv7("hex").replaceAll("-", ""); - // fragment containing another scape fragment for the field name - const orderBy = (field_name: string) => sql`ORDER BY ${sql(field_name)} DESC`; + await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (id int, name text)`; + const promises: Promise[] = []; + for (let i = 0; i < 10_000; i++) { + promises.push(sql`INSERT INTO ${sql(random_name)} VALUES (${i}, ${"test" + i})`); + if (i % 50 === 0 && i > 0) { + await Promise.all(promises); + promises.length = 0; + } + } + await Promise.all(promises); + await sql`SELECT * FROM ${sql(random_name)}`; + await sql`SELECT * FROM ${sql(random_name)}`; + await sql`SELECT * FROM ${sql(random_name)}`; - // dynamic information - const sortBy = { should_sort: true, field: "created_at" }; - const user = { hotel_id: 1 }; + expect().pass(); + }, 10_000); - // query containing the fragments - const results = await sql` + test("Handles numeric column names", async () => { + // deliberately out of order + const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 0 as "0"`; + expect(result).toEqual([{ "1": 1, "2": 2, "3": 3, "0": 0 }]); + + expect(Object.keys(result[0])).toEqual(["0", "1", "2", "3"]); + // Sanity check: ensure iterating through the properties doesn't crash. + Bun.inspect(result); + }); + + // Last one wins. + test("Handles duplicate numeric column names", async () => { + const result = await sql`select 1 as "1", 2 as "1", 3 as "1"`; + expect(result).toEqual([{ "1": 3 }]); + // Sanity check: ensure iterating through the properties doesn't crash. + Bun.inspect(result); + }); + + test("Handles mixed column names", async () => { + const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 4 as x`; + expect(result).toEqual([{ "1": 1, "2": 2, "3": 3, x: 4 }]); + // Sanity check: ensure iterating through the properties doesn't crash. + Bun.inspect(result); + }); + + test("Handles mixed column names with duplicates", async () => { + const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 4 as "1", 1 as x, 2 as x`; + expect(result).toEqual([{ "1": 4, "2": 2, "3": 3, x: 2 }]); + // Sanity check: ensure iterating through the properties doesn't crash. + Bun.inspect(result); + + // Named columns are inserted first, but they appear from JS as last. + expect(Object.keys(result[0])).toEqual(["1", "2", "3", "x"]); + }); + + test("Handles mixed column names with duplicates at the end", async () => { + const result = await sql`select 1 as "1", 2 as "2", 3 as "3", 4 as "1", 1 as x, 2 as x, 3 as x, 4 as "y"`; + expect(result).toEqual([{ "1": 4, "2": 2, "3": 3, x: 3, y: 4 }]); + + // Sanity check: ensure iterating through the properties doesn't crash. + Bun.inspect(result); + }); + + test("Handles mixed column names with duplicates at the start", async () => { + const result = await sql`select 1 as "1", 2 as "1", 3 as "2", 4 as "3", 1 as x, 2 as x, 3 as x`; + expect(result).toEqual([{ "1": 2, "2": 3, "3": 4, x: 3 }]); + // Sanity check: ensure iterating through the properties doesn't crash. + Bun.inspect(result); + }); + + test("Uses default database without slash", async () => { + const sql = new SQL("mysql://localhost"); + expect("mysql").toBe(sql.options.database); + }); + + test("Uses default database with slash", async () => { + const sql = new SQL("mysql://localhost/"); + expect("mysql").toBe(sql.options.database); + }); + + test("Result is array", async () => { + expect(await sql`select 1`).toBeArray(); + }); + + test("Create table", async () => { + await sql`create table test(id int)`; + await sql`drop table test`; + }); + + test("Drop table", async () => { + await sql`create table test(id int)`; + await sql`drop table test`; + // Verify that table is dropped + const result = await sql`select * from information_schema.tables where table_name = 'test'`; + expect(result).toBeArrayOfSize(0); + }); + + test("null", async () => { + expect((await sql`select ${null} as x`)[0].x).toBeNull(); + }); + + test("Unsigned Integer", async () => { + expect((await sql`select ${0x7fffffff + 2} as x`)[0].x).toBe(2147483649); + }); + + test("Signed Integer", async () => { + expect((await sql`select ${-1} as x`)[0].x).toBe(-1); + expect((await sql`select ${1} as x`)[0].x).toBe(1); + }); + + test("Double", async () => { + expect((await sql`select ${1.123456789} as x`)[0].x).toBe(1.123456789); + }); + + test("String", async () => { + expect((await sql`select ${"hello"} as x`)[0].x).toBe("hello"); + }); + + test("MediumInt/Int24", async () => { + let random_name = ("t_" + Bun.randomUUIDv7("hex").replaceAll("-", "")).toLowerCase(); + await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a mediumint unsigned)`; + await sql`INSERT INTO ${sql(random_name)} VALUES (${1})`; + const result = await sql`select * from ${sql(random_name)}`; + expect(result[0].a).toBe(1); + const result2 = await sql`select * from ${sql(random_name)}`.simple(); + expect(result2[0].a).toBe(1); + }); + + test("Boolean/TinyInt/BIT", async () => { + // Protocol will always return 0 or 1 for TRUE and FALSE when not using a table. + expect((await sql`select ${false} as x`)[0].x).toBe(0); + expect((await sql`select ${true} as x`)[0].x).toBe(1); + let random_name = ("t_" + Bun.randomUUIDv7("hex").replaceAll("-", "")).toLowerCase(); + await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a bool)`; + const values = [{ a: true }, { a: false }, { a: 8 }, { a: -1 }]; + await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; + const [[a], [b], [c], [d]] = await sql`select * from ${sql(random_name)}`.values(); + expect(a).toBe(1); + expect(b).toBe(0); + expect(c).toBe(8); + expect(d).toBe(-1); + { + random_name += "2"; + await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a tinyint(1) unsigned)`; + try { + const values = [{ a: -1 }]; + await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; + expect.unreachable(); + } catch (e: any) { + expect(e.code).toBe("ERR_MYSQL_SERVER_ERROR"); + expect(e.message).toContain("Out of range value for column 'a'"); + } + + const values = [{ a: 255 }]; + await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; + const [[a]] = await sql`select * from ${sql(random_name)}`.values(); + expect(a).toBe(255); + } + + { + random_name += "3"; + await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a bit(1), b bit(2))`; + const values = [ + { a: true, b: 1 }, + { a: false, b: 2 }, + ]; + await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; + const results = await sql`select * from ${sql(random_name)}`; + // return true or false for BIT(1) and buffer for BIT(n) + expect(results[0].a).toBe(true); + expect(results[0].b).toEqual(Buffer.from([1])); + expect(results[1].a).toBe(false); + expect(results[1].b).toEqual(Buffer.from([2])); + // text protocol should behave the same + const results2 = await sql`select * from ${sql(random_name)}`.simple(); + expect(results2[0].a).toBe(true); + expect(results2[0].b).toEqual(Buffer.from([1])); + expect(results2[1].a).toBe(false); + expect(results2[1].b).toEqual(Buffer.from([2])); + } + }); + + test("Date", async () => { + const now = new Date(); + const then = (await sql`select ${now} as x`)[0].x; + expect(then).toEqual(now); + }); + + test("Timestamp", async () => { + { + const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL -25 SECOND) as x`)[0].x; + expect(result.getTime()).toBe(-25000); + } + { + const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL 25 SECOND) as x`)[0].x; + expect(result.getSeconds()).toBe(25); + } + { + const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL 251000 MICROSECOND) as x`)[0].x; + expect(result.getMilliseconds()).toBe(251); + } + { + const result = (await sql`select DATE_ADD(FROM_UNIXTIME(0), INTERVAL -251000 MICROSECOND) as x`)[0].x; + expect(result.getTime()).toBe(-251); + } + }); + + test("JSON", async () => { + const x = (await sql`select CAST(${{ a: "hello", b: 42 }} AS JSON) as x`)[0].x; + expect(x).toEqual({ a: "hello", b: 42 }); + + const y = (await sql`select CAST('{"key": "value", "number": 123}' AS JSON) as x`)[0].x; + expect(y).toEqual({ key: "value", number: 123 }); + + const random_name = ("t_" + Bun.randomUUIDv7("hex").replaceAll("-", "")).toLowerCase(); + await sql`CREATE TEMPORARY TABLE ${sql(random_name)} (a json)`; + const values = [{ a: { b: 1 } }, { a: { b: 2 } }]; + await sql`INSERT INTO ${sql(random_name)} ${sql(values)}`; + const [[a], [b]] = await sql`select * from ${sql(random_name)}`.values(); + expect(a).toEqual({ b: 1 }); + expect(b).toEqual({ b: 2 }); + }); + + test("bulk insert nested sql()", async () => { + await sql`create table users (name text, age int)`; + const users = [ + { name: "Alice", age: 25 }, + { name: "Bob", age: 30 }, + ]; + try { + await sql`insert into users ${sql(users)}`; + const result = await sql`select * from users`; + expect(result).toEqual([ + { name: "Alice", age: 25 }, + { name: "Bob", age: 30 }, + ]); + } finally { + await sql`drop table users`; + } + }); + + test("Escapes", async () => { + expect(Object.keys((await sql`select 1 as ${sql('hej"hej')}`)[0])[0]).toBe('hej"hej'); + }); + + test("null for int", async () => { + const result = await sql`create table test (x int)`; + expect(result.count).toBe(0); + try { + await sql`insert into test values(${null})`; + const result2 = await sql`select * from test`; + expect(result2).toEqual([{ x: null }]); + } finally { + await sql`drop table test`; + } + }); + + test("should be able to execute different queries in the same connection #16774", async () => { + const sql = new SQL({ ...options, max: 1 }); + const random_table_name = `test_user_${Math.random().toString(36).substring(2, 15)}`; + await sql`CREATE TEMPORARY TABLE IF NOT EXISTS ${sql(random_table_name)} (id int, name text)`; + + const promises: Array> = []; + // POPULATE TABLE + for (let i = 0; i < 1_000; i++) { + promises.push(sql`insert into ${sql(random_table_name)} values (${i}, ${`test${i}`})`.execute()); + } + await Promise.all(promises); + + // QUERY TABLE using execute() to force executing the query immediately + { + for (let i = 0; i < 1_000; i++) { + // mix different parameters + switch (i % 3) { + case 0: + promises.push(sql`select id, name from ${sql(random_table_name)} where id = ${i}`.execute()); + break; + case 1: + promises.push(sql`select id from ${sql(random_table_name)} where id = ${i}`.execute()); + break; + case 2: + promises.push(sql`select 1, id, name from ${sql(random_table_name)} where id = ${i}`.execute()); + break; + } + } + await Promise.all(promises); + } + }); + + test("Prepared transaction", async () => { + await using sql = new SQL(options); + await sql`create table test (a int)`; + + try { + await sql.beginDistributed("tx1", async sql => { + await sql`insert into test values(1)`; + }); + await sql.commitDistributed("tx1"); + expect((await sql`select count(*) from test`).count).toBe(1); + } finally { + await sql`drop table test`; + } + }); + + test("Idle timeout retry works", async () => { + await using sql = new SQL({ ...options, idleTimeout: 1 }); + await sql`select 1`; + await Bun.sleep(1100); // 1.1 seconds so it should retry + await sql`select 1`; + expect().pass(); + }); + + test("Fragments in transactions", async () => { + const sql = new SQL({ ...options, debug: true, idle_timeout: 1, fetch_types: false }); + expect((await sql.begin(sql => sql`select 1 as x where ${sql`1=1`}`))[0].x).toBe(1); + }); + + test("Helpers in Transaction", async () => { + const result = await sql.begin(async sql => await sql`select ${sql.unsafe("1 as x")}`); + expect(result[0].x).toBe(1); + }); + + test("Undefined values throws", async () => { + const result = await sql`select ${undefined} as x`; + expect(result[0].x).toBeNull(); + }); + + test("Null sets to null", async () => expect((await sql`select ${null} as x`)[0].x).toBeNull()); + + // Add code property. + test("Throw syntax error", async () => { + await using sql = new SQL({ ...options, max: 1 }); + const err = await sql`wat 1`.catch(x => x); + expect(err.code).toBe("ERR_MYSQL_SYNTAX_ERROR"); + }); + + test("should work with fragments", async () => { + await using sql = new SQL({ ...options, max: 1 }); + const random_name = sql("test_" + randomUUIDv7("hex").replaceAll("-", "")); + await sql`CREATE TEMPORARY TABLE IF NOT EXISTS ${random_name} (id int, hotel_id int, created_at timestamp)`; + await sql`INSERT INTO ${random_name} VALUES (1, 1, '2024-01-01 10:00:00')`; + // single escaped identifier + { + const results = await sql`SELECT * FROM ${random_name}`; + expect(results).toEqual([{ id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }]); + } + // multiple escaped identifiers + { + const results = await sql`SELECT ${random_name}.* FROM ${random_name}`; + expect(results).toEqual([{ id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }]); + } + // even more complex fragment + { + const results = + await sql`SELECT ${random_name}.* FROM ${random_name} WHERE ${random_name}.hotel_id = ${1} ORDER BY ${random_name}.created_at DESC`; + expect(results).toEqual([{ id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }]); + } + }); + test("should handle nested fragments", async () => { + await using sql = new SQL({ ...options, max: 1 }); + const random_name = sql("test_" + randomUUIDv7("hex").replaceAll("-", "")); + + await sql`CREATE TEMPORARY TABLE IF NOT EXISTS ${random_name} (id int, hotel_id int, created_at timestamp)`; + await sql`INSERT INTO ${random_name} VALUES (1, 1, '2024-01-01 10:00:00')`; + await sql`INSERT INTO ${random_name} VALUES (2, 1, '2024-01-02 10:00:00')`; + await sql`INSERT INTO ${random_name} VALUES (3, 2, '2024-01-03 10:00:00')`; + + // fragment containing another scape fragment for the field name + const orderBy = (field_name: string) => sql`ORDER BY ${sql(field_name)} DESC`; + + // dynamic information + const sortBy = { should_sort: true, field: "created_at" }; + const user = { hotel_id: 1 }; + + // query containing the fragments + const results = await sql` SELECT ${random_name}.* FROM ${random_name} WHERE ${random_name}.hotel_id = ${user.hotel_id} ${sortBy.should_sort ? orderBy(sortBy.field) : sql``}`; - expect(results).toEqual([ - { id: 2, hotel_id: 1, created_at: new Date("2024-01-02T10:00:00.000Z") }, - { id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }, - ]); - }); - - test("Support dynamic password function", async () => { - await using sql = new SQL({ ...options, password: () => "bun", max: 1 }); - return expect((await sql`select 1 as x`)[0].x).toBe(1); - }); - - test("Support dynamic async resolved password function", async () => { - await using sql = new SQL({ - ...options, - password: () => Promise.resolve("bun"), - max: 1, - }); - return expect((await sql`select 1 as x`)[0].x).toBe(1); - }); - - test("Support dynamic async password function", async () => { - await using sql = new SQL({ - ...options, - max: 1, - password: async () => { - await Bun.sleep(10); - return "bun"; - }, - }); - return expect((await sql`select 1 as x`)[0].x).toBe(1); - }); - test("Support dynamic async rejected password function", async () => { - await using sql = new SQL({ - ...options, - password: () => Promise.reject(new Error("password error")), - max: 1, - }); - try { - await sql`select true as x`; - expect.unreachable(); - } catch (e: any) { - expect(e.message).toBe("password error"); - } - }); - test("Support dynamic async password function that throws", async () => { - await using sql = new SQL({ - ...options, - max: 1, - password: async () => { - await Bun.sleep(10); - throw new Error("password error"); - }, - }); - try { - await sql`select true as x`; - expect.unreachable(); - } catch (e: any) { - expect(e).toBeInstanceOf(Error); - expect(e.message).toBe("password error"); - } - }); - test("sql file", async () => { - await using sql = new SQL(options); - expect((await sql.file(rel("select.sql")))[0].x).toBe(1); - }); - - test("sql file throws", async () => { - await using sql = new SQL(options); - expect(await sql.file(rel("selectomondo.sql")).catch(x => x.code)).toBe("ENOENT"); - }); - test("Parameters in file", async () => { - await using sql = new SQL(options); - const result = await sql.file(rel("select-param.sql"), ["hello"]); - return expect(result[0].x).toBe("hello"); - }); - - test("Connection ended promise", async () => { - const sql = new SQL(options); - - await sql.end(); - - expect(await sql.end()).toBeUndefined(); - }); - - test("Connection ended timeout", async () => { - const sql = new SQL(options); - - await sql.end({ timeout: 10 }); - - expect(await sql.end()).toBeUndefined(); - }); - - test("Connection ended error", async () => { - const sql = new SQL(options); - await sql.end(); - return expect(await sql``.catch(x => x.code)).toBe("ERR_MYSQL_CONNECTION_CLOSED"); - }); - - test("Connection end does not cancel query", async () => { - const sql = new SQL(options); - - const promise = sql`select SLEEP(1) as x`.execute(); - await sql.end(); - return expect(await promise).toEqual([{ x: 0 }]); - }); - - test("Connection destroyed", async () => { - const sql = new SQL(options); - process.nextTick(() => sql.end({ timeout: 0 })); - expect(await sql``.catch(x => x.code)).toBe("ERR_MYSQL_CONNECTION_CLOSED"); - }); - - test("Connection destroyed with query before", async () => { - const sql = new SQL(options); - const error = sql`select SLEEP(0.2)`.catch(err => err.code); - - sql.end({ timeout: 0 }); - return expect(await error).toBe("ERR_MYSQL_CONNECTION_CLOSED"); - }); - - test("unsafe", async () => { - await sql`create table test (x int)`; - try { - await sql.unsafe("insert into test values (?)", [1]); - const [{ x }] = await sql`select * from test`; - expect(x).toBe(1); - } finally { - await sql`drop table test`; - } - }); - - test("unsafe simple", async () => { - await using sql = new SQL({ ...options, max: 1 }); - expect(await sql.unsafe("select 1 as x")).toEqual([{ x: 1 }]); - }); - - test("simple query with multiple statements", async () => { - await using sql = new SQL({ ...options, max: 1 }); - const result = await sql`select 1 as x;select 2 as x`.simple(); - expect(result).toBeDefined(); - expect(result.length).toEqual(2); - expect(result[0][0].x).toEqual(1); - expect(result[1][0].x).toEqual(2); - }); - - test("simple query using unsafe with multiple statements", async () => { - await using sql = new SQL({ ...options, max: 1 }); - const result = await sql.unsafe("select 1 as x;select 2 as x"); - expect(result).toBeDefined(); - expect(result.length).toEqual(2); - expect(result[0][0].x).toEqual(1); - expect(result[1][0].x).toEqual(2); - }); - - test("only allows one statement", async () => { - expect(await sql`select 1; select 2`.catch(e => e.message)).toBe( - "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select 2' at line 1", - ); - }); - - test("await sql() throws not tagged error", async () => { - try { - await sql("select 1"); - expect.unreachable(); - } catch (e: any) { - expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); - } - }); - - test("sql().then throws not tagged error", async () => { - try { - await sql("select 1").then(() => { - /* noop */ + expect(results).toEqual([ + { id: 2, hotel_id: 1, created_at: new Date("2024-01-02T10:00:00.000Z") }, + { id: 1, hotel_id: 1, created_at: new Date("2024-01-01T10:00:00.000Z") }, + ]); }); - expect.unreachable(); - } catch (e: any) { - expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); - } - }); - test("sql().catch throws not tagged error", async () => { - try { - sql("select 1").catch(() => { - /* noop */ + test("Support dynamic password function", async () => { + await using sql = new SQL({ ...options, password: () => "bun", max: 1 }); + return expect((await sql`select 1 as x`)[0].x).toBe(1); }); - expect.unreachable(); - } catch (e: any) { - expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); - } - }); - test("sql().finally throws not tagged error", async () => { - try { - sql("select 1").finally(() => { - /* noop */ + test("Support dynamic async resolved password function", async () => { + await using sql = new SQL({ + ...options, + password: () => Promise.resolve("bun"), + max: 1, + }); + return expect((await sql`select 1 as x`)[0].x).toBe(1); }); - expect.unreachable(); - } catch (e: any) { - expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); - } - }); - test("little bobby tables", async () => { - await using sql = new SQL({ ...options, max: 1 }); - const name = "Robert'); DROP TABLE students;--"; - - try { - await sql`create table students (name text, age int)`; - await sql`insert into students (name) values (${name})`; - - expect((await sql`select name from students`)[0].name).toBe(name); - } finally { - await sql`drop table students`; - } - }); - - test("Connection errors are caught using begin()", async () => { - let error; - try { - const sql = new SQL({ host: "localhost", port: 1, adapter: "mysql" }); - - await sql.begin(async sql => { - await sql`insert into test (label, value) values (${1}, ${2})`; + test("Support dynamic async password function", async () => { + await using sql = new SQL({ + ...options, + max: 1, + password: async () => { + await Bun.sleep(10); + return "bun"; + }, + }); + return expect((await sql`select 1 as x`)[0].x).toBe(1); + }); + test("Support dynamic async rejected password function", async () => { + await using sql = new SQL({ + ...options, + password: () => Promise.reject(new Error("password error")), + max: 1, + }); + try { + await sql`select true as x`; + expect.unreachable(); + } catch (e: any) { + expect(e.message).toBe("password error"); + } + }); + test("Support dynamic async password function that throws", async () => { + await using sql = new SQL({ + ...options, + max: 1, + password: async () => { + await Bun.sleep(10); + throw new Error("password error"); + }, + }); + try { + await sql`select true as x`; + expect.unreachable(); + } catch (e: any) { + expect(e).toBeInstanceOf(Error); + expect(e.message).toBe("password error"); + } + }); + test("sql file", async () => { + await using sql = new SQL(options); + expect((await sql.file(rel("select.sql")))[0].x).toBe(1); }); - } catch (err) { - error = err; - } - expect(error.code).toBe("ERR_MYSQL_CONNECTION_CLOSED"); - }); - test("dynamic table name", async () => { - await using sql = new SQL({ ...options, max: 1 }); - await sql`create table test(a int)`; - try { - return expect((await sql`select * from ${sql("test")}`).length).toBe(0); - } finally { - await sql`drop table test`; - } - }); + test("sql file throws", async () => { + await using sql = new SQL(options); + expect(await sql.file(rel("selectomondo.sql")).catch(x => x.code)).toBe("ENOENT"); + }); + test("Parameters in file", async () => { + await using sql = new SQL(options); + const result = await sql.file(rel("select-param.sql"), ["hello"]); + return expect(result[0].x).toBe("hello"); + }); - test("dynamic column name", async () => { - await using sql = new SQL({ ...options, max: 1 }); - const result = await sql`select 1 as ${sql("!not_valid")}`; - expect(Object.keys(result[0])[0]).toBe("!not_valid"); - }); + test("Connection ended promise", async () => { + const sql = new SQL(options); - test("dynamic insert", async () => { - await using sql = new SQL({ ...options, max: 1 }); - await sql`create table test (a int, b text)`; - try { - const x = { a: 42, b: "the answer" }; - await sql`insert into test ${sql(x)}`; - const [{ b }] = await sql`select * from test`; - expect(b).toBe("the answer"); - } finally { - await sql`drop table test`; - } - }); + await sql.end(); - test("dynamic insert pluck", async () => { - await using sql = new SQL({ ...options, max: 1 }); - try { - await sql`create table test2 (a int, b text)`; - const x = { a: 42, b: "the answer" }; - await sql`insert into test2 ${sql(x, "a")}`; - const [{ b, a }] = await sql`select * from test2`; - expect(b).toBeNull(); - expect(a).toBe(42); - } finally { - await sql`drop table test2`; - } - }); + expect(await sql.end()).toBeUndefined(); + }); - test("bigint is returned as String", async () => { - await using sql = new SQL(options); - expect(typeof (await sql`select 9223372036854777 as x`)[0].x).toBe("string"); - }); + test("Connection ended timeout", async () => { + const sql = new SQL(options); - test("bigint is returned as BigInt", async () => { - await using sql = new SQL({ - ...options, - bigint: true, - }); - expect((await sql`select 9223372036854777 as x`)[0].x).toBe(9223372036854777n); - }); + await sql.end({ timeout: 10 }); - test("int is returned as Number", async () => { - await using sql = new SQL(options); - expect((await sql`select CAST(123 AS SIGNED) as x`)[0].x).toBe(123); - }); + expect(await sql.end()).toBeUndefined(); + }); - test("flush should work", async () => { - await using sql = new SQL(options); - await sql`select 1`; - sql.flush(); - }); + test("Connection ended error", async () => { + const sql = new SQL(options); + await sql.end(); + return expect(await sql``.catch(x => x.code)).toBe("ERR_MYSQL_CONNECTION_CLOSED"); + }); - test.each(["connect_timeout", "connectTimeout", "connectionTimeout", "connection_timeout"] as const)( - "connection timeout key %p throws", - async key => { - const server = net.createServer().listen(); + test("Connection end does not cancel query", async () => { + const sql = new SQL(options); - const port = (server.address() as import("node:net").AddressInfo).port; + const promise = sql`select SLEEP(1) as x`.execute(); + await sql.end(); + return expect(await promise).toEqual([{ x: 0 }]); + }); - const sql = new SQL({ adapter: "mysql", port, host: "127.0.0.1", [key]: 0.2 }); + test("Connection destroyed", async () => { + const sql = new SQL(options); + process.nextTick(() => sql.end({ timeout: 0 })); + expect(await sql``.catch(x => x.code)).toBe("ERR_MYSQL_CONNECTION_CLOSED"); + }); - try { + test("Connection destroyed with query before", async () => { + const sql = new SQL(options); + const error = sql`select SLEEP(0.2)`.catch(err => err.code); + + sql.end({ timeout: 0 }); + return expect(await error).toBe("ERR_MYSQL_CONNECTION_CLOSED"); + }); + + test("unsafe", async () => { + await sql`create table test (x int)`; + try { + await sql.unsafe("insert into test values (?)", [1]); + const [{ x }] = await sql`select * from test`; + expect(x).toBe(1); + } finally { + await sql`drop table test`; + } + }); + + test("unsafe simple", async () => { + await using sql = new SQL({ ...options, max: 1 }); + expect(await sql.unsafe("select 1 as x")).toEqual([{ x: 1 }]); + }); + + test("simple query with multiple statements", async () => { + await using sql = new SQL({ ...options, max: 1 }); + const result = await sql`select 1 as x;select 2 as x`.simple(); + expect(result).toBeDefined(); + expect(result.length).toEqual(2); + expect(result[0][0].x).toEqual(1); + expect(result[1][0].x).toEqual(2); + }); + + test("simple query using unsafe with multiple statements", async () => { + await using sql = new SQL({ ...options, max: 1 }); + const result = await sql.unsafe("select 1 as x;select 2 as x"); + expect(result).toBeDefined(); + expect(result.length).toEqual(2); + expect(result[0][0].x).toEqual(1); + expect(result[1][0].x).toEqual(2); + }); + + test("only allows one statement", async () => { + expect(await sql`select 1; select 2`.catch(e => e.message)).toBe( + "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select 2' at line 1", + ); + }); + + test("await sql() throws not tagged error", async () => { + try { + await sql("select 1"); + expect.unreachable(); + } catch (e: any) { + expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); + } + }); + + test("sql().then throws not tagged error", async () => { + try { + await sql("select 1").then(() => { + /* noop */ + }); + expect.unreachable(); + } catch (e: any) { + expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); + } + }); + + test("sql().catch throws not tagged error", async () => { + try { + sql("select 1").catch(() => { + /* noop */ + }); + expect.unreachable(); + } catch (e: any) { + expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); + } + }); + + test("sql().finally throws not tagged error", async () => { + try { + sql("select 1").finally(() => { + /* noop */ + }); + expect.unreachable(); + } catch (e: any) { + expect(e.code).toBe("ERR_MYSQL_NOT_TAGGED_CALL"); + } + }); + + test("little bobby tables", async () => { + await using sql = new SQL({ ...options, max: 1 }); + const name = "Robert'); DROP TABLE students;--"; + + try { + await sql`create table students (name text, age int)`; + await sql`insert into students (name) values (${name})`; + + expect((await sql`select name from students`)[0].name).toBe(name); + } finally { + await sql`drop table students`; + } + }); + + test("Connection errors are caught using begin()", async () => { + let error; + try { + const sql = new SQL({ host: "localhost", port: 1, adapter: "mysql" }); + + await sql.begin(async sql => { + await sql`insert into test (label, value) values (${1}, ${2})`; + }); + } catch (err) { + error = err; + } + expect(error.code).toBe("ERR_MYSQL_CONNECTION_CLOSED"); + }); + + test("dynamic table name", async () => { + await using sql = new SQL({ ...options, max: 1 }); + await sql`create table test(a int)`; + try { + return expect((await sql`select * from ${sql("test")}`).length).toBe(0); + } finally { + await sql`drop table test`; + } + }); + + test("dynamic column name", async () => { + await using sql = new SQL({ ...options, max: 1 }); + const result = await sql`select 1 as ${sql("!not_valid")}`; + expect(Object.keys(result[0])[0]).toBe("!not_valid"); + }); + + test("dynamic insert", async () => { + await using sql = new SQL({ ...options, max: 1 }); + await sql`create table test (a int, b text)`; + try { + const x = { a: 42, b: "the answer" }; + await sql`insert into test ${sql(x)}`; + const [{ b }] = await sql`select * from test`; + expect(b).toBe("the answer"); + } finally { + await sql`drop table test`; + } + }); + + test("dynamic insert pluck", async () => { + await using sql = new SQL({ ...options, max: 1 }); + try { + await sql`create table test2 (a int, b text)`; + const x = { a: 42, b: "the answer" }; + await sql`insert into test2 ${sql(x, "a")}`; + const [{ b, a }] = await sql`select * from test2`; + expect(b).toBeNull(); + expect(a).toBe(42); + } finally { + await sql`drop table test2`; + } + }); + + test("bigint is returned as String", async () => { + await using sql = new SQL(options); + expect(typeof (await sql`select 9223372036854777 as x`)[0].x).toBe("string"); + }); + + test("bigint is returned as BigInt", async () => { + await using sql = new SQL({ + ...options, + bigint: true, + }); + expect((await sql`select 9223372036854777 as x`)[0].x).toBe(9223372036854777n); + }); + + test("int is returned as Number", async () => { + await using sql = new SQL(options); + expect((await sql`select CAST(123 AS SIGNED) as x`)[0].x).toBe(123); + }); + + test("flush should work", async () => { + await using sql = new SQL(options); await sql`select 1`; - throw new Error("should not reach"); - } catch (e) { - expect(e).toBeInstanceOf(Error); - expect(e.code).toBe("ERR_MYSQL_CONNECTION_TIMEOUT"); - expect(e.message).toMatch(/Connection timed out after 200ms/); - } finally { - sql.close(); - server.close(); - } - }, - { - timeout: 1000, + sql.flush(); + }); + + test.each(["connect_timeout", "connectTimeout", "connectionTimeout", "connection_timeout"] as const)( + "connection timeout key %p throws", + async key => { + const server = net.createServer().listen(); + + const port = (server.address() as import("node:net").AddressInfo).port; + + const sql = new SQL({ adapter: "mysql", port, host: "127.0.0.1", [key]: 0.2 }); + + try { + await sql`select 1`; + throw new Error("should not reach"); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.code).toBe("ERR_MYSQL_CONNECTION_TIMEOUT"); + expect(e.message).toMatch(/Connection timed out after 200ms/); + } finally { + sql.close(); + server.close(); + } + }, + { + timeout: 1000, + }, + ); + test("Array returns rows as arrays of columns", async () => { + await using sql = new SQL(options); + return [(await sql`select CAST(1 AS SIGNED) as x`.values())[0][0], 1]; + }); }, ); - test("Array returns rows as arrays of columns", async () => { - await using sql = new SQL(options); - return [(await sql`select CAST(1 AS SIGNED) as x`.values())[0][0], 1]; - }); - }, -); + } +} diff --git a/test/js/sql/sql.test.ts b/test/js/sql/sql.test.ts index abc9be5ebc..57740fa7eb 100644 --- a/test/js/sql/sql.test.ts +++ b/test/js/sql/sql.test.ts @@ -1,10 +1,10 @@ import { $, randomUUIDv7, sql, SQL } from "bun"; import { afterAll, describe, expect, mock, test } from "bun:test"; -import { bunEnv, bunExe, isCI, isLinux, tempDirWithFiles } from "harness"; +import { bunEnv, bunExe, isCI, isDockerEnabled, tempDirWithFiles } from "harness"; import path from "path"; const postgres = (...args) => new SQL(...args); -import { exec, execSync } from "child_process"; +import { exec } from "child_process"; import net from "net"; import { promisify } from "util"; @@ -88,23 +88,6 @@ async function startContainer(): Promise<{ port: number; containerName: string } } } -function isDockerEnabled(): boolean { - if (!dockerCLI) { - return false; - } - - // TODO: investigate why its not starting on Linux arm64 - if (isLinux && process.arch === "arm64") { - return false; - } - - try { - const info = execSync(`${dockerCLI} info`, { stdio: ["ignore", "pipe", "inherit"] }); - return info.toString().indexOf("Server Version:") !== -1; - } catch { - return false; - } -} if (isDockerEnabled()) { const container: { port: number; containerName: string } = await startContainer(); afterAll(async () => {