Skip to content

Commit 99af8b1

Browse files
committed
fix: complete TablesNamesFinder integration for CREATE POLICY
Add expression visitor calls to traverse USING and WITH CHECK clauses, enabling discovery of all table references in subqueries. This completes the TablesNamesFinder visitor implementation for CREATE POLICY statements by following the same pattern used in Update, Delete, and PlainSelect statements. Includes comprehensive test coverage (12 tests) covering simple subqueries, nested subqueries, CTEs, JOINs, and edge cases.
1 parent 9c9fd18 commit 99af8b1

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1852,6 +1852,17 @@ public <S> Void visit(CreatePolicy createPolicy, S context) {
18521852
if (createPolicy.getTable() != null) {
18531853
visit(createPolicy.getTable(), context);
18541854
}
1855+
1856+
// Visit USING expression to find tables in subqueries
1857+
if (createPolicy.getUsingExpression() != null) {
1858+
createPolicy.getUsingExpression().accept(this, context);
1859+
}
1860+
1861+
// Visit WITH CHECK expression to find tables in subqueries
1862+
if (createPolicy.getWithCheckExpression() != null) {
1863+
createPolicy.getWithCheckExpression().accept(this, context);
1864+
}
1865+
18551866
return null;
18561867
}
18571868

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2025 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.create;
11+
12+
import net.sf.jsqlparser.JSQLParserException;
13+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
14+
import net.sf.jsqlparser.statement.Statement;
15+
import net.sf.jsqlparser.util.TablesNamesFinder;
16+
import org.junit.jupiter.api.Test;
17+
18+
import java.util.List;
19+
20+
import static org.junit.jupiter.api.Assertions.*;
21+
22+
/**
23+
* Tests for TablesNamesFinder integration with PostgreSQL CREATE POLICY statements.
24+
*
25+
* <p>
26+
* These tests verify that TablesNamesFinder correctly identifies ALL tables referenced in a CREATE
27+
* POLICY statement, including:
28+
* <ul>
29+
* <li>The policy's target table</li>
30+
* <li>Tables in USING expression subqueries</li>
31+
* <li>Tables in WITH CHECK expression subqueries</li>
32+
* <li>Tables in complex expressions (JOINs, CTEs, nested subqueries)</li>
33+
* </ul>
34+
*
35+
* <p>
36+
* <strong>Current Status:</strong> These tests will FAIL until
37+
* TablesNamesFinder.visit(CreatePolicy) is updated to traverse USING and WITH CHECK expressions.
38+
* This is incomplete feature support, not a regression - CREATE POLICY parsing works correctly, but
39+
* analysis tools don't yet have complete integration.
40+
*
41+
* <p>
42+
* <strong>Expected Behavior:</strong> Once fixed, TablesNamesFinder should find tables in policy
43+
* expressions using the same pattern as other statements (CreateView, Insert, Update).
44+
*/
45+
public class CreatePolicyTablesFinderTest {
46+
47+
// =========================================================================
48+
// Helper Methods
49+
// =========================================================================
50+
51+
/**
52+
* Parse SQL and extract table names using TablesNamesFinder.
53+
*/
54+
private List<String> getTablesFromSQL(String sql) throws JSQLParserException {
55+
Statement stmt = CCJSqlParserUtil.parse(sql);
56+
TablesNamesFinder finder = new TablesNamesFinder();
57+
return finder.getTableList(stmt);
58+
}
59+
60+
/**
61+
* Assert that the actual table list contains exactly the expected tables.
62+
*/
63+
private void assertContainsAllTables(List<String> actual, String... expected) {
64+
assertEquals(expected.length, actual.size(),
65+
"Expected " + expected.length + " tables but found " + actual.size() + ". " +
66+
"Expected: " + java.util.Arrays.toString(expected) + ", " +
67+
"Actual: " + actual);
68+
69+
for (String table : expected) {
70+
assertTrue(actual.contains(table),
71+
"Expected to find table '" + table + "' but it was missing. " +
72+
"Found tables: " + actual);
73+
}
74+
}
75+
76+
// =========================================================================
77+
// Simple Subqueries - Basic USE Cases
78+
// =========================================================================
79+
80+
@Test
81+
public void testTablesFinderWithSubqueryInUsing() throws JSQLParserException {
82+
String sql = "CREATE POLICY tenant_policy ON documents " +
83+
"USING (tenant_id IN (SELECT tenant_id FROM tenant_access))";
84+
85+
List<String> tables = getTablesFromSQL(sql);
86+
87+
// Should find: target table + table in USING subquery
88+
assertContainsAllTables(tables, "documents", "tenant_access");
89+
}
90+
91+
@Test
92+
public void testTablesFinderWithSubqueryInWithCheck() throws JSQLParserException {
93+
String sql = "CREATE POLICY data_policy ON user_data " +
94+
"WITH CHECK (status IN (SELECT allowed_status FROM status_config))";
95+
96+
List<String> tables = getTablesFromSQL(sql);
97+
98+
// Should find: target table + table in WITH CHECK subquery
99+
assertContainsAllTables(tables, "user_data", "status_config");
100+
}
101+
102+
@Test
103+
public void testTablesFinderWithBothUsingAndWithCheck() throws JSQLParserException {
104+
String sql = "CREATE POLICY dual_check_policy ON records " +
105+
"USING (user_id IN (SELECT id FROM active_users)) " +
106+
"WITH CHECK (status IN (SELECT status FROM valid_statuses))";
107+
108+
List<String> tables = getTablesFromSQL(sql);
109+
110+
// Should find: target table + table in USING + table in WITH CHECK
111+
assertContainsAllTables(tables, "records", "active_users", "valid_statuses");
112+
}
113+
114+
// =========================================================================
115+
// Complex Expressions - Multiple/Nested Subqueries
116+
// =========================================================================
117+
118+
@Test
119+
public void testTablesFinderWithMultipleSubqueries() throws JSQLParserException {
120+
String sql = "CREATE POLICY complex_policy ON documents " +
121+
"USING (" +
122+
" tenant_id IN (SELECT tenant_id FROM tenant_access) " +
123+
" AND status IN (SELECT status FROM allowed_statuses) " +
124+
" AND department_id = (SELECT id FROM departments WHERE name = 'Engineering')" +
125+
")";
126+
127+
List<String> tables = getTablesFromSQL(sql);
128+
129+
// Should find: target table + 3 tables from subqueries
130+
assertContainsAllTables(tables, "documents", "tenant_access", "allowed_statuses",
131+
"departments");
132+
}
133+
134+
@Test
135+
public void testTablesFinderWithNestedSubqueries() throws JSQLParserException {
136+
String sql = "CREATE POLICY nested_policy ON orders " +
137+
"USING (customer_id IN (" +
138+
" SELECT customer_id FROM customer_access " +
139+
" WHERE region_id IN (SELECT id FROM regions WHERE active = true)" +
140+
"))";
141+
142+
List<String> tables = getTablesFromSQL(sql);
143+
144+
// Should find: target table + tables from nested subqueries
145+
assertContainsAllTables(tables, "orders", "customer_access", "regions");
146+
}
147+
148+
@Test
149+
public void testTablesFinderWithJoinsInSubquery() throws JSQLParserException {
150+
String sql = "CREATE POLICY join_policy ON orders " +
151+
"USING (EXISTS (" +
152+
" SELECT 1 FROM customers c " +
153+
" JOIN customer_access ca ON c.id = ca.customer_id " +
154+
" WHERE c.id = orders.customer_id" +
155+
"))";
156+
157+
List<String> tables = getTablesFromSQL(sql);
158+
159+
// Should find: target table + tables from JOIN in subquery
160+
assertContainsAllTables(tables, "orders", "customers", "customer_access");
161+
}
162+
163+
// =========================================================================
164+
// Advanced SQL Features - CTEs, Schema Qualification, Functions
165+
// =========================================================================
166+
167+
@Test
168+
public void testTablesFinderWithCTE() throws JSQLParserException {
169+
String sql = "CREATE POLICY cte_policy ON documents " +
170+
"USING (tenant_id IN (" +
171+
" WITH active_tenants AS (SELECT id FROM tenants WHERE active = true) " +
172+
" SELECT id FROM active_tenants" +
173+
"))";
174+
175+
List<String> tables = getTablesFromSQL(sql);
176+
177+
// Should find: target table + table referenced in CTE
178+
assertContainsAllTables(tables, "documents", "tenants");
179+
}
180+
181+
@Test
182+
public void testTablesFinderWithSchemaQualifiedTables() throws JSQLParserException {
183+
String sql = "CREATE POLICY schema_policy ON myschema.documents " +
184+
"USING (tenant_id IN (SELECT id FROM otherschema.tenants))";
185+
186+
List<String> tables = getTablesFromSQL(sql);
187+
188+
// Should find both schema-qualified tables
189+
assertEquals(2, tables.size(),
190+
"Should find both schema-qualified tables. Found: " + tables);
191+
192+
// Check if tables are found (with or without schema prefix depending on TablesNamesFinder
193+
// behavior)
194+
boolean foundDocuments = tables.stream()
195+
.anyMatch(t -> t.contains("documents"));
196+
boolean foundTenants = tables.stream()
197+
.anyMatch(t -> t.contains("tenants"));
198+
199+
assertTrue(foundDocuments, "Should find documents table. Found: " + tables);
200+
assertTrue(foundTenants, "Should find tenants table. Found: " + tables);
201+
}
202+
203+
@Test
204+
public void testTablesFinderWithTableFunctions() throws JSQLParserException {
205+
// PostgreSQL table-valued functions can be used in FROM clauses
206+
String sql = "CREATE POLICY function_policy ON documents " +
207+
"USING (tenant_id IN (" +
208+
" SELECT tenant_id FROM get_accessible_tenants(current_user_id())" +
209+
"))";
210+
211+
List<String> tables = getTablesFromSQL(sql);
212+
213+
// Should at least find the target table
214+
// Note: Table-valued functions might not be reported as "tables" depending on
215+
// implementation
216+
assertTrue(tables.contains("documents"),
217+
"Should at least find the target table. Found: " + tables);
218+
}
219+
220+
// =========================================================================
221+
// Edge Cases - EXISTS, UNION, Empty Policies
222+
// =========================================================================
223+
224+
@Test
225+
public void testTablesFinderWithExistsClause() throws JSQLParserException {
226+
String sql = "CREATE POLICY exists_policy ON documents " +
227+
"USING (EXISTS (" +
228+
" SELECT 1 FROM tenant_access " +
229+
" WHERE tenant_id = documents.tenant_id AND active = true" +
230+
"))";
231+
232+
List<String> tables = getTablesFromSQL(sql);
233+
234+
// Should find: target table + table in EXISTS subquery
235+
assertContainsAllTables(tables, "documents", "tenant_access");
236+
}
237+
238+
@Test
239+
public void testTablesFinderWithUnionInSubquery() throws JSQLParserException {
240+
String sql = "CREATE POLICY union_policy ON documents " +
241+
"USING (tenant_id IN (" +
242+
" SELECT tenant_id FROM primary_tenants " +
243+
" UNION " +
244+
" SELECT tenant_id FROM secondary_tenants" +
245+
"))";
246+
247+
List<String> tables = getTablesFromSQL(sql);
248+
249+
// Should find: target table + both tables in UNION
250+
assertContainsAllTables(tables, "documents", "primary_tenants", "secondary_tenants");
251+
}
252+
253+
@Test
254+
public void testTablesFinderEmptyPolicy() throws JSQLParserException {
255+
// Policy with no USING or WITH CHECK clauses
256+
String sql = "CREATE POLICY simple_policy ON documents";
257+
258+
List<String> tables = getTablesFromSQL(sql);
259+
260+
// Should only find the target table
261+
assertContainsAllTables(tables, "documents");
262+
}
263+
}

0 commit comments

Comments
 (0)