Skip to content

Commit edb19fc

Browse files
committed
version bump to 0.35; add extract relations
1 parent 1c23624 commit edb19fc

File tree

9 files changed

+334
-290
lines changed

9 files changed

+334
-290
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.1.33
2+
current_version = 0.1.35
33
commit = True
44
tag = True
55

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 0.1.35
2+
3+
- Added `extract_relations` function to assist in extracting table references from the AST in Rust.

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sqloxide"
3-
version = "0.1.33"
3+
version = "0.1.35"
44
authors = ["Will Eaton <me@wseaton.com>"]
55
edition = "2018"
66

@@ -9,12 +9,12 @@ name = "sqloxide"
99
crate-type = ["cdylib"]
1010

1111
[dependencies]
12-
pythonize = "0.17"
12+
pythonize = "0.19"
1313

1414
[dependencies.pyo3]
15-
version = "0.17.1"
15+
version = "0.19.0"
1616
features = ["extension-module"]
1717

1818
[dependencies.sqlparser]
19-
version = "0.33.0"
20-
features = ["json_example"]
19+
version = "0.35.0"
20+
features = ["serde", "visitor"]

poetry.lock

Lines changed: 253 additions & 274 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "sqloxide"
3-
version = "0.1.33"
3+
version = "0.1.35"
44
repository = "https://github.com/wseaton/sqloxide"
55
license = "MIT"
66
description = "Python bindings for sqlparser-rs"

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setup_kwargs = {
99
"name": "sqloxide",
10-
"version": "0.1.33",
10+
"version": "0.1.35",
1111
"description": "Python bindings for sqlparser-rs",
1212
"long_description": open("readme.md").read(),
1313
"long_description_content_type": "text/markdown",

sqloxide/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
from .sqloxide import parse_sql
1+
from .sqloxide import parse_sql, extract_relations
22

3-
__all__ = ["parse_sql"]
3+
__all__ = ["parse_sql", "extract_relations"]

src/lib.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use pyo3::exceptions::PyValueError;
22
use pyo3::prelude::*;
33
use pyo3::wrap_pyfunction;
4+
use pythonize::PythonizeError;
5+
use sqlparser::ast::Statement;
46

7+
use core::ops::ControlFlow;
58
use pythonize::pythonize;
6-
9+
use sqlparser::ast::visit_relations;
710
use sqlparser::dialect::*;
811
use sqlparser::parser::Parser;
912

@@ -65,8 +68,53 @@ fn parse_sql(py: Python, sql: &str, dialect: &str) -> PyResult<PyObject> {
6568
Ok(output)
6669
}
6770

71+
///
72+
/// Function to extract relations from a parsed query.
73+
/// Returns a nested list of relations, one list per query statement.
74+
///
75+
/// Example:
76+
/// ```python
77+
/// from sqloxide import parse_sql, extract_relations
78+
///
79+
/// sql = "SELECT * FROM table1 JOIN table2 ON table1.id = table2.id"
80+
/// parsed_query = parse_sql(sql, "generic")
81+
/// relations = extract_relations(parsed_query)
82+
/// print(relations)
83+
/// ```
84+
///
85+
#[pyfunction]
86+
#[pyo3(text_signature = "(parsed_query)")]
87+
fn extract_relations(py: Python, parsed_query: &PyAny) -> PyResult<PyObject> {
88+
let parse_result: Result<Vec<Statement>, PythonizeError> = pythonize::depythonize(parsed_query);
89+
90+
let mut relations = Vec::new();
91+
92+
match parse_result {
93+
Ok(statements) => {
94+
for statement in statements {
95+
visit_relations(&statement, |relation| {
96+
relations.push(relation.clone());
97+
ControlFlow::<()>::Continue(())
98+
});
99+
}
100+
}
101+
Err(_e) => {
102+
let msg = _e.to_string();
103+
return Err(PyValueError::new_err(format!(
104+
"Query serialization failed.\n\t{}",
105+
msg
106+
)));
107+
}
108+
};
109+
110+
let output = pythonize(py, &relations).expect("Internal python deserialization failed.");
111+
112+
Ok(output)
113+
}
114+
68115
#[pymodule]
69116
fn sqloxide(_py: Python, m: &PyModule) -> PyResult<()> {
70117
m.add_function(wrap_pyfunction!(parse_sql, m)?)?;
118+
m.add_function(wrap_pyfunction!(extract_relations, m)?)?;
71119
Ok(())
72120
}

tests/test_sqloxide.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import pytest
2-
from sqloxide import parse_sql
2+
from sqloxide import parse_sql, extract_relations
33

44

55
def test_parse_sql():
6-
76
sql = """
87
SELECT employee.first_name, employee.last_name,
98
call.start_time, call.end_time, call_outcome.outcome_text
@@ -23,9 +22,24 @@ def test_parse_sql():
2322

2423

2524
def test_throw_exception():
26-
2725
sql = """
2826
SELECT $# as 1;
2927
"""
30-
with pytest.raises(ValueError, match=r"Query parsing failed.\n\tsql parser error: .+"):
31-
ast = parse_sql(sql=sql, dialect="ansi")[0]
28+
with pytest.raises(
29+
ValueError, match=r"Query parsing failed.\n\tsql parser error: .+"
30+
):
31+
_ast = parse_sql(sql=sql, dialect="ansi")[0]
32+
33+
34+
def test_extract_relations():
35+
sql = """
36+
SELECT employee.first_name, employee.last_name,
37+
call.start_time, call.end_time, call_outcome.outcome_text
38+
FROM employee
39+
INNER JOIN call ON call.employee_id = employee.id
40+
INNER JOIN call_outcome ON call.call_outcome_id = call_outcome.id
41+
ORDER BY call.start_time ASC;
42+
"""
43+
44+
ast = parse_sql(sql=sql, dialect="ansi")
45+
print(extract_relations(parsed_query=ast))

0 commit comments

Comments
 (0)