From b9eef4a8d2580e0c8f0429ad1bbb51c5ccd8fe63 Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Wed, 8 Oct 2025 23:40:52 +0800 Subject: [PATCH] feat: VIEW where NOT NULL --- internal/diff/view.go | 24 +++++++++++++++++ ir/formatter.go | 18 +++++++++++++ testdata/diff/create_view/add_view/diff.sql | 14 +++++----- testdata/diff/create_view/add_view/new.sql | 27 ++++++++++++-------- testdata/diff/create_view/add_view/old.sql | 11 +++++--- testdata/diff/create_view/add_view/plan.json | 8 +++--- testdata/diff/create_view/add_view/plan.sql | 14 +++++----- testdata/diff/create_view/add_view/plan.txt | 16 +++++++----- 8 files changed, 96 insertions(+), 36 deletions(-) diff --git a/internal/diff/view.go b/internal/diff/view.go index 8995da96..fc27f022 100644 --- a/internal/diff/view.go +++ b/internal/diff/view.go @@ -455,6 +455,15 @@ func compareExpressions(expr1, expr2 *pg_query.Node) bool { return compareCoalesceExprs(coalesceExpr1, coalesceExpr2) } + // Handle NullTest (IS NULL, IS NOT NULL) + if nullTest1 := expr1.GetNullTest(); nullTest1 != nil { + nullTest2 := expr2.GetNullTest() + if nullTest2 == nil { + return false + } + return compareNullTests(nullTest1, nullTest2) + } + // TODO: Add other expression types as needed return false @@ -569,6 +578,21 @@ func getColumnName(colRef *pg_query.ColumnRef) string { return "" } +// compareNullTests compares NULL test expressions (IS NULL, IS NOT NULL) +func compareNullTests(null1, null2 *pg_query.NullTest) bool { + if null1 == nil || null2 == nil { + return null1 == null2 + } + + // Must have the same null test type (IS NULL vs IS NOT NULL) + if null1.Nulltesttype != null2.Nulltesttype { + return false + } + + // Compare the argument expressions + return compareExpressions(null1.Arg, null2.Arg) +} + // compareAConsts compares constant values func compareAConsts(const1, const2 *pg_query.A_Const) bool { if const1 == nil || const2 == nil { diff --git a/ir/formatter.go b/ir/formatter.go index d9db5c18..e5322eb9 100644 --- a/ir/formatter.go +++ b/ir/formatter.go @@ -240,6 +240,8 @@ func (f *postgreSQLFormatter) formatExpression(expr *pg_query.Node) { f.formatSubLink(expr.GetSubLink()) case expr.GetCoalesceExpr() != nil: f.formatCoalesceExpr(expr.GetCoalesceExpr()) + case expr.GetNullTest() != nil: + f.formatNullTest(expr.GetNullTest()) default: // Fallback to deparse for complex expressions if deparseResult, err := f.deparseNode(expr); err == nil { @@ -550,3 +552,19 @@ func (f *postgreSQLFormatter) formatSubLink(subLink *pg_query.SubLink) { f.buffer.WriteString(deparseResult) } } + +// formatNullTest formats NULL test expressions (IS NULL, IS NOT NULL) +func (f *postgreSQLFormatter) formatNullTest(nullTest *pg_query.NullTest) { + // Format the argument expression + if nullTest.Arg != nil { + f.formatExpression(nullTest.Arg) + } + + // Add the appropriate NULL test operator + switch nullTest.Nulltesttype { + case pg_query.NullTestType_IS_NULL: + f.buffer.WriteString(" IS NULL") + case pg_query.NullTestType_IS_NOT_NULL: + f.buffer.WriteString(" IS NOT NULL") + } +} diff --git a/testdata/diff/create_view/add_view/diff.sql b/testdata/diff/create_view/add_view/diff.sql index ecbdf38b..9eac46b6 100644 --- a/testdata/diff/create_view/add_view/diff.sql +++ b/testdata/diff/create_view/add_view/diff.sql @@ -1,7 +1,9 @@ -CREATE OR REPLACE VIEW active_employees AS +CREATE OR REPLACE VIEW employee_department_view AS SELECT - id, - name, - salary - FROM employees - WHERE status = 'active'; + e.id, + e.name AS employee_name, + d.name AS department_name, + d.manager_id + FROM employees e + JOIN departments d ON e.department_id = d.id + WHERE e.name IS NOT NULL AND d.manager_id IS NOT NULL; diff --git a/testdata/diff/create_view/add_view/new.sql b/testdata/diff/create_view/add_view/new.sql index b8bbf0fb..1db56cb8 100644 --- a/testdata/diff/create_view/add_view/new.sql +++ b/testdata/diff/create_view/add_view/new.sql @@ -1,14 +1,21 @@ CREATE TABLE public.employees ( id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - salary DECIMAL(10,2) NOT NULL, - status VARCHAR(20) DEFAULT 'active' + name VARCHAR(100), + department_id INTEGER ); -CREATE VIEW public.active_employees AS -SELECT - id, - name, - salary -FROM employees -WHERE status = 'active'; \ No newline at end of file +CREATE TABLE public.departments ( + id SERIAL PRIMARY KEY, + name VARCHAR(100), + manager_id INTEGER +); + +CREATE VIEW public.employee_department_view AS +SELECT + e.id, + e.name AS employee_name, + d.name AS department_name, + d.manager_id +FROM employees e +JOIN departments d ON e.department_id = d.id +WHERE e.name IS NOT NULL AND d.manager_id IS NOT NULL; \ No newline at end of file diff --git a/testdata/diff/create_view/add_view/old.sql b/testdata/diff/create_view/add_view/old.sql index b22d326d..ac757190 100644 --- a/testdata/diff/create_view/add_view/old.sql +++ b/testdata/diff/create_view/add_view/old.sql @@ -1,6 +1,11 @@ CREATE TABLE public.employees ( id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - salary DECIMAL(10,2) NOT NULL, - status VARCHAR(20) DEFAULT 'active' + name VARCHAR(100), + department_id INTEGER +); + +CREATE TABLE public.departments ( + id SERIAL PRIMARY KEY, + name VARCHAR(100), + manager_id INTEGER ); \ No newline at end of file diff --git a/testdata/diff/create_view/add_view/plan.json b/testdata/diff/create_view/add_view/plan.json index c9b9b04e..d8464d9a 100644 --- a/testdata/diff/create_view/add_view/plan.json +++ b/testdata/diff/create_view/add_view/plan.json @@ -1,18 +1,18 @@ { "version": "1.0.0", - "pgschema_version": "1.0.0", + "pgschema_version": "1.2.0", "created_at": "1970-01-01T00:00:00Z", "source_fingerprint": { - "hash": "9d5778b7b11d01c6ae81040401fac81834395f4d86bd09b45077a694a1301edd" + "hash": "95ad22fa390833179c9661028a1d5b17d27f87223dac3481576654941198336c" }, "groups": [ { "steps": [ { - "sql": "CREATE OR REPLACE VIEW active_employees AS\n SELECT\n id,\n name,\n salary\n FROM employees\n WHERE status = 'active';", + "sql": "CREATE OR REPLACE VIEW employee_department_view AS\n SELECT\n e.id,\n e.name AS employee_name,\n d.name AS department_name,\n d.manager_id\n FROM employees e\n JOIN departments d ON e.department_id = d.id\n WHERE e.name IS NOT NULL AND d.manager_id IS NOT NULL;", "type": "view", "operation": "create", - "path": "public.active_employees" + "path": "public.employee_department_view" } ] } diff --git a/testdata/diff/create_view/add_view/plan.sql b/testdata/diff/create_view/add_view/plan.sql index ecbdf38b..9eac46b6 100644 --- a/testdata/diff/create_view/add_view/plan.sql +++ b/testdata/diff/create_view/add_view/plan.sql @@ -1,7 +1,9 @@ -CREATE OR REPLACE VIEW active_employees AS +CREATE OR REPLACE VIEW employee_department_view AS SELECT - id, - name, - salary - FROM employees - WHERE status = 'active'; + e.id, + e.name AS employee_name, + d.name AS department_name, + d.manager_id + FROM employees e + JOIN departments d ON e.department_id = d.id + WHERE e.name IS NOT NULL AND d.manager_id IS NOT NULL; diff --git a/testdata/diff/create_view/add_view/plan.txt b/testdata/diff/create_view/add_view/plan.txt index 6f4883ea..c315eaba 100644 --- a/testdata/diff/create_view/add_view/plan.txt +++ b/testdata/diff/create_view/add_view/plan.txt @@ -4,15 +4,17 @@ Summary by type: views: 1 to add Views: - + active_employees + + employee_department_view DDL to be executed: -------------------------------------------------- -CREATE OR REPLACE VIEW active_employees AS +CREATE OR REPLACE VIEW employee_department_view AS SELECT - id, - name, - salary - FROM employees - WHERE status = 'active'; + e.id, + e.name AS employee_name, + d.name AS department_name, + d.manager_id + FROM employees e + JOIN departments d ON e.department_id = d.id + WHERE e.name IS NOT NULL AND d.manager_id IS NOT NULL;