Skip to content

fix: defer functions that reference new tables to after table creation (#530) - #531

Merged
tianzhou merged 3 commits into
mainfrom
fix/issue-530-function-table-ordering
Aug 6, 2026
Merged

fix: defer functions that reference new tables to after table creation (#530)#531
tianzhou merged 3 commits into
mainfrom
fix/issue-530-function-table-ordering

Conversation

@tianzhou

@tianzhou tianzhou commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Problem

When a schema has the dependency chain function → table → function, pgschema creates all functions before all tables. This means a function that queries a new table fails with relation does not exist because the table hasn't been created yet.

Minimal reproduction

CREATE FUNCTION random_id()
RETURNS bigint LANGUAGE sql AS $$
    SELECT CAST(1000000000 + floor(random() * 9000000000) AS bigint);
$$;

CREATE TABLE x (
    id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    public_id text NOT NULL UNIQUE DEFAULT random_id(),
    name text NOT NULL UNIQUE,
    flag boolean NOT NULL DEFAULT FALSE
);

CREATE FUNCTION x_is_flagged(id bigint)
RETURNS boolean LANGUAGE sql STABLE AS $$
    SELECT x.flag FROM x WHERE x.id = id;
$$;

The dependency graph is: random_idxx_is_flagged

The current generateCreateSQL order groups all functions first:

  1. random_id()
  2. x_is_flagged() ✗ (table x doesn't exist yet)
  3. x

Fix

Split functionsWithoutViewDeps into those that reference tables in tablesWithDeps and those that don't. Functions that query new tables are deferred to after all tables are created, preserving the correct dependency order.

New order:

  1. random_id() (no table deps)
  2. x (depends on random_id)
  3. x_is_flagged() (depends on x — created after all tables)

Changes

  • internal/diff/diff.go: Added functionReferencesNewTable helper using the existing containsIdentifier from view.go. Modified generateCreateSQL to split functions by table dependencies and emit table-dependent functions after tablesWithDeps.
  • Test: New test case at testdata/diff/dependency/issue_530_function_table_function_chain/ validates the correct ordering.

Closes #530

#530)

When a function depends on a table that itself depends on another
function (the chain fn1 -> table -> fn2), fn2 was incorrectly created
in the first function batch before the table it queries exists. This
caused a "relation does not exist" error.

Split functionsWithoutViewDeps into those that reference tables being
created later (tablesWithDeps) and those that do not. Table-dependent
functions are now created after all tables exist, preserving the
correct dependency chain.
Copilot AI lite review requested due to automatic review settings August 6, 2026 06:08
@greptile-apps

greptile-apps Bot commented Aug 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR splits newly added functions into pre-table and post-table batches so functions querying dependency-bearing new tables are emitted after those tables.

  • Adds textual table-reference detection for function definitions
  • Defers matching functions until after tablesWithDeps
  • Adds a regression fixture for the function → table → function ordering case

Confidence Score: 4/5

The PR should not merge until incidental table-name matches can no longer defer functions required by the tables created ahead of them.

Scanning raw function definitions treats comments, literals, and aliases as table dependencies, allowing the new batching logic to emit a table before a function required by its default or CHECK expression.

Files Needing Attention: internal/diff/diff.go

Important Files Changed

Filename Overview
internal/diff/diff.go Adds function-to-table textual classification and post-table emission, but incidental identifier matches can reverse a real table-to-function dependency.
testdata/diff/dependency/issue_530_function_table_function_chain/new.sql Defines the intended function → table → function dependency chain.
testdata/diff/dependency/issue_530_function_table_function_chain/diff.sql Captures the expected corrected DDL order for the basic dependency chain.
testdata/diff/dependency/issue_530_function_table_function_chain/old.sql Provides the empty starting schema for the regression fixture.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    F[New function]
    T[New table in tablesWithDeps]
    Scan{Function definition contains table name?}
    Early[Create function before tablesWithDeps]
    CreateTable[Create tablesWithDeps]
    Late[Create deferred function]

    F --> Scan
    Scan -- No --> Early
    Early --> CreateTable
    Scan -- Yes --> CreateTable
    CreateTable --> Late
Loading

Reviews (1): Last reviewed commit: "fix: defer functions that reference new ..." | Re-trigger Greptile

Comment thread internal/diff/diff.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Fixes migration statement ordering for schemas with a dependency chain function → table → function, ensuring functions that query newly created (function-dependent) tables are emitted after those tables are created, preventing relation does not exist errors during apply.

Changes:

  • Split “functions without view deps” into two batches: those safe to create early vs. those referencing newly-created function-dependent tables (issue #530).
  • Added functionReferencesNewTable helper leveraging existing identifier-matching logic.
  • Added a new file-based diff fixture to validate the corrected ordering for the minimal reproduction.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 2 comments.

File Description
internal/diff/diff.go Adjusts generateCreateSQL ordering to defer table-referencing functions until after creation of function-dependent tables; adds helper for detecting table references.
testdata/diff/dependency/issue_530_function_table_function_chain/old.sql Adds empty “old schema” fixture for regression coverage.
testdata/diff/dependency/issue_530_function_table_function_chain/new.sql Adds “new schema” fixture reproducing the function→table→function dependency chain.
testdata/diff/dependency/issue_530_function_table_function_chain/diff.sql Adds expected migration output verifying function/table/function ordering.

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

Comment thread internal/diff/diff.go
Comment thread internal/diff/diff.go Outdated
Use a regex matching FROM/JOIN/INTO/UPDATE/DELETE TABLE patterns
to avoid false positives from table names appearing in comments,
string literals, or aliases within function bodies.

This addresses the concern that a function whose body incidentally
contains a table name (e.g., in a comment) could be incorrectly
deferred past a table that depends on that function.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

Suppressed comments (2)

internal/diff/diff.go:2637

  • tableRefPattern matches bare INTO, which can also occur in PL/pgSQL as SELECT ... INTO var. That can incorrectly classify var as a table reference and defer the function unnecessarily (potentially breaking ordering for tables/domains that depend on that function). Restrict the pattern to INSERT INTO (and optionally MERGE INTO) to reduce false positives.
var tableRefPattern = regexp.MustCompile(
	`(?i)(?:FROM|JOIN|INTO|UPDATE(?:\s+ONLY)?|DELETE\s+FROM|TABLE)\s+` +
		`([a-z_][a-z0-9_$]*(?:\.[a-z_][a-z0-9_$]*)*)`,
)

internal/diff/diff.go:2643

  • The doc comment says this approach is "avoiding false positives from comments, literals, and aliases", but the regex will still match FROM ... text inside SQL comments or string literals (it’s a heuristic, not a parser). Please adjust the comment to avoid overstating the behavior.
// functionReferencesNewTable determines if a function body references any newly
// added table that will be created after the first function batch (tablesWithDeps).
// It looks for table names in SQL table-reference contexts (FROM, JOIN,
// INSERT INTO, UPDATE, DELETE FROM) rather than scanning the entire body,
// avoiding false positives from comments, literals, and aliases.

@tianzhou
tianzhou merged commit 6c5f9b4 into main Aug 6, 2026
2 checks passed
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.

Statement order causes "relation does not exist" error

2 participants