Skip to content

Commit

Permalink
Merge pull request #10174 from Tishj/python_fetch_unnamed_struct_as_t…
Browse files Browse the repository at this point in the history
…uple

[Python] Output unnamed structs as `tuple` in `fetchone/many/all` methods
  • Loading branch information
Mytherin committed Jan 15, 2024
2 parents f1590e5 + 5a9598b commit a056c8e
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 28 deletions.
Expand Up @@ -196,6 +196,7 @@ struct PyTimezone {

struct PythonObject {
static void Initialize();
static py::object FromStruct(const Value &value, const LogicalType &id, const ClientProperties &client_properties);
static py::object FromValue(const Value &value, const LogicalType &id, const ClientProperties &client_properties);
};

Expand Down
38 changes: 27 additions & 11 deletions tools/pythonpkg/src/native/python_objects.cpp
Expand Up @@ -367,6 +367,32 @@ InfinityType GetTimestampInfinityType(timestamp_t &timestamp) {
return InfinityType::NONE;
}

py::object PythonObject::FromStruct(const Value &val, const LogicalType &type,
const ClientProperties &client_properties) {
auto &struct_values = StructValue::GetChildren(val);

auto &child_types = StructType::GetChildTypes(type);
if (StructType::IsUnnamed(type)) {
py::tuple py_tuple(struct_values.size());
for (idx_t i = 0; i < struct_values.size(); i++) {
auto &child_entry = child_types[i];
D_ASSERT(child_entry.first.empty());
auto &child_type = child_entry.second;
py_tuple[i] = FromValue(struct_values[i], child_type, client_properties);
}
return std::move(py_tuple);
} else {
py::dict py_struct;
for (idx_t i = 0; i < struct_values.size(); i++) {
auto &child_entry = child_types[i];
auto &child_name = child_entry.first;
auto &child_type = child_entry.second;
py_struct[child_name.c_str()] = FromValue(struct_values[i], child_type, client_properties);
}
return std::move(py_struct);
}
}

py::object PythonObject::FromValue(const Value &val, const LogicalType &type,
const ClientProperties &client_properties) {
auto &import_cache = *DuckDBPyConnection::ImportCache();
Expand Down Expand Up @@ -508,17 +534,7 @@ py::object PythonObject::FromValue(const Value &val, const LogicalType &type,
return std::move(py_struct);
}
case LogicalTypeId::STRUCT: {
auto &struct_values = StructValue::GetChildren(val);

py::dict py_struct;
auto &child_types = StructType::GetChildTypes(type);
for (idx_t i = 0; i < struct_values.size(); i++) {
auto &child_entry = child_types[i];
auto &child_name = child_entry.first;
auto &child_type = child_entry.second;
py_struct[child_name.c_str()] = FromValue(struct_values[i], child_type, client_properties);
}
return std::move(py_struct);
return FromStruct(val, type, client_properties);
}
case LogicalTypeId::UUID: {
auto uuid_value = val.GetValueUnsafe<hugeint_t>();
Expand Down
38 changes: 21 additions & 17 deletions tools/pythonpkg/tests/fast/types/test_nested.py
Expand Up @@ -3,47 +3,51 @@

class TestNested(object):
def test_lists(self, duckdb_cursor):
duckdb_conn = duckdb.connect()
result = duckdb_conn.execute("SELECT LIST_VALUE(1, 2, 3, 4) ").fetchall()
result = duckdb_cursor.execute("SELECT LIST_VALUE(1, 2, 3, 4) ").fetchall()
assert result == [([1, 2, 3, 4],)]

result = duckdb_conn.execute("SELECT LIST_VALUE() ").fetchall()
result = duckdb_cursor.execute("SELECT LIST_VALUE() ").fetchall()
assert result == [([],)]

result = duckdb_conn.execute("SELECT LIST_VALUE(1, 2, 3, NULL) ").fetchall()
result = duckdb_cursor.execute("SELECT LIST_VALUE(1, 2, 3, NULL) ").fetchall()
assert result == [([1, 2, 3, None],)]

def test_nested_lists(self, duckdb_cursor):
duckdb_conn = duckdb.connect()
result = duckdb_conn.execute("SELECT LIST_VALUE(LIST_VALUE(1, 2, 3, 4), LIST_VALUE(1, 2, 3, 4)) ").fetchall()
result = duckdb_cursor.execute("SELECT LIST_VALUE(LIST_VALUE(1, 2, 3, 4), LIST_VALUE(1, 2, 3, 4)) ").fetchall()
assert result == [([[1, 2, 3, 4], [1, 2, 3, 4]],)]

result = duckdb_conn.execute("SELECT LIST_VALUE(LIST_VALUE(1, 2, 3, 4), LIST_VALUE(1, 2, 3, NULL)) ").fetchall()
result = duckdb_cursor.execute(
"SELECT LIST_VALUE(LIST_VALUE(1, 2, 3, 4), LIST_VALUE(1, 2, 3, NULL)) "
).fetchall()
assert result == [([[1, 2, 3, 4], [1, 2, 3, None]],)]

def test_struct(self, duckdb_cursor):
duckdb_conn = duckdb.connect()
result = duckdb_conn.execute("SELECT STRUCT_PACK(a := 42, b := 43)").fetchall()
result = duckdb_cursor.execute("SELECT STRUCT_PACK(a := 42, b := 43)").fetchall()
assert result == [({'a': 42, 'b': 43},)]

result = duckdb_conn.execute("SELECT STRUCT_PACK(a := 42, b := NULL)").fetchall()
result = duckdb_cursor.execute("SELECT STRUCT_PACK(a := 42, b := NULL)").fetchall()
assert result == [({'a': 42, 'b': None},)]

def test_unnamed_struct(self, duckdb_cursor):
result = duckdb_cursor.execute("SELECT row('aa','bb') AS x").fetchall()
assert result == [(('aa', 'bb'),)]

result = duckdb_cursor.execute("SELECT row('aa',NULL) AS x").fetchall()
assert result == [(('aa', None),)]

def test_nested_struct(self, duckdb_cursor):
duckdb_conn = duckdb.connect()
result = duckdb_conn.execute("SELECT STRUCT_PACK(a := 42, b := LIST_VALUE(10, 9, 8, 7))").fetchall()
result = duckdb_cursor.execute("SELECT STRUCT_PACK(a := 42, b := LIST_VALUE(10, 9, 8, 7))").fetchall()
assert result == [({'a': 42, 'b': [10, 9, 8, 7]},)]

result = duckdb_conn.execute("SELECT STRUCT_PACK(a := 42, b := LIST_VALUE(10, 9, 8, NULL))").fetchall()
result = duckdb_cursor.execute("SELECT STRUCT_PACK(a := 42, b := LIST_VALUE(10, 9, 8, NULL))").fetchall()
assert result == [({'a': 42, 'b': [10, 9, 8, None]},)]

def test_map(self, duckdb_cursor):
duckdb_conn = duckdb.connect()
result = duckdb_conn.execute("select MAP(LIST_VALUE(1, 2, 3, 4),LIST_VALUE(10, 9, 8, 7))").fetchall()
result = duckdb_cursor.execute("select MAP(LIST_VALUE(1, 2, 3, 4),LIST_VALUE(10, 9, 8, 7))").fetchall()
assert result == [({'key': [1, 2, 3, 4], 'value': [10, 9, 8, 7]},)]

result = duckdb_conn.execute("select MAP(LIST_VALUE(1, 2, 3, 4),LIST_VALUE(10, 9, 8, NULL))").fetchall()
result = duckdb_cursor.execute("select MAP(LIST_VALUE(1, 2, 3, 4),LIST_VALUE(10, 9, 8, NULL))").fetchall()
assert result == [({'key': [1, 2, 3, 4], 'value': [10, 9, 8, None]},)]

result = duckdb_conn.execute("SELECT MAP() ").fetchall()
result = duckdb_cursor.execute("SELECT MAP() ").fetchall()
assert result == [({'key': [], 'value': []},)]

0 comments on commit a056c8e

Please sign in to comment.