sqlscope-rs 0.4.0__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sqlscope-rs
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Scope-aware SQL analysis and rewriting: row filters, CTE injection, table rewrites, column lineage.
5
5
  Project-URL: Repository, https://github.com/dcalsky/sqlscope
6
6
  License-Expression: MIT
@@ -41,7 +41,7 @@ from ._library import (
41
41
  load,
42
42
  )
43
43
 
44
- __version__ = "0.4.0"
44
+ __version__ = "0.5.0"
45
45
 
46
46
  __all__ = [
47
47
  "Clause",
@@ -137,6 +137,7 @@ class Clause(str, Enum):
137
137
  UPDATE_SET_VALUE = "UPDATE_SET_VALUE"
138
138
  MERGE_ON = "MERGE_ON"
139
139
  MERGE_WHEN = "MERGE_WHEN"
140
+ RETURNING = "RETURNING"
140
141
 
141
142
  def __str__(self) -> str:
142
143
  return self.value
@@ -189,7 +190,8 @@ def apply_row_filter(
189
190
  By default every physical table is filtered; ``table_names`` (bare,
190
191
  ``schema.table`` or ``catalog.schema.table``), ``table_patterns`` (regular
191
192
  expressions) and ``default_db`` restrict the scope. CTE references, table
192
- functions and ``DUAL`` are never wrapped. ``predicate`` is parsed as one
193
+ functions and the ``DUAL`` dummy table (Oracle, the MySQL family,
194
+ Snowflake) are never wrapped. ``predicate`` is parsed as one
193
195
  boolean expression; bind or escape its values before calling. Returns the
194
196
  input unchanged when no table is in scope.
195
197
  """
@@ -118,3 +118,56 @@ def test_deep_nesting_in_threads():
118
118
  thread.start()
119
119
  thread.join()
120
120
  assert results == [{"t": ["a"]}]
121
+
122
+
123
+ @pytest.mark.parametrize("query", [
124
+ "WITH a AS (SELECT id FROM orders), b AS (SELECT id FROM a) SELECT * FROM b ORDER BY id",
125
+ "SELECT orders.id FROM orders WHERE EXISTS (SELECT 1 FROM payments WHERE payments.id = orders.id) ORDER BY orders.id",
126
+ "SELECT o.id, p.id FROM orders o LEFT JOIN payments p ON o.id = p.id ORDER BY o.id",
127
+ "SELECT id FROM orders UNION SELECT id FROM payments ORDER BY id",
128
+ ])
129
+ def test_row_filter_preserves_sqlite_query_results(query):
130
+ # SQLite executes both sides independently of sqlscope's parser. The
131
+ # reference database contains only the rows visible to tenant 7.
132
+ import sqlite3
133
+
134
+ raw = sqlite3.connect(":memory:")
135
+ visible = sqlite3.connect(":memory:")
136
+ try:
137
+ for connection in (raw, visible):
138
+ connection.executescript("CREATE TABLE orders(id, tenant); CREATE TABLE payments(id, tenant);")
139
+ orders = [(1, 7), (2, 8), (3, 7)]
140
+ payments = [(1, 7), (2, 7), (3, 8)]
141
+ for table, rows in (("orders", orders), ("payments", payments)):
142
+ raw.executemany(f"INSERT INTO {table} VALUES (?, ?)", rows)
143
+ visible.executemany(f"INSERT INTO {table} VALUES (?, ?)", [row for row in rows if row[1] == 7])
144
+ rewritten = sqlscope.apply_row_filter(query, "tenant = 7", dialect="sqlite")
145
+ assert raw.execute(rewritten).fetchall() == visible.execute(query).fetchall()
146
+ finally:
147
+ raw.close()
148
+ visible.close()
149
+
150
+
151
+ def test_rewritten_correlated_query_executes_in_sqlite():
152
+ import sqlite3
153
+
154
+ connection = sqlite3.connect(":memory:")
155
+ try:
156
+ connection.executescript(
157
+ "ATTACH ':memory:' AS s; CREATE TABLE s.t(id); CREATE TABLE safe(id); CREATE TABLE u(id);"
158
+ "INSERT INTO safe VALUES (1), (2); INSERT INTO u VALUES (2);"
159
+ )
160
+ rewritten = sqlscope.rewrite_tables(
161
+ "SELECT t.id FROM s.t WHERE EXISTS (SELECT 1 FROM u WHERE u.id = t.id)",
162
+ [TableRewrite("s.t", union=UnionRewrite("renamed", ["id"], [TableRef("safe")]))],
163
+ dialect="sqlite",
164
+ )
165
+ assert connection.execute(rewritten).fetchall() == [(2,)]
166
+ finally:
167
+ connection.close()
168
+
169
+
170
+ def test_returning_usage():
171
+ assert ColumnUsage("t", "payload", Clause.RETURNING) in sqlscope.column_usages(
172
+ "DELETE FROM t WHERE id = 1 RETURNING payload", dialect="postgres"
173
+ )
File without changes
File without changes
File without changes