docs(quick-261001-ddh): record per-query collation decision superseding D-06
This commit is contained in:
31
.planning/notes/lagoon-per-query-collation.md
Normal file
31
.planning/notes/lagoon-per-query-collation.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Decision: Per-query collation in lagoon.OrderBy
|
||||
|
||||
**Date:** 2026-10-01
|
||||
**Status:** Accepted
|
||||
**Supersedes:** Phase 3 D-06 (database-level ICU `pl-PL` locale, implemented by `d0d8450` and `56be82f`)
|
||||
|
||||
## Decision
|
||||
|
||||
`lagoon.OrderBy` takes variadic `lagoon.OrderOption` values. `lagoon.Collate(name)` makes it emit `ORDER BY <column> COLLATE "<name>" ASC|DESC`. The collation name is validated (1-63 ASCII bytes; letters, digits and `_` first, then letters, digits, `_`, `-`, `.`, `@`) and emitted as a double-quoted identifier. An invalid name is an error returned before any SQL is built. Without the option, `OrderBy` emits exactly the clause it emitted before.
|
||||
|
||||
lagoon no longer checks the database locale. `lagoon.Open` and `lagoon.Use` only ping. The exported `CheckLocale` is removed: it was pre-v1 API, and nothing called it directly (the application's tests only asserted its error through `Open`/`Use`). No config key replaces it.
|
||||
|
||||
## Rationale
|
||||
|
||||
- The framework must know nothing about the application. The database-level requirement made every SummerCMS application, framework test container and docs example provision a Polish ICU database because one application's PHP original sorted four name columns with `COLLATE utf8mb4_polish_ci`.
|
||||
- `pl-x-icu` ships with any ICU-enabled PostgreSQL, including `postgres:16-alpine`, so a per-query collation needs no database setup.
|
||||
- The collation now sits where PHP applied `COLLATE utf8mb4_polish_ci`: the `PolishOrder::apply` call site.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Framework tests, READMEs and docs use plain PostgreSQL databases. `docs/database/queries-and-pagination.md` gains a "Sorting with a collation" section backed by `ExampleCollate`. `scripts/check-phase8.sh` keeps its ICU `pl-PL` initdb args because that gate boots the application, and its comment was corrected. Framework commit: `037dc53` (summercms.go).
|
||||
- The application passes `lagoon.Collate("pl-x-icu")` at the genre list call site. A libc `C` database test shows the Polish order comes from the query and not from the database. The application still recommends an ICU `pl-PL` default for queries without an explicit collation. Application commit: `2da9482` (fonoteka.go).
|
||||
|
||||
## Follow-ups
|
||||
|
||||
- Add a collated expression index on the PolishOrder columns once those tables grow. It helps only when it is built with the same collation (`pl-x-icu`).
|
||||
- The other three PolishOrder columns (`golem15_fonoteka_albums.artist_display`, `golem15_fonoteka_albums.name`, `golem15_fonoteka_artists.name`) adopt `lagoon.Collate` when their list endpoints are ported.
|
||||
- Drop ICU `pl-PL` from the application's test containers in a cleanup pass. They are harmless but no longer needed.
|
||||
- The application's admin relation option query (`albums_admin_controller.go`, `ORDER BY name ASC, id ASC`) orders by name without a collation, so it follows the database default. Decide whether it should pass the Polish collation.
|
||||
- ICU Polish and MariaDB `utf8mb4_polish_ci` can still differ on edge cases. That is why the recorded fixture-order parity test stays.
|
||||
- Execution deviations are recorded in `.planning/quick/261001-ddh-replace-lagoon-hardcoded-icu-pl-pl-datab/261001-ddh-SUMMARY.md`.
|
||||
@@ -23,7 +23,7 @@ Not in this phase: any other route (including `POST genres`), register/login/ref
|
||||
- **D-03:** Active-collection resolution lives in the `golem15.fonoteka` plugin as a small `ActiveCollection` service porting only the default path of `ActiveCollectionResolver.php` (stored active collection, else the user's own). Handlers call it the way PHP controllers call `context()`. It is not a framework middleware; the pipeline's org-context stage stays a generic slot. Phase 12 extends the service with switching and editor membership.
|
||||
- **D-04:** The handler is ported whole: `?non_empty` is validated (`nullable`, in `0|1`), bad values return PHP's 422 envelope (`{"error":"Validation failed","errors":{...}}`), and `non_empty=1` filters to genres with a positive count. No new fixtures are recorded for these; they are covered by Go tests with the expected bodies read from the PHP source.
|
||||
- **D-05:** The non-zero count path and access scoping are proven by a testcontainers integration test in `fonoteka.go` alongside the parity test: insert albums for alice in her active collection, albums in a collection she cannot access, assert per-genre counts, `non_empty=1` filtering and that foreign albums are not counted. The parity fixture stays exactly as recorded; the PHP recorder is not re-run in this phase.
|
||||
- **D-06:** The Postgres equivalent of `PolishOrder` is a database-level Polish ICU locale, confirmed by the user at plan review on 2026-09-17 after comparing query-level, column-level, and database-level options. Create the application database with ICU locale `pl-PL` before migrations; configure testcontainers to create the same kind of database and fail startup if the connected database uses a different provider or locale. A reusable `lagoon` ordering helper is still called at the same handler call site as PHP's `PolishOrder::apply`, validating the allowed column and direction while relying on the database default. Every later list endpoint can use that helper.
|
||||
- **D-06:** The Postgres equivalent of `PolishOrder` is a database-level Polish ICU locale, confirmed by the user at plan review on 2026-09-17 after comparing query-level, column-level, and database-level options. Create the application database with ICU locale `pl-PL` before migrations; configure testcontainers to create the same kind of database and fail startup if the connected database uses a different provider or locale. A reusable `lagoon` ordering helper is still called at the same handler call site as PHP's `PolishOrder::apply`, validating the allowed column and direction while relying on the database default. Every later list endpoint can use that helper. **Superseded 2026-10-01** by .planning/notes/lagoon-per-query-collation.md: the Polish order is now a per-query COLLATE "pl-x-icu" via lagoon.Collate, and lagoon no longer checks the database locale.
|
||||
|
||||
### JWT guard
|
||||
- **D-07:** Verification is real; only issuing is throwaway. The guard validates HS256 with the algorithm pinned (no `alg` from the token header, `none` rejected), checks `exp` and `sub`, loads the user row by `sub` from the minimal `users` table, and places the user in the request context. A token for a missing user is rejected. Phase 7 replaces how tokens are minted and keeps this verifier.
|
||||
|
||||
Reference in New Issue
Block a user