Skip to content

Commit de1e119

Browse files
authored
Allow list column elements to use "l" or "item" as the name (#139)
1 parent 95f17aa commit de1e119

File tree

3 files changed

+122
-13
lines changed

3 files changed

+122
-13
lines changed

c_src/adbc_arrow_array.hpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -580,8 +580,8 @@ ERL_NIF_TERM get_arrow_array_list_children(ErlNifEnv *env, struct ArrowSchema *
580580
const uint8_t * bitmap_buffer = (const uint8_t *)values->buffers[bitmap_buffer_index];
581581
struct ArrowSchema * items_schema = schema->children[0];
582582
struct ArrowArray * items_values = values->children[0];
583-
if (strcmp("item", items_schema->name) != 0) {
584-
return erlang::nif::error(env, "invalid ArrowSchema (list), its single child is not named item");
583+
if (!(strcmp("item", items_schema->name) == 0 || strcmp("l", items_schema->name) == 0)) {
584+
return erlang::nif::error(env, "invalid ArrowSchema (list), its single child is not named `item` or `l`");
585585
}
586586

587587
std::vector<ERL_NIF_TERM> children;
@@ -594,6 +594,8 @@ ERL_NIF_TERM get_arrow_array_list_children(ErlNifEnv *env, struct ArrowSchema *
594594
bool items_nullable = (schema->flags & ARROW_FLAG_NULLABLE) || (values->null_count > 0);
595595

596596
int has_error = 0;
597+
// Use "item" as the canonical name for list elements
598+
ERL_NIF_TERM item_name_term = erlang::nif::make_binary(env, "item");
597599
auto get_list_children_with_offsets = [&](auto offsets) -> void {
598600
for (int64_t i = offset; i < offset + count; i++) {
599601
if (bitmap_buffer && items_nullable) {
@@ -619,7 +621,8 @@ ERL_NIF_TERM get_arrow_array_list_children(ErlNifEnv *env, struct ArrowSchema *
619621
if (enif_is_identical(childrens[1], kAtomNil)) {
620622
children.emplace_back(kAtomNil);
621623
} else {
622-
children.emplace_back(make_adbc_column(env, schema, values, childrens[0], children_type, children_nullable, children_metadata, childrens[1]));
624+
// Use "item" instead of childrens[0] (the schema's name)
625+
children.emplace_back(make_adbc_column(env, schema, values, item_name_term, children_type, children_nullable, children_metadata, childrens[1]));
623626
}
624627
}
625628
}
@@ -637,6 +640,9 @@ ERL_NIF_TERM get_arrow_array_list_children(ErlNifEnv *env, struct ArrowSchema *
637640
if (count == -1) count = values->length;
638641
if (count > values->length) count = values->length - offset;
639642
bool items_nullable = (schema->flags & ARROW_FLAG_NULLABLE) || (values->null_count > 0);
643+
644+
// Use "item" as the canonical name for list elements
645+
ERL_NIF_TERM item_name_term = erlang::nif::make_binary(env, "item");
640646
for (int64_t child_i = offset; child_i < offset + count; child_i++) {
641647
if (bitmap_buffer && items_nullable) {
642648
uint8_t vbyte = bitmap_buffer[child_i / 8];
@@ -659,7 +665,8 @@ ERL_NIF_TERM get_arrow_array_list_children(ErlNifEnv *env, struct ArrowSchema *
659665
if (enif_is_identical(childrens[1], kAtomNil)) {
660666
children.emplace_back(kAtomNil);
661667
} else {
662-
children.emplace_back(make_adbc_column(env, schema, values, childrens[0], children_type, children_nullable, children_metadata, childrens[1]));
668+
// Use "item" instead of childrens[0] (the schema's name)
669+
children.emplace_back(make_adbc_column(env, schema, values, item_name_term, children_type, children_nullable, children_metadata, childrens[1]));
663670
}
664671
}
665672
}
@@ -704,8 +711,8 @@ ERL_NIF_TERM get_arrow_array_list_view(ErlNifEnv *env, struct ArrowSchema * sche
704711

705712
struct ArrowSchema * items_schema = schema->children[0];
706713
struct ArrowArray * items_values = values->children[0];
707-
if (strcmp("item", items_schema->name) != 0) {
708-
return erlang::nif::error(env, "invalid ArrowSchema (list), its single child is not named item");
714+
if (!(strcmp("item", items_schema->name) == 0 || strcmp("l", items_schema->name) == 0)) {
715+
return erlang::nif::error(env, "invalid ArrowSchema (list), its single child is not named `item` or `l`");
709716
}
710717
if (count == -1) count = values->length;
711718
if (count > values->length) count = values->length - offset;
@@ -733,7 +740,9 @@ ERL_NIF_TERM get_arrow_array_list_view(ErlNifEnv *env, struct ArrowSchema * sche
733740
values_term = kAtomNil;
734741
} else {
735742
bool nullable = items_schema->flags & ARROW_FLAG_NULLABLE || items_values->null_count > 0;
736-
values_term = make_adbc_column(env, schema, values, childrens[0], children_type, nullable, children_metadata, childrens[1]);
743+
// Use "item" as the canonical name for list elements
744+
ERL_NIF_TERM item_name_term = erlang::nif::make_binary(env, "item");
745+
values_term = make_adbc_column(env, schema, values, item_name_term, children_type, nullable, children_metadata, childrens[1]);
737746
}
738747
}
739748

c_src/adbc_arrow_schema.hpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,8 @@ static int get_list_element_schema(ErlNifEnv *env, struct ArrowSchema * schema,
157157
}
158158

159159
struct ArrowSchema * items_schema = schema->children[0];
160-
if (strcmp("item", items_schema->name) != 0) {
161-
return erlang::nif::error(env, "invalid ArrowSchema (list), its single child is not named item");
160+
if (!(strcmp("item", items_schema->name) == 0 || strcmp("l", items_schema->name) == 0)) {
161+
return erlang::nif::error(env, "invalid ArrowSchema (list), its single child is not named 'item' or 'l'");
162162
}
163163

164164
std::vector<ERL_NIF_TERM> childrens;
@@ -167,7 +167,10 @@ static int get_list_element_schema(ErlNifEnv *env, struct ArrowSchema * schema,
167167
if (arrow_schema_to_nif_term(env, items_schema, nullptr, level + 1, childrens, child_type, child_metadata, error) != 0) {
168168
return 1;
169169
}
170-
element_schema = make_adbc_column(env, items_schema, child_type, child_metadata);
170+
171+
// Always use "item" as the canonical name for list elements, regardless of what
172+
// the driver provides; using "item" appears to be conventional but duckdb uses "l"
173+
element_schema = make_adbc_column(env, items_schema, nullptr, erlang::nif::make_binary(env, "item"), child_type, items_schema->flags & ARROW_FLAG_NULLABLE, child_metadata, kAtomNil);
171174
return 0;
172175
}
173176

test/adbc_connection_test.exs

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,14 @@ defmodule Adbc.Connection.Test do
44

55
alias Adbc.Connection
66

7-
setup do
8-
db = start_supervised!({Adbc.Database, driver: :sqlite, uri: ":memory:"})
9-
%{db: db}
7+
setup tags do
8+
case Map.get(tags, :driver, :sqlite) do
9+
:sqlite ->
10+
%{db: start_supervised!({Adbc.Database, driver: :sqlite, uri: ":memory:"})}
11+
12+
:duckdb ->
13+
%{db: start_supervised!({Adbc.Database, driver: :duckdb})}
14+
end
1015
end
1116

1217
describe "start_link" do
@@ -734,4 +739,96 @@ defmodule Adbc.Connection.Test do
734739
{:ok, %{}} = Connection.get_table_types(conn)
735740
end
736741
end
742+
743+
describe "duckdb array handling" do
744+
@describetag driver: :duckdb
745+
test "handles empty string arrays without crashing", %{db: db} do
746+
conn = start_supervised!({Connection, database: db})
747+
748+
# Create a table with array column containing empty arrays
749+
{:ok, _} =
750+
Connection.query(conn, """
751+
CREATE TABLE test_arrays (
752+
number INTEGER,
753+
array_of_text TEXT[]
754+
)
755+
""")
756+
757+
# # Insert data with empty arrays
758+
{:ok, _} =
759+
Connection.query(conn, """
760+
INSERT INTO test_arrays VALUES
761+
(1, LIST_VALUE()),
762+
(2, '["hello", "world"]'),
763+
(4, '["single"]')
764+
""")
765+
766+
assert results =
767+
%Adbc.Result{
768+
num_rows: 0,
769+
data: [
770+
%Adbc.Column{
771+
name: "number",
772+
type: :s32,
773+
nullable: true,
774+
metadata: nil
775+
},
776+
%Adbc.Column{
777+
name: "array_of_text",
778+
type:
779+
{:list,
780+
%Adbc.Column{
781+
name: "item",
782+
type: :string,
783+
nullable: true,
784+
metadata: nil
785+
}},
786+
nullable: true,
787+
metadata: nil
788+
}
789+
]
790+
} = Connection.query!(conn, "SELECT * FROM test_arrays ORDER BY number")
791+
792+
assert %Adbc.Result{
793+
data: [
794+
%Adbc.Column{
795+
name: "number",
796+
type: :s32,
797+
nullable: true,
798+
metadata: nil,
799+
data: [1, 2, 4]
800+
},
801+
%Adbc.Column{
802+
name: "array_of_text",
803+
type: :list,
804+
nullable: true,
805+
metadata: nil,
806+
data: [
807+
%Adbc.Column{
808+
name: "item",
809+
type: :string,
810+
nullable: true,
811+
metadata: nil,
812+
data: []
813+
},
814+
%Adbc.Column{
815+
name: "item",
816+
type: :string,
817+
nullable: true,
818+
metadata: nil,
819+
data: ["hello", "world"]
820+
},
821+
%Adbc.Column{
822+
name: "item",
823+
type: :string,
824+
nullable: true,
825+
metadata: nil,
826+
data: ["single"]
827+
}
828+
]
829+
}
830+
]
831+
} = Adbc.Result.materialize(results)
832+
end
833+
end
737834
end

0 commit comments

Comments
 (0)