Skip to content
Merged
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: 4 additions & 1 deletion pg_ducklake/src/hooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -447,8 +447,11 @@ RewriteDuckdbRowViewStmt(ViewStmt *stmt, PlannedStmt *pstmt, const char *query_s
if (!OidIsValid(duckdb_row_oid))
return;

/* Analyze a copy: parse analysis rewrites the raw tree in place (JoinExpr
* larg/rarg, SubLink->subselect), which would corrupt stmt->query for
* DefineView's own analysis when we don't rewrite the view ourselves. */
RawStmt *rawstmt = makeNode(RawStmt);
rawstmt->stmt = stmt->query;
rawstmt->stmt = (Node *)copyObjectImpl(stmt->query);
rawstmt->stmt_location = pstmt->stmt_location;
rawstmt->stmt_len = pstmt->stmt_len;
#if PG_VERSION_NUM >= 150000
Expand Down
134 changes: 134 additions & 0 deletions pg_ducklake/test/regression/expected/create_view_shapes.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
-- CREATE VIEW over query shapes that carry raw JoinExpr / SubLink nodes.
-- The utility hook parse-analyzes the view body to detect duckdb_row columns;
-- doing that on the original raw tree corrupted it (JoinExpr larg/rarg and
-- SubLink->subselect are rewritten in place), so DefineView's own parse
-- analysis then failed. These must all succeed and return correct rows.
-- Heap tables --------------------------------------------------------------
CREATE TABLE h1 (id int PRIMARY KEY, val int);
CREATE TABLE h2 (id int PRIMARY KEY, label text);
INSERT INTO h1 VALUES (1, 10), (2, 20), (3, 30);
INSERT INTO h2 VALUES (1, 'a'), (2, 'b');
-- Explicit INNER JOIN
CREATE VIEW hv_join AS
SELECT h1.id, h2.label FROM h1 JOIN h2 ON h2.id = h1.id;
SELECT * FROM hv_join ORDER BY id;
id | label
----+-------
1 | a
2 | b
(2 rows)

-- Explicit LEFT JOIN
CREATE VIEW hv_left_join AS
SELECT h1.id, h2.label FROM h1 LEFT JOIN h2 ON h2.id = h1.id;
SELECT * FROM hv_left_join ORDER BY id;
id | label
----+-------
1 | a
2 | b
3 |
(3 rows)

-- EXISTS sublink in WHERE
CREATE VIEW hv_exists AS
SELECT h1.id FROM h1 WHERE EXISTS (SELECT 1 FROM h2 WHERE h2.id = h1.id);
SELECT * FROM hv_exists ORDER BY id;
id
----
1
2
(2 rows)

-- Correlated scalar subquery in the target list
CREATE VIEW hv_scalar_sub AS
SELECT h1.id, (SELECT h2.label FROM h2 WHERE h2.id = h1.id) AS label FROM h1;
SELECT * FROM hv_scalar_sub ORDER BY id;
id | label
----+-------
1 | a
2 | b
3 |
(3 rows)

-- IN (SELECT ...) sublink
CREATE VIEW hv_in_sub AS
SELECT h1.id FROM h1 WHERE h1.id IN (SELECT h2.id FROM h2);
SELECT * FROM hv_in_sub ORDER BY id;
id
----
1
2
(2 rows)

-- CTE
CREATE VIEW hv_cte AS
WITH c AS (SELECT id, label FROM h2)
SELECT h1.id, c.label FROM h1 JOIN c ON c.id = h1.id;
SELECT * FROM hv_cte ORDER BY id;
id | label
----+-------
1 | a
2 | b
(2 rows)

-- UNION set operation
CREATE VIEW hv_union AS
SELECT id FROM h1 UNION SELECT id FROM h2;
SELECT * FROM hv_union ORDER BY id;
id
----
1
2
3
(3 rows)

-- LATERAL join
CREATE VIEW hv_lateral AS
SELECT h1.id, sub.label
FROM h1 LEFT JOIN LATERAL (SELECT h2.label FROM h2 WHERE h2.id = h1.id) sub ON true;
SELECT * FROM hv_lateral ORDER BY id;
id | label
----+-------
1 | a
2 | b
3 |
(3 rows)

-- CREATE OR REPLACE VIEW over a JOIN
CREATE OR REPLACE VIEW hv_join AS
SELECT h1.id, h2.label, h1.val FROM h1 JOIN h2 ON h2.id = h1.id;
SELECT * FROM hv_join ORDER BY id;
id | label | val
----+-------+-----
1 | a | 10
2 | b | 20
(2 rows)

-- DuckLake tables ----------------------------------------------------------
CREATE TABLE d1 (id int, val int) USING ducklake;
CREATE TABLE d2 (id int, label text) USING ducklake;
INSERT INTO d1 VALUES (1, 10), (2, 20), (3, 30);
INSERT INTO d2 VALUES (1, 'a'), (2, 'b');
CREATE VIEW dv_join AS
SELECT d1.id, d2.label FROM d1 JOIN d2 ON d2.id = d1.id;
SELECT * FROM dv_join ORDER BY id;
id | label
----+-------
1 | a
2 | b
(2 rows)

CREATE VIEW dv_exists AS
SELECT d1.id FROM d1 WHERE EXISTS (SELECT 1 FROM d2 WHERE d2.id = d1.id);
SELECT * FROM dv_exists ORDER BY id;
id
----
1
2
(2 rows)

-- Cleanup
DROP VIEW hv_join, hv_left_join, hv_exists, hv_scalar_sub, hv_in_sub,
hv_cte, hv_union, hv_lateral, dv_join, dv_exists;
DROP TABLE h1, h2;
DROP TABLE d1, d2;
1 change: 1 addition & 0 deletions pg_ducklake/test/regression/schedule
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ test: import_foreign_schema
test: secrets
test: access_control
test: quoted_names
test: create_view_shapes
78 changes: 78 additions & 0 deletions pg_ducklake/test/regression/sql/create_view_shapes.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
-- CREATE VIEW over query shapes that carry raw JoinExpr / SubLink nodes.
-- The utility hook parse-analyzes the view body to detect duckdb_row columns;
-- doing that on the original raw tree corrupted it (JoinExpr larg/rarg and
-- SubLink->subselect are rewritten in place), so DefineView's own parse
-- analysis then failed. These must all succeed and return correct rows.

-- Heap tables --------------------------------------------------------------
CREATE TABLE h1 (id int PRIMARY KEY, val int);
CREATE TABLE h2 (id int PRIMARY KEY, label text);
INSERT INTO h1 VALUES (1, 10), (2, 20), (3, 30);
INSERT INTO h2 VALUES (1, 'a'), (2, 'b');

-- Explicit INNER JOIN
CREATE VIEW hv_join AS
SELECT h1.id, h2.label FROM h1 JOIN h2 ON h2.id = h1.id;
SELECT * FROM hv_join ORDER BY id;

-- Explicit LEFT JOIN
CREATE VIEW hv_left_join AS
SELECT h1.id, h2.label FROM h1 LEFT JOIN h2 ON h2.id = h1.id;
SELECT * FROM hv_left_join ORDER BY id;

-- EXISTS sublink in WHERE
CREATE VIEW hv_exists AS
SELECT h1.id FROM h1 WHERE EXISTS (SELECT 1 FROM h2 WHERE h2.id = h1.id);
SELECT * FROM hv_exists ORDER BY id;

-- Correlated scalar subquery in the target list
CREATE VIEW hv_scalar_sub AS
SELECT h1.id, (SELECT h2.label FROM h2 WHERE h2.id = h1.id) AS label FROM h1;
SELECT * FROM hv_scalar_sub ORDER BY id;

-- IN (SELECT ...) sublink
CREATE VIEW hv_in_sub AS
SELECT h1.id FROM h1 WHERE h1.id IN (SELECT h2.id FROM h2);
SELECT * FROM hv_in_sub ORDER BY id;

-- CTE
CREATE VIEW hv_cte AS
WITH c AS (SELECT id, label FROM h2)
SELECT h1.id, c.label FROM h1 JOIN c ON c.id = h1.id;
SELECT * FROM hv_cte ORDER BY id;

-- UNION set operation
CREATE VIEW hv_union AS
SELECT id FROM h1 UNION SELECT id FROM h2;
SELECT * FROM hv_union ORDER BY id;

-- LATERAL join
CREATE VIEW hv_lateral AS
SELECT h1.id, sub.label
FROM h1 LEFT JOIN LATERAL (SELECT h2.label FROM h2 WHERE h2.id = h1.id) sub ON true;
SELECT * FROM hv_lateral ORDER BY id;

-- CREATE OR REPLACE VIEW over a JOIN
CREATE OR REPLACE VIEW hv_join AS
SELECT h1.id, h2.label, h1.val FROM h1 JOIN h2 ON h2.id = h1.id;
SELECT * FROM hv_join ORDER BY id;

-- DuckLake tables ----------------------------------------------------------
CREATE TABLE d1 (id int, val int) USING ducklake;
CREATE TABLE d2 (id int, label text) USING ducklake;
INSERT INTO d1 VALUES (1, 10), (2, 20), (3, 30);
INSERT INTO d2 VALUES (1, 'a'), (2, 'b');

CREATE VIEW dv_join AS
SELECT d1.id, d2.label FROM d1 JOIN d2 ON d2.id = d1.id;
SELECT * FROM dv_join ORDER BY id;

CREATE VIEW dv_exists AS
SELECT d1.id FROM d1 WHERE EXISTS (SELECT 1 FROM d2 WHERE d2.id = d1.id);
SELECT * FROM dv_exists ORDER BY id;

-- Cleanup
DROP VIEW hv_join, hv_left_join, hv_exists, hv_scalar_sub, hv_in_sub,
hv_cte, hv_union, hv_lateral, dv_join, dv_exists;
DROP TABLE h1, h2;
DROP TABLE d1, d2;
Loading