Skip to content
Closed
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
7 changes: 7 additions & 0 deletions cmd/dump/dump_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ func TestDumpCommand_Issue83ExplicitConstraintName(t *testing.T) {
runExactMatchTest(t, "issue_83_explicit_constraint_name")
}

func TestDumpCommand_Issue91ColDefaultExpr(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
runExactMatchTest(t, "issue_91_col_default_expr")
}

func runExactMatchTest(t *testing.T, testDataDir string) {
runExactMatchTestWithContext(t, context.Background(), testDataDir)
}
Expand Down
9 changes: 6 additions & 3 deletions ir/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,13 @@ func normalizeDefaultValue(value string) string {
}
// Pattern: 'G'::schema.type_name -> 'G'
// Pattern: 'G'::type_name -> 'G'
// More precisely: strip only the ::type_name portion, preserving any content after it
if strings.Contains(value, "'::") {
if idx := strings.Index(value, "'::"); idx != -1 {
value = value[:idx+1]
}
// Use regex to match and remove type casts after string literals
// Pattern: 'string'::type_name -> 'string'
// This preserves content after the type cast, e.g., (expr 'val'::type) -> (expr 'val')
re := regexp.MustCompile(`'::([a-zA-Z_][a-zA-Z0-9_]*\.)?[a-zA-Z_][a-zA-Z0-9_]*`)
value = re.ReplaceAllString(value, "'")
}
}

Expand Down
10 changes: 10 additions & 0 deletions testdata/dump/issue_91_col_default_expr/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "issue_91_default_expr",
"description": "Test case for default expression with NOT NULL dumping (GitHub issue #91)",
"source": "https://github.com/pgschema/pgschema/issues/91",
"notes": [
"Tests that complex default expressions with NOT NULL constraints are correctly formatted",
"Bug: Missing closing parenthesis when default expression contains parentheses and column has NOT NULL",
"Example: DEFAULT (now() AT TIME ZONE 'utc') NOT NULL should not become DEFAULT (now() AT TIME ZONE 'utc' NOT NULL"
]
}
59 changes: 59 additions & 0 deletions testdata/dump/issue_91_col_default_expr/pgdump.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
--
-- PostgreSQL database dump
--

-- Dumped from database version 17.5 (Debian 17.5-1.pgdg120+1)
-- Dumped by pg_dump version 17.5 (Homebrew)

SET statement_timeout = 0;
SET lock_timeout = 0;
SET idle_in_transaction_session_timeout = 0;
SET transaction_timeout = 0;
SET client_encoding = 'UTF8';
SET standard_conforming_strings = on;
SELECT pg_catalog.set_config('search_path', '', false);
SET check_function_bodies = false;
SET xmloption = content;
SET client_min_messages = warning;
SET row_security = off;

SET default_tablespace = '';

SET default_table_access_method = heap;

--
-- Name: some_table; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.some_table (
id integer NOT NULL,
created_at timestamp without time zone DEFAULT (now() AT TIME ZONE 'utc'::text) NOT NULL
);


--
-- Name: some_table_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

ALTER TABLE public.some_table ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
SEQUENCE NAME public.some_table_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1
);


--
-- Name: some_table some_table_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.some_table
ADD CONSTRAINT some_table_pkey PRIMARY KEY (id);


--
-- PostgreSQL database dump complete
--

18 changes: 18 additions & 0 deletions testdata/dump/issue_91_col_default_expr/pgschema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--
-- pgschema database dump
--

-- Dumped from database version PostgreSQL 17.5
-- Dumped by pgschema version 1.4.0


--
-- Name: some_table; Type: TABLE; Schema: -; Owner: -
--

CREATE TABLE IF NOT EXISTS some_table (
id integer GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp DEFAULT (now() AT TIME ZONE 'utc') NOT NULL,
CONSTRAINT some_table_pkey PRIMARY KEY (id)
);

23 changes: 23 additions & 0 deletions testdata/dump/issue_91_col_default_expr/raw.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--
-- Test case for GitHub issue #91: Default expression with NOT NULL dumping
--
-- This test case reproduces a bug where column default expressions containing
-- parentheses combined with NOT NULL constraints are formatted incorrectly.
--
-- The issue occurs when:
-- 1. A column has a default expression wrapped in parentheses
-- 2. The default expression contains complex SQL like `now() AT TIME ZONE 'utc'`
-- 3. The column also has a NOT NULL constraint
--
-- Original bug: DEFAULT (now() AT TIME ZONE 'utc') NOT NULL
-- Gets corrupted to: DEFAULT (now() AT TIME ZONE 'utc' NOT NULL
-- (Missing closing parenthesis before NOT NULL!)
--

--
-- Test table with complex default expressions
--
CREATE TABLE some_table (
id serial primary key,
created_at timestamp without time zone default (now() at time zone 'utc') not null
);
Loading