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.
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/PKG-INFO +1 -1
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/src/sqlscope/__init__.py +4 -2
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/tests/test_sqlscope.py +53 -0
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/.gitignore +0 -0
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/README.md +0 -0
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/pyproject.toml +0 -0
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/src/sqlscope/_library.py +0 -0
- {sqlscope_rs-0.4.0 → sqlscope_rs-0.5.0}/src/sqlscope/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sqlscope-rs
|
|
3
|
-
Version: 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.
|
|
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``
|
|
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
|
|
File without changes
|
|
File without changes
|