BigQuery (Phase 1)
floci-gcp emulates the BigQuery v2 REST API (the surface the google-cloud-bigquery SDK and
the Discovery document define) for metadata, streaming inserts, and a limited SQL subset:
datasets, tables with schemas, tabledata.insertAll, tabledata.list, and query jobs. A real
GoogleSQL engine (Phase 2) and the Storage Read/Write gRPC API (Phase 3) are out of scope.
Configuration
| Variable | Default | Description |
|---|---|---|
FLOCI_GCP_SERVICES_BIGQUERY_ENABLED |
true |
Enable/disable BigQuery |
Endpoint
BigQuery has no *_EMULATOR_HOST convention in the Java SDK — point the client at floci-gcp
with setHost and disable credentials:
BigQuery bigquery = BigQueryOptions.newBuilder()
.setHost("http://localhost:4588")
.setProjectId("floci-local")
.setCredentials(NoCredentials.getInstance())
.build().getService();
REST paths live under /bigquery/v2/projects/{project}/....
Scope
- Datasets: insert, get, list, patch/update, delete (
deleteContentshonored; deleting a non-empty dataset without it → 400resourceInUse). Duplicate create → 409duplicate. - Tables: insert, get, list, patch/update, delete. Schemas accept standard or legacy type
names and are normalized to legacy names (
INT64→INTEGER,FLOAT64→FLOAT,BOOL→BOOLEAN,STRUCT→RECORD) withNULLABLEas the default mode, matching SDK round-trips. - tabledata.insertAll: schema-validated per row — HTTP 200 always, failures reported via
insertErrors(unknown fields honorignoreUnknownValues;skipInvalidRows=falseinserts nothing and marks valid rowsstopped; missingREQUIREDfields and uncoercible values are rejected with reasoninvalid). - tabledata.list:
{f:[{v:"..."}]}row encoding (scalars as strings,REPEATEDas arrays of{v},RECORDas nested{f:[...]}),maxResults/pageToken/startIndexpaging.maxResults=0returns zero rows (the SDK'sJob.waitFor()contract); apageTokenwins overstartIndex(the SDK sends both when auto-paging). - Jobs / queries:
jobs.query(fast path, always answersjobComplete=truesynchronously),jobs.insert(QUERY only),jobs.get,jobs.list,jobs.cancel,jobs.delete,getQueryResults. Query results are materialized into a hidden anonymous table (_floci_anon.anon_<jobId>) referenced as the job'sconfiguration.query.destinationTable, which is how the SDK'sJob.getQueryResults()reads rows.
Supported SQL
query := SELECT selection FROM table_ref [WHERE predicate {AND predicate}] [LIMIT int] [;]
selection := * | COUNT ( * ) | column {, column}
table_ref := [project .] dataset . table | table (backtick-quoted forms accepted)
predicate := column = literal
literal := 'string' | "string" | integer | float | TRUE | FALSE
- An unqualified
tablerequires the request'sdefaultDataset. - Typed equality per column:
INTEGER/FLOAT/BOOLEAN/STRING; comparing with a mismatched literal type → 400invalidQuery("No matching signature for operator ="). COUNT(*)returns a singleINTEGERcolumn namedf0_and respectsWHERE.- Anything else (JOIN, GROUP BY, ORDER BY, OR,
!=,<, functions, aliases, subqueries, DML/DDL,= NULL) fails fast with 400 and reasoninvalidQuerynaming the construct — never silent divergence. - Invalid SQL via
jobs.query→ HTTP 400; viajobs.insert→ HTTP 200DONEjob carryingstatus.errorResult(andgetQueryResultson that job → HTTP 400), matching real job semantics.
Type support
| Legacy type | Accepted insertAll values |
Wire encoding |
|---|---|---|
STRING |
string, number, boolean | as-is |
INTEGER (INT64) |
integral number, decimal string | decimal string |
FLOAT (FLOAT64) |
number, decimal string | decimal string |
BOOLEAN (BOOL) |
boolean, "true"/"false" |
"true"/"false" |
RECORD (STRUCT) |
object (validated against nested fields) | {f:[...]} |
any + REPEATED mode |
array of the base type | array of {v} |
other (TIMESTAMP, DATE, ...) |
stored/echoed textually | as-is |
Deviations from real BigQuery
- No
insertIdde-duplication (real BigQuery is best-effort anyway). - Jobs always complete synchronously (
jobComplete=true, stateDONE) — no PENDING/RUNNING phase. useLegacySqlis ignored; the SQL subset above is the only dialect.WHEREonTIMESTAMP/RECORD/REPEATEDcolumns is not supported.- Cross-project table references are rejected.
- Anonymous result tables persist until
jobs.delete; they are hidden from dataset listings. dryRunis not special-cased; Storage Read/Write API (gRPC) is not implemented (Phase 3).
Quick smoke (curl)
B=http://localhost:4588/bigquery/v2/projects/demo
curl -sX POST $B/datasets -H 'Content-Type: application/json' \
-d '{"datasetReference":{"datasetId":"ds1"}}'
curl -sX POST $B/datasets/ds1/tables -H 'Content-Type: application/json' \
-d '{"tableReference":{"tableId":"t1"},"schema":{"fields":[{"name":"name","type":"STRING"},{"name":"age","type":"INT64"}]}}'
curl -sX POST $B/datasets/ds1/tables/t1/insertAll -H 'Content-Type: application/json' \
-d '{"rows":[{"json":{"name":"ana","age":30}}]}'
curl -sX POST $B/queries -H 'Content-Type: application/json' \
-d '{"query":"SELECT name FROM ds1.t1 WHERE age = 30"}'