Skip to content

Comments

feat(postgres): support configurable schema#293

Merged
jamescmartinez merged 4 commits intoopenworkflowdev:mainfrom
thomasjiangcy:feat/postgres-custom-schema-signed
Feb 13, 2026
Merged

feat(postgres): support configurable schema#293
jamescmartinez merged 4 commits intoopenworkflowdev:mainfrom
thomasjiangcy:feat/postgres-custom-schema-signed

Conversation

@thomasjiangcy
Copy link
Contributor

@thomasjiangcy thomasjiangcy commented Feb 13, 2026

Summary

  • add optional schema to BackendPostgres.connect options (default openworkflow)
  • route Postgres runtime queries through schema-aware identifiers instead of hardcoded "openworkflow"
  • validate schema names before running migrations/queries and harden migration SQL interpolation
  • update docs for Postgres/production to describe configurable schema usage
  • add tests for custom schema behavior and invalid schema names

Notes

Closes #291

Disclosure: the changes were done via Codex CLI but I've reviewed each line of code before getting it to open a PR.


Additional notes

I wasn't sure what you meant exactly re. prepared statement issues, but I went with the assumption that you meant the number of prepared statements keeps growing when running the same query repeatedly.

So I got Codex to test it out on this branch, specifically whether pg_prepared_statements grew after running the same query shape.

This was the test code used:

import {
  DEFAULT_POSTGRES_URL,
  dropSchema,
  migrate,
  newPostgresMaxOne,
} from "./packages/openworkflow/postgres/postgres.ts";

const pg = newPostgresMaxOne(DEFAULT_POSTGRES_URL);
const schemaA = "ps_check_a";
const schemaB = "ps_check_b";

const countPrepared = async () => {
  const rows = await pg<{ count: number }[]>`
    SELECT COUNT(*)::int AS count FROM pg_prepared_statements
  `;
  return rows[0]?.count ?? 0;
};

const runQueryShape = async (schema: string) => {
  const workflowRuns = pg`${pg(schema)}.${pg("workflow_runs")}`;

  for (let i = 0; i < 300; i++) {
    await pg`
      SELECT "id"
      FROM ${workflowRuns}
      WHERE "namespace_id" = ${`ns_${i % 5}`}
        AND "workflow_name" = ${`wf_${i % 3}`}
      ORDER BY "created_at" DESC, "id" DESC
      LIMIT ${10}
    `;
  }
};

try {
  await dropSchema(pg, schemaA);
  await dropSchema(pg, schemaB);
  await migrate(pg, schemaA);
  await migrate(pg, schemaB);

  await pg`DEALLOCATE ALL`;

  const c0 = await countPrepared();
  await runQueryShape(schemaA);
  const c1 = await countPrepared();
  await runQueryShape(schemaA);
  const c2 = await countPrepared();
  await runQueryShape(schemaB);
  const c3 = await countPrepared();

  console.log({ c0, c1, c2, c3 });
  console.log(`same-schema plateau: ${c1 === c2}`);
  console.log(`cross-schema growth: ${c3 > c2}`);
} finally {
  await dropSchema(pg, schemaA);
  await dropSchema(pg, schemaB);
  await pg.end({ timeout: 5 });
}

Results:

{
  "c0": 1,
  "c1": 2,
  "c2": 2,
  "c3": 3
}
same-schema plateau: true
cross-schema growth: true

pg_prepared_statements showed one statement for ps_check_a.workflow_runs and one for ps_check_b.workflow_runs.

So the changes here didn't result in any regressions in that regard.

@thomasjiangcy thomasjiangcy marked this pull request as draft February 13, 2026 15:30
@thomasjiangcy thomasjiangcy force-pushed the feat/postgres-custom-schema-signed branch from 2e2cfe4 to 7477b5b Compare February 13, 2026 16:35
@thomasjiangcy thomasjiangcy marked this pull request as ready for review February 13, 2026 16:37
@jamescmartinez
Copy link
Contributor

Disclosure: the changes were done via Codex CLI but I've reviewed each line of code before getting it to open a PR.

thank you!

test it out on this branch, specifically whether pg_prepared_statements grew after running the same query shape

thank you x2!

Reviewing now.

@codecov
Copy link

codecov bot commented Feb 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for configurable database schemas in the PostgreSQL backend, allowing users to isolate OpenWorkflow data within custom schemas rather than being limited to the hardcoded openworkflow schema. This addresses the need for schema-level isolation in scenarios where namespace-level isolation is insufficient (e.g., control plane vs data plane separation).

Changes:

  • Added optional schema parameter to BackendPostgres.connect() with default value "openworkflow" to maintain backward compatibility
  • Implemented schema name validation to prevent SQL injection and ensure PostgreSQL identifier compliance
  • Updated all database queries to use dynamic schema references via helper methods
  • Added comprehensive tests for schema validation and custom schema behavior
  • Updated documentation to describe the new schema configuration option

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/openworkflow/postgres/postgres.ts Added schema validation, updated migrations and utility functions to use dynamic schema with proper SQL escaping
packages/openworkflow/postgres/backend.ts Added schema option, updated all queries to use schema-aware table references via helper methods
packages/openworkflow/postgres/postgres.test.ts Added tests for invalid schema names and length validation
packages/openworkflow/postgres/backend.test.ts Added tests for schema validation errors and custom schema data storage
packages/openworkflow/client.test.ts Refactored test to avoid accessing private backend properties, improved type safety
packages/docs/docs/production.mdx Updated documentation to describe configurable schema option and usage
packages/docs/docs/postgres.mdx Updated documentation with schema configuration examples and clarifications

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

{ name: "define-wrap-test" },
({ input }) => ({ doubled: (input as { n: number }).n * 2 }),
);
const workflow = client.defineWorkflow<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated, but I'll keep

@jamescmartinez jamescmartinez merged commit d218dcd into openworkflowdev:main Feb 13, 2026
9 checks passed
@jamescmartinez
Copy link
Contributor

jamescmartinez commented Feb 13, 2026

merged, will ship in the next release, most likely today
thank you!

@thomasjiangcy thomasjiangcy deleted the feat/postgres-custom-schema-signed branch February 13, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Postgres - allow custom schema

2 participants