Files
lux/docs/SQL_DESIGN_ANALYSIS.md
Brandon Lucas 7e76acab18 feat: rebuild website with full learning funnel
Website rebuilt from scratch based on analysis of 11 beloved language
websites (Elm, Zig, Gleam, Swift, Kotlin, Haskell, OCaml, Crystal, Roc,
Rust, Go).

New website structure:
- Homepage with hero, playground, three pillars, install guide
- Language Tour with interactive lessons (hello world, types, effects)
- Examples cookbook with categorized sidebar
- API documentation index
- Installation guide (Nix and source)
- Sleek/noble design (black/gold, serif typography)

Also includes:
- New stdlib/json.lux module for JSON serialization
- Enhanced stdlib/http.lux with middleware and routing
- New string functions (charAt, indexOf, lastIndexOf, repeat)
- LSP improvements (rename, signature help, formatting)
- Package manager transitive dependency resolution
- Updated documentation for effects and stdlib
- New showcase example (task_manager.lux)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-16 23:05:35 -05:00

331 lines
8.4 KiB
Markdown

# SQL in Lux: Built-in Effect vs Package
## Executive Summary
This document analyzes whether SQL database access should be a built-in language feature (as it currently is) or a separate package. After comparing approaches across 12+ languages, the recommendation is:
**Keep SQL as a built-in effect, but refactor the implementation to be more modular.**
## Current Implementation
Lux currently implements SQL as a built-in effect:
```lux
fn main(): Unit with {Console, Sql} = {
let db = Sql.openMemory()
Sql.execute(db, "CREATE TABLE users (...)")
let users = Sql.query(db, "SELECT * FROM users")
Sql.close(db)
}
```
The implementation uses rusqlite (SQLite) compiled directly into the Lux binary.
## How Other Languages Handle Database Access
### Languages with Built-in Database Support
| Language | Approach | Notes |
|----------|----------|-------|
| **Python** | `sqlite3` in stdlib | Most languages have SQLite in stdlib |
| **Ruby** | `sqlite3` gem + AR are common | ActiveRecord is de facto standard |
| **Go** | `database/sql` interface in stdlib | Drivers are packages |
| **Elixir** | Ecto as separate package | But universally used |
| **PHP** | PDO in core | Multiple backends |
### Languages with Package-Only Database Support
| Language | Approach | Notes |
|----------|----------|-------|
| **Rust** | rusqlite, diesel, sqlx packages | No stdlib database |
| **Node.js** | pg, mysql2, better-sqlite3 | Packages only |
| **Haskell** | postgresql-simple, persistent | Packages only |
| **OCaml** | caqti, postgresql-ocaml | Packages only |
### Analysis of Each Approach
#### Go's Model: Interface in Stdlib + Driver Packages
```go
import (
"database/sql"
_ "github.com/lib/pq" // PostgreSQL driver
)
db, _ := sql.Open("postgres", "...")
rows, _ := db.Query("SELECT * FROM users")
```
**Pros:**
- Standard interface for all databases
- Type-safe at compile time
- Drivers are swappable
**Cons:**
- Requires understanding interfaces
- Need external packages for actual database
#### Python's Model: SQLite in Stdlib
```python
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
c.execute('SELECT * FROM users')
```
**Pros:**
- Zero dependencies for getting started
- Great for learning/prototyping
- Always available
**Cons:**
- Other databases need packages
- stdlib vs package API differences
#### Rust's Model: Everything is Packages
```rust
use rusqlite::{Connection, Result};
fn main() -> Result<()> {
let conn = Connection::open("test.db")?;
conn.execute("CREATE TABLE users (...)", [])?;
Ok(())
}
```
**Pros:**
- Minimal core language
- Best-in-class implementations
- Clear ownership
**Cons:**
- Cargo.toml management
- Version conflicts possible
- Learning curve for package ecosystem
#### Elixir's Model: Strong Package Ecosystem
```elixir
# Ecto is technically a package but universally used
Repo.all(from u in User, where: u.age > 18)
```
**Pros:**
- Best API emerges naturally
- Core team can focus on language
- Community ownership
**Cons:**
- Package can become outdated
- Multiple competing solutions
## Arguments For Built-in SQL
### 1. Effect System Integration
The most compelling argument: **SQL fits naturally into Lux's effect system.**
```lux
// The effect signature documents database access
fn fetchUser(id: Int): User with {Sql} = { ... }
// Handlers enable testing without mocks
handler testDatabase(): Sql { ... }
```
This is harder to achieve with packages - they'd need to integrate deeply with the effect system.
### 2. Zero-Dependency Getting Started
New users can immediately:
- Follow tutorials that use databases
- Build real applications
- Learn effects with practical examples
```bash
lux run database_example.lux
# Just works - no package installation
```
### 3. Guaranteed API Stability
Built-in effects have stable, documented APIs. Package APIs can change between versions.
### 4. Teaching Functional Effects
SQL is an excellent teaching example for effects:
- Clear side effects (I/O to database)
- Handler swapping for testing
- Transaction scoping
### 5. Practical Utility
90%+ of real applications need database access. Making it trivial benefits most users.
## Arguments For SQL as Package
### 1. Smaller Binary Size
rusqlite adds significant binary size (~2-3MB). Package-based approach lets users opt-in.
### 2. Database Backend Choice
Currently locked to SQLite. A package ecosystem could offer:
- `lux-sqlite`
- `lux-postgres`
- `lux-mysql`
- `lux-mongodb`
### 3. Faster Core Language Evolution
Core team focuses on language; community builds integrations.
### 4. Better Specialization
Dedicated package maintainers might build better database tooling than core team.
### 5. Multiple Competing Implementations
Competition drives quality. The best SQL package wins adoption.
## Comparison Matrix
| Factor | Built-in | Package |
|--------|----------|---------|
| Effect integration | Excellent | Needs design work |
| Learning curve | Low | Medium |
| Binary size | Larger | User controls |
| Database options | Limited | Unlimited |
| API stability | Guaranteed | Version-dependent |
| Getting started | Instant | Requires install |
| Testing story | Built-in handlers | Package-specific |
| Maintenance burden | Core team | Community |
## Recommendation
### Keep SQL as Built-in Effect, With Changes
**Rationale:**
1. **Effect system is Lux's differentiator** - SQL showcases it perfectly
2. **Practicality matters** - 90% of apps need databases
3. **Teaching value** - SQL is ideal for learning effects
4. **Handler testing** - Built-in integration enables powerful testing
### Proposed Architecture
```
Core Lux
├── Sql effect (interface only)
│ ├── open/close
│ ├── execute/query
│ └── transaction operations
└── Default SQLite handler (built-in)
└── Uses rusqlite
Future packages (optional)
├── lux-postgres -- PostgreSQL handler
├── lux-mysql -- MySQL handler
└── lux-redis -- Redis (key-value, not Sql)
```
### Specific Changes to Consider
1. **Make SQLite compilation optional**
```toml
# Cargo.toml
[features]
default = ["sqlite"]
sqlite = ["rusqlite"]
```
2. **Define stable Sql effect interface**
```lux
effect Sql {
fn open(path: String): SqlConn
fn close(conn: SqlConn): Unit
fn execute(conn: SqlConn, sql: String): Int
fn query(conn: SqlConn, sql: String): List<SqlRow>
// ...
}
```
3. **Allow package handlers to implement Sql**
```lux
// In lux-postgres package
handler postgresHandler(connStr: String): Sql { ... }
// Usage
run myApp() with {
Sql -> postgresHandler("postgres://...")
}
```
4. **Add connection pooling to core**
Important for production, should be standard.
## Comparison to Similar Decisions
### Console Effect
Console is built-in. Nobody questions this because:
- Universally needed
- Simple interface
- Hard to get wrong
SQL is similar but more complex.
### HTTP Effect
HTTP client is built-in in Lux. This was the right call because:
- Most apps need HTTP
- Complex to implement well
- Effect system integration important
SQL follows same reasoning.
### File Effect
File I/O is built-in. Same rationale applies.
## What Other Effect-System Languages Do
| Language | Database | Built-in? |
|----------|----------|-----------|
| **Koka** | No database support | N/A |
| **Eff** | No database support | N/A |
| **Frank** | No database support | N/A |
| **Unison** | Abilities + packages | Both |
Lux is pioneering practical effects. Built-in SQL makes sense.
## Conclusion
SQL should remain a built-in effect in Lux because:
1. It demonstrates the power of effects for real-world use
2. It enables the handler-based testing story
3. It removes friction for most applications
4. It serves as a teaching example for effects
However, the implementation should evolve to:
- Support multiple database backends via handlers
- Make SQLite optional for minimal binaries
- Provide connection pooling
- Add parameterized query support
This hybrid approach gives users the best of both worlds: immediate productivity with built-in SQLite, and flexibility through package-provided handlers for other databases.
---
## Future Work
1. **Parameterized queries** - Critical for SQL injection prevention
2. **Connection pooling** - Required for production servers
3. **PostgreSQL handler** - Most requested database
4. **Migration support** - Schema evolution tooling
5. **Type-safe queries** - Compile-time SQL checking (ambitious)