Skip to content

About

Mock Altertable API

Resources

Stars

1 star

Watchers

3 watching

Forks

Latest commit

 

History

35 Commits

Folders and files

Repository files navigation

altertable-mock

License: MIT GHCR CI

A mock server that implements the full Altertable API surface — Arrow Flight SQL (gRPC), Lakehouse REST, and Product Analytics REST — backed by DuckLake on DuckDB 2.0.0. Intended for local development and testing.

What it is

Altertable exposes three interfaces:

  • Arrow Flight SQL (gRPC) — high-performance query and data ingestion protocol
  • Lakehouse REST (HTTP) — async SQL queries, validation, and data upload/upsert/append
  • Product Analytics REST (HTTP) — event tracking and user identity management

This server speaks all three protocols and behaves similarly to the real service, without requiring a real Altertable account or any external infrastructure.

Each authenticated user gets their own DuckLake. Its catalog is an in-memory DuckDB database and its data files live in a temporary directory for the life of that lakehouse. The default catalog name remains memory. Sessions within a user share that lakehouse. The server supports:

  • Ad-hoc SQL queries and DML statements
  • Prepared statements with parameter binding
  • Bulk Arrow record batch ingestion (including upsert / MERGE semantics)
  • Transactions (BEGIN, COMMIT, ROLLBACK)
  • Schema introspection (catalogs, schemas, tables, SQL info)

Why use it

  • Run integration tests against a real Flight SQL endpoint without hitting production
  • Develop locally without network access or credentials
  • Deterministic, ephemeral state — DuckLake metadata is discarded when the server stops
  • Fast and self-contained — no external services required

Ports

The server listens on three ports by default:

Port Protocol Description
15000 HTTP (REST) Lakehouse REST API
15001 HTTP (REST) Product Analytics API
15002 gRPC (Flight SQL) Arrow Flight SQL API

Usage

Docker (recommended)

A pre-built image is published at ghcr.io/altertable-ai/altertable-mock:

docker run -p 15000:15000 -p 15001:15001 -p 15002:15002 \
  -e ALTERTABLE_MOCK_USERS="alice:secret,bob:hunter2" \
  -e ALTERTABLE_MOCK_API_KEYS="myapikey" \
  -e ALTERTABLE_MOCK_ENVIRONMENTS="production,staging" \
  ghcr.io/altertable-ai/altertable-mock

Binary

cargo build --release
./target/release/altertable-mock --user alice:secret --user bob:hunter2

Named HTTP query parameters

POST /query accepts a params object with string, numeric, boolean, or null values. DuckDB binds them to named placeholders without changing the statement:

{ "statement": "SELECT $value AS value", "params": { "value": 42 } }

Missing, unknown, or non-scalar parameters return a query error through the usual response format.

Options

Flag Short Env var Default Description
--flight-port -f ALTERTABLE_MOCK_FLIGHT_PORT 15002 Port for the Arrow Flight SQL (gRPC) server
--lakehouse-port -p ALTERTABLE_MOCK_LAKEHOUSE_PORT 15000 Port for the Lakehouse REST (HTTP) server
--analytics-port -a ALTERTABLE_MOCK_ANALYTICS_PORT 15001 Port for the Product Analytics REST (HTTP) server
--user -u ALTERTABLE_MOCK_USERS — username:password pairs (repeatable, or comma-separated via env var)
--api-key -k ALTERTABLE_MOCK_API_KEYS — API keys for the Product Analytics server (repeatable, or comma-separated via env var)

Examples:

# Custom ports, multiple users and an API key via flags
altertable-mock --flight-port 9090 --lakehouse-port 9091 --analytics-port 9092 \
  --user alice:s3cr3t --user bob:hunter2 --api-key myapikey

# Via environment variables
ALTERTABLE_MOCK_FLIGHT_PORT=9090 ALTERTABLE_MOCK_LAKEHOUSE_PORT=9091 \
  ALTERTABLE_MOCK_ANALYTICS_PORT=9092 ALTERTABLE_MOCK_USERS=alice:s3cr3t,bob:hunter2 \
  ALTERTABLE_MOCK_API_KEYS=myapikey \
  altertable-mock

Log verbosity is controlled via the standard RUST_LOG environment variable (default: info).

API reference

Arrow Flight SQL — port 15002

Connect using any Arrow Flight SQL client pointed at localhost:15002. The altertable-flightsql Python library is the recommended client:

pip install altertable-flightsql
from altertable_flightsql import Client

with Client(username="alice", password="secret", host="localhost", port=15002) as client:
    reader = client.query("SELECT * FROM my_table")
    for batch in reader:
        print(batch.data.to_pandas())

Lakehouse REST API — port 15000

Authenticate with HTTP Basic Auth. All endpoints are on localhost:15000.

Method Path Description
POST /query Submit an async SQL query
GET /query/{query_id} Poll / fetch results for a query
DELETE /query/{query_id} Cancel a query
POST /validate Validate a SQL statement
POST /upload Upload data (CSV, JSON, Parquet; modes: create, append, create_append, overwrite)
POST /upsert Upsert data by one or more primary key columns, newest cursor_field value wins (CSV, JSON, Parquet)
POST /append Append Arrow data
curl -u alice:secret -X POST http://localhost:15000/query \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT 42 AS answer"}'

Product Analytics API — port 15001

Authenticate with an API key passed as either an X-API-Key header or an Authorization: Bearer <token> header. Each API key selects its own isolated in-memory DuckDB database.

The API key determines the environment. Legacy payload environment fields and --environment / ALTERTABLE_MOCK_ENVIRONMENTS settings are accepted and ignored.

Method Path Description
POST /track Ingest one or a batch of analytics events
POST /identify Create or update a user identity with traits
POST /alias Alias an anonymous ID to an identified user

All endpoints accept either a single object or a JSON array of objects.

POST /track

curl -H "X-API-Key: myapikey" -X POST http://localhost:15001/track \
  -H "Content-Type: application/json" \
  -d '{
    "event": "Button Clicked",
    "properties": {"button": "signup"},
    "distinct_id": "user_42",
    "anonymous_id": "anon_abc",
    "timestamp": "2025-06-15T12:00:00Z"
  }'
Field Type Required Description
event string yes Event name
properties object yes Arbitrary event properties
distinct_id string no Identified user ID
anonymous_id string no Anonymous / device ID (linked to distinct_id if both provided)
device_id string no Device identifier
session_id string no Session identifier
timestamp string or number no ISO 8601 string or Unix timestamp in milliseconds; defaults to now
mode string no product (default) or audience; see below

With "mode": "audience", the event is collected without identity: distinct_id, anonymous_id, device_id and session_id are rejected with 400 (audience-identity-not-allowed, the whole batch is refused), a distinct_id is derived from a daily-rotating salted HMAC of the API key, $url hostname, client IP and user agent, and IP, geo, user-agent, referer, device and language properties are dropped. The mode is stored in the mode column of events.

POST /identify

curl -H "X-API-Key: myapikey" -X POST http://localhost:15001/identify \
  -H "Content-Type: application/json" \
  -d '{
    "distinct_id": "user_42",
    "traits": {"email": "alice@example.com", "plan": "pro"},
    "anonymous_id": "anon_abc"
  }'
Field Type Required Description
distinct_id string yes Identified user ID
traits object no User traits to shallow-merge into existing traits
anonymous_id string no Anonymous ID to link to this identity

POST /alias

curl -H "X-API-Key: myapikey" -X POST http://localhost:15001/alias \
  -H "Content-Type: application/json" \
  -d '{
    "distinct_id": "anon_abc",
    "new_user_id": "user_42"
  }'
Field Type Required Description
distinct_id string yes The source ID to be merged away
new_user_id string yes The target ID that survives the merge

All endpoints return {"ok": true} on success or {"ok": false, "error_code": "..."} on failure.

Building from source

The build links stock DuckDB 2.0.0, downloaded from the version pinned by duckdb-rs. Set DUCKDB_LIB_DIR to link a local copy instead. Lakehouse connections install and load ducklake (INSTALL ducklake; LOAD ducklake;) and the semantic_layer community extension (INSTALL semantic_layer FROM community; LOAD semantic_layer;). The Docker image downloads both extensions first, so INSTALL finds them already present and LOAD succeeds.

cargo build --release

End-to-end tests

The e2e crate tests the Docker image the way its users run it. It builds the repository's Dockerfile with docker build, starts containers with testcontainers, and talks to them only over the published ports. It needs a running Docker daemon:

cargo test --manifest-path e2e/Cargo.toml

The first run builds the image from scratch; later runs reuse Docker's layer cache. Set ALTERTABLE_MOCK_IMAGE to test an existing image instead, such as ghcr.io/altertable-ai/altertable-mock:latest.

Lakehouse queries are golden files in e2e/cases/lakehouse. Each file runs as its own user, so files never share tables. A case is the SQL sent to POST /query and every response line after the header (the column line, then the rows or the error):

query {"params": {"value": 42}}
SELECT $value AS answer
----
[{"name":"answer","type":"BIGINT"}]
[42]

statement ok runs setup SQL and only checks that it succeeds. A JSON object after the directive is merged into the request body. To write or refresh the expected output, run the tests with E2E_BLESS=1 and review the diff.

About

Mock Altertable API

Resources

Stars

1 star

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages