# 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 // ... } ``` 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)