Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Erlang/OTP and Gleam
uses: erlef/setup-beam@v1
with:
otp-version: "26.0"
gleam-version: "1.11.0"

- name: Show Gleam version
run: gleam --version

- name: Cache Gleam deps and build
uses: actions/cache@v4
with:
path: |
~/.cache/gleam
~/.gleam
./_gleam_deps
./build
key: ${{ runner.os }}-gleam-1.11.0-cache-v1-${{ hashFiles('**/gleam.toml') }}
restore-keys: |
${{ runner.os }}-gleam-1.11.0-cache-v1-

- name: Install dependencies
run: gleam deps download

- name: Check formatting
run: gleam format --check src test

- name: Build project
run: gleam build

- name: Run unit tests
run: gleam test
81 changes: 81 additions & 0 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Integration tests

on:
workflow_dispatch:
push:
branches: ["main"]

jobs:
integration-tests:
runs-on: ubuntu-latest
concurrency:
group: integration-tests-${{ matrix.clickhouse_version }}
cancel-in-progress: false
timeout-minutes: 90
strategy:
matrix:
clickhouse_version: ["22.8", "23.7", "24.4", "latest"]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Cache Gleam build artifacts
uses: actions/cache@v4
with:
path: |
./build
~/.cache/gleam
key: ${{ runner.os }}-gleam-1.11.0-integration-${{ matrix.clickhouse_version }}-v1-${{ hashFiles('**/gleam.toml') }}

- name: Start ClickHouse ${{ matrix.clickhouse_version }} (docker-compose)
env:
CLICKHOUSE_IMAGE: clickhouse/clickhouse-server:${{ matrix.clickhouse_version }}
run: |
echo "Using CLICKHOUSE_IMAGE=$CLICKHOUSE_IMAGE"
# Ensure docker compose is available (Docker-hosted runners include docker)
docker compose pull clickhouse || true
docker compose up -d --remove-orphans clickhouse

- name: Wait for ClickHouse HTTP
run: |
echo "Waiting for ClickHouse HTTP on localhost:8123..."
for i in $(seq 1 60); do
if curl -sSf http://localhost:8123/ >/dev/null 2>&1; then
echo "ClickHouse is up"
break
fi
sleep 2
done

- name: Setup Erlang/OTP and Gleam
uses: erlef/setup-beam@v1
with:
otp-version: "26.0"
gleam-version: "1.11.0"

- name: Show Gleam version
run: gleam --version

- name: Run integration tests
env:
CLICKHOUSE_USER: test_user
CLICKHOUSE_PASSWORD: test_password
CLICKHOUSE_DB: test_db
CLICKHOUSE_URL: http://localhost:8123
run: |
gleam build
gleam test

- name: Upload integration test artifacts (logs)
if: failure()
uses: actions/upload-artifact@v4
with:
name: integration-logs-${{ matrix.clickhouse_version }}
path: |
./build/logs || true

- name: Cleanup ClickHouse
if: always()
run: |
docker compose down -v || true
65 changes: 65 additions & 0 deletions .github/workflows/smoke-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Smoke Integration (PR)

on:
pull_request:
branches: ["main"]

jobs:
smoke-integration:
name: Smoke Integration (ClickHouse)
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Start ClickHouse (docker-compose)
env:
CLICKHOUSE_IMAGE: clickhouse/clickhouse-server:23.7
run: |
echo "Using CLICKHOUSE_IMAGE=$CLICKHOUSE_IMAGE"
docker compose pull clickhouse || true
docker compose up -d --remove-orphans clickhouse

- name: Wait for ClickHouse HTTP
run: |
echo "Waiting for ClickHouse HTTP on localhost:8123..."
for i in $(seq 1 40); do
if curl -sSf http://localhost:8123/ >/dev/null 2>&1; then
echo "ClickHouse is up"
break
fi
sleep 2
done

- name: Smoke test - SELECT 1
run: |
set -e
OUT=$(curl -sS -u test_user:test_password "http://localhost:8123/?query=SELECT%201%20as%20result%20FORMAT%20JSONEachRow&database=test_db")
echo "Got: $OUT"
if [ "$OUT" != '{"result":1}' ] && [ "$OUT" != '{"result":1}\n' ]; then
echo "Unexpected response: $OUT"
exit 2
fi
Comment on lines +41 to +44

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

The literal \\n check is incorrect. The newline comparison won't work as expected because $OUT from curl won't contain a literal \\n string. Consider using grep or checking the content without the newline: echo \"$OUT\" | grep -q '{\"result\":1}' or strip trailing whitespace before comparison.

Suggested change
if [ "$OUT" != '{"result":1}' ] && [ "$OUT" != '{"result":1}\n' ]; then
echo "Unexpected response: $OUT"
exit 2
fi
echo "$OUT" | grep -q '{"result":1}' || {
echo "Unexpected response: $OUT"
exit 2
}

Copilot uses AI. Check for mistakes.

- name: Smoke test - create table, insert and select
run: |
set -e
DDL="CREATE TABLE IF NOT EXISTS smoke_test (id UInt32, name String) ENGINE = MergeTree() ORDER BY id"
curl -sS -u test_user:test_password -d "$DDL" "http://localhost:8123/?database=test_db"

INSERT="INSERT INTO smoke_test (id,name) VALUES (1,'smoke')"
curl -sS -u test_user:test_password -d "$INSERT" "http://localhost:8123/?database=test_db"

OUT=$(curl -sS -u test_user:test_password "http://localhost:8123/?query=SELECT%20*%20FROM%20smoke_test%20WHERE%20id%3D1%20FORMAT%20JSONEachRow&database=test_db")
echo "Select returned: $OUT"
if [ -z "$OUT" ]; then
echo "Select returned empty"
exit 3
fi

- name: Cleanup ClickHouse
if: always()
run: |
docker compose down -v || true
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,45 @@
<img src="assets/image.png" alt="Sparkling logo" width="240" />
</p>

[![License](https://img.shields.io/badge/license-Apache%202.0-yellow.svg)](LICENSE) [![Built with Gleam](https://img.shields.io/badge/Built%20with-Gleam-ffaff3)](https://gleam.run)
[![CI](https://github.com/lupodevelop/sparkling/actions/workflows/ci.yml/badge.svg)](https://github.com/lupodevelop/sparkling/actions/workflows/ci.yml) [![Integration Tests](https://github.com/lupodevelop/sparkling/actions/workflows/integration.yml/badge.svg)](https://github.com/lupodevelop/sparkling/actions/workflows/integration.yml) [![License](https://img.shields.io/badge/license-Apache%202.0-yellow.svg)](LICENSE) [![Built with Gleam](https://img.shields.io/badge/Built%20with-Gleam-ffaff3)](https://gleam.run) [![Gleam Version](https://img.shields.io/badge/gleam-%3E%3D1.11.0-ffaff3)](https://gleam.run)

**Sparkling** is a *lightweight*, **type-safe** data layer for **ClickHouse** written in Gleam. It provides a small, focused API for defining schemas, building queries, and encoding/decoding ClickHouse formats.

*No magic*, just small, composable functions that play nicely in Gleam apps.

> Why "Sparkling"? One rainy Tuesday a tiny inflatable rubber duck stole a shooting pink star and decided to become a freelance data wrangler, and it now guides queries through the night, humming 8-bit lullabies. Totally plausible.

## Quick start

See the extracted quick start example: `docs/quickstart.md` it contains a short walkthrough (define schema, build a query, execute it with a repo).

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

Missing punctuation after docs/quickstart.md. Should be: 'See the extracted quick start example: docs/quickstart.md. It contains a short walkthrough (define schema, build a query, execute it with a repo).'

Suggested change
See the extracted quick start example: `docs/quickstart.md` it contains a short walkthrough (define schema, build a query, execute it with a repo).
See the extracted quick start example: `docs/quickstart.md`. It contains a short walkthrough (define schema, build a query, execute it with a repo).

Copilot uses AI. Check for mistakes.

Minimal example:

```gleam
import sparkling/repo

let r = repo.new("http://localhost:8123")
|> repo.with_database("mydb")

case r.execute_sql(r, "SELECT 1 as result FORMAT JSONEachRow") {
Ok(body) -> io.println(body)
Error(_) -> io.println("query failed")
}
```

## What you'll find here

- `sparkling/schema` — typed table & column definitions
- `sparkling/query` — immutable query builder (to_sql)
- `sparkling/repo` — HTTP executor with retry hooks
- `sparkling/encode` / `sparkling/decode` — format handlers (JSONEachRow default)
- `sparkling/types` — helpers for Decimal, DateTime64, UUID, LowCardinality

For more examples see `docs/examples/` and `docs/quickstart.md`.

**Design note:** Sparkling's API and composable query builder were partly inspired by *Ecto*;
many ideas about schema definition and query composition borrow from its approach while keeping a small, Gleam-friendly surface.

## Development

Run tests and format/check locally:
Expand Down
25 changes: 25 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
services:
clickhouse:
# Image can be overridden via environment variable CLICKHOUSE_IMAGE.
# Use format: clickhouse/clickhouse-server:<version>
image: "${CLICKHOUSE_IMAGE:-clickhouse/clickhouse-server:latest}"
container_name: sparkling_clickhouse_test
ports:
- "8123:8123" # HTTP interface
- "9000:9000" # Native protocol (for future)
environment:
CLICKHOUSE_DB: test_db
CLICKHOUSE_USER: test_user
CLICKHOUSE_PASSWORD: test_password
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
volumes:
- clickhouse_data:/var/lib/clickhouse
healthcheck:
test: ["CMD", "clickhouse-client", "--query", "SELECT 1"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s

volumes:
clickhouse_data:
62 changes: 62 additions & 0 deletions test/TESTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Tests — Quick reference

This document explains how tests are organised in the repository and how to run them locally and in CI.

Directory layout

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

Missing markdown heading level indicator. This should be ## Directory layout to maintain proper heading hierarchy.

Suggested change
Directory layout
## Directory layout

Copilot uses AI. Check for mistakes.

- `test/sparkling/` — unit and component tests that do not require external services (fast).
- `test/smoke/` — smoke/sanity tests. Quick checks that the test runner and a minimal API behave as expected.
- `test/integration/` — integration tests that require external services (e.g. ClickHouse). These are slower and should not run on every PR.

Running tests locally

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

Missing markdown heading level indicator. This should be ## Running tests locally to maintain proper heading hierarchy.

Suggested change
Running tests locally
## Running tests locally

Copilot uses AI. Check for mistakes.

- Run the full test suite (if you have required services available):

```bash
# from the project root
gleam test
```

- Run unit tests only (recommended for PRs):

```bash
# if your test runner accepts paths:
gleam test test/sparkling
```

If your runner does not accept paths, run `gleam test` locally and ensure integration tests are not executed by default (integration tests should be placed under `test/integration/`).

- Run integration tests (requires ClickHouse or other external services):

```bash
# Start ClickHouse (example using Docker)
docker run -d --name clickhouse-server -p 8123:8123 -p 9000:9000 clickhouse/clickhouse-server:latest

# Run integration tests
gleam test test/integration

# When finished, stop/remove the container
docker stop clickhouse-server && docker rm clickhouse-server
```

Environment variables

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

Missing markdown heading level indicator. This should be ## Environment variables to maintain proper heading hierarchy.

Suggested change
Environment variables
## Environment variables

Copilot uses AI. Check for mistakes.

- Use environment variables to configure integration endpoints (example):

```bash
export CLICKHOUSE_URL=http://localhost:8123
```

Document required variables in `test/integration/README.md`.

CI guidance

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

Missing markdown heading level indicator. This should be ## CI guidance to maintain proper heading hierarchy.

Suggested change
CI guidance
## CI guidance

Copilot uses AI. Check for mistakes.

- Pull Requests: run unit tests only.
- Integration tests: run in separate CI jobs (manual trigger, nightly, or on release tags). Ensure the CI environment has access to required services and secrets before enabling integration jobs.

Best practices and PR checklist

Copilot AI Nov 9, 2025

Copy link

Choose a reason for hiding this comment

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

Missing markdown heading level indicator. This should be ## Best practices and PR checklist to maintain proper heading hierarchy.

Suggested change
Best practices and PR checklist
## Best practices and PR checklist

Copilot uses AI. Check for mistakes.

- [ ] Unit tests pass locally (`gleam test`).
- [ ] Integration tests are documented in `test/integration/README.md` with clear prerequisites and run instructions.
- [ ] Do not add CI workflows that perform sensitive operations (publishing) in the import PR. Add automation in a separate PR after the code is stable.
- [ ] Keep a lightweight smoke test under `test/smoke/` to validate the test runner and minimal API surface.
Loading
Loading