Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
"src/native/sdks/ts": "0.3.1",
"src/extensions/external/pg_hashids": "0.3.2",
"src/extensions/external/pg_ivm": "0.3.2",
"src/extensions/external/pg_partman": "0.0.0",
"src/extensions/external/pg_textsearch": "0.3.2",
"src/extensions/external/pg_uuidv7": "0.3.2",
"src/extensions/external/pgmq": "0.0.0",
"src/extensions/external/pgtap": "0.3.2",
"src/extensions/external/postgis": "0.3.2",
"src/extensions/external/vector": "0.3.2",
Expand Down
14 changes: 14 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,13 @@
"version-file": "VERSION",
"changelog-path": "CHANGELOG.md"
},
"src/extensions/external/pg_partman": {
"release-type": "simple",
"component": "oliphaunt-extension-pg-partman",
"package-name": "oliphaunt-extension-pg-partman",
"version-file": "VERSION",
"changelog-path": "CHANGELOG.md"
},
"src/extensions/external/pg_textsearch": {
"release-type": "simple",
"component": "oliphaunt-extension-pg-textsearch",
Expand All @@ -259,6 +266,13 @@
"version-file": "VERSION",
"changelog-path": "CHANGELOG.md"
},
"src/extensions/external/pgmq": {
"release-type": "simple",
"component": "oliphaunt-extension-pgmq",
"package-name": "oliphaunt-extension-pgmq",
"version-file": "VERSION",
"changelog-path": "CHANGELOG.md"
},
"src/extensions/external/pgtap": {
"release-type": "simple",
"component": "oliphaunt-extension-pgtap",
Expand Down
27 changes: 5 additions & 22 deletions src/examples/native/react-native-expo/src/SmokeDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
GENERATED_MOBILE_EXTENSION_SMOKE,
} from './generated/extension-smoke';
import {
activationSqlForExtension,
runMobileBindingProof,
runMobileReleaseExtensionProof,
runPostgresLifecycleResumeCheck,
Expand Down Expand Up @@ -113,7 +114,7 @@ const packagedCatalogProfile = process.env.EXPO_PUBLIC_OLIPHAUNT_CATALOG_PROFILE
const packagedCatalogProfileProbeSql = process.env.EXPO_PUBLIC_OLIPHAUNT_CATALOG_PROFILE_PROBE_SQL;
const packagedCatalogProfileProbeExpected =
process.env.EXPO_PUBLIC_OLIPHAUNT_CATALOG_PROFILE_PROBE_EXPECTED;
const packagedExtensions = process.env.EXPO_PUBLIC_OLIPHAUNT_EXTENSIONS;
const packagedExtensions: string | undefined = process.env.EXPO_PUBLIC_OLIPHAUNT_EXTENSIONS;
let initialLaunchUrlPromise: Promise<string | null> | undefined;

function smokeGlobalState(): SmokeGlobalState {
Expand Down Expand Up @@ -544,7 +545,9 @@ function mobileReleaseExtensionProofPlan() {
selected.has(extension.sqlName),
);
if (plan.length !== selected.size) {
const known = new Set(GENERATED_MOBILE_EXTENSION_PLAN.map((extension) => extension.sqlName));
const known = new Set<string>(
GENERATED_MOBILE_EXTENSION_PLAN.map((extension) => extension.sqlName),
);
const unknown = requested.filter((extension) => !known.has(extension));
throw new Error(`unknown packaged mobile extensions: ${unknown.join(',')}`);
}
Expand All @@ -565,26 +568,6 @@ function mobileReleaseExtensionProofPlan() {
});
}

function activationSqlForExtension(extension: {
readonly sqlName: string;
readonly createsExtension: boolean;
}): readonly string[] {
if (extension.createsExtension) {
return [`CREATE EXTENSION "${extension.sqlName}"`];
}
if (extension.sqlName === 'auto_explain') {
return [
"LOAD 'auto_explain'",
"SET auto_explain.log_min_duration = '0'",
"SET auto_explain.log_analyze = 'true'",
"SET auto_explain.log_level = 'NOTICE'",
];
}
throw new Error(
`generated mobile extension ${extension.sqlName} does not create an extension and has no runtime proof`,
);
}

function waitForBackgroundAndForeground(
stage: (name: string, extra?: Record<string, unknown>) => void,
): Promise<{ states: AppStateStatus[] }> {
Expand Down
145 changes: 142 additions & 3 deletions src/examples/native/react-native-expo/src/generated/extension-smoke.ts

Large diffs are not rendered by default.

34 changes: 28 additions & 6 deletions src/examples/native/react-native-expo/src/mobile-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,25 @@ async function provePgTextsearchEnglishSnowball(db: OliphauntDatabase): Promise<
}
}

export function activationSqlForExtension(extension: {
readonly sqlName: string;
readonly createsExtension: boolean;
}): readonly string[] {
if (extension.createsExtension) {
return [`CREATE EXTENSION "${extension.sqlName}"`];
}
if (extension.sqlName === 'auto_explain') {
return [
"LOAD 'auto_explain'",
"SET auto_explain.log_min_duration = '0'",
"SET auto_explain.log_analyze = 'true'",
"SET auto_explain.log_level = 'NOTICE'",
];
}
// Other LOAD-only modules are activated by their canonical smoke recipes.
return [];
}

export async function runMobileReleaseExtensionProof(
db: OliphauntDatabase,
plan: readonly MobileReleaseExtensionProof[],
Expand Down Expand Up @@ -277,12 +296,15 @@ export async function runMobileReleaseExtensionProof(
return `${extension.sqlName} ${version}; dependency closure ${extension.selectedExtensionDependencies.join(',') || 'none'}`;
}

const configured = await scalar(
db,
"SELECT current_setting('auto_explain.log_min_duration')::text AS value",
);
assertEqual(configured, '0', 'auto_explain load/configuration proof');
return 'auto_explain loaded and configured in the installed app session';
if (extension.sqlName === 'auto_explain') {
const configured = await scalar(
db,
"SELECT current_setting('auto_explain.log_min_duration')::text AS value",
);
assertEqual(configured, '0', 'auto_explain load/configuration proof');
return 'auto_explain loaded and configured in the installed app session';
}
return `${extension.sqlName} canonical LOAD-only smoke proof completed in the installed app session`;
},
onCheckStage,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import assert from 'node:assert/strict';
import { mock, test } from 'bun:test';
import {
GENERATED_MOBILE_EXTENSION_PLAN,
GENERATED_MOBILE_EXTENSION_SMOKE,
} from '../src/generated/extension-smoke';

class FixturePostgresError extends Error {}

Expand All @@ -10,7 +14,9 @@ mock.module('@oliphaunt/react-native', () => ({
},
}));

const { runMobileReleaseExtensionProof } = await import('../src/mobile-smoke.ts');
const { activationSqlForExtension, runMobileReleaseExtensionProof } = await import(
'../src/mobile-smoke.ts'
);
const { serializeExpoSmokePassReceipt } = await import('../src/smoke-pass-receipt.ts');

function queryResult(values) {
Expand Down Expand Up @@ -53,7 +59,10 @@ test('the successful pg_textsearch producer supplies every semantic PASS fact',
createsExtension: true,
nativeModuleStem: 'pg_textsearch',
selectedExtensionDependencies: [],
activationSql: ['CREATE EXTENSION pg_textsearch'],
activationSql: activationSqlForExtension({
sqlName: 'pg_textsearch',
createsExtension: true,
}),
smokeStatements: ['SELECT 1'],
},
]);
Expand All @@ -62,7 +71,7 @@ test('the successful pg_textsearch producer supplies every semantic PASS fact',
assert.equal(proof.extensionCatalogComplete, true);
assert.equal(proof.pgTextsearchEnglishBm25, true);
assert.deepEqual(executed, [
'CREATE EXTENSION pg_textsearch',
'CREATE EXTENSION "pg_textsearch"',
'DROP TABLE IF EXISTS oliphaunt_mobile_pg_textsearch_english',
'CREATE TABLE oliphaunt_mobile_pg_textsearch_english (id bigint PRIMARY KEY, body text NOT NULL)',
`INSERT INTO oliphaunt_mobile_pg_textsearch_english (id, body) VALUES
Expand Down Expand Up @@ -102,3 +111,79 @@ test('the successful pg_textsearch producer supplies every semantic PASS fact',
assert.equal(receipt.extensionCatalogComplete, true);
assert.equal(receipt.pgTextsearchEnglishBm25, true);
});

for (const sqlName of ['passwordcheck', 'pg_overexplain']) {
test(`${sqlName} proves its generated LOAD-only recipe without auto_explain`, async () => {
const extension = GENERATED_MOBILE_EXTENSION_PLAN.find((row) => row.sqlName === sqlName);
assert.ok(extension);
assert.equal(extension.createsExtension, false);
const smokeStatements = GENERATED_MOBILE_EXTENSION_SMOKE[sqlName];
const queried = [];
const db = {
async execute(sql) {
throw new Error(`unexpected LOAD-only activation: ${sql}`);
},
async query(sql) {
queried.push(sql);
if (smokeStatements.includes(sql)) return queryResult({});
if (sql.includes('string_agg')) return queryResult({ value: '' });
throw new Error(`unexpected LOAD-only query: ${sql}`);
},
};
const proof = await runMobileReleaseExtensionProof(db, [
{
...extension,
activationSql: activationSqlForExtension(extension),
smokeStatements,
},
]);
assert.deepEqual(queried.slice(0, -1), smokeStatements);
assert.deepEqual(proof.activatedExtensions, [sqlName]);
assert.equal(proof.extensionCatalogComplete, true);
assert.equal(proof.pgTextsearchEnglishBm25, false);
assert.equal(proof.checks[0].name, `extension activation: ${sqlName}`);
assert.equal(
proof.checks[0].detail,
`${sqlName} canonical LOAD-only smoke proof completed in the installed app session`,
);
});
}

test('auto_explain still requires its configured logging duration', async () => {
const extension = GENERATED_MOBILE_EXTENSION_PLAN.find((row) => row.sqlName === 'auto_explain');
assert.ok(extension);
const executed = [];
let logMinDuration = '0';
const db = {
async execute(sql) {
executed.push(sql);
},
async query(sql) {
if (GENERATED_MOBILE_EXTENSION_SMOKE.auto_explain.includes(sql)) return queryResult({});
if (sql === "SELECT current_setting('auto_explain.log_min_duration')::text AS value")
return queryResult({ value: logMinDuration });
if (sql.includes('string_agg')) return queryResult({ value: '' });
throw new Error(`unexpected auto_explain query: ${sql}`);
},
};
const plan = [
{
...extension,
activationSql: activationSqlForExtension(extension),
smokeStatements: GENERATED_MOBILE_EXTENSION_SMOKE.auto_explain,
},
];
const proof = await runMobileReleaseExtensionProof(db, plan);
assert.deepEqual(executed, [
"LOAD 'auto_explain'",
"SET auto_explain.log_min_duration = '0'",
"SET auto_explain.log_analyze = 'true'",
"SET auto_explain.log_level = 'NOTICE'",
]);
assert.equal(proof.extensionCatalogComplete, true);
logMinDuration = '-1';
await assert.rejects(
() => runMobileReleaseExtensionProof(db, plan),
/auto_explain load\/configuration proof/,
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ windows_extension_postgis_flatgeobuf() {
logged flatgeobuf-library.log native lib.exe /nologo "/OUT:$(native_path "$prefix/lib/flatgeobuf.lib")" "${objects[@]}"
}

windows_extension_install() {
windows_extension_install_pgtap() {
selected pgtap || return 0
local directory="$work_root/pgtap-windows" destination="$install_dir/share/postgresql/extension"
windows_extension_generate pgtap
Expand All @@ -152,3 +152,9 @@ windows_extension_install() {
mkdir -p "$destination"
cp "$directory/pgtap.control" "$directory/sql/"pgtap*.sql "$directory/sql/uninstall_pgtap.sql" "$destination/"
}

windows_extension_install() {
windows_extension_install_pgtap
if selected pgmq; then windows_extension_generate pgmq-install; fi
if selected pg_partman; then windows_extension_generate pg_partman-install; fi
}
15 changes: 10 additions & 5 deletions src/extensions/artifacts/native/tools/package-release-assets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -616,11 +616,13 @@ package_ios_target() {
ios_device_root="$mobile_extension_work_root/$target_id/ios-device"
macos_archive_root="$mobile_extension_work_root/$target_id/macos-extension-archives"
ios_xcframework_root="$mobile_extension_work_root/$target_id/ios-extension-xcframeworks"
require_dir "$ios_xcframework_root/out" "iOS extension XCFramework output"
tools/dev/bun.sh tools/packaging/platform-binary-contract.mts \
--target "$target_id" \
--root "$ios_xcframework_root/out" \
--required-apple-platforms macos,ios,ios-simulator
if [ -n "$mobile_extensions" ]; then
require_dir "$ios_xcframework_root/out" "iOS extension XCFramework output"
tools/dev/bun.sh tools/packaging/platform-binary-contract.mts \
--target "$target_id" \
--root "$ios_xcframework_root/out" \
--required-apple-platforms macos,ios,ios-simulator
fi
local sql_name pg_major creates_extension stem dependencies shared_preload desktop_prebuilt mobile_prebuilt mobile_static_required mobile_static_targets data_files artifact_policy runtime_artifact ios_artifact static_prefix registration_artifact dependency dependency_xcframework dependency_artifact
while IFS=$'\t' read -r sql_name pg_major creates_extension stem dependencies shared_preload desktop_prebuilt mobile_prebuilt mobile_static_required mobile_static_targets data_files artifact_policy; do
[ -n "$sql_name" ] || continue
Expand Down Expand Up @@ -764,6 +766,9 @@ package_android_target() {
done < <(catalog_rows)
}

# Tests can call the real packaging functions with independently produced inputs.
if [ "${BASH_SOURCE[0]}" != "$0" ]; then return 0; fi

phase="${OLIPHAUNT_EXTENSION_PHASE:-all}"
case "$phase:$target_id" in
all:*) fetch_extension_source_assets ;;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,30 @@ if OLIPHAUNT_EXTENSION_PHASE=android-package OLIPHAUNT_EXTENSION_TARGET=ios-xcfr
echo 'accepted Android phase for iOS' >&2; exit 1
fi
grep -q 'invalid extension phase' "$scratch/wrong-phase.log"
echo 'Android split extension producer checks passed'

# SQL-only iOS products do not produce an XCFramework. Exercise the real
# packager with a host fixture, replacing only the platform compiler producer.
(
export OLIPHAUNT_EXTENSION_TARGET=ios-xcframework
export OLIPHAUNT_EXTENSION_PRODUCTS=oliphaunt-extension-pgmq
export OLIPHAUNT_EXTENSION_HOST_RUNTIME_ROOT="$scratch/ios-host"
export OLIPHAUNT_EXTENSION_RELEASE_ASSET_DIR="$scratch/ios-assets"
export OLIPHAUNT_EXTENSION_RELEASE_STAGE_ROOT="$scratch/ios-stage"
ios_sql="$scratch/ios-host/share/postgresql/extension"
mkdir -p "$ios_sql"
bun - "$scratch/ios-host/postgres" <<'JS'
import {writeFileSync} from 'node:fs';
import {machoFixture} from './tools/packaging/testdata/release-fixture-utils.mts';
writeFileSync(process.argv[2], machoFixture());
JS
printf "default_version = '1.13.0'\n" > "$ios_sql/pgmq.control"
printf 'select 1;\n' > "$ios_sql/pgmq--1.13.0.sql"
source "$producer"
build_mobile_static_artifacts() { [ -z "$1" ]; }
bun "$packager" list-catalog > "$catalog_file"
write_indexes
package_ios_target
[ ! -e "$mobile_extension_work_root/ios-xcframework/ios-extension-xcframeworks/out" ]
[ "$(find "$out_dir" -name '*-pgmq-*-runtime.tar.gz' | wc -l)" = 1 ]
)
echo 'Android split and SQL-only iOS extension producer checks passed'
Loading
Loading