Compare commits
3
Commits
v0.4.0-RC.1
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73341e59c5
|
||
|
|
0d989683eb
|
||
|
|
6d733cde82 |
No files matched your search
@@ -4,6 +4,12 @@ All notable changes are documented here, following [Keep a Changelog](https://ke
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- PostgreSQL qualification (`RecordFilesPostgres`) accepts `full_page_writes = off` when, and only when, the server is Amazon Aurora PostgreSQL. Aurora reports `full_page_writes = off` (measured on engine 17.9) and the parameter is not in its cluster parameter group, so it cannot be changed; its storage layer does not write torn pages, so the protection that setting gives stock PostgreSQL is not needed there. Before this change Record Files refused every Aurora PostgreSQL database with its "exact writable PostgreSQL 17 database" message. Aurora is detected inside the existing settings query with `to_regprocedure('aurora_version()') IS NOT NULL`, which returns false and never raises on stock PostgreSQL. Nothing else is relaxed: stock PostgreSQL with `full_page_writes = off` is still refused, and on Aurora `fsync = on`, `synchronous_commit` of `on` or `remote_apply`, a writable primary, PostgreSQL 17, a UTC session, READ COMMITTED, a `lock_timeout` from 1 to 5000 ms and every owner-table check are required exactly as before. Covered by `PostgresDurabilityRuleTest` (every build) and `PostgresAuroraQualificationTest` (opt-in, real PostgreSQL 17, Aurora simulated by a test-only `aurora_version()` function).
|
||||
|
||||
## [0.1.0-RC.1] - 2026-11-06
|
||||
|
||||
Planned as **0.1.0-RC.1**, the first artifact published from this repository. It is published by pushing `develop` to `release/0.1`; the shared `KofTwentyTwo/ci-workflows` Maven workflow computes the `0.1.0-RC.N` version, deploys to the Gitea Maven registry (`https://git.kof22.com/api/packages/QRun-IO/maven`) and tags `v0.1.0-RC.N`. Nothing is published from `develop` except the `0.1.0-SNAPSHOT`.
|
||||
|
||||
### Added
|
||||
|
||||
@@ -7,3 +7,4 @@ Updated: 2026-11-06. These are unresolved items carried into the first release c
|
||||
- `qbit-collaboration.version` is still `0.1.0-SNAPSHOT`, a placeholder for source integration. The release candidate must pin a published Collaboration RC before `release/0.1` is pushed (see RELEASE-STATUS.md). No clean consumer installation of the complete public cohort has been demonstrated.
|
||||
- Full Record Files verification, database integration, retained-file recovery and final standard-UI acceptance remain incomplete for this reconciled source.
|
||||
- Permanent access depends on the host's durable object storage, backups, retention and permission configuration. A source-level interface or unit test alone does not prove operational permanence.
|
||||
- The Amazon Aurora PostgreSQL rule (`full_page_writes = off` accepted on Aurora only) is tested on stock PostgreSQL 17 with Aurora simulated by a test-only `aurora_version()` function. No test in this repository runs against a real Aurora cluster, and the opt-in PostgreSQL suites do not run in the shared CI build. The detection resolves `aurora_version()` through the session `search_path`, so a role that can create functions in a schema on that path of a stock PostgreSQL database could make it report Aurora; the application role must not hold that privilege.
|
||||
@@ -18,7 +18,7 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<properties>
|
||||
<revision>0.4.0-SNAPSHOT</revision>
|
||||
<revision>0.5.0-SNAPSHOT</revision>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<jacoco.version>0.8.15</jacoco.version>
|
||||
|
||||
@@ -46,14 +46,25 @@ import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSTableBackendDe
|
||||
** Qualification of this QBit's actual PostgreSQL 17 target (the native QQQ
|
||||
** postgres module: vendor and backend type "postgres") and its owned tables.
|
||||
** Observes only; no migrations or session/global setting changes happen here.
|
||||
**
|
||||
** Amazon Aurora PostgreSQL is the one accepted variant of the durability rule:
|
||||
** it reports full_page_writes = off, and that alone is accepted there (see
|
||||
** durable). Every other check is identical on stock PostgreSQL and Aurora.
|
||||
*******************************************************************************/
|
||||
final class RecordFilesPostgres
|
||||
{
|
||||
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 12 is true only on Amazon Aurora PostgreSQL, which defines aurora_version(). //
|
||||
// to_regprocedure returns NULL for a function that does not exist and never raises, //
|
||||
// so the probe is safe on stock PostgreSQL; calling aurora_version() directly there //
|
||||
// would abort the transaction, 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_schema(), current_database()";
|
||||
+ " (SELECT setting::bigint FROM pg_settings WHERE name='lock_timeout'), current_setting('transaction_isolation'), current_schema(), 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(?)";
|
||||
@@ -131,7 +142,7 @@ final class RecordFilesPostgres
|
||||
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))
|
||||
|| !durable(settings.getString(4), settings.getString(5), settings.getString(6), settings.getBoolean(12))
|
||||
|| !Set.of("UTC", "Etc/UTC").contains(settings.getString(7)) || settings.getLong(8) < 1 || settings.getLong(8) > 5000
|
||||
|| !"read committed".equals(settings.getString(9)) || settings.getString(10) == null || !catalog.equals(settings.getString(11)))
|
||||
{
|
||||
@@ -155,6 +166,30 @@ final class RecordFilesPostgres
|
||||
requireTables(connection, tables);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
** Committed durability from the observed server settings: synchronous_commit
|
||||
** on or remote_apply, fsync on, and torn-page protection.
|
||||
***************************************************************************/
|
||||
static boolean durable(String synchronousCommit, String fsync, String fullPageWrites, boolean aurora)
|
||||
{
|
||||
if(synchronousCommit == null || !Set.of("on", "remote_apply").contains(synchronousCommit) || !"on".equals(fsync))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Stock PostgreSQL needs full_page_writes = on: after a crash, a partly written page //
|
||||
// is repaired from the full page image in the write-ahead log. //
|
||||
// Amazon Aurora PostgreSQL reports full_page_writes = off (measured on engine 17.9) //
|
||||
// and the parameter cannot be changed there: it is not in the cluster parameter //
|
||||
// group. Its storage layer does not write torn pages, so the protection is not //
|
||||
// needed. Off is therefore accepted on Aurora only; on stock PostgreSQL it is still //
|
||||
// refused. fsync and synchronous_commit are required on Aurora exactly as on stock //
|
||||
// PostgreSQL. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
return ("on".equals(fullPageWrites) || (aurora && "off".equals(fullPageWrites)));
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
** Permanent tables, deterministic comparison, owner column types and complete unique keys.
|
||||
***************************************************************************/
|
||||
|
||||
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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.recordfiles;
|
||||
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Duration;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager;
|
||||
import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Opt-in qualification of the Amazon Aurora PostgreSQL rule on a real, stock
|
||||
** PostgreSQL 17 server (the disposable container of PostgresRecordFilesSupport).
|
||||
**
|
||||
** What is simulated: Aurora itself. The fixture superuser creates a function
|
||||
** public.aurora_version() in the test database, so the qualification's probe
|
||||
** to_regprocedure('aurora_version()') resolves as it does on Aurora, and sets
|
||||
** full_page_writes = off with ALTER SYSTEM and a configuration reload, the value
|
||||
** Aurora reports. Aurora's storage layer and its other settings are not
|
||||
** reproduced. Everything else is real: the server, the DML-only application
|
||||
** role, the shipped owner script, the settings query and the store calls.
|
||||
*******************************************************************************/
|
||||
@EnabledIfSystemProperty(named = "postgresRecordFilesTests", matches = "true")
|
||||
class PostgresAuroraQualificationTest extends RecordFilesFixture
|
||||
{
|
||||
private static final String REFUSAL = "Record Files requires its exact writable PostgreSQL 17 database";
|
||||
|
||||
private RDBMSBackendMetaData database;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Start the provider with fsync and full_page_writes left off the server command
|
||||
** line (both default to on): a setting fixed there cannot be changed by ALTER
|
||||
** SYSTEM, and these cases change both.
|
||||
*******************************************************************************/
|
||||
@BeforeAll
|
||||
static void startPostgres() throws Exception
|
||||
{
|
||||
PostgresRecordFilesSupport.start("synchronous_commit=on");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Remove the provider created by this class.
|
||||
*******************************************************************************/
|
||||
@AfterAll
|
||||
static void stopPostgres()
|
||||
{
|
||||
PostgresRecordFilesSupport.stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Every case ends on a stock server with durable settings.
|
||||
*******************************************************************************/
|
||||
@AfterEach
|
||||
void restoreStockDurableServer() throws Exception
|
||||
{
|
||||
execute("DROP FUNCTION IF EXISTS public.aurora_version()");
|
||||
execute("ALTER ROLE " + PostgresRecordFilesSupport.APP_ROLE + " RESET synchronous_commit");
|
||||
execute("ALTER SYSTEM RESET fsync");
|
||||
execute("ALTER SYSTEM RESET full_page_writes");
|
||||
execute("SELECT pg_reload_conf()");
|
||||
awaitSetting("fsync", "on");
|
||||
awaitSetting("full_page_writes", "on");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The native postgres backend with the DML-only application identity.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected RDBMSBackendMetaData createBackend() throws Exception
|
||||
{
|
||||
database = PostgresRecordFilesSupport.backend(backendName);
|
||||
return (database);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Generic tables, the Record Files owner script and the journal's PostgreSQL script.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected void createSchema(RDBMSBackendMetaData backend) throws Exception
|
||||
{
|
||||
PostgresRecordFilesSupport.createSchema(instance, config);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Owner-level connection for test-only schema fault injection.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
protected Connection adminConnection(RDBMSBackendMetaData backend) throws Exception
|
||||
{
|
||||
return (PostgresRecordFilesSupport.root());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The settings query runs as the application role on a stock server without an
|
||||
** error and reports "not Aurora" in column 12; with the function present it
|
||||
** reports Aurora.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void settingsQueryReportsAurora() throws Exception
|
||||
{
|
||||
assertThat(auroraColumn()).isFalse();
|
||||
simulateAurora();
|
||||
assertThat(auroraColumn()).isTrue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Stock PostgreSQL with full_page_writes off is still refused, by the probe and
|
||||
** by the transaction the store opens.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void stockPostgresWithFullPageWritesOffIsRefused() throws Exception
|
||||
{
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isTrue();
|
||||
server("full_page_writes", "off");
|
||||
assertThat(auroraColumn()).isFalse();
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isFalse();
|
||||
assertThatThrownBy(RecordFilesStore::openTransaction).isInstanceOf(QException.class).hasMessageStartingWith(REFUSAL);
|
||||
assertThatThrownBy(() -> collections.create("Refused", "document", 1, 1, null, null)).isInstanceOf(QException.class);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Aurora with full_page_writes off qualifies: the probe, the owned transaction
|
||||
** and a committed owner write.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void auroraWithFullPageWritesOffIsAccepted() throws Exception
|
||||
{
|
||||
server("full_page_writes", "off");
|
||||
simulateAurora();
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isTrue();
|
||||
var transaction = RecordFilesStore.openTransaction();
|
||||
try
|
||||
{
|
||||
RecordFilesStore.requireCompatibleTransaction(transaction);
|
||||
}
|
||||
finally
|
||||
{
|
||||
transaction.rollback();
|
||||
transaction.close();
|
||||
}
|
||||
var collection = collections.create("Aurora", "document", 1, 1, null, null);
|
||||
assertThat(RecordFilesStore.first(RecordFilesTables.COLLECTION, "id", collection.id()).getValueString("title")).isEqualTo("Aurora");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Aurora relaxes only full_page_writes: fsync off is refused.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void auroraWithFsyncOffIsRefused() throws Exception
|
||||
{
|
||||
server("full_page_writes", "off");
|
||||
simulateAurora();
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isTrue();
|
||||
server("fsync", "off");
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isFalse();
|
||||
assertThatThrownBy(RecordFilesStore::openTransaction).isInstanceOf(QException.class).hasMessageStartingWith(REFUSAL);
|
||||
server("fsync", "on");
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isTrue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Aurora relaxes only full_page_writes: synchronous_commit off or local is refused.
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void auroraWithSynchronousCommitOffIsRefused() throws Exception
|
||||
{
|
||||
server("full_page_writes", "off");
|
||||
simulateAurora();
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isTrue();
|
||||
for(String weak : new String[] {"off", "local"})
|
||||
{
|
||||
execute("ALTER ROLE " + PostgresRecordFilesSupport.APP_ROLE + " SET synchronous_commit='" + weak + "'");
|
||||
assertThat(setting("synchronous_commit")).isEqualTo(weak);
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isFalse();
|
||||
assertThatThrownBy(RecordFilesStore::openTransaction).isInstanceOf(QException.class).hasMessageStartingWith(REFUSAL);
|
||||
}
|
||||
execute("ALTER ROLE " + PostgresRecordFilesSupport.APP_ROLE + " RESET synchronous_commit");
|
||||
assertThat(RecordFilesStore.isDurableBackend(database)).isTrue();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The stand-in for Aurora: a function with Aurora's name in the test database.
|
||||
*******************************************************************************/
|
||||
private void simulateAurora() throws SQLException
|
||||
{
|
||||
execute("CREATE FUNCTION public.aurora_version() RETURNS text LANGUAGE sql AS 'SELECT ''17.9.simulated''::text'");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Change one reload-time server setting and wait until new application sessions see it.
|
||||
*******************************************************************************/
|
||||
private void server(String name, String value) throws Exception
|
||||
{
|
||||
execute("ALTER SYSTEM SET " + name + " = " + value);
|
||||
execute("SELECT pg_reload_conf()");
|
||||
awaitSetting(name, value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A reload is asynchronous; fail rather than assert against a stale value.
|
||||
*******************************************************************************/
|
||||
private void awaitSetting(String name, String value) throws Exception
|
||||
{
|
||||
long deadline = System.nanoTime() + Duration.ofSeconds(30).toNanos();
|
||||
while(!value.equals(setting(name)))
|
||||
{
|
||||
assertThat(System.nanoTime()).as("server setting " + name + " did not become " + value).isLessThan(deadline);
|
||||
Thread.sleep(50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** One setting as a new application-role session observes it.
|
||||
*******************************************************************************/
|
||||
private String setting(String name) throws Exception
|
||||
{
|
||||
try(Connection connection = ConnectionManager.getConnection(database); var statement = connection.createStatement();
|
||||
var rows = statement.executeQuery("SELECT current_setting('" + name + "')"))
|
||||
{
|
||||
rows.next();
|
||||
return (rows.getString(1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Column 12 of the shipped settings query, run as the application role.
|
||||
*******************************************************************************/
|
||||
private boolean auroraColumn() throws Exception
|
||||
{
|
||||
try(Connection connection = ConnectionManager.getConnection(database); var statement = connection.createStatement();
|
||||
var rows = statement.executeQuery(RecordFilesPostgres.SETTINGS))
|
||||
{
|
||||
assertThat(rows.next()).isTrue();
|
||||
assertThat(rows.getMetaData().getColumnCount()).isEqualTo(12);
|
||||
return (rows.getBoolean(12));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fixture superuser statement, separate from native QQQ actions.
|
||||
*******************************************************************************/
|
||||
private void execute(String sql) throws SQLException
|
||||
{
|
||||
try(Connection connection = PostgresRecordFilesSupport.root(); var statement = connection.createStatement())
|
||||
{
|
||||
statement.execute(sql);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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.recordfiles;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/***************************************************************************
|
||||
** The PostgreSQL durability decision as a pure function of the observed
|
||||
** setting values. No server is used here, so this runs in every build; the
|
||||
** same rule on a real PostgreSQL 17 server is PostgresAuroraQualificationTest.
|
||||
***************************************************************************/
|
||||
class PostgresDurabilityRuleTest
|
||||
{
|
||||
/************************************************************************
|
||||
** Stock PostgreSQL keeps the strict rule: full_page_writes must be on.
|
||||
************************************************************************/
|
||||
@Test
|
||||
void stockPostgresRequiresFullPageWrites()
|
||||
{
|
||||
assertThat(RecordFilesPostgres.durable("on", "on", "on", false)).isTrue();
|
||||
assertThat(RecordFilesPostgres.durable("remote_apply", "on", "on", false)).isTrue();
|
||||
assertThat(RecordFilesPostgres.durable("on", "on", "off", false)).isFalse();
|
||||
assertThat(RecordFilesPostgres.durable("remote_apply", "on", "off", false)).isFalse();
|
||||
assertThat(RecordFilesPostgres.durable("on", "on", null, false)).isFalse();
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
** Amazon Aurora PostgreSQL is accepted with full_page_writes off (and on).
|
||||
************************************************************************/
|
||||
@Test
|
||||
void auroraAcceptsFullPageWritesOff()
|
||||
{
|
||||
assertThat(RecordFilesPostgres.durable("on", "on", "off", true)).isTrue();
|
||||
assertThat(RecordFilesPostgres.durable("remote_apply", "on", "off", true)).isTrue();
|
||||
assertThat(RecordFilesPostgres.durable("on", "on", "on", true)).isTrue();
|
||||
assertThat(RecordFilesPostgres.durable("on", "on", null, true)).isFalse();
|
||||
assertThat(RecordFilesPostgres.durable("on", "on", "unknown", true)).isFalse();
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
** Aurora relaxes nothing else: fsync and synchronous_commit stay required.
|
||||
************************************************************************/
|
||||
@Test
|
||||
void auroraStillRequiresFsyncAndSynchronousCommit()
|
||||
{
|
||||
for(String fullPageWrites : new String[] {"off", "on"})
|
||||
{
|
||||
assertThat(RecordFilesPostgres.durable("on", "off", fullPageWrites, true)).isFalse();
|
||||
assertThat(RecordFilesPostgres.durable("on", null, fullPageWrites, true)).isFalse();
|
||||
assertThat(RecordFilesPostgres.durable(null, "on", fullPageWrites, true)).isFalse();
|
||||
for(String synchronousCommit : new String[] {"off", "local", "remote_write"})
|
||||
{
|
||||
assertThat(RecordFilesPostgres.durable(synchronousCommit, "on", fullPageWrites, true)).isFalse();
|
||||
assertThat(RecordFilesPostgres.durable(synchronousCommit, "on", fullPageWrites, false)).isFalse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/************************************************************************
|
||||
** The settings query carries the Aurora probe as its last column, in the
|
||||
** form that cannot raise on stock PostgreSQL. Its execution on a real
|
||||
** server is PostgresAuroraQualificationTest.settingsQueryReportsAurora.
|
||||
************************************************************************/
|
||||
@Test
|
||||
void settingsQueryProbesAuroraWithoutCallingTheFunction()
|
||||
{
|
||||
assertThat(RecordFilesPostgres.SETTINGS).endsWith(", to_regprocedure('aurora_version()') IS NOT NULL");
|
||||
assertThat(RecordFilesPostgres.SETTINGS).doesNotContain("SELECT aurora_version()").doesNotContain("pg_proc");
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ public final class PostgresRecordFilesSupport
|
||||
{
|
||||
private static final String DATABASE = "record_files_test";
|
||||
private static final String OPTIONS = "?connectTimeout=5&socketTimeout=30&options=-c%20TimeZone%3DUTC";
|
||||
private static final String APP_ROLE = "record_files_app";
|
||||
public static final String APP_ROLE = "record_files_app";
|
||||
private static final String ROOT_PASSWORD = UUID.randomUUID().toString();
|
||||
private static final String APP_PASSWORD = UUID.randomUUID().toString();
|
||||
private static GenericContainer<?> postgres;
|
||||
@@ -75,11 +75,29 @@ public final class PostgresRecordFilesSupport
|
||||
*******************************************************************************/
|
||||
public static void start() throws Exception
|
||||
{
|
||||
start("fsync=on", "synchronous_commit=on", "full_page_writes=on");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Start the disposable server with exactly these name=value settings fixed on
|
||||
** its command line. A setting fixed there cannot be changed later by ALTER
|
||||
** SYSTEM, so a suite that changes a reload-time setting leaves it off this list.
|
||||
*******************************************************************************/
|
||||
public static void start(String... commandLineSettings) throws Exception
|
||||
{
|
||||
List<String> serverCommand = new ArrayList<>(List.of("postgres"));
|
||||
for(String setting : commandLineSettings)
|
||||
{
|
||||
serverCommand.add("-c");
|
||||
serverCommand.add(setting);
|
||||
}
|
||||
previousZone = TimeZone.getDefault();
|
||||
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(serverCommand.toArray(String[]::new))
|
||||
.withExposedPorts(5432)
|
||||
.withCreateContainerCmdModifier(command -> command.getHostConfig().withMemory(768L * 1024 * 1024)
|
||||
.withPortBindings(FixtureEndpoint.publishedBinding(5432)))
|
||||
|
||||
Reference in new issue
Block a user