diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1244f65a..516e3402 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -112,6 +112,57 @@ jobs: - name: Run All Integration Tests run: cargo test --test '*' --verbose + # Database integration tests against live PostgreSQL and MariaDB servers. + # SQLite database tests need no services and already run everywhere via + # `cargo test`; this job exercises the env-gated PostgreSQL/MariaDB paths. + database-tests: + name: Database Tests (PostgreSQL + MariaDB) + runs-on: ubuntu-latest + needs: fmt + timeout-minutes: 15 + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: wfl + POSTGRES_PASSWORD: wfl + POSTGRES_DB: wfl_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U wfl" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + mariadb: + image: mariadb:11 + env: + MARIADB_USER: wfl + MARIADB_PASSWORD: wfl + MARIADB_DATABASE: wfl_test + MARIADB_ROOT_PASSWORD: root + ports: + - 3306:3306 + options: >- + --health-cmd "healthcheck.sh --connect --innodb_initialized" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + WFL_TEST_POSTGRES_URL: postgres://wfl:wfl@localhost:5432/wfl_test + WFL_TEST_MYSQL_URL: mysql://wfl:wfl@localhost:3306/wfl_test + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + + - name: Cache Cargo registry and target directory + uses: Swatinem/rust-cache@v2 + with: + shared-key: database-tests + + - name: Run Database Tests + run: cargo test --test database_test --verbose + # Run WFL test programs to verify the interpreter works correctly run-wfl-programs: name: Run WFL Programs @@ -414,7 +465,7 @@ jobs: bump-version: name: Bump Version runs-on: ubuntu-latest - needs: [fmt, clippy-and-test, integration-tests, run-wfl-programs] + needs: [fmt, clippy-and-test, integration-tests, database-tests, run-wfl-programs] if: github.event_name == 'push' && github.ref == 'refs/heads/main' permissions: contents: write diff --git a/CHANGELOG.md b/CHANGELOG.md index ba08c6db..380a2680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Request objects from `wait for request` now carry `method`, `path`, `client_ip`, `body` and `headers` properties (in addition to the existing standalone variables) - Errors in executed files (missing file, parse errors, runtime errors) are catchable in the parent with `try`/`when`, including `when file not found` - Nesting depth guard (4 levels) protects against a file that executes itself +- Built-in database support for SQLite, PostgreSQL, and MariaDB/MySQL backed by sqlx connection pooling: + - `open database at "" as db` (alias: `connect to database at ... as ...`) routed by URL scheme (`sqlite://`, `sqlite::memory:`, `postgres://`, `postgresql://`, `mysql://`, `mariadb://`) + - `store rows as query db with "" [and parameters [...]]` returns a list of row objects keyed by column name + - `store result as execute db with "" [and parameters [...]]` returns `{affected_rows, last_insert_id}` (`last_insert_id` is `nothing` on PostgreSQL — use `RETURNING`) + - `close database db` + - Parameters always bind through the database driver (never string interpolation), so SQL injection via values is not possible; placeholders are driver-native (`?` for SQLite/MariaDB, `$1` for PostgreSQL) + - Type-aware decoding: integers/floats/decimals → number, `NULL` → `nothing`, `BOOLEAN` → boolean, `BLOB`/`BYTEA` → binary, `DATE`/`TIME`/`TIMESTAMP` → date/time/datetime + - Database errors are catchable with `try`/`when error` + - Note: `store as query with ...` and `store as execute with ...` are now reserved statement shapes; a multi-word variable whose name starts with the word `query`, followed by a `with` concatenation, would previously have parsed as an expression +- Web route parameter helpers in the standard library: + - `path_params of and "