Skip to content

Commit 771d6ec

Browse files
committed
Fwd ports of:
* #18749 Fixing lazy polars execution on query result * #18682 Make sure parse errors are wrapped in ErrorData
1 parent 7822b69 commit 771d6ec

7 files changed

Lines changed: 77 additions & 8 deletions

File tree

src/duckdb_py/include/duckdb_python/pyrelation.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace duckdb {
2828
struct DuckDBPyRelation {
2929
public:
3030
explicit DuckDBPyRelation(shared_ptr<Relation> rel);
31-
explicit DuckDBPyRelation(unique_ptr<DuckDBPyResult> result);
31+
explicit DuckDBPyRelation(shared_ptr<DuckDBPyResult> result);
3232
~DuckDBPyRelation();
3333

3434
public:
@@ -288,7 +288,7 @@ struct DuckDBPyRelation {
288288
shared_ptr<Relation> rel;
289289
vector<LogicalType> types;
290290
vector<string> names;
291-
unique_ptr<DuckDBPyResult> result;
291+
shared_ptr<DuckDBPyResult> result;
292292
std::string rendered_result;
293293
};
294294

src/duckdb_py/include/duckdb_python/pyresult.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ struct DuckDBPyResult {
5959
const vector<string> &GetNames();
6060
const vector<LogicalType> &GetTypes();
6161

62+
ClientProperties GetClientProperties();
63+
6264
private:
6365
void FillNumpy(py::dict &res, idx_t col_idx, NumpyResultConversion &conversion, const char *name);
6466

src/duckdb_py/pyconnection.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -614,17 +614,17 @@ unique_ptr<QueryResult> DuckDBPyConnection::PrepareAndExecuteInternal(unique_ptr
614614
}
615615

616616
vector<unique_ptr<SQLStatement>> DuckDBPyConnection::GetStatements(const py::object &query) {
617-
vector<unique_ptr<SQLStatement>> result;
618-
auto &connection = con.GetConnection();
619-
620617
shared_ptr<DuckDBPyStatement> statement_obj;
621618
if (py::try_cast(query, statement_obj)) {
619+
vector<unique_ptr<SQLStatement>> result;
622620
result.push_back(statement_obj->GetStatement());
623621
return result;
624622
}
625623
if (py::isinstance<py::str>(query)) {
624+
auto &connection = con.GetConnection();
626625
auto sql_query = std::string(py::str(query));
627-
return connection.ExtractStatements(sql_query);
626+
auto statements = connection.ExtractStatements(sql_query);
627+
return std::move(statements);
628628
}
629629
throw InvalidInputException("Please provide either a DuckDBPyStatement or a string representing the query");
630630
}

src/duckdb_py/pyrelation.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ DuckDBPyRelation::~DuckDBPyRelation() {
6666
rel.reset();
6767
}
6868

69-
DuckDBPyRelation::DuckDBPyRelation(unique_ptr<DuckDBPyResult> result_p) : rel(nullptr), result(std::move(result_p)) {
69+
DuckDBPyRelation::DuckDBPyRelation(shared_ptr<DuckDBPyResult> result_p) : rel(nullptr), result(std::move(result_p)) {
7070
if (!result) {
7171
throw InternalException("DuckDBPyRelation created without a result");
7272
}
@@ -984,7 +984,14 @@ PolarsDataFrame DuckDBPyRelation::ToPolars(idx_t batch_size, bool lazy) {
984984
ArrowSchema arrow_schema;
985985
auto result_names = names;
986986
QueryResult::DeduplicateColumns(result_names);
987-
auto client_properties = rel->context->GetContext()->GetClientProperties();
987+
ClientProperties client_properties;
988+
if (rel) {
989+
client_properties = rel->context->GetContext()->GetClientProperties();
990+
} else if (result) {
991+
client_properties = result->GetClientProperties();
992+
} else {
993+
throw InternalException("DuckDBPyRelation To Polars must have a valid relation or result");
994+
}
988995
ArrowConverter::ToArrowSchema(&arrow_schema, types, result_names, client_properties);
989996
py::list batches;
990997
// Now we create an empty arrow table

src/duckdb_py/pyresult.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ DuckDBPyResult::~DuckDBPyResult() {
4141
}
4242
}
4343

44+
ClientProperties DuckDBPyResult::GetClientProperties() {
45+
return result->client_properties;
46+
}
47+
4448
const vector<string> &DuckDBPyResult::GetNames() {
4549
if (!result) {
4650
throw InternalException("Calling GetNames without a result object");

tests/fast/arrow/test_polars.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,14 @@ def test_polars_from_json_error(self, duckdb_cursor):
101101
my_res = duckdb.query("select my_str from my_table where my_str != 'y'")
102102
assert my_res.fetchall() == [('x',)]
103103

104+
def test_polars_lazy_from_conn(self, duckdb_cursor):
105+
duckdb_conn = duckdb.connect()
106+
107+
result = duckdb_conn.execute("SELECT 42 as bla")
108+
109+
lazy_df = result.pl(lazy=True)
110+
assert lazy_df.collect().to_dicts() == [{'bla': 42}]
111+
104112
def test_polars_lazy(self, duckdb_cursor):
105113
con = duckdb.connect()
106114
con.execute("Create table names (a varchar, b integer)")

tests/fast/test_json_logging.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import json
2+
3+
import duckdb
4+
import pytest
5+
6+
7+
def _parse_json_func(error_prefix: str):
8+
"""Helper to check that the error message is indeed parsable json"""
9+
10+
def parse_func(exception):
11+
msg = exception.args[0]
12+
assert msg.startswith(error_prefix)
13+
json_str = msg.split(error_prefix, 1)[1]
14+
try:
15+
json.loads(json_str)
16+
except:
17+
return False
18+
return True
19+
20+
return parse_func
21+
22+
23+
def test_json_syntax_error():
24+
conn = duckdb.connect()
25+
conn.execute("SET errors_as_json='true'")
26+
with pytest.raises(duckdb.ParserException, match="SYNTAX_ERROR", check=_parse_json_func("Parser Error: ")):
27+
conn.execute("syntax error")
28+
29+
30+
def test_json_catalog_error():
31+
conn = duckdb.connect()
32+
conn.execute("SET errors_as_json='true'")
33+
with pytest.raises(duckdb.CatalogException, match="MISSING_ENTRY", check=_parse_json_func("Catalog Error: ")):
34+
conn.execute("SELECT * FROM nonexistent_table")
35+
36+
37+
def test_json_syntax_error_extract_statements():
38+
conn = duckdb.connect()
39+
conn.execute("SET errors_as_json='true'")
40+
with pytest.raises(duckdb.ParserException, match="SYNTAX_ERROR", check=_parse_json_func("Parser Error: ")):
41+
conn.extract_statements("syntax error")
42+
43+
44+
def test_json_syntax_error_get_table_names():
45+
conn = duckdb.connect()
46+
conn.execute("SET errors_as_json='true'")
47+
with pytest.raises(duckdb.ParserException, match="SYNTAX_ERROR", check=_parse_json_func("Parser Error: ")):
48+
conn.get_table_names("syntax error")

0 commit comments

Comments
 (0)