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
5 changes: 5 additions & 0 deletions .changeset/pglite-pg-partman-initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@electric-sql/pglite-pg_partman': patch
---

pglite-pg_partman: initial deployment
34 changes: 34 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ jobs:
packages/pglite-postgis/release
packages/pglite-pgvector/release
packages/pglite-pgmq/release
packages/pglite-pg_partman/release
key: wasm-build-${{ steps.postgres-pglite-commit-id.outputs.hash }}-pglite-${{ steps.pglite-version.outputs.version }}

- name: Build all if necessary
Expand Down Expand Up @@ -187,6 +188,14 @@ jobs:
path: ./packages/pglite-pgmq/release/**
retention-days: 60

- name: Upload pglite-pg_partman build artifacts to Github artifacts
id: upload-pglite-pg-partman-release-files
uses: actions/upload-artifact@v7
with:
name: pglite-pg-partman-release-files-node-v20.x
path: ./packages/pglite-pg_partman/release/**
retention-days: 60

- name: Typecheck pglite
working-directory: ${{ github.workspace }}/packages/pglite
run: pnpm typecheck
Expand Down Expand Up @@ -394,6 +403,12 @@ jobs:
with:
name: pglite-pgmq-release-files-node-v20.x
path: ./packages/pglite-pgmq/release/

- name: Download pglite-pg_partman build artifacts
uses: actions/download-artifact@v8
with:
name: pglite-pg-partman-release-files-node-v20.x
path: ./packages/pglite-pg_partman/release/

- name: Install dependencies
run: pnpm install --frozen-lockfile
Expand Down Expand Up @@ -483,6 +498,13 @@ jobs:
name: pglite-pgmq-dist-node-v${{ matrix.node }}
path: ./packages/pglite-pgmq/dist/*

- name: Upload pglite-pg_partman distribution artifact
id: upload-pglite-pg-partman-dist
uses: actions/upload-artifact@v7
with:
name: pglite-pg-partman-dist-node-v${{ matrix.node }}
path: ./packages/pglite-pg_partman/dist/*

publish-website-with-demos:
name: Publish website with demos
runs-on: blacksmith-4vcpu-ubuntu-2204
Expand Down Expand Up @@ -576,6 +598,12 @@ jobs:
name: pglite-pgmq-dist-node-v20.x
path: ./packages/pglite-pgmq/dist/

- name: Download pglite-pg_partman dist artifacts
uses: actions/download-artifact@v8
with:
name: pglite-pg-partman-dist-node-v20.x
path: ./packages/pglite-pg_partman/dist/

- name: Install dependencies
run: pnpm install --frozen-lockfile

Expand Down Expand Up @@ -732,6 +760,12 @@ jobs:
name: pglite-pgmq-release-files-node-v20.x
path: ./packages/pglite-pgmq/release/

- name: Download pglite-pg_partman build artifacts
uses: actions/download-artifact@v8
with:
name: pglite-pg-partman-release-files-node-v20.x
path: ./packages/pglite-pg_partman/release/

- name: Download pglite-age build artifacts
uses: actions/download-artifact@v8
with:
Expand Down
18 changes: 18 additions & 0 deletions docs/extensions/extensions.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,24 @@ const baseExtensions: Extension[] = [
size: 41908,
npmjsUrl: 'https://www.npmjs.com/package/@electric-sql/pglite-pgmq',
},
{
name: 'pg_partman',
description: `
Automated creation and maintenance of time-based and serial-based table
partition sets. The background worker is not included; call
run_maintenance() from your application instead.

This is an **external** extension, distributed as a separate package at https://www.npmjs.com/package/@electric-sql/pglite-pg_partman
`,
shortDescription:
'Automated creation and maintenance of table partition sets.',
docs: 'https://github.com/pgpartman/pg_partman',
tags: ['postgres extension', 'external'],
importPath: '@electric-sql/pglite-pg_partman',
importName: 'pg_partman',
size: 1270062,
npmjsUrl: 'https://www.npmjs.com/package/@electric-sql/pglite-pg_partman',
},
]

const tags = [
Expand Down
1 change: 1 addition & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"@electric-sql/pglite-pg_uuidv7": "workspace:*",
"@electric-sql/pglite-pgtap": "workspace:*",
"@electric-sql/pglite-pgmq": "workspace:*",
"@electric-sql/pglite-pg_partman": "workspace:*",
"@uiw/codemirror-theme-github": "^4.23.0",
"dedent": "^1.5.3"
}
Expand Down
1 change: 1 addition & 0 deletions docs/repl/allExtensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,4 @@ export { uuid_ossp } from '@electric-sql/pglite/contrib/uuid_ossp'
export { vector } from '@electric-sql/pglite-pgvector'
export { pgmq } from '@electric-sql/pglite-pgmq'
export { moddatetime } from '@electric-sql/pglite/contrib/moddatetime'
export { pg_partman } from '@electric-sql/pglite-pg_partman'
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"ts:build": "pnpm -r --filter \"./packages/**\" build",
"ts:build:debug": "DEBUG=true pnpm ts:build",
"wasm:copy-pgmq": "mkdir -p ./packages/pglite-pgmq/release && cp ./postgres-pglite/dist/extensions/other/pgmq-extension.tar.gz ./packages/pglite-pgmq/release/pgmq.tar.gz",
"wasm:copy-pg_partman": "mkdir -p ./packages/pglite-pg_partman/release && cp ./postgres-pglite/dist/extensions/other/pg_partman.tar.gz ./packages/pglite-pg_partman/release",
"wasm:copy-pgvector": "mkdir -p ./packages/pglite-pgvector/release && cp ./postgres-pglite/dist/extensions/other/vector.tar.gz ./packages/pglite-pgvector/release",
"wasm:copy-pg_uuidv7": "mkdir -p ./packages/pglite-pg_uuidv7/release && cp ./postgres-pglite/dist/extensions/other/pg_uuidv7.tar.gz ./packages/pglite-pg_uuidv7/release",
"wasm:copy-pgtap": "mkdir -p ./packages/pglite-pgtap/release && cp ./postgres-pglite/dist/extensions/other/pgtap.tar.gz ./packages/pglite-pgtap/release",
Expand All @@ -21,7 +22,7 @@
"wasm:copy-age": "mkdir -p ./packages/pglite-age/release && cp ./postgres-pglite/dist/extensions/other/age.tar.gz ./packages/pglite-age/release",
"wasm:copy-pg_textsearch": "mkdir -p ./packages/pglite-pg_textsearch/release && cp ./postgres-pglite/dist/extensions/other/pg_textsearch.tar.gz ./packages/pglite-pg_textsearch/release",
"wasm:copy-postgis": "mkdir -p ./packages/pglite-postgis/release && cp ./postgres-pglite/dist/extensions/other/postgis.tar.gz ./packages/pglite-postgis/release",
"wasm:copy-other_extensions": "pnpm wasm:copy-postgis; pnpm wasm:copy-pg_textsearch; pnpm wasm:copy-age; pnpm wasm:copy-pg_hashids; pnpm wasm:copy-pg_ivm; pnpm wasm:copy-pgtap; pnpm wasm:copy-pg_uuidv7; pnpm wasm:copy-pgvector; pnpm wasm:copy-pgmq",
"wasm:copy-other_extensions": "pnpm wasm:copy-postgis; pnpm wasm:copy-pg_textsearch; pnpm wasm:copy-age; pnpm wasm:copy-pg_hashids; pnpm wasm:copy-pg_ivm; pnpm wasm:copy-pgtap; pnpm wasm:copy-pg_uuidv7; pnpm wasm:copy-pgvector; pnpm wasm:copy-pgmq; pnpm wasm:copy-pg_partman",
"wasm:copy-initdb": "mkdir -p ./packages/pglite/release && cp ./postgres-pglite/dist/bin/initdb.* ./packages/pglite/release",
"wasm:copy-pgdump": "mkdir -p ./packages/pglite-tools/release && cp ./postgres-pglite/dist/bin/pg_dump.* ./packages/pglite-tools/release",
"wasm:copy-pglite": "mkdir -p ./packages/pglite/release/ && cp ./postgres-pglite/dist/bin/pglite.* ./packages/pglite/release/ && cp ./postgres-pglite/dist/extensions/*.tar.gz ./packages/pglite/release/",
Expand Down
2 changes: 2 additions & 0 deletions packages/pglite-pg_partman/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
release/*
dist
70 changes: 70 additions & 0 deletions packages/pglite-pg_partman/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# @electric-sql/pglite-pg_partman

[pg_partman](https://github.com/pgpartman/pg_partman) extension for
[PGlite](https://pglite.dev).

Automated creation and maintenance of time-based and serial-based table
partition sets, running entirely in the browser or Node via WASM.

## Installation

```bash
npm install @electric-sql/pglite-pg_partman
```

## Usage

```typescript
import { PGlite } from '@electric-sql/pglite'
import { pg_partman } from '@electric-sql/pglite-pg_partman'

const pg = new PGlite({
extensions: {
pg_partman,
},
})

// pg_partman is not relocatable and is conventionally installed
// into its own schema
await pg.exec(`
CREATE SCHEMA partman;
CREATE EXTENSION IF NOT EXISTS pg_partman SCHEMA partman;
`)

// register a partitioned table with pg_partman
await pg.exec(`
CREATE TABLE public.events (
id int,
payload text,
created_at timestamptz NOT NULL DEFAULT now()
) PARTITION BY RANGE (created_at);

SELECT partman.create_parent('public.events', 'created_at', '1 day');
`)

// periodically create new partitions / apply retention
await pg.exec('SELECT partman.run_maintenance();')
```

## Differences from pg_partman on a Postgres server

- **No background worker.** pg_partman's optional `pg_partman_bgw` scheduler
is a Postgres background worker, which cannot exist in single-process WASM.
This package ships the (officially supported) SQL-only build; the embedding
application decides when to call `run_maintenance()`, for example from a
timer.
- **Run your session in UTC.** pg_partman computes partition boundaries using
the session timezone, and in a browser that defaults to the user's local
timezone. Upstream strongly recommends UTC:
`SET TIME ZONE 'UTC';`
- Procedures that manage their own transactions (`run_maintenance_proc()`,
`partition_data_proc()`, ...) work when `CALL`ed outside a transaction
block, but not inside `pg.transaction()`. The plain function equivalents
(`run_maintenance()`, `partition_data_time()`, ...) work everywhere.

See the [pg_partman documentation](https://github.com/pgpartman/pg_partman/blob/master/doc/pg_partman.md)
for the full API.

## License

Apache-2.0
21 changes: 21 additions & 0 deletions packages/pglite-pg_partman/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import globals from 'globals'
import rootConfig from '../../eslint.config.js'

export default [
...rootConfig,
{
ignores: ['release/**/*', 'dist/**/*'],
},
{
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
},
rules: {
...rootConfig.rules,
'@typescript-eslint/no-explicit-any': 'off',
},
},
]
66 changes: 66 additions & 0 deletions packages/pglite-pg_partman/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"name": "@electric-sql/pglite-pg_partman",
"version": "0.0.1",
"description": "pg_partman extension for PGlite",
"author": "Electric DB Limited",
"homepage": "https://pglite.dev",
"license": "Apache-2.0",
"keywords": [
"postgres",
"sql",
"database",
"wasm",
"pglite",
"pg_partman"
],
"private": false,
"publishConfig": {
"access": "public"
},
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"files": [
"dist"
],
"repository": {
"type": "git",
"url": "git+https://github.com/electric-sql/pglite.git",
"directory": "packages/pglite-pg_partman"
},
"scripts": {
"build": "tsup && tsx scripts/bundle-wasm.ts",
"check:exports": "attw . --pack --profile node16",
"lint": "eslint ./src ./tests --report-unused-disable-directives --max-warnings 0",
"format": "prettier --write ./src ./tests",
"sync-pgtap-spec": "tsx scripts/sync-pgtap-spec.ts",
"typecheck": "tsc",
"stylecheck": "pnpm lint && prettier --check ./src ./tests",
"test": "vitest",
"prepublishOnly": "pnpm check:exports"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.1",
"@electric-sql/pglite": "workspace:*",
"@electric-sql/pglite-pgtap": "workspace:*",
"@electric-sql/pglite-utils": "workspace:*",
"@types/node": "^20.16.11",
"vitest": "^2.1.2"
},
"peerDependencies": {
"@electric-sql/pglite": "workspace:*"
}
}
8 changes: 8 additions & 0 deletions packages/pglite-pg_partman/scripts/bundle-wasm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { copyFiles, findAndReplaceInDir } from '@electric-sql/pglite-utils/scripts/fileUtils'

async function main() {
await copyFiles('./release', './dist')
await findAndReplaceInDir('./dist', /\.\.\/release\//g, './', ['.js', '.cjs'])
}

await main()
63 changes: 63 additions & 0 deletions packages/pglite-pg_partman/scripts/sync-pgtap-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Refresh the vendored pgTAP spec files in tests/pgtap/ from upstream.
*
* The files must stay byte-identical to the pg_partman release the
* postgres-pglite submodule builds the WASM bundle from. To bump versions:
* update UPSTREAM_TAG, run `pnpm sync-pgtap-spec`, and update the version
* assertion in tests/pg_partman.test.ts (which fails loudly if the bundle
* and this spec ever disagree).
*/
import { mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { fileURLToPath } from 'node:url'

const UPSTREAM_TAG = 'v5.5.0'

const SPEC_FILES = [
'test-id-1-bigint-list.sql',
'test-id-1-int-list.sql',
'test-id-10.sql',
'test-time-daily.sql',
'test-time-infinite-no-data.sql',
'test-time-maintenance-order.sql',
'test-time-monthly-infinity.sql',
'test-time-monthly.sql',
]

const tag = process.argv[2] ?? UPSTREAM_TAG
if (tag !== UPSTREAM_TAG) {
console.warn(
`Syncing ${tag} instead of the pinned ${UPSTREAM_TAG} — remember to update UPSTREAM_TAG`,
)
}

const specDir = fileURLToPath(new URL('../tests/pgtap', import.meta.url))
mkdirSync(specDir, { recursive: true })

// the harness runs every .sql in tests/pgtap, so a file that upstream renamed
// away must not linger here as a stale spec
for (const file of readdirSync(specDir)) {
if (file.endsWith('.sql') && !SPEC_FILES.includes(file)) {
rmSync(join(specDir, file))
console.log(`removed ${file} (no longer in SPEC_FILES)`)
}
}

let failed = false
for (const file of SPEC_FILES) {
const url = `https://raw.githubusercontent.com/pgpartman/pg_partman/${tag}/test/${file}`
const response = await fetch(url)
if (!response.ok) {
console.error(`${response.status} ${response.statusText}: ${url}`)
console.error(
` was the file renamed or removed upstream? Update SPEC_FILES to match ${tag}'s test/ directory`,
)
failed = true
continue
}
writeFileSync(join(specDir, file), await response.text())
console.log(`synced ${file} (${tag})`)
}
if (failed) {
process.exit(1)
}
17 changes: 17 additions & 0 deletions packages/pglite-pg_partman/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type {
Extension,
ExtensionSetupResult,
PGliteInterface,
} from '@electric-sql/pglite'

const setup = async (_pg: PGliteInterface, emscriptenOpts: any) => {
return {
emscriptenOpts,
bundlePath: new URL('../release/pg_partman.tar.gz', import.meta.url),
} satisfies ExtensionSetupResult
}

export const pg_partman = {
name: 'pg_partman',
setup,
} satisfies Extension
Loading