Skip to content

Commit 1ebf241

Browse files
authored
Merge pull request #1 from lupodevelop/import/sparkling-lib
Import/sparkling lib workflows MUST be added in a separate PR
2 parents 15ab503 + bd2011e commit 1ebf241

38 files changed

Lines changed: 5746 additions & 0 deletions

CHANGELOG.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Added
11+
12+
- (Unreleased changes go here)
13+
14+
## [0.1.0] - 2025-11-09
15+
16+
### Added
17+
18+
- Initial release of sparkling — Gleam ClickHouse client
19+
- Type-safe query builder with composable DSL
20+
- Schema reflection and metadata discovery
21+
- Multiple format support (JSONEachRow, TabSeparated, CSV)
22+
- Changeset validation (Ecto-style)
23+
- HTTP resilience with exponential backoff retry
24+
- Hook system for extensibility and observability
25+
- Comprehensive test suite (unit + integration)
26+
- Docker setup for testing with ClickHouse
27+
- Query Module: composable SELECT/INSERT queries with type safety
28+
- Schema Module: table/column reflection via DESCRIBE queries
29+
- Repo Module: HTTP execution with authentication and error handling
30+
- Encode/Decode: format registry with pluggable handlers
31+
- Changeset: data validation and casting pipeline
32+
- Retry: automatic failure recovery with configurable backoff
33+
34+
### Supported ClickHouse types
35+
36+
- Primitives: UInt/Int 8/16/32/64, Float32/64, String, Bool
37+
- Date/Time: Date, Date32, DateTime, DateTime64
38+
- Complex: Array(T), Tuple(...), Map(K,V), Nested
39+
- Special: UUID, Enum8/16, Nullable(T), Decimal
40+
41+
### Architecture
42+
43+
- HTTP-only communication (no native protocol)
44+
- Zero external dependencies
45+
- Immutable, functional design
46+
- Hook-based extensibility
47+
- Comprehensive error handling
48+
49+
### Testing
50+
51+
- Unit tests with HTTP mocking
52+
- Integration tests with ClickHouse Docker
53+
- Round-trip tests for type safety
54+
- CI/CD with GitHub Actions
55+
56+
### Documentation
57+
58+
- API reference and examples
59+
- Quick start guide
60+
- Style guide for contributions
61+
62+
<!-- Link references for versions -->
63+
[Unreleased]: https://github.com/lupodevelop/sparkling/compare/v0.1.0...HEAD
64+
[0.1.0]: https://github.com/lupodevelop/sparkling/releases/tag/v0.1.0

CONTRIBUTING.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Contributing to Sparkling
2+
3+
Thanks for your interest in contributing! A quick guide to help you get started.
4+
5+
1. Fork the repository and create a feature branch from `main`.
6+
2. Follow the existing code style (run `gleam format --check` locally).
7+
3. Add unit tests when you add new behavior (`gleam test`).
8+
4. Open a Pull Request describing the change and link any relevant issues.
9+
10+
PR checklist
11+
- [ ] Code follows style (`gleam format --check`)
12+
- [ ] Tests added/updated and passing (`gleam test`)
13+
- [ ] Documentation updated if needed (README, docs/)
14+
15+
Maintainers will review PRs and request changes when necessary.

docker-compose.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
services:
2+
clickhouse:
3+
# Image can be overridden via environment variable CLICKHOUSE_IMAGE.
4+
# Use format: clickhouse/clickhouse-server:<version>
5+
image: "${CLICKHOUSE_IMAGE:-clickhouse/clickhouse-server:latest}"
6+
container_name: sparkling_clickhouse_test
7+
ports:
8+
- "8123:8123" # HTTP interface
9+
- "9000:9000" # Native protocol (for future)
10+
environment:
11+
CLICKHOUSE_DB: test_db
12+
CLICKHOUSE_USER: test_user
13+
CLICKHOUSE_PASSWORD: test_password
14+
CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT: 1
15+
volumes:
16+
- clickhouse_data:/var/lib/clickhouse
17+
healthcheck:
18+
test: ["CMD", "clickhouse-client", "--query", "SELECT 1"]
19+
interval: 5s
20+
timeout: 3s
21+
retries: 5
22+
start_period: 10s
23+
24+
volumes:
25+
clickhouse_data:

docs/examples/decode_examples.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Examples extracted from sparkling/src/sparkling/decode.gleam
2+
3+
## decode_json_each_row_streaming (usage example)
4+
5+
```gleam
6+
let count = decode_json_each_row_streaming(
7+
large_response,
8+
my_decoder,
9+
fn(user) { io.println("Processing: " <> user.name) }
10+
)
11+
```

docs/examples/schema_examples.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Examples extracted from sparkling/src/sparkling/schema.gleam
2+
3+
## FieldType examples
4+
5+
```gleam
6+
UInt32
7+
Nullable(of: String)
8+
Array(of: Int32)
9+
Decimal(precision: 18, scale: 2)
10+
DateTime64(precision: 3)
11+
```
12+
13+
## table/field examples
14+
15+
```gleam
16+
table("users", [
17+
field("id", UInt64),
18+
field("email", String),
19+
field("created_at", DateTime64(3)),
20+
])
21+
22+
field("user_id", UInt64)
23+
field("name", Nullable(of: String))
24+
```
25+
26+
## field_type_to_sql examples
27+
28+
```gleam
29+
field_type_to_sql(UInt32)
30+
// => "UInt32"
31+
32+
field_type_to_sql(Nullable(of: String))
33+
// => "Nullable(String)"
34+
35+
field_type_to_sql(Array(of: Int32))
36+
// => "Array(Int32)"
37+
```
38+
39+
## to_create_table_sql example
40+
41+
```gleam
42+
let users_table = table("users", [
43+
field("id", UInt64),
44+
field("email", String),
45+
])
46+
47+
to_create_table_sql(users_table, engine: "MergeTree()")
48+
// => "CREATE TABLE users (id UInt64, email String) ENGINE = MergeTree()"
49+
```
50+
51+
## find_field / field_names examples
52+
53+
```gleam
54+
let tbl = table("users", [field("id", UInt64)])
55+
find_field(tbl, "id")
56+
// => Some(Field("id", UInt64))
57+
58+
find_field(tbl, "unknown")
59+
// => None
60+
61+
let tbl = table("users", [field("id", UInt64), field("name", String)])
62+
field_names(tbl)
63+
// => ["id", "name"]
64+
```

docs/examples/types_examples.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Examples extracted from sparkling/src/sparkling/types.gleam
2+
3+
## Decimal examples
4+
5+
```gleam
6+
let price = Decimal("123.45")
7+
let amount = Decimal("0.000001")
8+
```
9+
10+
## DateTime64 example
11+
12+
```gleam
13+
let timestamp = DateTime64("2024-01-15 10:30:45.123", 3, Some("UTC"))
14+
```
15+
16+
## UUID example
17+
18+
```gleam
19+
let id = UUID("550e8400-e29b-41d4-a716-446655440000")
20+
```
21+
22+
## LowCardinality example
23+
24+
```gleam
25+
let status = low_cardinality_string("active")
26+
```
27+
28+
## Enum example
29+
30+
```gleam
31+
let status = Enum8([#("active", 1), #("inactive", 2)])
32+
```

docs/quickstart.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Quick Start
2+
3+
This quick start shows a typical workflow using Sparkling's API in Gleam.
4+
5+
```gleam
6+
import sparkling/query
7+
import sparkling/schema
8+
import sparkling/types
9+
10+
// 1. Define your schema
11+
let users_table = schema.table("users", [
12+
schema.field("id", schema.UInt32),
13+
schema.field("name", schema.String),
14+
schema.field("email", schema.String),
15+
schema.field("created_at", schema.DateTime64),
16+
])
17+
18+
// 2. Create a repository
19+
let repo = repo.new("http://localhost:8123")
20+
|> repo.with_database("mydb")
21+
22+
// 3. Build and execute queries
23+
let query = query.from(users_table)
24+
|> query.select([expr.field("id"), expr.field("name")])
25+
|> query.where_(expr.gt(expr.field("age"), expr.value("18")))
26+
|> query.limit(10)
27+
28+
// 4. Execute with repo
29+
let sql = query.to_sql(query)
30+
case repo.execute_sql(repo, sql) {
31+
Ok(result) -> // parse result
32+
Error(err) -> // handle error
33+
}
34+
```
35+
36+
## Related examples
37+
- `docs/examples/schema_examples.md`
38+
- `docs/examples/types_examples.md`
39+
- `docs/examples/decode_examples.md`

gleam.toml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name = "sparkling"
2+
version = "0.1.0"
3+
description = "A fast, type-safe ClickHouse client for Gleam with composable queries"
4+
licenses = ["Apache-2.0"]
5+
authors = ["Scaratti Daniele aka lupodevelp"]
6+
repository = { type = "github", user = "lupodevelop", repo = "sparkling" }
7+
links = [
8+
{ title = "GitHub", href = "https://github.com/lupodevelop/sparkling" }
9+
]
10+
#
11+
# For a full reference of all the available options, you can have a look at
12+
# https://gleam.run/writing-gleam/gleam-toml/.
13+
14+
[dependencies]
15+
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
16+
gleam_json = ">= 3.0.0 and < 4.0.0"
17+
gleam_http = ">= 4.0.0 and < 5.0.0"
18+
gleam_httpc = ">= 4.0.0 and < 5.0.0"
19+
gleam_erlang = "~> 1.0"
20+
21+
[dev-dependencies]
22+
gleeunit = ">= 1.0.0 and < 2.0.0"
23+
24+
[package]
25+
files = ["src", "README.md", "CHANGELOG.md", "LICENSE"]

manifest.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# This file was generated by Gleam
2+
# You typically do not need to edit this file
3+
4+
packages = [
5+
{ name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" },
6+
{ name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" },
7+
{ name = "gleam_httpc", version = "4.1.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C670EBD46FC1472AD5F1F74F1D3938D1D0AC1C7531895ED1D4DDCB6F07279F43" },
8+
{ name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" },
9+
{ name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" },
10+
{ name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" },
11+
]
12+
13+
[requirements]
14+
gleam_erlang = { version = "~> 1.0" }
15+
gleam_http = { version = ">= 4.0.0 and < 5.0.0" }
16+
gleam_httpc = { version = ">= 4.0.0 and < 5.0.0" }
17+
gleam_json = { version = ">= 3.0.0 and < 4.0.0" }
18+
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
19+
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }

src/sparkling.gleam

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Core imports (must come before re-exports)
2+
/// Sparkling - An Ecto-like data layer for ClickHouse in Gleam
3+
///
4+
/// This library provides a type-safe, composable API for working with ClickHouse:
5+
/// - Schema definitions with typed tables and fields
6+
/// - Query builder DSL for SELECT/INSERT operations
7+
/// - Repository pattern with HTTP transport
8+
/// - Encode/decode for multiple formats (JSONEachRow, TabSeparated, CSV)
9+
/// - Changeset validation pipeline
10+
/// - Support for complex ClickHouse types (Decimal, DateTime64, Array, Map, UUID, etc.)
11+
///
12+
/// Examples: docs/quickstart.md
13+
///
14+
/// # Main Modules
15+
///
16+
/// - `sparkling/schema` - Table and field definitions
17+
/// - `sparkling/expr` - SQL expression AST
18+
/// - `sparkling/query` - Query builder DSL
19+
/// - `sparkling/repo` - Repository with HTTP transport
20+
/// - `sparkling/encode` - Encoding to ClickHouse formats
21+
/// - `sparkling/decode` - Decoding from ClickHouse formats
22+
/// - `sparkling/changeset` - Validation pipeline
23+
/// - `sparkling/types` - Complex ClickHouse types (Decimal, DateTime64, etc.)
24+
/// - `sparkling/format` - Format handlers registry
25+
import sparkling/changeset
26+
import sparkling/expr
27+
import sparkling/format
28+
import sparkling/query
29+
import sparkling/repo
30+
import sparkling/schema
31+
import sparkling/types
32+
33+
// Re-export main types for convenience
34+
pub type Repo =
35+
repo.Repo
36+
37+
pub type RepoError =
38+
repo.RepoError
39+
40+
pub type Query =
41+
query.Query
42+
43+
pub type Table =
44+
schema.Table
45+
46+
pub type Field =
47+
schema.Field
48+
49+
pub type FieldType =
50+
schema.FieldType
51+
52+
pub type Expr =
53+
expr.Expr
54+
55+
pub type Changeset(a) =
56+
changeset.Changeset(a)
57+
58+
pub type FormatHandler =
59+
format.FormatHandler
60+
61+
// Re-export common complex types
62+
pub type Decimal =
63+
types.Decimal
64+
65+
pub type DateTime64 =
66+
types.DateTime64
67+
68+
pub type UUID =
69+
types.UUID
70+
71+
pub type LowCardinality =
72+
types.LowCardinality
73+
74+
pub type Enum8 =
75+
types.Enum8
76+
77+
pub type Enum16 =
78+
types.Enum16

0 commit comments

Comments
 (0)