Skip to content

Releases: gizmodata/hardcharger

python-v2.0.1

Choose a tag to compare

@github-actions github-actions released this 22 Oct 13:50

Documentation

  • Add a total PyPI downloads badge to the package and repository READMEs.
  • Use absolute logo image URLs in the repository README.
  • This Python package still downloads Hardcharger server 2.0.0 by default.

v2.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Oct 12:00

The first general release of Hardcharger: a SQL server on DuckDB 2.0.0 that speaks Warp SQL
1.0.1 over plain HTTP, with access control (users, roles, grants, catalog access, scoped
tokens, sign-in through an identity provider or a client certificate), a web UI embedded in
the server (query editor, results, schema browser, uploads, sessions, API page), the
hardcharger command-line client, the Python package, Docker images, builds for Linux, macOS
and Windows, and the app for iPhone and iPad. This section lists what changed since
2.0.0-rc.1; the rc.1 section below has everything since 0.3.0.

Added

  • Docker: multi-architecture :2.0.0-adbc / :latest-adbc variants on GHCR and Docker
    Hub, with GizmoSQL's ten dbc-managed drivers and the native GizmoSQL, Oracle, Db2 and
    Quack drivers, plus the Hardcharger ADBC driver. System-wide manifests, stable library
    paths, version/digest inventories and notices are included. The separate ADBC images
    workflow checks every driver loads and queries Hardcharger by manifest on both native
    architectures before publishing the multi-architecture tags.

  • Server: a startup banner in a text log: the "Hardcharger" wordmark (plain ASCII), the
    server's, Warp SQL's and DuckDB's versions, the web UI's address (or the Warp SQL endpoint's
    without --web-ui) and the documentation's, within 80 columns. Never in a JSON log.
    --no-banner, --banner=false, HARDCHARGER_NO_BANNER=true, HARDCHARGER_BANNER=false or
    banner: false leaves it out; every boolean option now takes --no-NAME. The Warp SQL
    version is the specification's (info.version of its OpenAPI document, read at configure
    time). Tests: test_startup_log.py (the art byte for byte against
    tests/fixtures/banner-art.txt, each way to turn it off, JSON), startup_banner_test.cc.

  • Server: log_emoji (--log-emoji, HARDCHARGER_LOG_EMOJI): auto (the default) marks
    text log lines' levels with emoji only when the log goes to a terminal and the locale is
    UTF-8 (on Windows, a UTF-8 console code page), always, or never; JSON lines never have
    them. ✅ marks an outcome (ready, stopped), ⚠️ a warning, ❌ an error, 💥 the error that stops
    the server; info and debug lines get none. Tests at a pty (auto with a UTF-8 locale, with
    C, never, JSON) and in a file.

  • Client: --no-tls connects without TLS (plain HTTP), as --tls=false does; --tls false
    (with a space) works too, as do true/false, 1/0, yes/no and on/off after
    --tls and --tls=. Only those words are taken after a space; anything else stays the
    positional URI, and the error then names it (--uri cannot be combined with --tls (the argument 'maybe' is read as the URI)). --help starts with examples, the TLS-off one among
    them. In the shell, .connect HOST PORT USERNAME [PASSWORD] takes --no-tls, --tls,
    --tls=BOOL or --tls BOOL (a URI says it itself and refuses them), and .help and the
    "not connected" hint say so. Tests: test_client_cli.py (each spelling against a plain
    server, and each spelling of on as its twin).

  • Upload data in the web UI: a CSV, TSV, Excel (.xlsx, .xls, .xlsb), Parquet, JSON lines /
    JSON array or Arrow IPC file into a new or existing table, from the query toolbar's Upload
    data
    button, the schema browser (its toolbar, and Upload data… on a catalog's or
    schema's menu), or a file dropped anywhere on the page. The dialog follows GizmoData Cloud's
    SQL editor (the same fields, labels, modes, table-name derivation and result message): File,
    Catalog, Schema (or a new one), Table, If the table… (create, which fails if it exists;
    append, with the file's columns matched to the table's by name and NULL for the ones it
    lacks; replace, the table's columns and rows), Header row, and the CSV delimiter or the Excel
    worksheet (a picker of the workbook's sheets). Preview & column types, folded away until
    opened, shows the first 100 rows as they will load, with each column's inferred type to
    change (VARCHAR, BIGINT, DOUBLE, DECIMAL(p,s), BOOLEAN, DATE, TIMESTAMP, TIMESTAMPTZ, TIME),
    rename or skip; a value that wouldn't fit shows in red. An append warns where the file's
    types differ from the table's, and stops before it starts when the file has a column the
    table hasn't. The file is read and converted to Arrow in the browser, in a Web Worker (a
    large or malformed file can't freeze the page), and sent over Warp SQL's bulk ingest in
    pieces of about 8 MB, one request each, in one transaction of the upload's own session (as
    the tab's Run as role): the server never stores the raw file, a progress bar counts
    rows and bytes, Cancel upload rolls everything back, and a failed upload leaves the
    table as it was. CSV, JSON lines, Parquet (row group by row group, with parquet-wasm) and
    Arrow streams are read as they upload, at any size; Excel workbooks and JSON arrays are read
    whole, up to 100 MB. CSV: the delimiter (, ; tab |, or one typed, up to 4 characters),
    quotes, line breaks inside quotes and the header row are detected; UTF-8, UTF-16 with a byte
    order mark, and otherwise Windows-1252 with a note. A whole number with a leading zero
    (00123) stays text. Excel: dates and times from the cells' number formats and the
    workbook's own date system (1900 or 1904), a formula's saved value (none is calculated),
    numbers, booleans, text (a "number stored as text" stays text), error cells as NULL, empty
    rows left out and merged ranges' values in their first cell, each said in a note. A value
    that doesn't convert fails the upload with its line or cell and column (Cell C12, column "amount": "n/a" is not a whole number); the server's permission errors (CREATE, INSERT,
    row policies) are shown as it words them. The result says the rows, mode, format and time,
    lists the table's columns, refreshes the schema browser and autocomplete, and Query this
    table
    opens a tab with SELECT * … LIMIT 100. Excel is read with SheetJS Community
    Edition 0.20.3 (Apache-2.0), vendored from SheetJS's own CDN at a pinned SHA-256
    (ui/vendor/sheetjs.json, checked by npm run vendor:verify), not npm's abandoned xlsx
    0.18.5 (CVE-2023-30533, CVE-2024-22363): it reads .xlsx, .xls and .xlsb without running
    macros or formulas and needs no change to the UI's Content-Security-Policy (no eval, no
    workers of its own, nothing fetched). It and the converters load only when a file is read,
    in the upload worker (SheetJS about 490 KB, the worker 230 KB, the dialog 33 KB; the main
    bundle grows by about 3 KB).

  • default_catalog and default_schema: where every new session starts, unless the client's
    own catalog / schema session options say otherwise. With neither set, an init SQL that
    ends in USE x.y sets it, as in GizmoSQL 1.x (on rc.1 sessions ignored it and started in
    the database's catalog), and the server logs that it did. Both are checked after the init
    SQL: a catalog that isn't attached, a missing schema or one of the server's own catalogs
    stops the server (exit 1). Under access control a session moves there only if its user's own
    USE could; otherwise it starts where it would have, and a tenant's default catalog wins.
    hardcharger_server_info() shows the administrator the effective defaults.

  • rbac_store_timeout_seconds (default 10): every access-control store round trip is
    bounded. With PostgreSQL paused, a CREATE TABLE waited 152 s for the store; it now fails
    with access control store unavailable: timed out after 10 s, and what it was doing is
    rolled back once the store answers. PostgreSQL connection strings get libpq's
    connect_timeout, TCP keepalives, tcp_user_timeout and a statement_timeout, each
    unless they set it.

  • Store outages are visible: a warning when the store stops answering (then every 30 s,
    with the snapshot's age), a line when it answers again, and the metrics
    hardcharger_rbac_store_up and hardcharger_rbac_snapshot_age_seconds.
    rbac_max_snapshot_age_seconds (default 0, no limit) refuses every principal but the
    administrator once the store has not answered for longer (fail closed).

  • The access-control store belongs to one cluster: it records the first cluster_id that
    opens it and refuses a server of another cluster (the access control store belongs to cluster ...); rbac_store_shared: true shares users, roles and grants between clusters
    on purpose. The store's schema version is checked too: a store a later version upgraded
    is refused.

  • The engine, host and process gauges GizmoData Cloud's Engine Metrics page reads from
    GizmoSQL 1.x, under hardcharger_* names with 1.x's meanings and units, in /metrics and
    hardcharger_metrics(): hardcharger_duckdb_threads, hardcharger_duckdb_temp_storage_bytes
    (from duckdb_memory()), hardcharger_duckdb_spill_files and hardcharger_duckdb_spill_bytes
    (live files in duckdb_temporary_files()), hardcharger_database_file_bytes and
    hardcharger_wal_bytes (the main database file DuckDB opened and its .wal; attached catalogs
    don't count), hardcharger_health_check_duration_seconds,
    hardcharger_tls_certificate_expiry_seconds (the serving certificate's notAfter as Unix time,
    read again whenever a client CRL reload rebuilds the TLS context),
    hardcharger_metrics_collection_duration_seconds, hardcharger_host_memory_bytes,
    hardcharger_host_cpus, hardcharger_cgroup_memory_limit_bytes and
    hardcharger_cgroup_cpu_quota_cores (cgroup v2, else v1, read on every collection so an
    in-place pod resize shows), and the standard process_start_time_seconds, process_open_fds
    and process_threads (Linux, macOS and Windows; Windows has no process_open_fds). As in 1.x,
    a measurement that can't be taken is left out rather than reported as 0: no file sizes for an
    in-memory or remote database, no certificate expiry without TLS, no cgroup gauges wi...

Read more

python-v2.0.0

Choose a tag to compare

@github-actions github-actions released this 21 Oct 12:13

Changed

  • The package's version now tracks the server's: 2.0.0 installs Hardcharger 2.0.0 (the Python
    package was 0.1.0 while the server was pre-release). Both the runtime __version__ and
    the default downloaded server are 2.0.0; the [adbc] extra requires the 2.0.0 driver.
    PyPI metadata identifies the package as stable, and the download documentation uses
    anonymous access with an optional token to raise the API rate limit.

  • The server version to download (SERVER_VERSION, HARDCHARGER_VERSION, ensure_server(version=...))
    may be a release candidate in the server's form (2.0.0-rc.1, tag v2.0.0-rc.1) or in
    PEP 440's (2.0.0rc1, how this package spells its own candidates); both fetch v2.0.0-rc.1.

  • A python-v* GitHub Release is never made the repository's latest release, which stays the
    server's (what releases/latest and the installer resolve), and a PEP 440 pre-release
    (python-v2.0.0rc1) is marked a pre-release.

  • The README's banner is the Hardcharger™ bull logo (the horizontal mascot lockup).

Documentation

  • README, License: the package itself has no third-party code; the server it downloads carries
    THIRD_PARTY_NOTICES.md and THIRD_PARTY_NOTICES-web-ui.txt, which the install keeps beside
    bin/ (a unit test checks they arrive), and prints them with --licenses.

Fixed

  • CI (.github/workflows/workflow.yml) failed on every push to main: the client job installs
    the [adbc] extra from PyPI, where adbc-driver-hardcharger isn't published yet. It runs on
    python-v* tags only now, where it still gates the publish.

v2.0.0-rc.1

v2.0.0-rc.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 20 Oct 05:43

Added

  • Third-party notices in everything that ships. THIRD_PARTY_NOTICES.md lists the 49
    third-party components the binaries link statically, each with its version, licence, the
    binaries and platforms it is in, and its full licence and NOTICE texts: DuckDB, the 31
    libraries and data sets it vendors (Mbed TLS, RE2, Thrift, the Unicode data, jemalloc on
    Linux, the TPC-H generator, ...), the httpfs, DuckLake and PostgreSQL extensions, Arrow and
    what it bundles (zstd, LZ4, uriparser, xsimd), Boost, OpenSSL, jwt-cpp, yaml-cpp, zlib,
    replxx, CRoaring, libpq and libcurl. It is generated
    (server/scripts/third-party-notices.py) from server/third-party-notices/: each
    component's reviewed version and its texts, fetched from the source at that version. CI
    (--check, in every build job) fails when a recipe pins another version than the one the
    notices were made for, when the build declares a dependency (FetchContent, a DuckDB
    extension, an extension library, a vcpkg package) no component covers, or when the file is
    stale. The web UI's build writes third-party-notices.txt (served at
    /ui/third-party-notices.txt, linked from About as Open-source licences) from the
    packages actually in the bundle, lazy chunks and the editor's worker included (15 today:
    React, Monaco, Swagger UI, Apache Arrow, parquet-wasm, ...), with every licence and notice
    file each ships (Monaco's ThirdPartyNotices.txt, Swagger UI's NOTICE and its bundle's
    swagger-ui-es-bundle.js.LICENSE.txt), and fails when a bundled package declares no
    licence, has no licence file, or has a licence outside the allow-list (MIT, ISC, BSD-2/3,
    Apache-2.0, 0BSD, BlueOak, CC0, Unlicense, Python-2.0, MPL-2.0; Inter's OFL-1.1 by name).
    The tarballs, the Windows zips (whose THIRD_PARTY_NOTICES.md ends with the versions and
    copyright files of the libraries the Windows build links from vcpkg), the MSIs (installed
    under Program Files\Hardcharger) and the container image (/opt/hardcharger/ and
    /usr/share/doc/hardcharger/) carry THIRD_PARTY_NOTICES.md and
    THIRD_PARTY_NOTICES-web-ui.txt, and hardcharger-server --licenses and
    hardcharger --licenses print them from the binary itself (the server's followed by its
    embedded UI's). Tests: test_server_cli.py, test_client_cli.py, the unit test of the UI
    file reader, third-party-notices.test.ts (the allow-list, module-to-package mapping, the
    file's contents and its failures), about.spec.ts (Chromium and WebKit), the tarball smoke
    test and the image smoke test.

  • Web UI, API page (/ui/api, next to Query and Sessions): the server's Warp SQL API in
    Swagger UI (swagger-ui-dist 5.33.3, in the build, loaded only when the page is opened:
    +1.7 MB, +435 KiB gzipped in the binary), from the OpenAPI 3.1 document the server now serves
    at GET /warp/v1/openapi.json (no sign-in; discovery lists capability openapi and
    links.openapi). Every request goes to the server's own origin (no server switcher, no
    Authorize dialog); Try it out carries the signed-in user's token, added to each request
    to this origin as it leaves and never to another origin, stored, or shown in the Curl box.
    Arrow answers are offered as a download (result.arrows); the dark theme applies; a server
    without the document gets a note. No change to the Content-Security-Policy. e2e:
    api.spec.ts in Chromium and WebKit; unit: apiDocs.test.ts.

  • Web UI, the rest of the editor's behavior: How the editor works (the ? in the tab bar):
    sessions, with this server's idle timeout (the session document's idle_timeout_s),
    transactions, statements, results, and what stays in this browser; the Saved queries panel
    says they're kept in this browser. On a screen 760 px wide or less the side panel sits above
    the editor at full width (it was hidden) and the page scrolls, with a note, hidden for good
    by its ×, that a larger screen is easier. After a save, the cursor goes back to the editor
    (it was left on the page, so the next shortcut went nowhere). e2e: idle.spec.ts against a
    second server whose sessions end after 2 s idle (a new session takes over and says so; with
    a transaction open the run is refused and nothing of it executes; the schema browser carries
    on), layout.spec.ts, and every editor shortcut (Run, Run all, Save, close) acting on the
    shown tab rather than the tab opened last (tabs.spec.ts).

  • Web UI: the README's screenshots show the editor's tabs, a script's result tabs and an open
    transaction; tests for a script's 64 MiB cap (unit) and for a browser that blocks IndexedDB
    (saved-blocked.spec.ts: the panel says it can't keep saved queries, Save says so, queries
    still run).

  • Web UI, saved queries, Run as and downloads: saved queries in folders, kept in this
    browser (IndexedDB, per user; the page works without it), with Save (Ctrl/⌘+S; the same
    query after the first time), Save as…, the unsaved dot, new folder, rename, move by drag,
    delete (asking, with counts), import .sql files, a query's .sql or a folder's .zip, opening
    a query (or going to its tab), and the whole library exported to a file and imported in
    another browser. Run as: under access control a user's tab acts with one of their roles
    (SET ROLE) or all of them, the schema browser and autocomplete listing what that role sees;
    locked in a transaction; no picker for an administrator or without access control.
    Downloads: CSV, TSV, JSON and Excel (.xlsx, written here) of the rows as shown (the grid's
    filter and sort), Parquet and Arrow IPC (.arrows) of the whole result with its exact types.
    e2e saved.spec.ts, roles.spec.ts, export.spec.ts (Excel, Arrow, as shown); unit tests of
    the zip and xlsx writers (a workbook also read back with openpyxl).

  • Web UI, the schema browser and autocomplete: Tables and Views groups, column type icons,
    double-click or drag a fully qualified, quoted-where-needed name into the editor, a menu
    (insert or copy a name, preview data in a new tab on that catalog and schema, insert column
    names or a SELECT, a view's definition, use this catalog and schema; not during a
    transaction), search of every table and view the user may see (two or more characters, up
    to 100), re-listing after a run that changes the catalog, Retry on a failed listing, and a
    hideable, resizable side panel (kept in this browser). Autocomplete of catalogs, schemas,
    tables, views, columns (through aliases), functions and keywords from the server's
    metadata, as access control filters it; nothing inside strings or comments. e2e
    explorer.spec.ts, autocomplete.spec.ts (an RBAC user is offered only what they may see),
    unit tests of the completion parser and quoting.

  • Web UI, the results grid: sort by a column (asc, desc, off; NULLs last, typed: exact
    decimals and 64-bit integers, byte-order text), a filter across every column, column
    resizing (drag, double-click to fit, arrow keys), the spreadsheet selection with its whole
    keyboard (arrows, Home/End, Page Up/Down, Ctrl/⌘ jumps, Shift ranges, drag with edge
    scrolling, row numbers, Ctrl/⌘+A, Esc), a copy that pastes into Excel and Google Sheets as
    cells (text plus an HTML table: full values, NULL empty, line breaks inside a cell,
    00123 kept as text; one cell as its bare value; through the copy command, so Safari
    copies), a cell inspector (double-click or Enter; nested values and JSON text
    pretty-printed with exact numbers; Copy value), Copy of the rows shown, and Expand /
    Restore. e2e grid.spec.ts (Chromium and WebKit; with the copy command taken out, its
    WebKit copy test fails); unit tests for the selection moves, the clipboard forms, sorting
    and the JSON printer.

  • Web UI, the SQL editor's tabs, runs and sessions:
    up to 20 tabs (Alt+N, Alt+W asking first about an open transaction or unsaved text, rename,
    drag to reorder, running/failed/transaction markers), each its own Warp SQL session; Run
    runs the statement at the cursor (margin bar) or exactly the selection, and Run all
    (⇧+Ctrl/⌘+Enter) every statement, stopping at the first error, with a result tab per result
    set and Messages (at most 50 result sets and 64 MiB kept in the browser); Cancel notes how
    far a script got and an aborted transaction; In transaction / Transaction failed badges with
    Commit and Rollback, from the session document's transaction; Catalog and Schema pickers
    (session options, locked in a transaction, following USE); row limits 100 to 1,000,000
    (default 1,000) with "truncated at … rows"; the session chip (the server's session id, copied
    on click); New session; a session the server lost is replaced with a note, or, with a
    transaction open, nothing runs and the message says it was rolled back; leaving the page
    with a transaction open asks first; a resizable editor (drag, keys, double-click, kept).
    The editor's tab connections no longer replace a lost session silently. e2e (Chromium and
    WebKit): tabs.spec.ts, run.spec.ts, session.spec.ts; unit tests for the tab store,
    the script runner and the session's loss handling.

  • Introspection without instrumentation or metrics (README, Introspection; warp-sql 64eb65c):
    hardcharger_active_connections(), a row per open session from the server's own session
    tables: session_id (the Warp SQL s_... id), instance_id, cluster_id, username, role,
    tenant, auth_method; the latest request's client_address and client_info (User-Agent);
    session_tags (MAP(VARCHAR, VARCHAR), the Warp SQL session_tag option's keys, then
    SET hardcharger.session_tag's); tls, tls_version, certificate_identity,
    certificate_thumbprint, token_certificate_bound; state (idle, queued, running,
    streaming, idle_in_transaction, idle_in_transaction_aborted) and transaction; the
    statement_id and statement (redacted by the statement log's own code), statement_started_at,
    queued_since, session_started_...

Read more

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 19 Oct 02:48

A built-in web UI, client certificates as identity (mutual TLS), a DuckDB-file
access-control store for single servers, faster remote transfer (split results and
partitioned uploads by default), Windows builds, and the hardcharger command-line client,
TLS by default. Security fixes from adversarial reviews: metadata follows privileges,
CREATE VIEW needs SELECT on what a view reads, catalog_access holes, and SQL can no
longer reach the server's own files (see Fixed).

Added

  • A refused token says why (Warp SQL §4.2): after ALTER USER ... WITH PASSWORD, ALTER USER ... DISABLE, DROP USER or DROP SCOPED TOKEN, the session's own token gets 401 invalid_token with a reason (credentials_changed, principal_dropped,
    token_revoked), and an expired token token_expired. Before, only a token from a new
    sign-in, used on the old session, learned the reason (from session_ended). Only a
    token this server signed and that nobody altered gets a reason; one signed with another
    key or with a changed signature gets none, whatever user it names. A session opened with
    a scoped token now ends when the scope expires (session_ended, token_expired)
    instead of refusing each statement. test_token_refusal_reasons.py (8 tests), and the
    conformance suite's expired-token case.

  • hardcharger: when the server refuses the sign-in's token it says why, from the reason,
    e.g. "The server no longer accepts this sign-in: the user's password or credentials
    changed (credentials_changed). Sign in again (.connect)."; a session the server ended
    reads "the server ended this session: an administrator ended it (killed)".

  • hardcharger: HUGEINT and UHUGEINT print as exact integers with
    arrow_lossless_conversion on (DuckDB's lossless form, Arrow's opaque 16-byte type), in
    every output mode (unquoted numbers in JSON, right-aligned in boxes, type
    hugeint/uhugeint); they printed as raw bytes.

  • Allowed CORS origins (cors_origins) get Access-Control-Allow-Credentials: true, preflights
    included (Warp SQL §9.5), so a page on another origin can sign in with the user's client
    certificate (credentials: "include"). Other origins still get no CORS headers. The README's
    mTLS section says such pages need mtls_require_client_cert: false (a preflight carries no
    certificate). From warp-sql ec318ff; CI runs the conformance suite's credentials tests with
    WARP_SQL_TEST_SERVER_CORS_FLAG=--cors-origins.

  • Client certificates as identity (Warp SQL §4.4): with mtls_ca_cert, the
    client_certificate grant signs a client in with its verified certificate and no
    password. mtls_identity_source (san_email, the default; san_uri; subject_cn) says
    where the identity comes from; a certificate with none or several values is refused.
    mtls_admin_identities sign in as the administrator; under access control any other
    identity must be an enabled store user (CREATE USER "joe@example.com", no password
    needed), and grants decide what it may do; without access control nothing else signs in.
    The tokens carry RFC 8705's cnf (x5t#S256) and work only on connections presenting the
    same certificate. mtls_crl_file refuses revoked certificates at the handshake, is reloaded
    when the file changes (looked at every 5 s), and reaches open connections at their next
    request. mtls_require_client_cert (default true) and mtls_require_identity_match
    (default false: a password or token sign-in over a certificate must be for its identity).
    Certificate sign-ins, allowed and refused, are audit records (kind: authentication,
    method: client_certificate, the identity, subject, issuer, serial and thumbprint).
    A session opened with a bound token is used only with tokens bound to the same certificate,
    and a scoped token (CREATE SCOPED TOKEN) minted over a bound token carries its cnf too.
    hardcharger_sessions() shows client_certificate as the auth method. Tests:
    test_mtls_identity.py (42), and the conformance suite's test_mtls.py (41).

  • From the security review: certificate sign-in is opt-in (mtls_certificate_sign_in,
    default false; mtls_admin_identities needs it), so mtls_ca_cert alone keeps mTLS a
    transport check. CRLs cover every certificate of the chain, and a CA without one is refused
    unless mtls_crl_allow_missing_ca_crls; open connections are re-checked when a CRL passes
    its nextUpdate. Identities are printable ASCII. A session takes only tokens bound as its
    opener's was, so a scoped token always carries the binding of the request that minted it,
    and a bound session can't mint FOR USER another user. Certificate refusals are security
    warnings and audit records (user, token id, thumbprint, peer), and a token is logged as
    validated only after its certificate checks.

  • Second review: SPIFFE IDs (spiffe://…, any URI-form name) can be access-control users;
    they are always system scope, a tenant admin can't create or drop one, and a role can't be
    named like one. Certificate refusals are rate-limited per source by the SDK (summaries carry
    suppressed), and nothing else logs them per request; the handshake refusing a client
    certificate is an audit record (the TLS handshake refused a client certificate). CRL
    files: the newest CRL per CA, a CA's CRL must be signed by it, no delta CRLs (the SDK).

  • Third review: handshake refusals are rate-limited by the client's address (IPv6 /64), not
    by the unverified certificate, and every client-supplied field of a refusal record is cut
    at 512 bytes; a refused password sign-in logs at most 256 bytes of the user name. A failed
    CRL reload is retried. A scoped token bound to a certificate can't be exchanged (as user
    token's password) over another one. URI identities (SPIFFE IDs) must match a user name
    exactly, case included; e-mail and CN identities still ignore ASCII case. CI runs
    warpsql-sdk-tests (the limiter's unit tests), under TSan too.

  • The hardcharger client: --auth-type mtls (or authType=mtls in a URI) signs in with
    the --mtls-cert certificate, and signs in again with it when the server ends the session.

  • The built-in web UI: web_ui (--web-ui, HARDCHARGER_WEB_UI, off by default) serves a
    browser UI at /ui on the server's own listener (same port, origin and TLS certificate as
    Warp SQL): a SQL editor with tabs and cancel, a results grid that copies in every browser,
    a schema browser that follows access control, CSV/TSV/JSON/Parquet exports, the session
    list, and sign-in with a password or, under mutual TLS, a client certificate. Its source is
    in ui/ (folded in from the hardcharger-ui repository with its history), styled in
    GizmoData's brand with Inter embedded (SIL Open Font License 1.1), with Hardcharger™ and
    GizmoData™ marked on the wordmark, the badge and a trademark notice in its footers. CI builds it once per
    run and every server build in that run embeds it (-DHARDCHARGER_WEB_UI): CMake checks each
    file against the tarball's manifest, embeds it gzipped and logs the content SHA-256. The
    server answers gzip to clients that accept it and inflates (zlib 1.3.2) for those that
    don't, with an ETag per representation and 304 for If-None-Match. Answers follow the
    UI's serving contract: index.html for the app's routes under /ui/, hashed assets
    immutable, everything else revalidated, 404 (never cached) for missing files, and a strict
    Content-Security-Policy and security headers on every answer; nothing at all when off.
    A build without the UI refuses web_ui. The Warp SQL SDK gained plain_handler, through
    which the server answers GET and HEAD outside /warp, with the request's headers. Under
    mutual TLS the page needs a client certificate like any client, and offers certificate
    sign-in when mtls_certificate_sign_in is on. Tests: test_web_ui.py and
    web_ui_test.cc on the server; the UI's lint, typecheck, unit tests and Playwright e2e in
    Chromium and WebKit against the server built in the same run. Releases attach the UI's
    own tarball (hardcharger-ui-<version>.tar.gz) and its content SHA-256. The Windows
    builds embed it too.

  • hardcharger, the command-line SQL client: GizmoSQL 1.x's gizmosql_client on Warp
    SQL, with the same options (in Hardcharger's names: HARDCHARGER_HOST, _PORT, _USER,
    _PASSWORD, _TLS, _TLS_ROOTS, _OAUTH_PORT, _SESSION_TAG, _QUERY_TAG;
    hardcharger:// URIs; ~/.hardchargerrc, ~/.hardcharger_history), dot commands, output
    modes, heredoc/pipe/-c/-f scripting and exit codes, the pager, history, completion,
    syntax highlighting, query and session tags, TLS and mutual TLS, and the server's
    browser sign-in (--auth-type external). Ctrl-C cancels the running statement through
    Warp SQL's cancel and the shell goes on. It ships in the tarballs' bin/ next to
    hardcharger-server, and the image has both on PATH (the entrypoint stays the server).
    docs/migrating-from-gizmosql.md maps the names and lists what behaves differently.

  • GizmoSQL 1.x's client tests, ported: test_interactive_client.cpp (115 cases, as C++
    against the client's classes), test_client_cli.cpp (18), test_client_config.cpp (5),
    the 4 client cases of test_instance_session_query_tags.cpp, and test_client_shell.sh
    (64 assertions), all against a real hardcharger-server; plus the shell at a terminal
    (pexpect), TLS, mutual TLS, browser sign-in and session-recovery cases.

  • replxx (release-0.0.4), the client's line editor, built by scripts/build-deps.sh and
    cached with DuckDB and Arrow.

  • Results split into partitions that a client reads at once over separate connections
    (Warp SQL §6.10, capability result_partitions): max_result_partitions (default 32,
    1 never splits) caps the split. With Warp SQL's drivers' result_partitions option,
    one large result uses several network flows and client cores.

  • `hardcharger-server --print-options...

Read more

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 17:10

Role-based access control and GizmoSQL 1.x's server features (instrumentation, catalog
logging, catalog access rules, statement queue, metrics, KILL SESSION, the OAuth server,
JWKS tokens and more), checked against 404 of the 423 server-side cases of GizmoSQL's
test battery, plus two security reviews of the result (see Fixed).

Added

  • Role-based access control, lifted from GizmoSQL 1.x's RBAC: CREATE USER/ROLE,
    GRANT/REVOKE (roles, table, view, column, schema and catalog privileges, WITH GRANT OPTION, WITH ADMIN OPTION, cascading revokes),
    SHOW GRANTS, ownership (ALTER ... OWNER TO), views that read with their owner's
    rights, system privileges (READ FILESYSTEM, MANAGE CATALOGS, ...), row and masking
    policies, tenants, SET ROLE, identity-provider group mappings, scoped tokens and an
    Open Policy Agent service. Every statement is checked inside DuckDB against its own
    plan, when it is bound and again before it is optimized; anything the check cannot
    classify is refused. Turned on by rbac_store_uri (a PostgreSQL database every replica
    shares, reached through DuckDB's postgres extension); rbac_opa_url,
    rbac_opa_timeout_ms and token_groups_claim too.
  • Store users sign in with their password; their access tokens are checked against the
    store on every request, so dropping or disabling a user, changing their password or
    revoking a scoped token ends their sessions and tokens at once, on every replica, and a
    revoke stops a statement that is running.
  • Statements that reach outside the database (files and URLs, ATTACH, extensions,
    secrets, instance-wide settings, query(), table functions not on the allow-list) are
    refused for store users before DuckDB binds them, unless they hold the system privilege
    that opens them. Settings that write files (log_query_path, profiling_output) need
    WRITE FILESYSTEM, and settings that hold credentials (http_proxy_password, ...) need
    MANAGE CONFIG to read.
  • A tenant's user is told about another tenant's objects exactly what it is told about
    missing ones, in errors, metadata, DESCRIBE, CREATE VIEW and session options.
  • Access-control audit records form a tamper-evident hash chain (HMAC-SHA256 with
    audit_chain_key, SHA-256 without); hardcharger --verify-audit-log FILE checks a JSON log.
  • DuckDB is built with a small binder patch (server/scripts/patch-duckdb.cmake) that
    lets access control see which part of a plan came from which view.
  • Tests for access control (server/tests/test_rbac_*.py, 262), including two replicas on
    one store and a gate that access control costs no concurrency. They need PostgreSQL.
  • SQL functions for the session's context: HARDCHARGER_USER(), _ROLE(), _VERSION(),
    _CURRENT_SESSION() (a UUID), _CURRENT_INSTANCE(), _CURRENT_CLUSTER(),
    _INSTRUMENTATION_ENABLED(), _INSTRUMENTATION_CATALOG(), _INSTRUMENTATION_SCHEMA();
    table functions hardcharger_settings(), hardcharger_sessions() and
    hardcharger_metrics().
  • Server settings in SQL: SET [SESSION|GLOBAL] hardcharger.<name> and RESET, with
    1.x's scopes: query_timeout, query_log_level, graceful_shutdown,
    shutdown_grace_period_seconds, the statement queue's limits, bypass_queue, the
    session and query tags; startup settings say where to change them. SET GLOBAL needs
    an admin.
  • KILL SESSION '<uuid>': ends another session at once,
    frees its max_sessions place, and its next request gets session_ended.
  • max_sessions (admins are never capped; too_many_sessions), session_idle_timeout.
  • The admin command guard (servers without access control): users without the admin role can't ATTACH/DETACH, INSTALL or
    LOAD, SET GLOBAL (or instance-wide settings), CHECKPOINT, COPY to or from local files,
    read local files (read_csv('/x'), FROM 'x.csv'), EXPORT/IMPORT DATABASE, CONNECT,
    manage secrets, or use quack_*(); only literal remote URLs pass where a path decides.
  • The sensitive path guard (block_sensitive_paths, sensitive_paths): no statement may
    name credential or system files (~/.ssh, /etc/shadow, the server's TLS key, ...), however spelled; secret_directory can't change after startup. And
    allow_unredacted_secrets can't be turned on (block_unredacted_secrets); SQL with a
    NUL byte is refused.
  • _hardcharger_system, a read-only catalog with hardcharger_index_info and
    hardcharger_view_definition.
  • The statement queue: max_concurrent_statements, max_queued_statements,
    max_queue_wait, admin_bypass_queue_default.
  • Metrics: enable_metrics, GET /metrics on metrics_port (hardcharger_* metrics),
    hardcharger_metrics(), the unclean-exit marker.
  • The health check runs health_check_query in the background every
    health_check_interval_seconds; a probe answers from the last outcome, which goes stale
    after health_check_staleness_seconds.
  • Graceful shutdown: graceful_shutdown (off: the first signal stops at once) and
    shutdown_grace_period_seconds (0: wait forever), both adjustable live with
    SET GLOBAL; a second signal stops at once.
  • token_authorized_emails: which users a sign-in token may name.
  • auth_log_level, query_log_level, session_log_level, log_file; a bearer token's
    first use logs at INFO, its repeats at DEBUG; JSON lines carry instance_id and
    cluster_id.
  • cluster_id, max_header_bytes (what 1.x's --max-metadata-size capped),
    result_batch_rows (default 2048, as 1.x streamed).
  • Instrumentation: enable_instrumentation records instances, sessions, statements and
    executions (with their queue wait, fetch timeline and status) in _hardcharger_instr,
    with 1.x's tables, views and columns (the version column is hardcharger_version), in a DuckDB
    file next to the database (instrumentation_db_path) or in a DuckLake or PostgreSQL
    catalog the init SQL attaches (instrumentation_catalog, instrumentation_schema). A
    killed server's open rows are closed at the next start. Queued statements show as queued while they wait.
  • instance_tag, SET hardcharger.session_tag and SET hardcharger.query_tag, recorded with
    the instance, the session and each statement; capture_query_profile (off,
    standard, detailed; also SET [GLOBAL]) stores DuckDB's JSON query profile with
    each execution.
  • Catalog logging: enable_catalog_logging also writes every log line to
    _hardcharger_logs.logs (1.x's columns), in its own file
    (log_catalog_db_path) or a DuckLake or PostgreSQL catalog (log_catalog,
    log_schema), in batches from a background writer.
  • The instrumentation and log catalogs are the server's: admins may read them, nobody may
    write, detach or USE them, and non-admins don't see them in metadata.
  • Identity provider sign-in: ID tokens verified with the
    provider's JWKS (token_jwks_uri, or the one token_allowed_issuer's OpenID
    configuration names), RS256/384/512 and ES256/384/512, through token_exchange or as
    the password of user token.
  • Browser sign-in: oauth_client_id runs GizmoSQL 1.x's OAuth server on oauth_port
    (default 31339) with its routes (/oauth/initiate,
    /oauth/start, /oauth/callback, /oauth/token/<uuid>), as a confidential client
    (oauth_client_secret, oauth_scopes, oauth_base_url, oauth_redirect_uri,
    oauth_instance_id for a shared callback proxy, oauth_disable_tls behind a TLS proxy).
  • --verify-tls URL: fetches a URL with the system's CA bundle (SSL_CERT_FILE, else the
    first well-known bundle), as outbound HTTPS does, and exits 0 or 1.
  • The GizmoSQL 1.x test battery, ported (server/tests, scoreboard in
    server/tests/BATTERY.md), with C++ unit tests (server/unit, googletest 1.18.0), on
    the one build that ships.
  • Per-catalog permissions: a sign-in token's
    catalog_access rules (read, write, none per catalog name or glob, first match
    wins, no match is no access) are enforced on every statement's plan by access control,
    without a store: reads and writes per catalog (temp included), and what the user sees
    in SHOW DATABASES, information_schema, duckdb_*(), the _hardcharger_system views and
    the metadata requests; refusals read as in GizmoSQL 1.x ("Access denied: You do not have
    write access to catalog ..."). Under RBAC the rules grant nothing.
  • The instrumentation and log catalogs are now also left out of SQL's own catalog
    functions (duckdb_databases(), information_schema, SHOW ...) for non-administrators.
  • Instrumentation writes to a shared DuckLake catalog that lose a commit race to another
    server are tried again (a few times, backing off) instead of being dropped.
  • PIVOT without an IN list runs (it expands into several statements), also for
    read-only principals.
  • Access control and the battery's features work together: store users never see or
    write the instrumentation and log catalogs (admins read them, nobody writes them), may
    call the server's SQL functions (hardcharger_settings(), ...), and are refused files,
    URLs, ATTACH and the like by access control's system privileges rather than by the
    admin command guard, which stays for servers without access control. Access-control
    statements are counted, logged and recorded like other statements
    (test_rbac_battery.py).

Changed

  • The server's executable is hardcharger-server (the Docker image's entrypoint too); the
    plain hardcharger name is kept for a command-line client.
  • No license: every feature that was Enterprise in GizmoSQL 1.x is available to everyone,
    each turned on by its own option. The license options (license_key,
    license_key_file) and their environment variables are gone: a license flag is an
    unknown option like any other. There is one build, and it has everything: no
    test-license build, no Enterprise/Core build switch, no edition (EDITION(), the
    edition setting, server-info and metrics label, and the instances table's...
Read more

v0.1.2

Choose a tag to compare

@github-actions github-actions released this 17 Oct 06:38

0.1.1 was tagged but never released: its release gate failed on a race in deleting a
running write (fixed here). 0.1.2 carries everything listed for it.

Fixed

  • A use-after-free: prepared statements kept parameter types that DuckDB had lent
    them, so closing a prepared statement or ending its session could crash the server or
    corrupt its heap (found by the JDBC driver's tests). Bulk ingest freed borrowed column
    types the same way. Both are fixed, and CI now runs the server tests and the
    conformance suite on an AddressSanitizer build that gates every release.
  • Deleting a statement before the engine had said whether it writes could cancel a
    write; such a delete is now settled once it does (a write finishes, a read stops).
  • A long poll on a finished statement answered only after its whole wait; it answers at
    once.
  • A streamed query sent its Arrow schema only with its first batch, so an aggregate's
    response stayed silent until the query ended and proxies' idle timeouts could cut it;
    the schema now goes first.
  • A client that hung up while a query's stream waited for its next batch went unnoticed
    until the query ended; the query is cancelled within a second. A statement that
    changes data (INSERT … RETURNING) is never undone because its reader left or deleted
    it: it runs to the end.
  • Finished statements stayed in their session forever; they are forgotten 10 minutes
    after they finish (once any retained result has expired), or beyond 1,000 a session.
  • Prepared statements describe their result (result_schema, result_kind) without
    running; executing a query for zero parameter rows returns no rows instead of a count.
  • Table schemas from metadata report nullability, precision and scale; the session
    document follows SQL USE; a missing or existing table is SQLSTATE 42P01 / 42P07.

Added

  • POST /warp/v1/sessions/{id}/cancel stops whatever the session is running.
  • Primary and imported keys of every table in one metadata request.

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 17 Oct 05:15

Added

  • The Hardcharger server (server/): DuckDB 2.0 behind the Warp SQL protocol, built on
    the Warp SQL server SDK (third_party/warp-sql). It passes the Warp SQL conformance
    suite.
  • Configuration from flags, environment variables (HARDCHARGER_*, plus GizmoSQL 1.x's
    names, so GizmoData Cloud's settings work unchanged) and a YAML file
    (--config/HARDCHARGER_CONFIG), in that order of precedence; unknown keys are refused.
  • Access tokens as JWTs signed with secret_key (HS256): valid across restarts and, with
    allow_cross_instance_tokens, across replicas. GizmoSQL 1.x-issued server tokens are
    accepted.
  • Sign-in with RS256 tokens from a trusted issuer (token_exchange, or as the password of
    user token as in 1.x), with issuer and audience checks and the admin, user and
    readonly roles; catalog_access needs the catalog_permissions license feature.
  • GizmoData Enterprise license keys, verified exactly as GizmoSQL 1.x does; a bad key
    stops the server.
  • Init SQL (commands, then file; any failure stops the server), read-only databases,
    memory limit, storage version, unsigned extensions, a plaintext health port, a default
    statement timeout, statement logging, text or JSON logs, TLS and mTLS, graceful drain.
  • Tests (server/tests, 27) for all of the above.
  • Release artifacts: hardcharger-linux-amd64, -linux-arm64 and -macos-arm64
    tarballs (server plus libduckdb), and the image ghcr.io/gizmodata/hardcharger.
  • A TPC-H SF10 benchmark (bench/tpch.py, results in docs/benchmarks.md): 1.44× faster
    than GizmoSQL 1.41 over the 22 queries, and 1.6× faster at moving 10M rows.

Known gaps

  • The readonly role allows only SELECT and EXPLAIN until the authorization module
    brings DuckDB's own read-only analysis.
  • Not yet ported from GizmoSQL 1.x: RBAC, instrumentation, metrics, the statement queue,
    KILL SESSION, OAuth sign-in, the admin and sensitive-path guards.

v0.0.1

Choose a tag to compare

@github-actions github-actions released this 17 Oct 03:05

Added

  • First release, holding the hardcharger name on PyPI. No functionality yet.