diff --git a/CHANGELOG.md b/CHANGELOG.md index e21dd13..3ecd8a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.2] - 2026-09-04 + +### Fixed + +- Database integration tests now serialize direct extension provisioning with + `createIndex`, preventing concurrent `CREATE EXTENSION` calls from racing on + fresh CI databases. + ## [0.1.1] - 2026-09-04 ### Fixed diff --git a/bun.lock b/bun.lock index 66f31a0..4193b12 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ }, "packages/core": { "name": "searchgres", - "version": "0.1.1", + "version": "0.1.2", "dependencies": { "@opentelemetry/api": "^1.9.1", "ai": "^7.0.79", diff --git a/packages/core/package.json b/packages/core/package.json index 2bc158a..8cae090 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "searchgres", - "version": "0.1.1", + "version": "0.1.2", "description": "Postgres-native hybrid search: semantic + BM25 retrieval with composable hierarchy, metadata, temporal, and regex filtering.", "license": "Apache-2.0", "type": "module", diff --git a/packages/core/src/create-index.ts b/packages/core/src/create-index.ts index d835857..907e01e 100644 --- a/packages/core/src/create-index.ts +++ b/packages/core/src/create-index.ts @@ -1,7 +1,7 @@ import type postgres from "postgres"; import { type IndexConfig, normalizeIndexConfig } from "./config.ts"; import { ensureExtension } from "./db/extensions.ts"; -import { acquireAdvisoryLock, advisoryLockKey } from "./db/lock.ts"; +import { acquireAdvisoryLock, CREATE_INDEX_LOCK_KEY } from "./db/lock.ts"; import { ensurePostgresVersion } from "./db/preflight.ts"; import { createBatchUpsertRoutine } from "./db/routines/batch-upsert.ts"; import { createRecordRoutines } from "./db/routines/records.ts"; @@ -43,7 +43,7 @@ export async function createIndex( ...DEFAULT_MIGRATION_TIMEOUTS, lockTimeout: DEFAULT_MIGRATION_LOCK_TIMEOUT, }); - await acquireAdvisoryLock(tx, advisoryLockKey("searchgres:create-index")); + await acquireAdvisoryLock(tx, CREATE_INDEX_LOCK_KEY); await setLockTimeout(tx, DEFAULT_MIGRATION_TIMEOUTS.lockTimeout); await ensurePostgresVersion(tx); diff --git a/packages/core/src/db/lock.ts b/packages/core/src/db/lock.ts index 0db9351..f3d972e 100644 --- a/packages/core/src/db/lock.ts +++ b/packages/core/src/db/lock.ts @@ -10,6 +10,9 @@ export function advisoryLockKey(name: string): AdvisoryLockKey { return [digest.readInt32BE(0), digest.readInt32BE(4)]; } +/** Serializes index provisioning, including required extension installation. */ +export const CREATE_INDEX_LOCK_KEY = advisoryLockKey("searchgres:create-index"); + /** Block transaction-locally until an advisory lock is acquired or times out. */ export async function acquireAdvisoryLock( sql: postgres.ISql, diff --git a/packages/core/src/version.ts b/packages/core/src/version.ts index cd724f4..f44ad77 100644 --- a/packages/core/src/version.ts +++ b/packages/core/src/version.ts @@ -10,4 +10,4 @@ * This is package metadata only. Immutable index schemas use their own format * marker and never derive compatibility from the library package version. */ -export const LIBRARY_VERSION = "0.1.1"; +export const LIBRARY_VERSION = "0.1.2"; diff --git a/packages/core/test/db.integration.test.ts b/packages/core/test/db.integration.test.ts index ac103c4..a6d3c59 100644 --- a/packages/core/test/db.integration.test.ts +++ b/packages/core/test/db.integration.test.ts @@ -5,7 +5,11 @@ import { type ExtensionRequirement, ensureExtension, } from "../src/db/extensions.ts"; -import { acquireAdvisoryLock, advisoryLockKey } from "../src/db/lock.ts"; +import { + acquireAdvisoryLock, + advisoryLockKey, + CREATE_INDEX_LOCK_KEY, +} from "../src/db/lock.ts"; import { ensurePostgresVersion, MINIMUM_POSTGRES_VERSION_NUM, @@ -39,6 +43,11 @@ test("accepts PostgreSQL 18", async () => { test("ensures supplied extensions with an empty search path", async () => { const extensions = await sql.begin(async (tx) => { + // This test provisions extensions directly while other test files may call + // createIndex against the same fresh database. Honor ensureExtension's + // caller-owned locking contract so those CREATE EXTENSION statements + // cannot race. + await acquireAdvisoryLock(tx, CREATE_INDEX_LOCK_KEY); await tx.unsafe("set local search_path to ''"); const results = []; for (const requirement of requiredExtensions) {