Skip to content

feat: complete the BigQuery driver for 0.2.0 - #3

Merged
debba merged 42 commits into
mainfrom
release/0.2.0
Oct 18, 2026
Merged

debba merged 42 commits into
mainfrom
release/0.2.0

Conversation

@debba

@debba debba commented Oct 18, 2026

Copy link
Copy Markdown
Contributor

Stacked on #2. Review only the commits after it; the base switches to main once #2 is merged.

Summary

Completes the BigQuery driver for a 0.2.0 release: a robust query runtime, the everyday workflows the built-in PostgreSQL driver offers, and correct GoogleSQL for editing and DDL. Requires Tabularis 0.29.0 (min_runtime_version).

Query runtime

  • One task per request with a single stdout writer, so a slow query no longer blocks metadata calls. The host cancel notification cancels the BigQuery job (jobs.cancel), and notifications never get a reply.
  • Jobs carry a requestId (retries cannot run a statement twice), jobTimeoutMs, labels, and run in the billing project. Default timeout is 110 s, below the host call timeout.
  • Retries on 429, 5xx and 403 rateLimitExceeded / backendError with jitter; one token refresh after 401; failure judged by status.errorResult.
  • Pagination follows pageToken and reuses the finished job instead of re-running the SQL for every page (exports included). Timestamps are exact (int64).
  • Plain SELECT * FROM table [LIMIT/OFFSET] on native tables is served by the free tabledata.list.
  • BigQuery sessions for the host session_id (in_transaction, release_session), so BEGIN TRANSACTION, DECLARE and temp tables survive between runs. Scripts return the last result and the summed DML row count.
  • Visual EXPLAIN: dry-run estimate, and EXPLAIN ANALYZE from the job query plan with a bundled parser.
  • Error reasons mapped to clear messages with hints (access, quota, bytes billed, partition filter, streaming buffer).

Workflows

  • Projects as databases (get_databases from projects.list, plus additional_projects such as bigquery-public-data and a separate billing_project), datasets as schemas, fully qualified generated SQL.
  • Routines through the free REST API, with create from template, edit script, call SQL and drop (routine_management).
  • get_table_ddl, view and materialized view definitions from INFORMATION_SCHEMA (OPTIONS, partitioning and clustering preserved), materialized view refresh.
  • Bulk metadata for ER diagrams and AI context: one fetch per table with bounded concurrency, short TTL cache cleared after DDL.
  • Only DDL BigQuery accepts (no SET NOT NULL, no ADD COLUMN ... NOT NULL, widening-only type changes, NOT ENFORCED keys), column descriptions (column_comments), and get_drop_table_sql with the right keyword for snapshots, external tables, views and materialized views (drop_table_sql).
  • Row editing with typed query parameters and valid GoogleSQL literals; keyless tables are matched on comparable columns and the change is rolled back if it would touch more than one row.

Connection and auth

  • Impersonated service account ADC files and an impersonate_service_account option; token_uri restricted to Google endpoints.
  • Advanced options: API endpoint override (for emulators and Private Service Connect; http and none auth only on loopback), billing project, additional projects.
  • Switching to ADC clears a previously stored credential.

Manifest

  • sql_syntax declares GoogleSQL (backtick identifiers, triple-quoted and raw strings, # comments, procedural blocks, backslash literals, TRUE / FALSE booleans, BigQuery formatter, keywords and functions), with sql_dialect: mysql as the fallback for older hosts.
  • min_runtime_version: 0.29.0, routine_management, column_comments, drop_table_sql, explain_parsers, complete type_mappings, registry metadata. Validates against the live registry.

Host changes: TabularisDB/tabularis#629, TabularisDB/tabularis#1199, TabularisDB/tabularis#1200.

Tests

  • 146 tests: unit, mocked HTTP runtime, host-shape conformance, DDL context payloads, manifest contract, and live tests against the BigQuery emulator (gated on BIGQUERY_EMULATOR_HOST, run in CI as a service container).
  • cargo fmt --check, cargo clippy -D warnings, UI and explain bundle typecheck, build and tests pass.
  • Not yet verified against a real BigQuery project: sessions, @@row_count in the keyless edit guard and INFORMATION_SCHEMA.TABLES.ddl (the emulator does not support them). To be checked before tagging.

debba added 28 commits August 13, 2026 14:26
Dispatch requests concurrently with a single writer and cancel the running job on the host cancel notification. Start idempotent, labelled jobs with a server-side timeout, retry transient failures, fill pages across page tokens, reuse finished jobs for later pages, browse native tables through tabledata.list, map host sessions to BigQuery sessions, return script results, and describe common errors with hints. Add impersonation, an API endpoint override and emulator-only unauthenticated access, and fix the client cache key.
explain_query returns the raw bigquery-job-json payload: dry-run bytes with a labelled on-demand cost estimate, or the executed stage plan for EXPLAIN ANALYZE. The TypeScript parser is declared in explain_parsers and requires Tabularis 0.23.0.
Expose the API endpoint, the service account to impersonate and the emulator-only no-authentication method. Clear the stored credential when switching to a method that does not use one, and default the query timeout to 110 seconds.
Quote every identifier part, escape control characters, and render NUMERIC, BIGNUMERIC, GEOGRAPHY, INTERVAL, RANGE, JSON, BYTES, ARRAY and STRUCT values correctly. Parse rangeElementType and parameterized type fields from table schemas.
Run jobs in the billing project (billing_project extra or database), list projects, datasets, tables and routines for any project, and add run_statement with named query parameters, default dataset and location. Fix the double slash in REST URLs.
List projects as databases, fetch table resources once with bounded concurrency and a short cache, fill descriptions and parameterized types, read DDL from INFORMATION_SCHEMA with a metadata fallback, refresh materialized views, and read and manage routines through the routines REST API with host-compatible parameters.
Reject NOT NULL on added columns, SET NOT NULL and unsupported type changes with clear errors, set column descriptions through OPTIONS, add and drop NOT ENFORCED primary keys, and accept optional partitioning, clustering and description for new tables.
Bind values as query parameters, report real affected-row counts, refuse edits on snapshots and external tables, match keyless rows on comparable columns inside a transaction that rolls back when more than one row matches, and explain streaming-buffer errors.
Deserialize RPC results into mirrors of the host models and add live metadata and CRUD checks gated on BIGQUERY_EMULATOR_HOST.
Enable routine management, complete type mappings, stop requiring NUMERIC precision, add registry metadata, and document multi-project browsing, permissions and BigQuery-specific behaviour.
Adds optional billing_project and additional_projects text fields under Advanced options. The backend that reads them lands separately.
Query, dry-run, session abort and table browsing pick their project through job_project() and data_project(), so a separate billing project can be wired in one place. The client cache key also covers billing_project and additional_projects.
# Conflicts:
#	.tabularium
#	README.md
#	src/client.rs
#	src/lib.rs
#	tests/conformance.rs
#	tests/emulator.rs
…he data project

Wire the two project helpers of the query runtime to the multi-project
connection settings: jobs run and are billed in `billing_project` (or the
GCP Project ID), while `defaultDataset.projectId`, table browsing and
`ping` use the selected project. The data project is part of the client
cache key. Mocked tests cover the job URL and default dataset, and that
row edits reach the job runtime with request IDs, labels, job timeouts,
the dataset location and typed named parameters.
Statements run from a query tab that are not plain queries may create,
alter or drop objects, so the short-lived metadata cache is cleared once
they have run, also after a failure since a script may have applied part
of its changes.
Every DDL builder now qualifies objects with the data project from the
connection parameters the host sends (falling back to the billing project
in all-databases mode, and to dataset-qualified names on older hosts).

Add the optional get_drop_table_sql RPC: it looks up the object type and
returns DROP TABLE, DROP EXTERNAL TABLE, DROP SNAPSHOT TABLE, DROP VIEW or
DROP MATERIALIZED VIEW on the fully qualified name.

Clear a column description with SET OPTIONS(description = NULL) when the
host omits the new comment. Conformance tests replay the exact host
request payloads.
Set min_runtime_version to 0.29.0, the first host with plugin connection
fields and secrets, the DDL connection context and parser bundles, and fold
the old Visual EXPLAIN requirement into it. Fix the GCP Project ID
placeholder, which suggested the billing project.
pnpm 10+ refuses frozen installs with unapproved build scripts and pnpm 11+
no longer reads the pnpm field of package.json. Each package directory gets
its own pnpm-workspace.yaml with the build allowlist; the packages entry
keeps pnpm 9, pinned in CI, working.
Comment thread src/handlers/ddl.rs Outdated
.map(str::trim)
.filter(|value| !value.is_empty())
{
output.push_str(" DEFAULT ");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: column_sql emits DEFAULT <expr> before NOT NULL, producing invalid GoogleSQL for required columns with defaults

For a column that is both required and defaulted, the emitted clause order is `col` INT64 DEFAULT 0 NOT NULL (the DEFAULT block at lines 381-389 runs before the NOT NULL block at 390-398), but BigQuery's column_schema grammar is column_name type [NOT NULL] [DEFAULT expr] [OPTIONS(...)] — NOT NULL must come first. create_table_sql is the only caller with allow_required = true, so a CREATE TABLE from the column editor with such a column fails at execution with a syntax error. The pre-PR code emitted NOT NULL first, so this is a regression; the new tests never combine the two options (the NOT NULL column has no default, the defaulted column is nullable). Move the DEFAULT block after the NOT NULL block.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bce4e0f. Column definitions now emit NOT NULL before DEFAULT and OPTIONS, both in the column editor DDL (handlers/ddl.rs) and in the metadata-based table DDL fallback (schema_ddl.rs). Added tests for a required column with a default and a description.

Comment thread src/client/jobs.rs
let page_size = request.page_size;
let offset = u64::from(request.page.saturating_sub(1)) * u64::from(page_size);
let session_key = session.as_ref().and_then(|state| state.bigquery_id.clone());
let cache_key = self.result_key(request, session_key.as_deref());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Result-cache key is computed before the BigQuery session id exists, so paging past page 1 of the first statement in a session silently re-executes the query

cache_key is derived from state.bigquery_id, which is still None for the first statement of a session; the id is only captured later in finish, and the result is stored under the no-session key. When the host requests page 2, bigquery_id is now Some(..), the lookup key differs, lookup_result misses, is_read_only_query passes, and the whole statement runs again as a fresh job. That second execution is then cached under the session key, so page 1 and pages 3+ can come from two different executions that disagree on a mutating table. Consider deferring the store until the session id is known (or keying the cache by the host session id).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b38aace. The cache key is now computed after the session id of a newly created BigQuery session is captured, so page 2 of the first statement in a session finds the cached job. Added a mock test asserting only one jobs.query call for pages 1 and 2.

Comment thread src/client/jobs.rs
if has_errors {
let job_resource = self.get_job(job).await?;
if let Some(error) = job_resource.pointer("/status/errorResult") {
return Err(ApiError::from_error_proto(error).describe());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: A failed createSession job returns before the session id is captured, leaking the BigQuery session server-side

When the job has errors, finish returns here before reaching the session-capture block at lines 296-317. A job submitted with createSession: true may still have created a session, and its id is readable at /statistics/sessionInfo/sessionId in the job resource that was just fetched (the success path falls back to exactly that location). Since the id never lands in state.bigquery_id, session::abort can never run BQ.ABORT_SESSION for it: every failed statement in a session tab orphans a BigQuery session (which only expires after ~24h idle) and the next statement creates yet another fresh one.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b38aace. On the job error path the session id is captured from statistics.sessionInfo.sessionId (or the query response) before returning the error, so release_session still runs BQ.ABORT_SESSION. Covered by a mock test.

Comment thread src/client/jobs.rs Outdated
async fn list_child_jobs(&self, job: &JobRef) -> Result<Vec<Value>, String> {
let mut children = Vec::new();
let mut page_token: Option<String> = None;
loop {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: list_child_jobs loops forever with unbounded memory growth if the server keeps returning a nextPageToken

The loop only exits when nextPageToken is absent; a misbehaving endpoint (an emulator or proxy repeating a token) makes children grow without bound and the request hang. tabledata.rs in this same PR explicitly distrusts server paging for exactly this reason, so the missing repeated-token guard here is inconsistent with the codebase's own assumptions about endpoints it talks to.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b38aace. list_child_jobs now fails on a repeated nextPageToken and stops after 100 pages, with a mock test that returns the same token forever.

Comment thread src/server.rs Outdated
let mut tasks = JoinSet::new();
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
while tasks.try_join_next().is_some() {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Finished task results are dropped uninspected, so a panicking request handler is silently swallowed and its JSON-RPC request is never answered

while tasks.try_join_next().is_some() {} (and the drain at line 68) discards the Result<_, JoinError> from each completed task. If a handler panics, no response is ever sent for that request id — the host waits until its own call timeout — and the panic is not even logged, though eprintln! is used elsewhere in this file. Inspecting the join result and logging errors would preserve observability.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 89fa84f. The server tracks the request id per task, inspects each JoinError, logs it to stderr and replies with a JSON-RPC internal error (-32603). A unit test with a panicking handler checks that both the failing and the following request are answered.

Comment thread src/session.rs Outdated
}

pub async fn abort(state: &mut SessionState) -> Result<(), String> {
let (Some(client), Some(id)) = (state.client.clone(), state.bigquery_id.take()) else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: abort clears bigquery_id even when client is None, dropping the session id without aborting the server-side session

Both tuple elements are evaluated before the pattern match, so state.bigquery_id.take() runs regardless; if client is None the else branch returns Ok(()) with the id already erased and abort_session never called. Currently unreachable because the runtime sets client before bigquery_id can be set, but the fields are public and the invariant is implicit — taking the id only after matching would remove the footgun.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 53339ff. abort now checks for a client first and takes bigquery_id only after that, so the id is never dropped without aborting the session. Added a unit test.

Comment thread src/statements.rs

/// Split a script into statements on top-level semicolons, skipping quoted
/// strings, quoted identifiers and comments.
pub fn split(sql: &str) -> Vec<String> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: split does not track scripting BEGIN...END block nesting, so transaction-control statements inside blocks are invisible to transaction_state_after / ends_transaction

IF x THEN COMMIT; END IF; splits into IF x THEN COMMIT and END IF, and transaction_control matches neither piece, leaving state.in_transaction stale after BigQuery actually ended the transaction. Execution routing is unaffected (may_be_script still detects the script); impact is limited to the host's transaction badge and session decisions for scripts that bury COMMIT/ROLLBACK inside blocks.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c2b2958. Transaction tracking now scans the script with block depth: statements start after ;, BEGIN, THEN, ELSE, DO, LOOP and REPEAT, but not inside parentheses or CASE expressions. COMMIT/ROLLBACK inside IF ... END IF, loops and exception handlers now update in_transaction. Unit tests cover these cases plus block BEGIN, CASE expressions and quoted names.

Comment thread src/statements.rs Outdated
statements.len() == 1
&& matches!(
words(&statements[0]).first().map(String::as_str),
Some("SELECT" | "WITH" | "(")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The "(" alternative never matches a query written as (SELECT ...) because words() splits on whitespace

For (SELECT 1) the first word is "(SELECT", not "(", so a leading-parenthesis SELECT is treated as non-read-only: every run triggers a full catalog invalidation, and expired cached results cannot be transparently re-run for later pages. Minor UX/cache-efficiency issue only; no wrong data.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c2b2958. is_read_only_query strips leading parentheses before checking for SELECT or WITH, so (SELECT ...) UNION ALL (SELECT ...) counts as read-only. Tests added.

Comment thread src/auth.rs Outdated
*provider.cached.lock().await = None;
Box::pin(provider.source.invalidate()).await;
}
Self::Google(_) | Self::AccessToken(_) | Self::None => {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: invalidate() is a no-op for AuthProvider::Google, so the "refresh token once after 401" retry reuses the same cached token for ADC and service-account connections

gcp_auth's TokenProvider caches tokens internally and offers no force-refresh here; after a 401 (e.g. a revoked but unexpired token) the retry gets the identical token from gcp_auth's cache and fails again. The error still surfaces correctly to the user, but the doc comment on transport::call ("a 401 refreshes the token once") overstates the behavior for the two most common auth methods.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2a898b4. Google (ADC and service account) providers are now wrapped so invalidate() rebuilds the gcp_auth provider, and the retry after a 401 gets a fresh token. The transport::call doc comment now also says a fixed access token cannot be refreshed. Unit test with a fake token provider.

Comment thread .github/workflows/ci.yml Outdated
sleep 2
done
exit 1
- run: cargo test --test emulator

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: The emulator CI job runs only cargo test --test emulator, so tests/emulator_metadata.rs never executes in CI

Both emulator test binaries are gated on BIGQUERY_EMULATOR_HOST and share this setup; --test emulator selects only one target, so the metadata/DDL/CRUD round-trip tests in emulator_metadata.rs (including the get_drop_table_sql live check added by this PR) are never run.

Suggested change
- run: cargo test --test emulator
- run: cargo test --test emulator --test emulator_metadata

Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 4e7c4a6: the emulator job now runs cargo test --test emulator --test emulator_metadata. Both suites pass locally against the emulator.

@kilo-code-bot

kilo-code-bot Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0

Fix these issues in Kilo Cloud

Issue Details (click to expand)

WARNING

File Line Issue
src/server.rs 59 A panicked handler is answered only when the next input line arrives (JoinSet drained solely inside the read loop and after EOF), so an idle host still waits until its own timeout for the promised -32603 reply
Files Reviewed (22 files)

Incremental review of changes since the previous review (bdae214 → af5e16c). All 13 previous findings were re-verified as fixed in the current head (ddl.rs/schema_ddl.rs clause order, jobs.rs cache re-keying + failed-session capture + pagination guards, server.rs panic handling, metadata.rs batch filtering, plan.ts stage validation, session.rs abort ordering, statements.rs block-aware transaction tracking + paren stripping, auth.rs provider rebuild on invalidate, ci.yml emulator test coverage).

  • src/server.rs - 1 issue
  • .github/workflows/ci.yml, .tabularium, CHANGELOG.md, Cargo.lock, Cargo.toml, README.md, bigquery-icon.svg, justfile - no issues
  • explain/src/plan.ts, explain/tests/plan.test.ts - no issues
  • src/auth.rs, src/client/jobs.rs, src/client/transport.rs, src/handlers/ddl.rs, src/handlers/metadata.rs, src/schema_ddl.rs, src/session.rs, src/statements.rs - no issues
  • tests/manifest_contract.rs, tests/projects_mock.rs, tests/runtime_mock.rs - no issues
Previous Review Summary (commit bdae214)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit bdae214)

Status: 13 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 6
SUGGESTION 6

Fix these issues in Kilo Cloud

Issue Details (click to expand)

CRITICAL

File Line Issue
src/handlers/ddl.rs 387 column_sql emits DEFAULT <expr> before NOT NULL, producing invalid GoogleSQL for required columns with defaults (regression)

WARNING

File Line Issue
src/client/jobs.rs 209 Result-cache key is computed before the session id exists; paging past page 1 of the first statement in a session silently re-executes the query
src/client/jobs.rs 291 Failed createSession job returns before the session id is captured, leaking the BigQuery session server-side
src/client/jobs.rs 578 list_child_jobs loops forever with unbounded growth if the server keeps returning a nextPageToken
src/server.rs 41 Finished task results are dropped uninspected: a panicking handler is never answered and the panic is not logged
src/handlers/metadata.rs 573 batch_metadata fetches every table in the dataset and fails the whole batch on any single fetch error, ignoring the requested subset (regression)
explain/src/plan.ts 194 Null/non-object query_plan elements crash with an opaque TypeError instead of the parser's clean validation errors

SUGGESTION

File Line Issue
src/client/jobs.rs 494 complete_page follows pageToken without a repeated-token guard; a misbehaving server can inject duplicate rows
src/session.rs 63 abort clears bigquery_id even when client is None, dropping the id without aborting the server-side session
src/statements.rs 6 split does not track BEGIN...END block nesting; COMMIT/ROLLBACK inside scripting blocks leave in_transaction stale
src/statements.rs 184 The "(" arm of is_read_only_query never matches (SELECT ...) because words() splits on whitespace
src/auth.rs 152 invalidate() is a no-op for gcp_auth providers, so the refresh-once-after-401 retry reuses the same cached token
.github/workflows/ci.yml 60 The emulator CI job runs only cargo test --test emulator, so tests/emulator_metadata.rs never executes in CI
Files Reviewed (72 files)
  • src/handlers/ddl.rs - 1 issue
  • src/client/jobs.rs - 4 issues
  • src/server.rs - 1 issue
  • src/handlers/metadata.rs - 1 issue
  • explain/src/plan.ts - 1 issue
  • src/session.rs - 1 issue
  • src/statements.rs - 2 issues
  • src/auth.rs - 1 issue
  • .github/workflows/ci.yml - 1 issue
  • src/client.rs, src/client/tabledata.rs, src/client/transport.rs, src/endpoint.rs, src/errors.rs, src/cancel.rs, src/bigquery.rs, src/explain.rs, src/sql.rs, src/schema_ddl.rs, src/browse.rs, src/catalog.rs, src/models.rs, src/routines.rs, src/rpc.rs, src/lib.rs, src/main.rs, src/handlers/crud.rs, src/handlers/query.rs, src/handlers/routines.rs, src/handlers/blob.rs, src/handlers/connection.rs, src/handlers/mod.rs - no issues
  • explain/src/index.ts, explain/src/parser.ts, explain/src/iife.ts, explain/package.json, explain/tsup.config.ts, explain/tsconfig.json, explain/tests/*, explain/README.md, explain/LICENSE, explain/pnpm-workspace.yaml - no issues
  • ui/src/connection-fields.tsx, ui/src/styles.ts, ui/pnpm-workspace.yaml - no issues
  • .tabularium, Cargo.toml, Cargo.lock, .gitignore, .github/workflows/release.yml, CHANGELOG.md, README.md, explain/pnpm-lock.yaml - no issues
  • tests/conformance.rs, tests/conformance_metadata.rs, tests/ddl_context.rs, tests/dispatch.rs, tests/emulator.rs, tests/emulator_metadata.rs, tests/manifest_contract.rs, tests/projects_mock.rs, tests/rpc_contract.rs, tests/runtime_mock.rs, tests/support/mod.rs, tests/fixtures/rest/* - no issues

Note: the PR head moved from 6f019ef to bdae214 during review (merge of the updated base with security dependency fixes); all findings were re-verified against the new head — only Cargo.lock changed.


Reviewed by kimi-k3 · Input: 596.7K · Output: 30K · Cached: 903.2K

debba added 2 commits October 18, 2026 15:38
# Conflicts:
#	.github/workflows/ci.yml
#	.github/workflows/release.yml
#	.gitignore
#	.tabularium
#	CHANGELOG.md
#	README.md
#	src/auth.rs
#	tests/manifest_contract.rs
#	ui/src/connection-fields.tsx
#	ui/src/styles.ts
@debba
debba changed the base branch from feat/secure-connection-fields to main October 18, 2026 13:39
debba added 12 commits October 18, 2026 15:40
GoogleSQL column_schema is name type [NOT NULL] [DEFAULT expr] [OPTIONS(...)],
so a required column with a default produced invalid CREATE TABLE SQL.
…okens

- Key the result cache after capturing a newly created BigQuery session, so
  page 2 of the first statement in a session reuses its job instead of
  running the statement again.
- Capture the session id of a failed createSession job, so releasing the
  editor tab still aborts the server-side session.
- Fail on a repeated pageToken while filling a result page and on a
  repeated nextPageToken (or more than 100 pages) while listing script
  child jobs, instead of duplicating rows or looping forever.
Finished tasks were dropped uninspected, so a panicking handler was never
logged and its request never answered. Track the request id per task, log
the JoinError to stderr and reply with a JSON-RPC internal error.
get_all_columns_batch and get_all_foreign_keys_batch fetched every table of
the dataset and failed the whole batch on one error. Filter to the requested
tables before fetching and skip (and log) tables that cannot be read.
- Scan scripts with block depth so COMMIT / ROLLBACK / BEGIN TRANSACTION
  nested in IF, BEGIN...END, loops or exception handlers update the session
  transaction state, while CASE expressions and parentheses do not start
  statements.
- Treat a query that starts with parentheses, such as (SELECT ...), as
  read-only.
invalidate() was a no-op for ADC and service account connections because
gcp_auth caches tokens internally, so the retry after a 401 sent the same
token again. Rebuild the provider instead, so the retry fetches a new token.
Null or non-object query_plan elements (and malformed steps) crashed the
parser with a TypeError. Validate stages, inputStages, steps and substeps
and throw the parser's usual descriptive errors.
@debba
debba merged commit c3750b2 into main Oct 18, 2026
7 checks passed
Comment thread src/server.rs
let mut pending: HashMap<Id, Value> = HashMap::new();
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
while let Some(joined) = tasks.try_join_next_with_id() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: A panicked handler is only answered once the next input line arrives — an idle host still waits for a reply that is ready but not sent

The task set is drained only inside the read loop (here, before each new line) and once after EOF. When a handler panics, the -32603 response is emitted by settle solely at those points, so if the panicking request is the only one in flight, the host sits waiting for its response until its own call timeout fires — the same host-visible outcome this change set out to eliminate ("its request still gets a JSON-RPC internal error, so the host never waits for a reply that cannot come"). The unit test passes only because it closes stdin (drop(host_write)), which forces the drain. Consider driving the main loop with tokio::select! over lines.next_line() and tasks.join_next_with_id() so a panicked task is settled as soon as it finishes.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@debba
debba deleted the release/0.2.0 branch October 18, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant