Compare commits

..
11 Commits
Author SHA1 Message Date
CI 48a627c952 Bump version to 1.1.0-RC.2 [skip ci] 2026-11-10 13:08:02 +00:00
james.maes 8f04068371 Merge pull request 'fix(upload-journal): accept Amazon Aurora PostgreSQL (full_page_writes=off) [release/1.1]' (#64) from fix/aurora-full-page-writes-1.1 into release/1.1
build / maven (push) Successful in 7m59s
maven / build (push) Successful in 7m59s
2026-11-10 13:00:13 +00:00
james.maesandClaude Fable 5.2 c6edde7339 fix(upload-journal): accept Amazon Aurora PostgreSQL (full_page_writes=off)
build / maven (push) Successful in 8m25s
maven / build (push) Successful in 8m26s
The PostgreSQL 17 journal qualification refused every call on Amazon Aurora
PostgreSQL, which reports full_page_writes=off and does not expose the
parameter in its cluster parameter group.

- The settings query gains an eleventh column,
  to_regprocedure('aurora_version()') IS NOT NULL, which is true on Aurora,
  false on stock PostgreSQL 17 and never raises.
- full_page_writes=off is accepted only when that column is true. Stock
  PostgreSQL with full_page_writes=off is still refused, and synchronous_commit,
  fsync, recovery, isolation, time zone and lock timeout are unchanged on Aurora.
- The decision is the pure function tornPagesPrevented, covered by an always-on
  unit test. The opt-in real PostgreSQL 17 suite simulates Aurora with a
  function named aurora_version() and full_page_writes=off on the server.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-09 20:21:43 -06:00
CI fbccd185df Bump version to 1.1.0-RC.1 [skip ci] 2026-11-06 20:26:33 +00:00
james.maes 1c6ae14f59 fix(security): check parent-table read permission with a real GetInput (#62)
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 13s
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (push) Successful in 14s
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 10s
build / maven (push) Successful in 15m15s
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 15m15s
2026-11-06 19:30:40 +00:00
James Maes 9248f3f32c test: keep develop's CollaborationApiHttpTest unchanged
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (pull_request) Successful in 11s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 10s
build / maven (push) Successful in 10m50s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
maven / build (push) Successful in 10m51s
2026-11-06 13:16:55 -06:00
James MaesandClaude Fable 5.1 468dc42751 fix(security): check parent-table read permission with a real GetInput
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 539bec410f7926fec3ef7fabe51934a91e0248be] (pull_request) Successful in 11s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 8s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 8s
build / maven (push) Successful in 11m14s
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
maven / build (push) Successful in 11m14s
ParentRecordAccess.canReadTable passed a bare AbstractActionInput to
PermissionsHelper.hasTablePermission, so the READ check did not run as an actual
table read. Pass a GetInput for the table instead, which is what a host's table
permission rules evaluate. Ported from the GG Forge parent-read qualification.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 13:16:06 -06:00
james.maes 73681b3a2e Merge pull request 'ci: pin shared workflow to v1' (#60) from ci/pin-v1-develop into develop
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 14s
QRun-IO/ci-policy: native / native-result (push) Successful in 12s
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Waiting to run
build / maven (push) Successful in 9m44s
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 9m44s
2026-11-06 16:04:53 +00:00
james.maes 483dc271fe Merge pull request 'fix(custom-fields): reject over-long STRING values and reload help after publication' (#61) from fix/custom-field-length-and-help-20261106 into develop
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (push) Successful in 10s
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 8s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-voyage (push) Skipped
QRun-IO/ci-policy: ci / original-cli (push) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (push) Skipped
QRun-IO/ci-policy: ci / ci-result (push) Successful in 8s
build / maven (push) Successful in 10m55s
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
maven / build (push) Successful in 10m55s
2026-11-06 15:32:01 +00:00
james.maesandClaude Fable 5.1 ef625ca300 fix(custom-fields): reject over-long STRING values and reload help after publication
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 7s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 6s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
build / maven (push) Successful in 5m44s
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 6s
maven / build (push) Successful in 5m44s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
Bounded STRING custom fields now carry QQQ's native ValueTooLongBehavior.ERROR,
so a write longer than maxLength is rejected with a field error and the stored
value is preserved. The QQQ default is PASS_THROUGH, which let over-long values
through unchecked.

After the runtime metadata overlay refreshes (immediately, or in the after-commit
callback of the originating transaction), persisted help content is reloaded so
sections and fields created by publishing a definition receive their stored
helpContent rows without a restart.

Two CustomFieldsTest regressions cover invalid-write preservation and stored
help on a newly published section. Carried locally by GG Forge as
collaboration-custom-fields.patch (supersedes collaboration-field-length.patch).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: James Maes <james@kof22.com>
2026-11-06 08:39:48 -06:00
james.maesandClaude Opus 5.5 6060b737c5 ci: pin shared workflow to v1
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
build / maven (push) Successful in 3m35s
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / Scoped workflow [workflow 1e25bb5a243dfdcd95d4b9a4f780200ff8c8778a] (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / original-voyage (pull_request) Skipped
QRun-IO/ci-policy: ci / original-cli (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: ci / shared-pipeline (pull_request) Skipped
QRun-IO/ci-policy: ci / ci-result (pull_request) Waiting to run
maven / build (push) Successful in 3m36s
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 14:06:32 +00:00
11 changed files with 340 additions and 11 deletions

No files matched your search

+1 -1
View File
@@ -3,5 +3,5 @@ on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@main
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
+3
View File
@@ -29,6 +29,9 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
### Fixed
- The retained upload journal's PostgreSQL 17 qualification accepts Amazon Aurora PostgreSQL, which reports `full_page_writes=off` and does not let a deployment change it. `off` is accepted only when the settings query detects Aurora (`to_regprocedure('aurora_version()') IS NOT NULL`); stock PostgreSQL with `full_page_writes=off` is still refused, and every other requirement is unchanged on Aurora. See `docs/RETAINED-UPLOAD-JOURNAL.md`.
- Bounded STRING custom fields carry QQQ's native `ValueTooLongBehavior.ERROR`, so a write longer than `maxLength` is rejected with a field error and the stored value is preserved, instead of passing through unchecked (the QQQ default `PASS_THROUGH`).
- Persisted help content is reloaded after each committed runtime metadata refresh, so sections and fields created by publishing a custom-field definition receive their stored `helpContent` rows without a restart.
- QBit metadata reports the Maven artifact version from a filtered resource, including release versions.
- `presence.changed` events and presence snapshots always carry `viewers`, sending `[]` once the last viewer leaves, as the schema requires (#52).
+5 -1
View File
@@ -25,7 +25,11 @@ Both owner tables must use the same QQQ backend. Hosts apply DDL through their m
| --- | --- | --- |
| File H2 | Native `h2`, `jdbc:h2:file:`, actual configured connection, writable real transaction, live `WRITE_DELAY=0` | `UploadJournalTable.h2Script()` |
| MySQL 8.4 | Native `mysql`, actual MySQL 8.4 product/version, exact configured URL target and catalog, writable real transaction, both owner tables InnoDB, fixed owner table mappings, journal `utf8mb4_0900_bin` columns/table, single-column unique locator/intake indexes, synchronous commit settings | `UploadJournalTable.mysqlScript()` |
| Aurora MySQL, MariaDB, PostgreSQL, other versions/providers | Refused; no inferred compatibility or durability claim | None |
| PostgreSQL 17 | Native `postgres` (vendor and backend type), actual PostgreSQL 17 product/version, exact configured `jdbc:postgresql://host/catalog` target and catalog, writable real transaction, not in recovery, `synchronous_commit` `on` or `remote_apply`, `fsync=on`, `full_page_writes=on` (see the Aurora rule below), session `TimeZone` UTC, `lock_timeout` from 1 to 5000 ms, READ COMMITTED, both owner tables permanent ordinary tables with fixed mappings, no non-deterministic collation, complete unique indexes on `locator_hash` and `intake_key` | `UploadJournalTable.postgresScript()` |
| Amazon Aurora PostgreSQL 17 | Every PostgreSQL 17 requirement above, except that `full_page_writes=off` is accepted | `UploadJournalTable.postgresScript()` |
| Aurora MySQL, MariaDB, other versions/providers | Refused; no inferred compatibility or durability claim | None |
Aurora rule: stock PostgreSQL must report `full_page_writes=on`, because recovery repairs a partly written page from the full page image in the write-ahead log. Amazon Aurora PostgreSQL reports `full_page_writes=off` and the parameter is not in its cluster parameter group, so a deployment cannot turn it on; its storage layer does not write torn pages, so the protection is not needed there. The qualification therefore accepts `off` only when the same settings query reports `to_regprocedure('aurora_version()') IS NOT NULL`, which is true on Aurora, false on stock PostgreSQL 17 and never raises. Nothing else is relaxed on Aurora: `synchronous_commit`, `fsync`, recovery state, isolation, time zone and lock timeout are checked exactly as on stock PostgreSQL. The detection is a name lookup, so a function named `aurora_version()` on the session's search path of a stock server is reported as Aurora; creating one is a schema change inside the host's trust boundary, like the settings themselves.
The MySQL adapter resolves explicit or native-generated URLs through QQQ `ConnectionManager`. It accepts a single ordinary `jdbc:mysql://host:port/catalog` target with a simple catalog identifier. Proxy/load-balancing URLs, alternate catalogs or table remapping fail closed. Error messages do not echo URLs or credentials. Driver options remain host-owned; TLS and credentials are not changed by the journal.
+1 -1
View File
@@ -21,7 +21,7 @@
<!-- Only the published Redis API package is a permitted cross-qbit boundary. -->
<qbit.guardrails.allowedApiPackages>com.kingsrook.qbits.objectstorage.api,com.kingsrook.qbits.email.api,com.kingsrook.qbits.redis.api</qbit.guardrails.allowedApiPackages>
<qbit.guardrails.allowedQbitArtifacts>com.kingsrook.qbits:qbit-object-storage,com.kingsrook.qbits:qbit-email,com.kingsrook.qbits:*-api,com.kingsrook.qbits:qbit-redis</qbit.guardrails.allowedQbitArtifacts>
<revision>0.1.0-SNAPSHOT</revision>
<revision>1.1.0-RC.2</revision>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<jacoco.version>0.8.15</jacoco.version>
@@ -29,10 +29,12 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterfa
import com.kingsrook.qqq.backend.core.context.CapturedContext;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Publish committed policy changes through the core overlay.
@@ -77,7 +79,7 @@ public class FeatureSettingsCustomizer implements TableCustomizerInterface
var instance = QContext.getQInstance();
if(transaction == null)
{
RuntimeMetaDataOverlay.refresh(instance);
refreshMetadata(instance);
}
else
{
@@ -86,7 +88,7 @@ public class FeatureSettingsCustomizer implements TableCustomizerInterface
{
try
{
QContext.withTemporaryContext(context, () -> RuntimeMetaDataOverlay.refresh(instance));
QContext.withTemporaryContext(context, () -> refreshMetadata(instance));
}
catch(QException e)
{
@@ -95,4 +97,13 @@ public class FeatureSettingsCustomizer implements TableCustomizerInterface
});
}
}
/*******************************************************************************
** Bind persisted help after the overlay creates its dynamic fields and sections.
*******************************************************************************/
private static void refreshMetadata(QInstance instance) throws QException
{
RuntimeMetaDataOverlay.refresh(instance);
QInstanceHelpContentManager.reloadHelpContent(instance);
}
}
@@ -47,14 +47,25 @@ import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDe
** pair, on the native QQQ postgres backend (vendor and backend type "postgres").
** Observation only: no schema migration and no session or server setting is
** changed at runtime.
**
** Amazon Aurora PostgreSQL 17 is the one qualified variant: it is held to every
** rule here except full_page_writes, which Aurora reports as "off" and does not
** let a deployment change (see tornPagesPrevented).
*******************************************************************************/
final class PostgresJournalDatabase
{
static final String VENDOR = "postgres";
private static final String SETTINGS = "SELECT current_setting('server_version_num')::int, current_setting('transaction_read_only'), pg_is_in_recovery(),"
/////////////////////////////////////////////////////////////////////////////////////////////
// Column 11 detects Amazon Aurora PostgreSQL inside the same statement. to_regprocedure //
// returns NULL when no aurora_version() function resolves, so it is true on Aurora, false //
// on stock PostgreSQL 17 and never raises. Calling aurora_version() directly would abort //
// the transaction on stock PostgreSQL, and pg_proc does not list the function on Aurora. //
/////////////////////////////////////////////////////////////////////////////////////////////
static final String SETTINGS = "SELECT current_setting('server_version_num')::int, current_setting('transaction_read_only'), pg_is_in_recovery(),"
+ " current_setting('synchronous_commit'), current_setting('fsync'), current_setting('full_page_writes'), current_setting('TimeZone'),"
+ " (SELECT setting::bigint FROM pg_settings WHERE name='lock_timeout'), current_setting('transaction_isolation'), current_database()";
+ " (SELECT setting::bigint FROM pg_settings WHERE name='lock_timeout'), current_setting('transaction_isolation'), current_database(),"
+ " to_regprocedure('aurora_version()') IS NOT NULL";
private static final String TABLES = "SELECT c.relname, c.relkind, c.relpersistence FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace"
+ " WHERE n.nspname=current_schema() AND c.relname = ANY(?)";
@@ -124,7 +135,8 @@ final class PostgresJournalDatabase
try(ResultSet settings = statement.executeQuery(SETTINGS))
{
if(!settings.next() || settings.getInt(1) / 10000 != 17 || !"off".equals(settings.getString(2)) || settings.getBoolean(3)
|| !Set.of("on", "remote_apply").contains(settings.getString(4)) || !"on".equals(settings.getString(5)) || !"on".equals(settings.getString(6))
|| !Set.of("on", "remote_apply").contains(settings.getString(4)) || !"on".equals(settings.getString(5))
|| !tornPagesPrevented(settings.getString(6), settings.getBoolean(11))
|| !Set.of("UTC", "Etc/UTC").contains(settings.getString(7)) || settings.getLong(8) < 1 || settings.getLong(8) > 5000
|| !"read committed".equals(settings.getString(9)) || !catalog.equals(settings.getString(10)))
{
@@ -147,6 +159,28 @@ final class PostgresJournalDatabase
/*******************************************************************************
** The torn-page rule, as a pure decision over the observed full_page_writes
** value and the Aurora detection column.
**
** Stock PostgreSQL must report full_page_writes "on": after a crash that leaves
** a partly written page, recovery repairs it from the full page image in the
** write-ahead log, so "off" there can lose committed journal rows.
**
** Amazon Aurora PostgreSQL reports full_page_writes "off" (measured on engine
** 17.9: source default, and the parameter is not in the cluster parameter group,
** so a deployment cannot turn it on). Its storage layer does not write torn
** pages, so the protection is not needed there. "off" is therefore accepted
** only when the server is Aurora; every other rule (synchronous_commit, fsync,
** recovery, isolation, time zone, lock timeout) stays as strict on Aurora.
*******************************************************************************/
static boolean tornPagesPrevented(String fullPageWrites, boolean aurora)
{
return ("on".equals(fullPageWrites) || (aurora && "off".equals(fullPageWrites)));
}
/*******************************************************************************
** Permanent (logged) ordinary tables only, deterministic text comparison, and
** complete valid non-partial unique indexes for the journal's two identities.
@@ -43,6 +43,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QVirtualFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueRangeBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
@@ -127,6 +128,10 @@ public class CustomFieldsProvider implements RuntimeMetaDataProviderInterface
.withCriteriaExpander(new QCodeReference(FieldCriteriaExpander.class));
field.withLabel(definition.getValueString("label")).withIsRequired(json && Boolean.TRUE.equals(definition.getValueBoolean("required")))
.withMaxLength(definition.getValueInteger("maxLength"));
if(type == QFieldType.STRING && definition.getValueInteger("maxLength") != null)
{
field.withBehavior(ValueTooLongBehavior.ERROR);
}
if(definition.getValue("defaultValue") != null && json)
{
field.withDefaultValue(com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsFieldType(type, definition.getValue("defaultValue")));
@@ -29,7 +29,7 @@ import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
@@ -52,7 +52,7 @@ public final class ParentRecordAccess
{
QTableMetaData table = tableName == null ? null : QContext.getQInstance().getTable(tableName);
return (table != null && table.getPrimaryKeyField() != null && !tableName.startsWith(CollaborationQBitConfig.TABLE_NAME_PREFIX)
&& PermissionsHelper.hasTablePermission(new AbstractActionInput(), tableName, TablePermissionSubType.READ));
&& PermissionsHelper.hasTablePermission(new GetInput(tableName), tableName, TablePermissionSubType.READ));
}
/*******************************************************************************
@@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
@@ -44,9 +45,12 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContentMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.JsonPathStorage;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
import com.kingsrook.qbits.collaboration.app.CollaborationHome;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.TableData;
@@ -62,6 +66,48 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
*******************************************************************************/
public class CustomFieldsTest extends PersonalServiceFixture
{
/*******************************************************************************
** Publication binds stored guidance to sections that did not exist at startup.
*******************************************************************************/
@Test
void publishedSectionReceivesStoredHelp() throws Exception
{
new HelpContentMetaDataProvider().defineAll(instance, "memory", table ->
table.withPermissionRules(new QPermissionRules()
.withLevel(PermissionLevel.NOT_PROTECTED)));
new QInstanceEnricher(instance).enrich();
admin();
var help = new InsertAction().execute(new InsertInput("helpContent").withRecords(List.of(new QRecord()
.withValue("key", "table:document;section:customFields").withValue("role", "ALL_SCREENS")
.withValue("format", "MARKDOWN").withValue("content", "Synthetic dynamic section guidance.")))).getRecords().getFirst();
assertThat(help.getErrors()).isNullOrEmpty();
QRecord definition = draft("guided", "STRING");
FieldDefinitions.apply(definition.getValue("id"), "Publish");
assertThat(instance.getTable("document").getSection("customFields").getHelpContents())
.anySatisfy(content -> assertThat(content.getContent()).contains("Synthetic dynamic section guidance."));
}
/*******************************************************************************
** A configured string bound rejects writes without replacing persisted values.
*******************************************************************************/
@Test
void publishedStringRejectsValuesBeyondItsMaximumLength() throws Exception
{
admin();
QRecord definition = draft("note", "STRING");
new UpdateAction().execute(new UpdateInput(CollaborationTables.FIELD_DEFINITION).withRecords(List.of(
new QRecord().withValue("id", definition.getValue("id")).withValue("maxLength", 5))));
FieldDefinitions.apply(definition.getValue("id"), "Publish");
login("alice");
var valid = new UpdateAction().execute(new UpdateInput("document").withRecords(List.of(
new QRecord().withValue("id", 1).withValue("cf_note", "valid")))).getRecords().getFirst();
assertThat(valid.getErrors()).isNullOrEmpty();
var invalid = new UpdateAction().execute(new UpdateInput("document").withRecords(List.of(
new QRecord().withValue("id", 1).withValue("cf_note", "too long")))).getRecords().getFirst();
assertThat(invalid.getErrors()).isNotEmpty();
assertThat(query("cf_note", QCriteriaOperator.EQUALS, "valid")).hasSize(1);
}
/*******************************************************************************
** Refreshing a SELECT must replace its generated vocabulary and allow later fields.
*******************************************************************************/
@@ -0,0 +1,83 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.collaboration.attachments.uploadjournal;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** The torn-page decision of the PostgreSQL journal qualification, without a
** database: the rule is a pure function of the observed full_page_writes value
** and the Aurora detection column. The real-server cases, including the
** detection column itself, are in the opt-in PostgresUploadJournalTest.
*******************************************************************************/
class PostgresJournalDatabaseTest
{
/*******************************************************************************
** "on" qualifies everywhere; "off" qualifies only on Amazon Aurora PostgreSQL.
*******************************************************************************/
@Test
void fullPageWritesOffIsAcceptedOnlyOnAurora()
{
assertThat(PostgresJournalDatabase.tornPagesPrevented("on", false)).isTrue();
assertThat(PostgresJournalDatabase.tornPagesPrevented("on", true)).isTrue();
assertThat(PostgresJournalDatabase.tornPagesPrevented("off", true)).isTrue();
assertThat(PostgresJournalDatabase.tornPagesPrevented("off", false)).isFalse();
}
/*******************************************************************************
** Anything that is neither "on" nor Aurora's "off" is refused, Aurora or not:
** a missing value, another spelling or an unexpected value.
*******************************************************************************/
@Test
void unknownFullPageWritesValuesAreRefused()
{
for(boolean aurora : new boolean[] { false, true })
{
assertThat(PostgresJournalDatabase.tornPagesPrevented(null, aurora)).isFalse();
assertThat(PostgresJournalDatabase.tornPagesPrevented("", aurora)).isFalse();
assertThat(PostgresJournalDatabase.tornPagesPrevented("OFF", aurora)).isFalse();
assertThat(PostgresJournalDatabase.tornPagesPrevented("ON", aurora)).isFalse();
assertThat(PostgresJournalDatabase.tornPagesPrevented("false", aurora)).isFalse();
}
}
/*******************************************************************************
** The settings query detects Aurora only through to_regprocedure, as its last
** column. It never calls aurora_version() directly, which would abort the
** transaction on stock PostgreSQL.
*******************************************************************************/
@Test
void settingsQueryDetectsAuroraOnlyThroughToRegprocedure()
{
String detection = "to_regprocedure('aurora_version()') IS NOT NULL";
assertThat(PostgresJournalDatabase.SETTINGS).endsWith(", " + detection);
assertThat(PostgresJournalDatabase.SETTINGS.replace(detection, "")).doesNotContain("aurora");
}
}
@@ -81,6 +81,11 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
** dynamic port, random synthetic passwords); the application connects as a
** DML-only role, and the superuser is used only for fixture DDL and fault
** injection. The attachment row is a minimal stand-in, not a full intake fixture.
**
** No Amazon Aurora server is used. The Aurora cases run on this stock PostgreSQL
** 17 server and simulate exactly two things: a function named aurora_version()
** created in the suite's database (so to_regprocedure resolves, as it does on
** Aurora), and full_page_writes turned off on the server (as Aurora reports it).
*******************************************************************************/
@EnabledIfSystemProperty(named = "postgresJournalTests", matches = "true")
class PostgresUploadJournalTest
@@ -107,6 +112,9 @@ class PostgresUploadJournalTest
/*******************************************************************************
** Start only the disposable server with explicit durable settings. pgjdbc
** announces the JVM zone as the session TimeZone, so the suite runs in UTC.
** fsync and full_page_writes are set through ALTER SYSTEM rather than on the
** command line, because a command-line value cannot be changed by a reload and
** the Aurora and durability cases need to turn each one off and back on.
*******************************************************************************/
@BeforeAll
static void start() throws Exception
@@ -115,7 +123,7 @@ class PostgresUploadJournalTest
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
postgres = new GenericContainer<>("postgres:17")
.withEnv("POSTGRES_PASSWORD", ROOT_PASSWORD).withEnv("POSTGRES_DB", DATABASE)
.withCommand("postgres", "-c", "fsync=on", "-c", "synchronous_commit=on", "-c", "full_page_writes=on")
.withCommand("postgres", "-c", "synchronous_commit=on")
.withExposedPorts(5432)
.withCreateContainerCmdModifier(command ->
{
@@ -138,6 +146,8 @@ class PostgresUploadJournalTest
statement.execute("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO " + APP_ROLE);
statement.execute("ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO " + APP_ROLE);
}
serverSetting("fsync", "on");
serverSetting("full_page_writes", "on");
}
@@ -174,6 +184,9 @@ class PostgresUploadJournalTest
execute("ALTER ROLE " + APP_ROLE + " RESET synchronous_commit");
execute("ALTER ROLE " + APP_ROLE + " RESET default_transaction_isolation");
execute("ALTER ROLE " + APP_ROLE + " RESET default_transaction_read_only");
serverSetting("fsync", "on");
serverSetting("full_page_writes", "on");
execute("DROP FUNCTION IF EXISTS aurora_version()");
backend = new PostgreSQLBackendMetaData();
backend.setName("postgres_journal");
backend.setDatabaseName(DATABASE);
@@ -440,6 +453,67 @@ class PostgresUploadJournalTest
/*******************************************************************************
** The settings query carries the Aurora detection as its eleventh column. As
** the DML-only application role on a stock server it reports false and does not
** raise, so the transaction stays usable; once aurora_version() resolves (the
** simulated Aurora) it reports true.
*******************************************************************************/
@Test
void settingsQueryDetectsAuroraWithoutRaising() throws Exception
{
assertThat(settingsQueryReportsAurora()).isFalse();
simulateAurora();
assertThat(settingsQueryReportsAurora()).isTrue();
}
/*******************************************************************************
** full_page_writes=off is refused on stock PostgreSQL and accepted only once
** the server is detected as Aurora (simulated); removing the detection refuses
** it again, with no row written by a refused call.
*******************************************************************************/
@Test
void fullPageWritesOffIsAcceptedOnlyOnAurora() throws Exception
{
serverSetting("full_page_writes", "off");
assertUnsupported();
assertThat(count("collaboration_upload_journal")).isZero();
simulateAurora();
ConnectionManager.closeConnectionProviders();
assertThat(journal.prepare(candidate("aurora")).id()).isPositive();
assertThat(count("collaboration_upload_journal")).isEqualTo(1);
execute("DROP FUNCTION aurora_version()");
assertUnsupported();
assertThat(count("collaboration_upload_journal")).isEqualTo(1);
}
/*******************************************************************************
** Aurora (simulated, full_page_writes=off) gets no other allowance: with
** synchronous_commit off, or with fsync off, it is refused like any server.
*******************************************************************************/
@Test
void auroraIsStillRefusedWithoutSynchronousCommitOrFsync() throws Exception
{
simulateAurora();
serverSetting("full_page_writes", "off");
assertThat(journal.prepare(candidate("aurora-baseline")).id()).isPositive();
execute("ALTER ROLE " + APP_ROLE + " SET synchronous_commit='off'");
assertUnsupported();
execute("ALTER ROLE " + APP_ROLE + " RESET synchronous_commit");
serverSetting("fsync", "off");
assertUnsupported();
serverSetting("fsync", "on");
ConnectionManager.closeConnectionProviders();
assertThat(journal.prepare(candidate("aurora-restored")).id()).isPositive();
assertThat(count("collaboration_upload_journal")).isEqualTo(2);
}
/*******************************************************************************
** Physical drift is refused: an unlogged owner table (lost in crash recovery)
** and a non-deterministic collation on a journal column.
@@ -565,6 +639,75 @@ class PostgresUploadJournalTest
/*******************************************************************************
** Make this stock server look like Amazon Aurora PostgreSQL to the detection:
** a function named aurora_version() in the suite's schema. Nothing else about
** Aurora is simulated by this call.
*******************************************************************************/
private void simulateAurora() throws SQLException
{
execute("CREATE FUNCTION aurora_version() RETURNS text LANGUAGE sql AS 'SELECT ''17.9''::text'");
}
/*******************************************************************************
** Run the production settings query as the application role inside one
** transaction, then prove that transaction was not aborted by it.
*******************************************************************************/
private boolean settingsQueryReportsAurora() throws SQLException
{
try(Connection connection = DriverManager.getConnection(appUrl(), APP_ROLE, APP_PASSWORD); Statement statement = connection.createStatement())
{
connection.setAutoCommit(false);
boolean aurora;
try(ResultSet settings = statement.executeQuery(PostgresJournalDatabase.SETTINGS))
{
assertThat(settings.getMetaData().getColumnCount()).isEqualTo(11);
assertThat(settings.next()).isTrue();
aurora = settings.getBoolean(11);
assertThat(settings.wasNull()).isFalse();
}
try(ResultSet stillUsable = statement.executeQuery("SELECT 1"))
{
assertThat(stillUsable.next()).isTrue();
}
connection.rollback();
return (aurora);
}
}
/*******************************************************************************
** Change a reload-level (sighup) setting of the disposable server and wait
** until a new session reports it, so the next pooled session observes it too.
*******************************************************************************/
private static void serverSetting(String name, String value) throws Exception
{
try(Connection connection = root(); Statement statement = connection.createStatement())
{
statement.execute("ALTER SYSTEM SET " + name + "=" + value);
statement.execute("SELECT pg_reload_conf()");
}
long deadline = System.nanoTime() + Duration.ofSeconds(30).toNanos();
while(System.nanoTime() < deadline)
{
try(Connection connection = root(); Statement statement = connection.createStatement();
ResultSet setting = statement.executeQuery("SELECT current_setting('" + name + "')"))
{
if(setting.next() && value.equals(setting.getString(1)))
{
return;
}
}
Thread.sleep(100);
}
throw (new IllegalStateException("The disposable server did not apply " + name + "=" + value + "."));
}
/*******************************************************************************
** Prepare a leased manifest whose stored bytes have been verified by owner.
*******************************************************************************/