diff --git a/.gitignore b/.gitignore index 970720b6..22dc7220 100644 --- a/.gitignore +++ b/.gitignore @@ -67,7 +67,15 @@ _tmp* __debug_bin .DS_Store -# Kernel (SEA) backend build artifacts — produced by `make kernel-lib`, never committed. +# Kernel (SEA) backend build artifacts. +# +# The committed distribution model (see README) commits the small +# platform-independent C header (include/) and each platform's prebuilt archive +# under kernellib// (nested modules), so `go get` needs no build step. +# Those committed paths are intentionally NOT ignored. +# +# The paths below remain ignored: they are scratch dirs still used by +# `make kernel-lib` for platforms not yet committed (linux/windows) and for +# source builds — never committed. /build/kernel-src/ /internal/backend/kernel/lib/ -/internal/backend/kernel/include/ diff --git a/README.md b/README.md index 425c0c62..aa2d5aa9 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ standard `database/sql` interface. ## Contents - [Quick start](#quick-start) +- [Cloning the repository](#cloning-the-repository) - [Choosing a backend (Thrift vs SEA/kernel)](#choosing-a-backend-thrift-vs-seakernel) - [Building](#building) - [Connecting](#connecting) @@ -46,6 +47,48 @@ defer rows.Close() See [`doc.go`](./doc.go) for full package documentation or the Databricks documentation for the [SQL Driver for Go](https://docs.databricks.com/dev-tools/go-sql-driver.html). +> **Using the driver in your own project?** You never clone this repository — you +> add it with `go get github.com/databricks/databricks-sql-go` and `go build`. +> `go get` fetches per-version module archives, not git history, and for a +> default Thrift build it pulls **no** kernel binaries at all. The guidance below +> is only for people who `git clone` this repo directly (contributors / CI). + +## Cloning the repository + +This repo commits a small number of **prebuilt kernel binaries** (per-platform +`libdatabricks_sql_kernel.a`, ~62 MB each, each in its own nested module under +`internal/backend/kernel/kernellib/`) so that the SEA/kernel backend +works straight from `go get` with **no build step** (see +[SEA/kernel](#seakernel--cgo--a-linked-rust-static-library); for how these are +versioned and published, see [docs/RELEASING.md](./docs/RELEASING.md)). A +consumer's `go get` pulls only the target platform's archive at the +driver-pinned version — never all platforms. Committed binaries cannot be +delta-compressed by git, so a *full* clone accumulates their whole history over +releases. + +**If you clone this repo directly, use a partial clone** so you download only the +binary versions you actually check out, not the entire history: + +```bash +git clone --filter=blob:none https://github.com/databricks/databricks-sql-go +``` + +`--filter=blob:none` fetches commits and trees immediately and pulls file blobs +lazily, only when a checkout needs them. This keeps `.git` small and — unlike a +naive `git clone` — it does **not** grow with the number of releases (only your +current checkout's blobs are fetched). GitHub serves this by default. To also +avoid materializing other platforms' archives in your working tree, add +`--sparse` and select the paths you need: + +```bash +git clone --filter=blob:none --sparse https://github.com/databricks/databricks-sql-go +cd databricks-sql-go +git sparse-checkout set --no-cone '/*' '!/internal/backend/kernel/kernellib' \ + 'internal/backend/kernel/kernellib/darwin_arm64' # keep only your platform +``` + +CI checkouts in this repo use `--filter=blob:none` for the same reason. + ## Choosing a backend (Thrift vs SEA/kernel) The driver has **two execution backends**, selected once per connection: diff --git a/docs/RELEASING.md b/docs/RELEASING.md new file mode 100644 index 00000000..bf8768a3 --- /dev/null +++ b/docs/RELEASING.md @@ -0,0 +1,114 @@ +# Releasing the driver (with the kernel/SEA backend) + +This repo ships the kernel/SEA backend as **committed, per-platform, prebuilt +static archives** carried by `go get` — no `make kernel-lib` step for consumers, +no Rust toolchain. This document explains how those archives are versioned and +published so that a `go get github.com/databricks/databricks-sql-go@vX.Y.Z` +resolves the matching kernel archive automatically. + +## The module layout + +The driver is a multi-module repository: + +``` +github.com/databricks/databricks-sql-go (the driver module) +└── internal/backend/kernel/kernellib// (one NESTED module per platform) + ├── go.mod → github.com/databricks/databricks-sql-go/internal/backend/kernel/kernellib/ + ├── link.go → //go:build cgo && databricks_kernel && && (+ #cgo LDFLAGS) + └── libdatabricks_sql_kernel.a (the committed prebuilt archive for this platform) +``` + +Each `kernellib/` directory is **its own Go module** (it has a +`go.mod`). This is deliberate: Go downloads a module's zip only when a build +compiles a file from it, and each `link.go` is build-tag-gated to one platform + +`databricks_kernel`. So: + +- a **Thrift build** (`CGO_ENABLED=0`, no tag) downloads **none** of them; +- a **kernel build** for, say, darwin/arm64 downloads **only** the + `darwin_arm64` module — never the other platforms' archives. + +## How versioning works + +The driver's `go.mod` `require`s each platform module at a **real version**, and +also carries a `replace` pointing at the in-tree source: + +``` +require github.com/databricks/databricks-sql-go/internal/backend/kernel/kernellib/darwin_arm64 v1.2.3 + +replace github.com/databricks/databricks-sql-go/internal/backend/kernel/kernellib/darwin_arm64 => ./internal/backend/kernel/kernellib/darwin_arm64 +``` + +- The **`require` version pins the kernel**. `go get .../databricks-sql-go@v1.2.3` + reads that tag's `go.mod`, sees `require .../darwin_arm64 v1.2.3`, and resolves + that exact archive version from the module proxy. **Upgrading the driver is what + moves the kernel version** — deterministic, per-driver-version pinning. +- The **`replace` is local-only and consumer-invisible.** Per the + [Go module spec](https://go.dev/ref/mod#go-mod-file-replace), a `replace` in a + *dependency's* `go.mod` is ignored — it applies only when this repo is the main + module. So it lets THIS repo build against the committed archive during + development, while a downstream `go get` always resolves the published version + from the proxy. (Verified: a consumer building against a published module sees + the proxy copy, not the replace target.) + +## Publishing: path-prefixed tags + +Go publishes a nested module using a **tag whose name is the module's +subdirectory path plus the version**. To release the darwin/arm64 kernel module +at `v1.2.3`: + +``` +git tag internal/backend/kernel/kernellib/darwin_arm64/v1.2.3 +git tag internal/backend/kernel/kernellib/linux_amd64/v1.2.3 +# ... one tag per platform module ... +git tag v1.2.3 # the driver module itself +git push origin --tags +``` + +The module proxy serves each nested module's zip **excluding** any nested-module +subtree, and serves it at the version from its path-prefixed tag. The driver +module's own zip (tag `v1.2.3`) excludes the `kernellib/*` subtrees — consumers +pull those separately at the versions the driver `require`s. + +## Release steps + +1. **Build the archives.** The kernel repo's `build-c-abi-libs` workflow builds + `libdatabricks_sql_kernel.a` per platform on native runners and pushes them + into the `kernellib//` directories here (see that repo's workflow; + it opens a sync PR against this repo). Alternatively, drop a locally built + archive in for a single platform during development. +2. **Bump the `require` versions** in the driver `go.mod` to the new release + version (keep the matching `replace` lines). +3. **Tag every module** at the new version using the path-prefixed tags above, + plus the plain `vX.Y.Z` for the driver. +4. **Push tags.** The proxy indexes each module at its tag; `go get @vX.Y.Z` + now resolves the driver and, transitively, the matching per-platform kernel + archive. +5. **Refresh `go.sum`.** While developing, the `replace` points at the in-tree + source so no `go.sum` hash is needed for the nested modules. Once they are + published and the driver `require`s the real versions *without* relying on the + replace for resolution (i.e. for the tagged release consumers fetch), run + `GOFLAGS=-mod=mod GOWORK=off go mod tidy` against the published versions so the + nested-module checksums land in `go.sum`. Consumers verify against these. + (The committed `replace` still shadows the download in THIS repo's own builds; + the `go.sum` entries are what a downstream `go get` verifies.) + +## Adding a new platform + +1. Create `internal/backend/kernel/kernellib//` with its own `go.mod`, + a build-tag-gated `link.go` (matching `//go:build` + `#cgo LDFLAGS`), and the + committed archive. +2. Add a `require` + `replace` pair for it in the driver `go.mod`. +3. Add a build-tagged shim (`cgo__.go`) in the `kernel` package that + blank-imports the new module (so its `#cgo LDFLAGS` are collected at link). +4. Tag it alongside the others at release. + +## Consumer experience (for reference) + +- **Thrift (default):** `go get ...` + `go build` — pure Go, no cgo, no archive + downloaded. +- **Kernel/SEA:** `go get ...` + `CGO_ENABLED=1 go build -tags databricks_kernel` + — pulls only the target platform's archive at the driver-pinned version; no + `make kernel-lib`, no Rust. +- **Cloning this repo directly** (contributors/CI): use + `git clone --filter=blob:none` to skip the committed-archive history. See the + README "Cloning the repository" section. diff --git a/go.mod b/go.mod index b2cea939..95b4bc23 100644 --- a/go.mod +++ b/go.mod @@ -52,3 +52,24 @@ require ( github.com/rs/zerolog v1.28.0 golang.org/x/sys v0.45.0 // indirect ) + +// Nested per-platform kernel library modules. Each carries one platform's +// prebuilt kernel static archive + its cgo link directive; a build downloads +// only the archive for the platform it targets (build-tag gated), and a pure-Go +// Thrift build downloads none of them. +// +// The `require` versions are REAL published versions, released via path-prefixed +// tags (e.g. `internal/backend/kernel/kernellib/darwin_arm64/v1.2.3`) and bumped +// in lockstep with each driver release — see docs/RELEASING.md. A consumer's +// `go get github.com/databricks/databricks-sql-go@v1.2.3` therefore transitively +// pins the matching per-platform kernel archive, and upgrading the driver is what +// moves the kernel version. +// +// The `replace` directives point each module at its in-tree source so THIS repo +// builds against the committed archive during development. Per the Go module +// spec, a `replace` in a dependency's go.mod is IGNORED by consumers — it only +// applies when this repo is the main module — so it is safe to ship: it never +// affects a downstream `go get`, which always resolves the published version. +require github.com/databricks/databricks-sql-go/internal/backend/kernel/kernellib/darwin_arm64 v0.0.1 + +replace github.com/databricks/databricks-sql-go/internal/backend/kernel/kernellib/darwin_arm64 => ./internal/backend/kernel/kernellib/darwin_arm64 diff --git a/internal/backend/kernel/cgo_darwin.go b/internal/backend/kernel/cgo_darwin.go index bc59d19e..1f0cb792 100644 --- a/internal/backend/kernel/cgo_darwin.go +++ b/internal/backend/kernel/cgo_darwin.go @@ -2,19 +2,15 @@ package kernel -// Link flags for darwin/arm64. NOTE: this platform is not yet exercised in CI -// (M0 is linux/amd64); the flags below are the intended shape but must be -// validated on a mac before darwin is enabled. +// darwin/arm64 link wiring. The kernel static archive and its `#cgo LDFLAGS` +// live in a NESTED per-platform module +// (internal/backend/kernel/kernellib/darwin_arm64) so that a `go get`/`go build` +// only downloads the darwin archive when actually building for darwin/arm64 with +// the databricks_kernel tag — see that module's link.go and the repo README. // -// Two darwin-specific differences from linux: -// - Apple's ld64 does NOT accept the GNU `-l:.a` extension, so the -// archive is passed as a positional input by absolute ${SRCDIR} path -// instead. Since only the .a is placed under lib/darwin_arm64 (see -// kernel-lib.sh), there is no .so to accidentally prefer. -// - -lc++ (not -lstdc++) is the macOS C++ runtime; @loader_path keeps any -// dynamic reference resolvable relative to the built binary. - -/* -#cgo LDFLAGS: ${SRCDIR}/lib/darwin_arm64/libdatabricks_sql_kernel.a -lc++ -lm -Wl,-rpath,@loader_path -*/ -import "C" +// This file's sole job is to import that module for its link side-effect: cgo +// collects `#cgo LDFLAGS` from every imported cgo package at final link time, so +// the blank import below is what pulls libdatabricks_sql_kernel.a into the +// binary. It carries the same build constraint as the nested link.go so the two +// are always selected (or excluded) together. +import _ "github.com/databricks/databricks-sql-go/internal/backend/kernel/kernellib/darwin_arm64" diff --git a/internal/backend/kernel/include/databricks_kernel.h b/internal/backend/kernel/include/databricks_kernel.h new file mode 100644 index 00000000..d7ca6f19 --- /dev/null +++ b/internal/backend/kernel/include/databricks_kernel.h @@ -0,0 +1,754 @@ +/* + * Copyright (c) 2026 Databricks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Databricks SQL Kernel — C ABI (v0). + * + * Hand-written header for the kernel's custom C ABI (cbindgen + * integration is a v0.1 concern). The bodies live in `src/c_abi/` in the + * `databricks-sql-kernel` crate, compiled as a `cdylib` / `staticlib`. + * + * This is the surface ODBC / Go consumers link against. It is NOT ADBC: + * the kernel exposes a Databricks-native session / statement / result / + * metadata model. Result data crosses the boundary via the Arrow C Data + * Interface (`FFI_ArrowArray` / `FFI_ArrowSchema`), one array per + * `kernel_result_stream_next_batch` call (a pull model). + * + * ## Error reporting + * + * Every fallible entry point returns a `KernelStatusCode`. On any + * non-`Success` return, call `kernel_get_last_error` to fetch the typed + * detail for the current thread. The thread-local error buffer is reset + * at the start of every kernel call, and the `char*` fields it hands + * back are valid only until the next kernel call on the same thread — + * copy them out before calling again. + * + * ## ABI versioning + * + * There is no ABI promise through v0.x: struct layouts and the function + * set may change freely; ABI stability is committed at v1.0. All handle + * types below are *opaque* (incomplete) to C — you only ever hold + * pointers to them, never read their fields — so their Rust-side layout + * is irrelevant. The one caller-*readable* struct, `KernelError`, is laid + * out exactly as declared here; copy its fields out before the next + * `kernel_*` call on the thread. + * + * ## Lifecycle & threading notes + * + * - **Re-execution invalidates prior handles.** `kernel_statement_execute` + * auto-cancels and closes any `kernel_executed_statement_t` previously + * returned by the same statement; calls on the stale handle then return + * `KernelStatusCode_InvalidStatementHandle`. Don't hold two live + * executed handles from one statement. + * - **A result stream borrows its executed handle.** Closing the executed + * handle invalidates any stream obtained from it: subsequent stream + * calls return `InvalidStatementHandle` (a defined error, not UB). + * - **Close is best-effort async.** `kernel_session_close` initiates the + * server-side delete before returning — a detached task runs it on the + * kernel's process-wide runtime — but does not wait for completion. A + * process that exits immediately after `kernel_session_close` may drop + * the detached task before it runs, leaving the server session to expire + * on its own idle timeout. + * - **Close the session LAST.** Because close initiates the server-side + * delete, any handle still open against that session — a statement / + * executed handle, or a metadata result stream (which holds its own + * session reference and remains drivable) — must be drained and closed + * BEFORE `kernel_session_close`. Driving such a handle afterwards issues + * RPCs against a session whose delete has already been initiated and + * will surface server errors. Order teardown: result streams → executed + * handles → statements → session. (Statement cancellers are not in this + * chain — freeing one drives no RPC; see the next bullet.) + * - **Free statement cancellers too.** A `kernel_statement_canceller_t` + * holds its own session reference, so a leaked canceller pins the + * client-side session (and its connection pool) past + * `kernel_session_close`. Freeing one drives no RPC, so its order is + * independent of the chain above: free it once its execute has returned, + * regardless of statement/session close. + */ + +#ifndef DATABRICKS_KERNEL_H +#define DATABRICKS_KERNEL_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ─── Arrow C Data Interface ────────────────────────────────────────── + * + * Mirrors the canonical `ArrowArray` / `ArrowSchema` from the Arrow C + * Data Interface (https://arrow.apache.org/docs/format/CDataInterface.html). + * The kernel populates these via the `arrow-array` crate's `ffi` module. + * If the consuming project already declares these (e.g. via Arrow's + * `abi.h`), define `DATABRICKS_KERNEL_NO_ARROW_CDATA` before including + * this header to suppress the duplicate definitions. + */ +#ifndef DATABRICKS_KERNEL_NO_ARROW_CDATA + +struct ArrowSchema { + const char* format; + const char* name; + const char* metadata; + int64_t flags; + int64_t n_children; + struct ArrowSchema** children; + struct ArrowSchema* dictionary; + void (*release)(struct ArrowSchema*); + void* private_data; +}; + +struct ArrowArray { + int64_t length; + int64_t null_count; + int64_t offset; + int64_t n_buffers; + int64_t n_children; + const void** buffers; + struct ArrowArray** children; + struct ArrowArray* dictionary; + void (*release)(struct ArrowArray*); + void* private_data; +}; + +#endif /* DATABRICKS_KERNEL_NO_ARROW_CDATA */ + +/* ─── Status + error ────────────────────────────────────────────────── + * + * Discriminants match `crate::c_abi::KernelStatusCode`. Keep this enum in + * lockstep with that Rust enum (the Rust side enforces a 1:1 mapping + * against `ErrorCode` with an exhaustive match). + */ +typedef enum KernelStatusCode { + KernelStatusCode_Success = 0, + KernelStatusCode_InvalidArgument = 1, + KernelStatusCode_Unauthenticated = 2, + KernelStatusCode_PermissionDenied = 3, + KernelStatusCode_NotFound = 4, + KernelStatusCode_ResourceExhausted = 5, + KernelStatusCode_Unavailable = 6, + KernelStatusCode_Timeout = 7, + KernelStatusCode_Cancelled = 8, + KernelStatusCode_DataLoss = 9, + KernelStatusCode_Internal = 10, + KernelStatusCode_InvalidStatementHandle = 11, + KernelStatusCode_NetworkError = 12, + KernelStatusCode_SqlError = 13, +} KernelStatusCode; + +/* Server-side async execution status (mirror of + * `KernelStatementStatusKind`). Reserved for the async submit path, + * which is not exposed over the v0 C ABI. */ +typedef enum KernelStatementStatusKind { + KernelStatementStatusKind_Pending = 0, + KernelStatementStatusKind_Running = 1, + KernelStatementStatusKind_Succeeded = 2, + KernelStatementStatusKind_Failed = 3, + KernelStatementStatusKind_Cancelled = 4, + KernelStatementStatusKind_Closed = 5, +} KernelStatementStatusKind; + +/* + * Typed last-error detail. Populated by `kernel_get_last_error`. The + * `char*` fields point into the calling thread's last-error buffer and + * are valid only until the next kernel call on that thread; a NULL + * pointer means the field was absent. `vendor_code` / `http_status` are + * `0` when absent. + */ +typedef struct KernelError { + /* Discriminant value of `KernelStatusCode`. */ + int32_t code; + const char* message; + /* 5 ASCII chars + NUL, or NULL if absent. */ + const char* sql_state; + int32_t vendor_code; + uint16_t http_status; + bool retryable; + const char* query_id; +} KernelError; + +/* + * Fetch the last error recorded on the calling thread. Returns `false` + * (leaving `*out` untouched) if no error has been recorded since the + * last kernel call cleared the buffer. + */ +bool kernel_get_last_error(KernelError* out); + +/* ─── Opaque handles ────────────────────────────────────────────────── + * + * All handles are owned heap allocations on the Rust side; the C side + * holds them by pointer and releases each via its matching `*_close` / + * `*_free`. + */ +typedef struct kernel_session_config_t KernelSessionConfig; +typedef struct kernel_session_t kernel_session_t; +typedef struct kernel_statement_t kernel_statement_t; +typedef struct kernel_executed_statement_t kernel_executed_statement_t; +/* Async-consumption executed handle. Reserved: the async submit path is + * deferred in v0 (see kernel_statement_submit), so no consumer receives a + * live instance yet. */ +typedef struct kernel_executed_async_statement_t kernel_executed_async_statement_t; +typedef struct kernel_result_stream_t kernel_result_stream_t; +/* Detached canceller for a sync-executing statement, returned by + * kernel_statement_canceller_new. The returned handle is safe to use from + * another thread concurrently with the blocking kernel_statement_execute on the + * originating statement (the _new call itself is NOT — see its doc). + * Statement-scoped, not execution-scoped: it tracks whichever query is currently + * in flight on its statement, so a canceller retained across a re-execute + * cancels the LATER query. Use one per execute (new before execute, free after + * it returns). */ +typedef struct kernel_statement_canceller_t kernel_statement_canceller_t; + +/* ─── Session config ────────────────────────────────────────────────── + * + * Build a config, set exactly one connection form and exactly one auth + * form, then hand it to `kernel_session_open` (which consumes it). Only + * PAT, OAuth M2M, and OAuth U2M (interactive browser flow) auth are + * exposed over the C ABI. + */ +KernelStatusCode kernel_session_config_new(KernelSessionConfig** out); +void kernel_session_config_free(KernelSessionConfig* config); + +/* Connection identity — call exactly one; the last one wins. */ +KernelStatusCode kernel_session_config_set_warehouse(KernelSessionConfig* config, + const char* host, + const char* warehouse_id); +KernelStatusCode kernel_session_config_set_http_path(KernelSessionConfig* config, + const char* host, + const char* http_path); + +/* Authentication — call exactly one; the last one wins. */ +KernelStatusCode kernel_session_config_set_auth_pat(KernelSessionConfig* config, + const char* token); +KernelStatusCode kernel_session_config_set_auth_m2m(KernelSessionConfig* config, + const char* client_id, + const char* client_secret); +/* OAuth U2M (user-to-machine: authorization code + PKCE, browser flow). + * All args optional: `client_id` NULL → public `databricks-cli` client; + * `redirect_port` 0 → kernel default (8020); `scopes` (comma-separated) + * NULL → kernel default (`all-apis`,`offline_access`; `offline_access` + * yields a cached, auto-refreshed refresh token). INTERACTIVE: opening the + * session starts a localhost listener and opens the user's browser. */ +KernelStatusCode kernel_session_config_set_auth_u2m(KernelSessionConfig* config, + const char* client_id, + uint16_t redirect_port, + const char* scopes); + +/* Add (or overwrite) one session-conf entry. Keys are normally server SET + * parameters forwarded on the SEA wire (allowlist-filtered). A small set of + * reserved keys are instead CLIENT-ONLY: they tune the kernel locally, are + * consumed before session creation, and NEVER reach the wire. Client-only keys + * (matched case-insensitively): + * + * cloudfetch_max_chunks_in_memory — positive integer; how many decompressed + * CloudFetch chunks the kernel holds in memory at once (bounds peak RSS on + * large result sets; default 16). A value above the kernel ceiling (256) is + * clamped; a non-numeric / non-positive value is ignored (default kept) with + * a warning — a bad tuning value never fails session open. */ +KernelStatusCode kernel_session_config_set_session_conf(KernelSessionConfig* config, + const char* key, + const char* value); + +/* Append one HTTP header sent on every request. Call once per header + * (order preserved); `name` and `value` are both required. */ +KernelStatusCode kernel_session_config_set_custom_header(KernelSessionConfig* config, + const char* name, const char* value); + +/* Initialize kernel logging, process-wide and ONCE (first call wins; + * later calls are no-ops). `level` is OFF/ERROR/WARN/INFO/DEBUG/TRACE + * (NULL → RUST_LOG, default warn); `file_path` NULL → stderr. Not tied to + * a session — lets a host route kernel logs into its own log file. + * + * A non-UTF-8 `file_path` returns InvalidArgument. Otherwise the result + * reflects the FIRST call's outcome (a later call's level/file have no + * effect and are not described by its return): Success when logging was + * installed against the request (or skipped for level OFF); Internal (with + * a stored last-error) when that install did not fully honour the request + * — the file could not be opened (falls back to stderr) or a global + * subscriber was already installed (no kernel subscriber installed). */ +KernelStatusCode kernel_init_logging(const char* level, const char* file_path); + +/* The C ABI version this library implements. A consumer that loads the + * kernel as a shared library (.so/.dylib/.dll) at run time should call this + * right after loading and refuse to proceed if the value differs from the + * version it was built against — turning an ABI mismatch (a wrong or stale + * shared library) into a clear error instead of silent memory corruption. + * Bumped on every breaking C ABI change (added/removed/renumbered function, + * changed struct layout or signature). Not the crate semver — tracks only + * the C ABI shape. Starts at 1. Takes no pointers; always safe to call. */ +uint32_t kernel_abi_version(void); + +/* ─── Proxy / TLS (optional) ────────────────────────────────────────── + * + * All optional; defaults are: no explicit proxy (reqwest honours + * HTTP(S)_PROXY / NO_PROXY env vars) and strict TLS (system trust store, + * valid chain, hostname verified). Any setter below is a relaxation or + * override. + */ + +/* Route traffic through an HTTP/HTTPS proxy. `url` is required (e.g. + * "http://proxy.corp:8080"); `username` / `password` / `bypass_hosts` + * (comma-separated) are optional — pass NULL to omit. */ +KernelStatusCode kernel_session_config_set_proxy(KernelSessionConfig* config, const char* url, + const char* username, const char* password, + const char* bypass_hosts); + +/* Add a PEM-encoded CA-certificate bundle (`pem`/`len` bytes) to the TLS + * trust store on top of the system roots (corporate re-signing proxy / + * on-prem CA). */ +KernelStatusCode kernel_session_config_set_tls_trusted_certs(KernelSessionConfig* config, + const uint8_t* pem, size_t len); + +/* Accept self-signed / invalid server certs (disables chain validation). + * Development / on-prem only. */ +KernelStatusCode kernel_session_config_set_tls_allow_self_signed(KernelSessionConfig* config, + bool allow); + +/* Skip the certificate hostname-vs-SNI check. Development / lab only. */ +KernelStatusCode kernel_session_config_set_tls_skip_hostname_verification(KernelSessionConfig* config, + bool skip); + +/* Configure the HTTP retry / backoff policy. `min_wait_ms` / `max_wait_ms` + * are the backoff-wait bounds between attempts (the wait is exponential + * with jitter, clamped to this range); `max_retries` is the number of + * retries AFTER the initial attempt (so at most `max_retries + 1` total + * attempts, and `0` disables retries); `overall_timeout_ms` is the cumulative + * retry budget across all attempts (`0` keeps the kernel default — pass + * non-zero to override it). Lets a host's RetryWaitMin / RetryWaitMax / + * RetryMax / retry-timeout surface override the kernel's built-in policy + * (default 5 retries, 1s..60s, 900s budget) when this setter is not called. + * + * The retry budget is ALWAYS bounded — there is no "unlimited" value. A large + * `max_retries` is still capped by the (default or overridden) budget: if the + * exponential backoff sums past `overall_timeout_ms` the remaining attempts do + * not run, so raise `overall_timeout_ms` alongside a large `max_retries` if you + * want them all to fire. A non-zero `overall_timeout_ms` smaller than + * `min_wait_ms` guarantees ZERO retries (the first backoff already exceeds the + * budget); it is accepted but the kernel logs a warning. + * + * `min_wait_ms == 0` or `max_wait_ms < min_wait_ms` returns InvalidArgument. + * Benefits ODBC too. */ +KernelStatusCode kernel_session_config_set_retry_config(KernelSessionConfig* config, + uint64_t min_wait_ms, uint64_t max_wait_ms, + uint32_t max_retries, + uint64_t overall_timeout_ms); + +/* Whether transaction control is ignored (no-oped). `ignore = true` (the + * default) gives IgnoreTransactions=1 semantics: commit / rollback / + * autocommit-off become silent no-ops. `ignore = false` enables real + * multi-statement transactions (Private Preview; requires catalog-managed + * tables server-side). Shared by all drivers (ODBC / Python / Go / Node). */ +KernelStatusCode kernel_session_config_set_ignore_transactions(KernelSessionConfig* config, + bool ignore); + +/* Configure the on-disk OAuth token cache for U2M (browser) auth. Mirrors + * the EnableTokenCache / TokenCachePassPhrase pair exposed by the reference + * ODBC driver and JDBC. + * + * `enabled = true` (the default) persists the refresh token under + * ~/.config/databricks-sql-kernel/oauth/ so the user is not sent through + * the browser on every connection. The file is ALWAYS encrypted + * (AES-256-CBC, PBKDF2-HMAC-SHA256) — there is no plaintext mode. A + * non-NULL `passphrase` keys it; NULL or blank falls back to a + * machine-local derived key, matching the reference driver, which caches + * with an empty TokenCachePassPhrase. Supplying a passphrase is stronger: + * the derived key defends against the cache file being read elsewhere (a + * backup, an image layer, a support bundle), not against an attacker who + * can already run code as this user. + * + * `enabled = false` disables DISK PERSISTENCE ONLY; tokens are still + * reused and refreshed in memory for the session's lifetime, so this does + * not re-trigger the browser flow on every refresh. Use it when the + * process must not write credentials to disk at all. `passphrase` is + * ignored in that case. + * + * Only U2M auth touches the cache; PAT and M2M ignore this setting. + * + * The on-disk format is wire-compatible with the JDBC driver's + * EncryptedFileTokenCache (given the same passphrase). */ +KernelStatusCode kernel_session_config_set_u2m_token_cache_config(KernelSessionConfig* config, + bool enabled, + const char* passphrase); + +/* Set one CLIENT-SIDE result knob by name (key = value). + * + * Counterpart to kernel_session_config_set_session_conf: that one carries + * SERVER session confs (ANSI_MODE, TIMEZONE, ...) to the SEA wire; this one + * sets CLIENT-SIDE knobs that tune how the kernel fetches, buffers, and + * post-processes results and are NEVER sent to the server. The two channels + * are deliberately separate (conflating them is what made a client flag + * masquerade as a rejected server conf). + * + * Key/value (rather than one typed setter per knob) so the signature never + * changes as new result knobs are added, and so a host that already has DSN + * / connection-string attributes can forward them in a loop. Unknown keys + * and unparseable values are REJECTED (InvalidArgument + stored error), not + * silently ignored. + * + * Recognised keys (case-insensitive): + * - "cloudfetch_enabled" = "true"|"false" — false serves every result + * inline (SEA disposition = INLINE), mirroring JDBC EnableQueryResultDownload=0. + * - "cloudfetch_link_prefetch_window" = positive integer. + * - "cloudfetch_max_chunks_in_memory" = positive integer (bounds peak RSS; + * values above the kernel ceiling of 256 are clamped with a warning). + * - "inline_max_chunks_in_memory" = positive integer (inline-Arrow prefetch + * window, used when cloudfetch_enabled = false). + * - "complex_types_as_json" = "true"|"false" — render ARRAY / MAP / STRUCT / + * VARIANT / GEOMETRY / GEOGRAPHY as JSON text (PySQL + * _use_arrow_native_complex_types=False, JDBC complexDatatypeSupport=false). + * - "intervals_as_string" = "true"|"false" — render INTERVAL / DURATION as + * canonical Databricks text (for pyarrow-Python bindings). + * + * A server session-conf key passed here (e.g. "ANSI_MODE") is rejected — + * use kernel_session_config_set_session_conf for those. */ +KernelStatusCode kernel_session_config_set_client_conf(KernelSessionConfig* config, + const char* key, const char* value); + +/* ─── Session lifecycle ───────────────────────────────────────────────*/ + +/* + * Open a session. CONSUMES `config` on both success and failure — do not + * use or free `config` afterwards. On success, `*out` holds a session + * handle released with `kernel_session_close`. + */ +KernelStatusCode kernel_session_open(KernelSessionConfig* config, kernel_session_t** out); +KernelStatusCode kernel_session_close(kernel_session_t* session); + +/* Report whether a session is still OPEN (its delete has not been + * initiated). Writes true/false to `*out`. LOCAL check only — no server + * round-trip, so it does NOT detect a server idle-timeout or dropped + * link; it reports only whether the handle has been closed/superseded. + * A host backing SQL_ATTR_CONNECTION_DEAD should read `true` as + * "not known-dead", not a positive liveness guarantee. No ownership. */ +KernelStatusCode kernel_session_is_open(const kernel_session_t* session, bool* out); + +/* Construct a new mutable statement bound to this session. */ +KernelStatusCode kernel_session_new_statement(kernel_session_t* session, + kernel_statement_t** out); + +/* ─── Transaction control ───────────────────────────────────────────── + * + * Transaction control is expressed as session-scoped SQL and shared by all + * drivers. When the session was opened with ignore_transactions = true (the + * default), these are silent no-ops returning Success. When false (opt-in), + * they issue SET AUTOCOMMIT / COMMIT / ROLLBACK on the session; real + * multi-statement transactions require catalog-managed tables server-side. + * The three mutating calls run SQL and MUST be invoked from a native + * (non-async-runtime) thread. */ + +/* Set autocommit mode. false begins an explicit transaction; true returns to + * auto-commit. Issues `SET AUTOCOMMIT = TRUE|FALSE`. No-op when transactions + * are ignored. */ +KernelStatusCode kernel_session_set_autocommit(kernel_session_t* session, bool enabled); + +/* Read the current autocommit mode into `*out`. Local read — no round-trip. */ +KernelStatusCode kernel_session_get_autocommit(const kernel_session_t* session, bool* out); + +/* Commit the current transaction (issues `COMMIT`). No-op when ignored. */ +KernelStatusCode kernel_session_commit(kernel_session_t* session); + +/* Roll back the current transaction (issues `ROLLBACK`). No-op when ignored. */ +KernelStatusCode kernel_session_rollback(kernel_session_t* session); + +/* ─── Statement ───────────────────────────────────────────────────────*/ + +/* Set the statement's SQL text, replacing any previously set SQL. Also clears + * every previously-bound parameter (raw and the typed positional binders below), + * so a reused statement handle starts each query with a fresh parameter set. */ +KernelStatusCode kernel_statement_set_sql(kernel_statement_t* stmt, const char* sql); + +/* Typed positional parameter binding (1-based ordinal); the kernel infers the + * SEA wire type from the C type. These are the "typed params" the + * kernel_statement_bind_parameter contract refers to: within one statement they + * cannot be combined with kernel_statement_bind_parameter (raw binds) — pick one + * binding style. The conflict is reported at kernel_statement_execute, not here. */ +KernelStatusCode kernel_statement_bind_null(kernel_statement_t* stmt, size_t ordinal); +KernelStatusCode kernel_statement_bind_int64(kernel_statement_t* stmt, size_t ordinal, + int64_t value); +KernelStatusCode kernel_statement_bind_double(kernel_statement_t* stmt, size_t ordinal, + double value); +KernelStatusCode kernel_statement_bind_bool(kernel_statement_t* stmt, size_t ordinal, bool value); +KernelStatusCode kernel_statement_bind_string(kernel_statement_t* stmt, size_t ordinal, + const char* value); + +/* Bind a pre-marshalled parameter: host-supplied Databricks type name + + * stringified value, positional or named. For a host that already stringifies + * values and knows the SQL type (e.g. the Go driver). name NULL/empty -> + * positional (ordinal assigned at execute in push order); non-empty -> named. + * value NULL -> SQL NULL (pass sql_type "VOID"); sql_type must be non-NULL and + * non-empty (an empty type is rejected here with InvalidArgument). + * + * Lifecycle: raw binds ACCUMULATE (each call appends a marker in push order); + * they carry no ordinal to overwrite by. kernel_statement_set_sql is the sole + * reset point — it clears ALL prior binds (raw AND the typed positional binders + * below) so each set_sql begins a fresh parameter set. kernel_statement_execute + * does NOT clear them, so a host reusing one statement handle across queries must + * call set_sql before re-binding, or the prior query's markers persist and the + * server sees the wrong parameter count. + * + * Two mutual-exclusion rules hold within one statement, both enforced at + * kernel_statement_execute (this call always returns Success on a valid + * arg — the conflict surfaces later as InvalidArgument from execute): + * - positional and named raw params cannot be mixed; + * - raw params cannot be mixed with the typed positional binders + * (kernel_statement_bind_null/int64/double/bool/string above). */ +KernelStatusCode kernel_statement_bind_parameter(kernel_statement_t* stmt, + const char* name, + const char* sql_type, + const char* value); + +/* + * Wait-for-result execution. On success, `*out` holds an executed handle + * released with `kernel_executed_statement_close`. + */ +KernelStatusCode kernel_statement_execute(kernel_statement_t* stmt, + kernel_executed_statement_t** out); + +/* + * Submit-and-return (async). DEFERRED in v0: this always returns + * `KernelStatusCode_InvalidArgument` (with an explanatory last error) and + * writes nothing to `*out`. Use the synchronous `kernel_statement_execute` + * path. Declared so the symbol/contract is visible; wired when an ODBC / + * Go consumer needs caller-driven polling. + */ +KernelStatusCode kernel_statement_submit(kernel_statement_t* stmt, + kernel_executed_async_statement_t** out); + +/* + * Free the statement lifecycle owner. Does NOT free executed-handle + * boxes — each executed handle is a separate allocation that you must + * release with `kernel_executed_statement_close`. + * + * ORDERING: close the statement LAST. Executed handles produced by this + * statement (and any result stream borrowed from them) share a validity + * flag with the statement; closing the statement invalidates them, after + * which `kernel_executed_statement_get_result_stream`, + * `kernel_executed_statement_num_modified_rows`, and + * `kernel_result_stream_next_batch` on those handles return + * `KernelStatusCode_InvalidStatementHandle` (a defined error, never UB). + * Drain / close the result stream, then the executed handle(s), then the + * statement. The statement must outlive every handle it produced. + */ +KernelStatusCode kernel_statement_close(kernel_statement_t* stmt); + +/* ─── Sync-execute cancellation (detached canceller) ──────────────────*/ + +/* + * Create a detached canceller for `stmt`. Obtain it BEFORE + * `kernel_statement_execute`, which blocks the calling thread for the + * whole query — the statement cannot be cancelled through its own handle + * mid-execute (that would alias the mutable borrow). + * + * UNDEFINED BEHAVIOR: this call itself borrows `stmt` mutably. Calling it + * while a `kernel_statement_execute` on the same `stmt` is in flight on + * another thread aliases that execute's own mutable borrow — undefined + * behavior (memory corruption), not a returned error. Create the canceller on + * the same thread that owns `stmt`, before dispatching execute. The natural + * "spawn the blocking execute, then lazily create a canceller when the host + * decides to cancel" pattern is exactly this misuse — do not do it; create the + * canceller first, hand it to the cancelling thread, then dispatch execute. + * Only the RETURNED handle is concurrency-safe: it holds only cloned Arcs, so + * once you have it, cancelling from another thread while execute runs is fine. + * + * The canceller holds its own session reference, so it must be freed + * regardless of whether the statement or session is closed. On success `*out` + * holds a canceller released with `kernel_statement_canceller_free`. + */ +KernelStatusCode kernel_statement_canceller_new( + kernel_statement_t* stmt, kernel_statement_canceller_t** out); + +/* + * Cancel the originating statement, server-side. Statement-scoped: targets + * the server statement id currently observed for this statement. Idempotent + * and safe to call concurrently with `kernel_statement_execute`. + * + * On Success, `*dispatched` reports whether a cancel RPC was POSTed: + * true if a server statement id had been observed and the RPC was sent for it, + * false if no id has been observed yet — nothing has executed, or the query is + * still in its initial round-trip before the server issued an id. Both cases + * return Success; `*dispatched` is how a host tells a dispatched cancel from a + * no-op. `*dispatched` is written on every non-NULL path (false before the RPC, + * then the RPC outcome on Success), so it holds a defined value once this + * returns; a NULL `dispatched` is ignored. + * + * `dispatched == true` means only that a cancel was POSTed for the currently + * observed statement id — NOT that a running query was interrupted. The id slot + * retains the last observed id after a query finishes, so a cancel fired + * post-terminal (or, in the narrow re-execute handoff, against a superseded id) + * still POSTs and still reports true; the server treats a cancel of an + * already-finished statement as its own no-op. + * + * This call blocks on the cancel RPC, which on a retryable server error retries + * with backoff and can run far longer than one round-trip — bounded by the SEA + * retry budget (the overall_timeout / max_retries defaults in the kernel's retry + * config), which can reach minutes during a partial server outage. There is no + * cancel-of-cancel or timeout parameter, so a host wiring this into a Ctrl-C / + * query-timeout handler should call it from an abandonable/joinable thread rather + * than the app's main thread. + */ +KernelStatusCode kernel_statement_canceller_cancel( + kernel_statement_canceller_t* canceller, bool* dispatched); + +/* + * Free a canceller handle. Does not touch the originating statement. The + * canceller is single-owner: freeing it while another thread is inside + * kernel_statement_canceller_cancel on this handle is UNDEFINED BEHAVIOR + * (use-after-free), not merely wasteful. That call blocks on the cancel RPC, + * which on a retryable server error retries with backoff and can stay in flight + * far longer than one round-trip (see _cancel), so join or otherwise quiesce any + * cancelling thread BEFORE freeing. + */ +KernelStatusCode kernel_statement_canceller_free( + kernel_statement_canceller_t* canceller); + +/* ─── Executed statement (sync) ───────────────────────────────────────*/ + +/* + * Obtain a result-stream handle that BORROWS `exec`. The stream is valid + * only as long as `exec` is: close the stream + * (`kernel_result_stream_close`) before closing `exec`, and keep the + * parent statement open until the stream is drained/closed (see + * `kernel_statement_close`). At most one stream per executed handle; a + * second call returns `KernelStatusCode_InvalidArgument`. Returns + * `KernelStatusCode_InvalidStatementHandle` if the parent statement was + * re-executed or closed. + */ +KernelStatusCode kernel_executed_statement_get_result_stream(kernel_executed_statement_t* exec, + kernel_result_stream_t** out); + +/* + * Rows modified by a DML statement, or -1 if not applicable / unknown. + * A -1 with NO stored last error means "not applicable"; a -1 after a + * stored `InvalidStatementHandle` means the parent statement was + * re-executed or closed (call `kernel_get_last_error` to distinguish). + */ +int64_t kernel_executed_statement_num_modified_rows(const kernel_executed_statement_t* exec); + +/* + * Server statement (query) id of a successfully executed statement, as a + * NUL-terminated C string — the success-path counterpart to the query id on + * KernelError. Used by a host driver for EXECUTE_STATEMENT telemetry and + * query-history correlation. The pointer is BORROWED from the executed handle, + * valid until kernel_executed_statement_close; do NOT free it, and copy it out + * to outlive the handle. Returns NULL on a null / invalidated handle (with a + * stored last error); a live handle from a successful execute never returns NULL. + */ +const char* kernel_executed_statement_query_id(const kernel_executed_statement_t* exec); + +KernelStatusCode kernel_executed_statement_close(kernel_executed_statement_t* exec); + +/* ─── Result stream ─────────────────────────────────────────────────── + * + * The schema is exported once; each `next_batch` call exports one Arrow + * array. End-of-stream is signalled by a RELEASED array (its `release` + * callback is NULL) returned with `KernelStatusCode_Success`. + */ +KernelStatusCode kernel_result_stream_get_schema(kernel_result_stream_t* stream, + struct ArrowSchema* out); +KernelStatusCode kernel_result_stream_next_batch(kernel_result_stream_t* stream, + struct ArrowArray* out_array, + struct ArrowSchema* out_schema); +KernelStatusCode kernel_result_stream_close(kernel_result_stream_t* stream); + +/* ─── Metadata ──────────────────────────────────────────────────────── + * + * Each returns a self-contained result stream (released via + * `kernel_result_stream_close`) carrying the server-shaped + * (JDBC-canonical) columns. Pattern args accept SQL LIKE wildcards; + * identifier args are exact. + * + * NULL string args mean "unfiltered" for the list_* calls (catalogs / + * schemas / tables / columns). The key/constraint calls differ: + * `kernel_metadata_primary_keys` requires a non-NULL catalog, schema, AND + * table (Databricks SHOW KEYS must target a fully-qualified table), while + * `kernel_metadata_foreign_keys` keys off the foreign (referencing) table + * — a NULL foreign_table returns an empty result (0 rows, no wire call), + * not an error. Passing NULL for a required identifier returns + * `KernelStatusCode_InvalidArgument` with an explanatory last error (see + * each function's comment). + */ +KernelStatusCode kernel_metadata_list_catalogs(kernel_session_t* session, + kernel_result_stream_t** out); +KernelStatusCode kernel_metadata_list_schemas(kernel_session_t* session, const char* catalog, + const char* schema_pattern, + kernel_result_stream_t** out); +KernelStatusCode kernel_metadata_list_tables(kernel_session_t* session, const char* catalog, + const char* schema_pattern, const char* table_pattern, + const char* table_types_csv, + kernel_result_stream_t** out); +KernelStatusCode kernel_metadata_list_columns(kernel_session_t* session, const char* catalog, + const char* schema_pattern, const char* table_pattern, + const char* column_pattern, + kernel_result_stream_t** out); +/* `catalog`, `schema`, and `table` are all REQUIRED (non-NULL): the + * underlying SHOW KEYS targets a fully-qualified table. NULL for any of + * them returns `KernelStatusCode_InvalidArgument`. */ +KernelStatusCode kernel_metadata_primary_keys(kernel_session_t* session, const char* catalog, + const char* schema, const char* table, + kernel_result_stream_t** out); +/* Keys off the foreign (referencing) table. A NULL foreign_table means + * "nothing to look up" and returns an empty result (0 rows, no wire + * call) — this is the ODBC "primary-keys only" SQLForeignKeys form. When + * a foreign_table IS given, foreign_catalog and foreign_schema are then + * required (they scope SHOW FOREIGN KEYS) and a NULL there returns + * `KernelStatusCode_InvalidArgument`. The parent_* (referenced) triple + * is optional and filters the referenced side. */ +KernelStatusCode kernel_metadata_foreign_keys(kernel_session_t* session, const char* parent_catalog, + const char* parent_schema, const char* parent_table, + const char* foreign_catalog, + const char* foreign_schema, const char* foreign_table, + kernel_result_stream_t** out); +/* List the supported table types (`TABLE`, `VIEW`, `SYSTEM TABLE`, …). + * No filters and no wire call — the set is derived from the session's + * transport. Backs ODBC `SQLTables(table_type = SQL_ALL_TABLE_TYPES)`. + * The stream carries a single `TABLE_TYPE` column (JDBC getTableTypes). */ +KernelStatusCode kernel_metadata_list_table_types(kernel_session_t* session, + kernel_result_stream_t** out); +/* List stored procedures. `catalog` is an exact identifier (NULL → + * cross-catalog via system.information_schema); `schema_pattern` and + * `procedure_pattern` are SQL LIKE patterns (NULL → unfiltered). Backs + * ODBC `SQLProcedures` / JDBC `getProcedures`; the stream carries the + * JDBC getProcedures columns. */ +KernelStatusCode kernel_metadata_procedures(kernel_session_t* session, const char* catalog, + const char* schema_pattern, + const char* procedure_pattern, + kernel_result_stream_t** out); +/* List functions. Same argument semantics as + * `kernel_metadata_procedures`. Backs JDBC `getFunctions`; the stream + * carries the JDBC getFunctions columns. */ +KernelStatusCode kernel_metadata_functions(kernel_session_t* session, const char* catalog, + const char* schema_pattern, + const char* function_pattern, + kernel_result_stream_t** out); +/* List stored-procedure parameter / return columns (procedures only — + * function arguments are excluded). `catalog` is an exact identifier + * (NULL → cross-catalog); `schema_pattern` / `procedure_pattern` / + * `column_pattern` are SQL LIKE patterns (NULL → unfiltered). Backs ODBC + * `SQLProcedureColumns` / JDBC `getProcedureColumns`; the stream carries + * the JDBC getProcedureColumns columns. */ +KernelStatusCode kernel_metadata_procedure_columns(kernel_session_t* session, const char* catalog, + const char* schema_pattern, + const char* procedure_pattern, + const char* column_pattern, + kernel_result_stream_t** out); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* DATABRICKS_KERNEL_H */ diff --git a/internal/backend/kernel/kernellib/darwin_arm64/go.mod b/internal/backend/kernel/kernellib/darwin_arm64/go.mod new file mode 100644 index 00000000..9bf24520 --- /dev/null +++ b/internal/backend/kernel/kernellib/darwin_arm64/go.mod @@ -0,0 +1,10 @@ +// Nested per-platform module carrying the prebuilt darwin/arm64 kernel static +// library. It is its OWN Go module (separate go.mod) so that `go get`/`go build` +// for a given platform downloads only THAT platform's heavy archive — Go fetches +// a module's zip only when the build compiles a file from it, and link.go is +// guarded to darwin/arm64 + the databricks_kernel tag. See the repo README +// ("Cloning" / "Kernel (SEA) backend") for the full rationale and the +// `git clone --filter=blob:none` guidance for light clones. +module github.com/databricks/databricks-sql-go/internal/backend/kernel/kernellib/darwin_arm64 + +go 1.25.0 diff --git a/internal/backend/kernel/kernellib/darwin_arm64/libdatabricks_sql_kernel.a b/internal/backend/kernel/kernellib/darwin_arm64/libdatabricks_sql_kernel.a new file mode 100644 index 00000000..cd42e6ec Binary files /dev/null and b/internal/backend/kernel/kernellib/darwin_arm64/libdatabricks_sql_kernel.a differ diff --git a/internal/backend/kernel/kernellib/darwin_arm64/link.go b/internal/backend/kernel/kernellib/darwin_arm64/link.go new file mode 100644 index 00000000..8d1f4674 --- /dev/null +++ b/internal/backend/kernel/kernellib/darwin_arm64/link.go @@ -0,0 +1,28 @@ +//go:build cgo && databricks_kernel && darwin && arm64 + +// Package kernellibdarwinarm64 exists only to carry the darwin/arm64 kernel +// static archive (libdatabricks_sql_kernel.a) and the cgo link directive that +// pulls it into the final binary. It is a nested per-platform module (its own +// go.mod), imported for its link side-effect by the kernel package's +// cgo_darwin.go shim under a matching build constraint. Nothing here is called +// at run time — the exported symbols the kernel package needs come from the .a +// via the LDFLAGS below. +// +// Why a separate module: committing the ~62 MB archive here (rather than in the +// kernel package's own module) means a `go get`/`go build` for a NON-darwin +// platform never downloads this archive — Go only fetches a module's zip when +// the build compiles one of its files, and this file is constrained to +// darwin/arm64 + `databricks_kernel`. A pure-Go Thrift build (no tag) or a +// linux build pulls nothing from here. See the repo README. +// +// Darwin link specifics (same as the previous non-nested cgo_darwin.go): +// - Apple's ld64 does NOT accept the GNU `-l:.a` extension, so the +// archive is passed as a positional input by absolute ${SRCDIR} path. +// - -lc++ (not -lstdc++) is the macOS C++ runtime; @loader_path keeps any +// dynamic reference resolvable relative to the built binary. +package kernellibdarwinarm64 + +/* +#cgo LDFLAGS: ${SRCDIR}/libdatabricks_sql_kernel.a -lc++ -lm -Wl,-rpath,@loader_path +*/ +import "C"