From 00cdd176f8fdfbffb2fb73720fbff8956d6a473d Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 13:38:07 -0300 Subject: [PATCH 01/10] update --- bindings/dart/lib/src/ffi/bindings.dart | 171 ++++++++++++++++++++++++ bindings/julia/src/c_api.jl | 24 ++++ bindings/julia/src/database.jl | 88 ++++++++++++ include/psr/c/database.h | 27 ++++ include/psr/database.h | 8 ++ src/c_api_database.cpp | 147 ++++++++++++++++++++ src/database.cpp | 109 +++++++++++++++ 7 files changed, 574 insertions(+) diff --git a/bindings/dart/lib/src/ffi/bindings.dart b/bindings/dart/lib/src/ffi/bindings.dart index e0e2dfe..91d30ed 100644 --- a/bindings/dart/lib/src/ffi/bindings.dart +++ b/bindings/dart/lib/src/ffi/bindings.dart @@ -373,6 +373,117 @@ class PsrDatabaseBindings { ffi.Pointer>>, ffi.Pointer)>(); + int psr_database_read_vector_ints( + ffi.Pointer db, + ffi.Pointer collection, + ffi.Pointer attribute, + ffi.Pointer>> out_vectors, + ffi.Pointer> out_sizes, + ffi.Pointer out_count, + ) { + return _psr_database_read_vector_ints( + db, + collection, + attribute, + out_vectors, + out_sizes, + out_count, + ); + } + + late final _psr_database_read_vector_intsPtr = _lookup< + ffi.NativeFunction< + ffi.Int32 Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>>, + ffi.Pointer>, + ffi.Pointer)>>('psr_database_read_vector_ints'); + late final _psr_database_read_vector_ints = + _psr_database_read_vector_intsPtr.asFunction< + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>>, + ffi.Pointer>, + ffi.Pointer)>(); + + int psr_database_read_vector_doubles( + ffi.Pointer db, + ffi.Pointer collection, + ffi.Pointer attribute, + ffi.Pointer>> out_vectors, + ffi.Pointer> out_sizes, + ffi.Pointer out_count, + ) { + return _psr_database_read_vector_doubles( + db, + collection, + attribute, + out_vectors, + out_sizes, + out_count, + ); + } + + late final _psr_database_read_vector_doublesPtr = _lookup< + ffi.NativeFunction< + ffi.Int32 Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>>, + ffi.Pointer>, + ffi.Pointer)>>('psr_database_read_vector_doubles'); + late final _psr_database_read_vector_doubles = + _psr_database_read_vector_doublesPtr.asFunction< + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>>, + ffi.Pointer>, + ffi.Pointer)>(); + + int psr_database_read_vector_strings( + ffi.Pointer db, + ffi.Pointer collection, + ffi.Pointer attribute, + ffi.Pointer>>> out_vectors, + ffi.Pointer> out_sizes, + ffi.Pointer out_count, + ) { + return _psr_database_read_vector_strings( + db, + collection, + attribute, + out_vectors, + out_sizes, + out_count, + ); + } + + late final _psr_database_read_vector_stringsPtr = _lookup< + ffi.NativeFunction< + ffi.Int32 Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>>>, + ffi.Pointer>, + ffi.Pointer)>>('psr_database_read_vector_strings'); + late final _psr_database_read_vector_strings = + _psr_database_read_vector_stringsPtr.asFunction< + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>>>, + ffi.Pointer>, + ffi.Pointer)>(); + void psr_free_int_array( ffi.Pointer values, ) { @@ -418,6 +529,66 @@ class PsrDatabaseBindings { late final _psr_free_string_array = _psr_free_string_arrayPtr .asFunction>, int)>(); + void psr_free_int_vectors( + ffi.Pointer> vectors, + ffi.Pointer sizes, + int count, + ) { + return _psr_free_int_vectors( + vectors, + sizes, + count, + ); + } + + late final _psr_free_int_vectorsPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer>, + ffi.Pointer, ffi.Size)>>('psr_free_int_vectors'); + late final _psr_free_int_vectors = _psr_free_int_vectorsPtr.asFunction< + void Function( + ffi.Pointer>, ffi.Pointer, int)>(); + + void psr_free_double_vectors( + ffi.Pointer> vectors, + ffi.Pointer sizes, + int count, + ) { + return _psr_free_double_vectors( + vectors, + sizes, + count, + ); + } + + late final _psr_free_double_vectorsPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer>, + ffi.Pointer, ffi.Size)>>('psr_free_double_vectors'); + late final _psr_free_double_vectors = _psr_free_double_vectorsPtr.asFunction< + void Function( + ffi.Pointer>, ffi.Pointer, int)>(); + + void psr_free_string_vectors( + ffi.Pointer>> vectors, + ffi.Pointer sizes, + int count, + ) { + return _psr_free_string_vectors( + vectors, + sizes, + count, + ); + } + + late final _psr_free_string_vectorsPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer>>, + ffi.Pointer, ffi.Size)>>('psr_free_string_vectors'); + late final _psr_free_string_vectors = _psr_free_string_vectorsPtr.asFunction< + void Function(ffi.Pointer>>, + ffi.Pointer, int)>(); + ffi.Pointer psr_element_create() { return _psr_element_create(); } diff --git a/bindings/julia/src/c_api.jl b/bindings/julia/src/c_api.jl index 34c3051..b06f182 100644 --- a/bindings/julia/src/c_api.jl +++ b/bindings/julia/src/c_api.jl @@ -146,6 +146,30 @@ function psr_free_string_array(values, count) @ccall libpsr_database_c.psr_free_string_array(values::Ptr{Ptr{Cchar}}, count::Csize_t)::Cvoid end +function psr_database_read_vector_ints(db, collection, attribute, out_vectors, out_sizes, out_count) + @ccall libpsr_database_c.psr_database_read_vector_ints(db::Ptr{psr_database_t}, collection::Ptr{Cchar}, attribute::Ptr{Cchar}, out_vectors::Ptr{Ptr{Ptr{Int64}}}, out_sizes::Ptr{Ptr{Csize_t}}, out_count::Ptr{Csize_t})::psr_error_t +end + +function psr_database_read_vector_doubles(db, collection, attribute, out_vectors, out_sizes, out_count) + @ccall libpsr_database_c.psr_database_read_vector_doubles(db::Ptr{psr_database_t}, collection::Ptr{Cchar}, attribute::Ptr{Cchar}, out_vectors::Ptr{Ptr{Ptr{Cdouble}}}, out_sizes::Ptr{Ptr{Csize_t}}, out_count::Ptr{Csize_t})::psr_error_t +end + +function psr_database_read_vector_strings(db, collection, attribute, out_vectors, out_sizes, out_count) + @ccall libpsr_database_c.psr_database_read_vector_strings(db::Ptr{psr_database_t}, collection::Ptr{Cchar}, attribute::Ptr{Cchar}, out_vectors::Ptr{Ptr{Ptr{Ptr{Cchar}}}}, out_sizes::Ptr{Ptr{Csize_t}}, out_count::Ptr{Csize_t})::psr_error_t +end + +function psr_free_int_vectors(vectors, sizes, count) + @ccall libpsr_database_c.psr_free_int_vectors(vectors::Ptr{Ptr{Int64}}, sizes::Ptr{Csize_t}, count::Csize_t)::Cvoid +end + +function psr_free_double_vectors(vectors, sizes, count) + @ccall libpsr_database_c.psr_free_double_vectors(vectors::Ptr{Ptr{Cdouble}}, sizes::Ptr{Csize_t}, count::Csize_t)::Cvoid +end + +function psr_free_string_vectors(vectors, sizes, count) + @ccall libpsr_database_c.psr_free_string_vectors(vectors::Ptr{Ptr{Ptr{Cchar}}}, sizes::Ptr{Csize_t}, count::Csize_t)::Cvoid +end + function psr_element_create() @ccall libpsr_database_c.psr_element_create()::Ptr{psr_element_t} end diff --git a/bindings/julia/src/database.jl b/bindings/julia/src/database.jl index 79cba01..0640d66 100644 --- a/bindings/julia/src/database.jl +++ b/bindings/julia/src/database.jl @@ -89,3 +89,91 @@ function read_scalar_strings(db::Database, collection::String, attribute::String C.psr_free_string_array(out_values[], count) return result end + +function read_vector_ints(db::Database, collection::String, attribute::String) + out_vectors = Ref{Ptr{Ptr{Int64}}}(C_NULL) + out_sizes = Ref{Ptr{Csize_t}}(C_NULL) + out_count = Ref{Csize_t}(0) + + err = C.psr_database_read_vector_ints(db.ptr, collection, attribute, out_vectors, out_sizes, out_count) + if err != C.PSR_OK + throw(DatabaseException("Failed to read vector ints from '$collection.$attribute'")) + end + + count = out_count[] + if count == 0 || out_vectors[] == C_NULL + return Vector{Int64}[] + end + + vectors_ptr = unsafe_wrap(Array, out_vectors[], count) + sizes_ptr = unsafe_wrap(Array, out_sizes[], count) + result = Vector{Int64}[] + for i in 1:count + if vectors_ptr[i] == C_NULL || sizes_ptr[i] == 0 + push!(result, Int64[]) + else + push!(result, copy(unsafe_wrap(Array, vectors_ptr[i], sizes_ptr[i]))) + end + end + C.psr_free_int_vectors(out_vectors[], out_sizes[], count) + return result +end + +function read_vector_doubles(db::Database, collection::String, attribute::String) + out_vectors = Ref{Ptr{Ptr{Cdouble}}}(C_NULL) + out_sizes = Ref{Ptr{Csize_t}}(C_NULL) + out_count = Ref{Csize_t}(0) + + err = C.psr_database_read_vector_doubles(db.ptr, collection, attribute, out_vectors, out_sizes, out_count) + if err != C.PSR_OK + throw(DatabaseException("Failed to read vector doubles from '$collection.$attribute'")) + end + + count = out_count[] + if count == 0 || out_vectors[] == C_NULL + return Vector{Float64}[] + end + + vectors_ptr = unsafe_wrap(Array, out_vectors[], count) + sizes_ptr = unsafe_wrap(Array, out_sizes[], count) + result = Vector{Float64}[] + for i in 1:count + if vectors_ptr[i] == C_NULL || sizes_ptr[i] == 0 + push!(result, Float64[]) + else + push!(result, copy(unsafe_wrap(Array, vectors_ptr[i], sizes_ptr[i]))) + end + end + C.psr_free_double_vectors(out_vectors[], out_sizes[], count) + return result +end + +function read_vector_strings(db::Database, collection::String, attribute::String) + out_vectors = Ref{Ptr{Ptr{Ptr{Cchar}}}}(C_NULL) + out_sizes = Ref{Ptr{Csize_t}}(C_NULL) + out_count = Ref{Csize_t}(0) + + err = C.psr_database_read_vector_strings(db.ptr, collection, attribute, out_vectors, out_sizes, out_count) + if err != C.PSR_OK + throw(DatabaseException("Failed to read vector strings from '$collection.$attribute'")) + end + + count = out_count[] + if count == 0 || out_vectors[] == C_NULL + return Vector{String}[] + end + + vectors_ptr = unsafe_wrap(Array, out_vectors[], count) + sizes_ptr = unsafe_wrap(Array, out_sizes[], count) + result = Vector{String}[] + for i in 1:count + if vectors_ptr[i] == C_NULL || sizes_ptr[i] == 0 + push!(result, String[]) + else + str_ptrs = unsafe_wrap(Array, vectors_ptr[i], sizes_ptr[i]) + push!(result, [unsafe_string(ptr) for ptr in str_ptrs]) + end + end + C.psr_free_string_vectors(out_vectors[], out_sizes[], count) + return result +end diff --git a/include/psr/c/database.h b/include/psr/c/database.h index 095588b..d6b5cb4 100644 --- a/include/psr/c/database.h +++ b/include/psr/c/database.h @@ -71,11 +71,38 @@ PSR_C_API psr_error_t psr_database_read_scalar_strings(psr_database_t* db, char*** out_values, size_t* out_count); +// Read vector attributes +PSR_C_API psr_error_t psr_database_read_vector_ints(psr_database_t* db, + const char* collection, + const char* attribute, + int64_t*** out_vectors, + size_t** out_sizes, + size_t* out_count); + +PSR_C_API psr_error_t psr_database_read_vector_doubles(psr_database_t* db, + const char* collection, + const char* attribute, + double*** out_vectors, + size_t** out_sizes, + size_t* out_count); + +PSR_C_API psr_error_t psr_database_read_vector_strings(psr_database_t* db, + const char* collection, + const char* attribute, + char**** out_vectors, + size_t** out_sizes, + size_t* out_count); + // Memory cleanup for read results PSR_C_API void psr_free_int_array(int64_t* values); PSR_C_API void psr_free_double_array(double* values); PSR_C_API void psr_free_string_array(char** values, size_t count); +// Memory cleanup for vector read results +PSR_C_API void psr_free_int_vectors(int64_t** vectors, size_t* sizes, size_t count); +PSR_C_API void psr_free_double_vectors(double** vectors, size_t* sizes, size_t count); +PSR_C_API void psr_free_string_vectors(char*** vectors, size_t* sizes, size_t count); + #ifdef __cplusplus } #endif diff --git a/include/psr/database.h b/include/psr/database.h index 29c87d7..8ecde3c 100644 --- a/include/psr/database.h +++ b/include/psr/database.h @@ -55,6 +55,14 @@ class PSR_API Database { std::vector read_scalar_doubles(const std::string& collection, const std::string& attribute); std::vector read_scalar_strings(const std::string& collection, const std::string& attribute); + // Read vector attributes + std::vector> read_vector_ints(const std::string& collection, + const std::string& attribute); + std::vector> read_vector_doubles(const std::string& collection, + const std::string& attribute); + std::vector> read_vector_strings(const std::string& collection, + const std::string& attribute); + // Transaction management void begin_transaction(); void commit(); diff --git a/src/c_api_database.cpp b/src/c_api_database.cpp index f644096..a6bc9cf 100644 --- a/src/c_api_database.cpp +++ b/src/c_api_database.cpp @@ -299,4 +299,151 @@ PSR_C_API void psr_free_string_array(char** values, size_t count) { delete[] values; } +PSR_C_API psr_error_t psr_database_read_vector_ints(psr_database_t* db, + const char* collection, + const char* attribute, + int64_t*** out_vectors, + size_t** out_sizes, + size_t* out_count) { + if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { + return PSR_ERROR_INVALID_ARGUMENT; + } + + try { + auto vectors = db->db.read_vector_ints(collection, attribute); + *out_count = vectors.size(); + if (vectors.empty()) { + *out_vectors = nullptr; + *out_sizes = nullptr; + return PSR_OK; + } + *out_vectors = new int64_t*[vectors.size()]; + *out_sizes = new size_t[vectors.size()]; + for (size_t i = 0; i < vectors.size(); ++i) { + (*out_sizes)[i] = vectors[i].size(); + if (vectors[i].empty()) { + (*out_vectors)[i] = nullptr; + } else { + (*out_vectors)[i] = new int64_t[vectors[i].size()]; + std::copy(vectors[i].begin(), vectors[i].end(), (*out_vectors)[i]); + } + } + return PSR_OK; + } catch (const std::exception&) { + return PSR_ERROR_DATABASE; + } +} + +PSR_C_API psr_error_t psr_database_read_vector_doubles(psr_database_t* db, + const char* collection, + const char* attribute, + double*** out_vectors, + size_t** out_sizes, + size_t* out_count) { + if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { + return PSR_ERROR_INVALID_ARGUMENT; + } + + try { + auto vectors = db->db.read_vector_doubles(collection, attribute); + *out_count = vectors.size(); + if (vectors.empty()) { + *out_vectors = nullptr; + *out_sizes = nullptr; + return PSR_OK; + } + *out_vectors = new double*[vectors.size()]; + *out_sizes = new size_t[vectors.size()]; + for (size_t i = 0; i < vectors.size(); ++i) { + (*out_sizes)[i] = vectors[i].size(); + if (vectors[i].empty()) { + (*out_vectors)[i] = nullptr; + } else { + (*out_vectors)[i] = new double[vectors[i].size()]; + std::copy(vectors[i].begin(), vectors[i].end(), (*out_vectors)[i]); + } + } + return PSR_OK; + } catch (const std::exception&) { + return PSR_ERROR_DATABASE; + } +} + +PSR_C_API psr_error_t psr_database_read_vector_strings(psr_database_t* db, + const char* collection, + const char* attribute, + char**** out_vectors, + size_t** out_sizes, + size_t* out_count) { + if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { + return PSR_ERROR_INVALID_ARGUMENT; + } + + try { + auto vectors = db->db.read_vector_strings(collection, attribute); + *out_count = vectors.size(); + if (vectors.empty()) { + *out_vectors = nullptr; + *out_sizes = nullptr; + return PSR_OK; + } + *out_vectors = new char**[vectors.size()]; + *out_sizes = new size_t[vectors.size()]; + for (size_t i = 0; i < vectors.size(); ++i) { + (*out_sizes)[i] = vectors[i].size(); + if (vectors[i].empty()) { + (*out_vectors)[i] = nullptr; + } else { + (*out_vectors)[i] = new char*[vectors[i].size()]; + for (size_t j = 0; j < vectors[i].size(); ++j) { + (*out_vectors)[i][j] = new char[vectors[i][j].size() + 1]; + std::copy(vectors[i][j].begin(), vectors[i][j].end(), (*out_vectors)[i][j]); + (*out_vectors)[i][j][vectors[i][j].size()] = '\0'; + } + } + } + return PSR_OK; + } catch (const std::exception&) { + return PSR_ERROR_DATABASE; + } +} + +PSR_C_API void psr_free_int_vectors(int64_t** vectors, size_t* sizes, size_t count) { + if (!vectors) { + return; + } + for (size_t i = 0; i < count; ++i) { + delete[] vectors[i]; + } + delete[] vectors; + delete[] sizes; +} + +PSR_C_API void psr_free_double_vectors(double** vectors, size_t* sizes, size_t count) { + if (!vectors) { + return; + } + for (size_t i = 0; i < count; ++i) { + delete[] vectors[i]; + } + delete[] vectors; + delete[] sizes; +} + +PSR_C_API void psr_free_string_vectors(char*** vectors, size_t* sizes, size_t count) { + if (!vectors) { + return; + } + for (size_t i = 0; i < count; ++i) { + if (vectors[i]) { + for (size_t j = 0; j < sizes[i]; ++j) { + delete[] vectors[i][j]; + } + delete[] vectors[i]; + } + } + delete[] vectors; + delete[] sizes; +} + } // extern "C" diff --git a/src/database.cpp b/src/database.cpp index 1101923..65411e9 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -657,4 +657,113 @@ std::vector Database::read_scalar_strings(const std::string& collec return values; } +// Helper to find vector table for an attribute +static std::string find_vector_table(const Schema* schema, const std::string& collection, + const std::string& attribute) { + // First try: Collection_vector_attribute + auto vector_table = Schema::vector_table_name(collection, attribute); + if (schema->has_table(vector_table)) { + return vector_table; + } + + // Second try: search all vector tables for the collection + for (const auto& table_name : schema->table_names()) { + if (!schema->is_vector_table(table_name)) + continue; + if (schema->get_parent_collection(table_name) != collection) + continue; + + const auto* table_def = schema->get_table(table_name); + if (table_def && table_def->has_column(attribute)) { + return table_name; + } + } + + throw std::runtime_error("Vector attribute '" + attribute + "' not found for collection '" + collection + "'"); +} + +std::vector> Database::read_vector_ints(const std::string& collection, + const std::string& attribute) { + auto vector_table = find_vector_table(impl_->schema.get(), collection, attribute); + auto sql = "SELECT id, " + attribute + " FROM " + vector_table + " ORDER BY id, vector_index"; + auto result = execute(sql); + + std::vector> vectors; + int64_t current_id = -1; + + for (size_t i = 0; i < result.row_count(); ++i) { + auto id = result[i].get_int(0); + auto val = result[i].get_int(1); + + if (!id) + continue; + + if (*id != current_id) { + vectors.emplace_back(); + current_id = *id; + } + + if (val) { + vectors.back().push_back(*val); + } + } + return vectors; +} + +std::vector> Database::read_vector_doubles(const std::string& collection, + const std::string& attribute) { + auto vector_table = find_vector_table(impl_->schema.get(), collection, attribute); + auto sql = "SELECT id, " + attribute + " FROM " + vector_table + " ORDER BY id, vector_index"; + auto result = execute(sql); + + std::vector> vectors; + int64_t current_id = -1; + + for (size_t i = 0; i < result.row_count(); ++i) { + auto id = result[i].get_int(0); + auto val = result[i].get_double(1); + + if (!id) + continue; + + if (*id != current_id) { + vectors.emplace_back(); + current_id = *id; + } + + if (val) { + vectors.back().push_back(*val); + } + } + return vectors; +} + +std::vector> Database::read_vector_strings(const std::string& collection, + const std::string& attribute) { + auto vector_table = find_vector_table(impl_->schema.get(), collection, attribute); + auto sql = "SELECT id, " + attribute + " FROM " + vector_table + " ORDER BY id, vector_index"; + auto result = execute(sql); + + std::vector> vectors; + int64_t current_id = -1; + + for (size_t i = 0; i < result.row_count(); ++i) { + auto id = result[i].get_int(0); + auto val = result[i].get_string(1); + + if (!id) + continue; + + if (*id != current_id) { + vectors.emplace_back(); + current_id = *id; + } + + if (val) { + vectors.back().push_back(*val); + } + } + return vectors; +} + } // namespace psr From cb77151eef559a64390ab8b877d28f82cae9b691 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 13:45:12 -0300 Subject: [PATCH 02/10] [skip ci] update --- bindings/dart/lib/src/database.dart | 132 ++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/bindings/dart/lib/src/database.dart b/bindings/dart/lib/src/database.dart index 7ad635b..6756f63 100644 --- a/bindings/dart/lib/src/database.dart +++ b/bindings/dart/lib/src/database.dart @@ -185,6 +185,138 @@ class Database { } } + /// Reads all int vectors for a vector attribute from a collection. + List> readVectorInts(String collection, String attribute) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final outVectors = arena>>(); + final outSizes = arena>(); + final outCount = arena(); + + final err = bindings.psr_database_read_vector_ints( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + outVectors, + outSizes, + outCount, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to read vector ints from '$collection.$attribute'"); + } + + final count = outCount.value; + if (count == 0 || outVectors.value == nullptr) { + return []; + } + + final result = >[]; + for (var i = 0; i < count; i++) { + final size = outSizes.value[i]; + if (size == 0 || outVectors.value[i] == nullptr) { + result.add([]); + } else { + result.add(List.generate(size, (j) => outVectors.value[i][j])); + } + } + bindings.psr_free_int_vectors(outVectors.value, outSizes.value, count); + return result; + } finally { + arena.releaseAll(); + } + } + + /// Reads all double vectors for a vector attribute from a collection. + List> readVectorDoubles(String collection, String attribute) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final outVectors = arena>>(); + final outSizes = arena>(); + final outCount = arena(); + + final err = bindings.psr_database_read_vector_doubles( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + outVectors, + outSizes, + outCount, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to read vector doubles from '$collection.$attribute'"); + } + + final count = outCount.value; + if (count == 0 || outVectors.value == nullptr) { + return []; + } + + final result = >[]; + for (var i = 0; i < count; i++) { + final size = outSizes.value[i]; + if (size == 0 || outVectors.value[i] == nullptr) { + result.add([]); + } else { + result.add(List.generate(size, (j) => outVectors.value[i][j])); + } + } + bindings.psr_free_double_vectors(outVectors.value, outSizes.value, count); + return result; + } finally { + arena.releaseAll(); + } + } + + /// Reads all string vectors for a vector attribute from a collection. + List> readVectorStrings(String collection, String attribute) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final outVectors = arena>>>(); + final outSizes = arena>(); + final outCount = arena(); + + final err = bindings.psr_database_read_vector_strings( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + outVectors, + outSizes, + outCount, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to read vector strings from '$collection.$attribute'"); + } + + final count = outCount.value; + if (count == 0 || outVectors.value == nullptr) { + return []; + } + + final result = >[]; + for (var i = 0; i < count; i++) { + final size = outSizes.value[i]; + if (size == 0 || outVectors.value[i] == nullptr) { + result.add([]); + } else { + result.add(List.generate(size, (j) => outVectors.value[i][j].cast().toDartString())); + } + } + bindings.psr_free_string_vectors(outVectors.value, outSizes.value, count); + return result; + } finally { + arena.releaseAll(); + } + } + /// Closes the database and frees native resources. void close() { if (_isClosed) return; From 181c18e545a79a3131fbaef80beef41eac14ee5d Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 13:55:05 -0300 Subject: [PATCH 03/10] [skip ci] update --- src/c_api_database.cpp | 143 +++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 83 deletions(-) diff --git a/src/c_api_database.cpp b/src/c_api_database.cpp index a6bc9cf..39f4098 100644 --- a/src/c_api_database.cpp +++ b/src/c_api_database.cpp @@ -38,6 +38,58 @@ psr::DatabaseOptions to_cpp_options(const psr_database_options_t* options) { return cpp_options; } +// Helper template for reading numeric scalars +template +psr_error_t read_scalars_impl(const std::vector& values, T** out_values, size_t* out_count) { + *out_count = values.size(); + if (values.empty()) { + *out_values = nullptr; + return PSR_OK; + } + *out_values = new T[values.size()]; + std::copy(values.begin(), values.end(), *out_values); + return PSR_OK; +} + +// Helper template for reading numeric vectors +template +psr_error_t read_vectors_impl(const std::vector>& vectors, + T*** out_vectors, + size_t** out_sizes, + size_t* out_count) { + *out_count = vectors.size(); + if (vectors.empty()) { + *out_vectors = nullptr; + *out_sizes = nullptr; + return PSR_OK; + } + *out_vectors = new T*[vectors.size()]; + *out_sizes = new size_t[vectors.size()]; + for (size_t i = 0; i < vectors.size(); ++i) { + (*out_sizes)[i] = vectors[i].size(); + if (vectors[i].empty()) { + (*out_vectors)[i] = nullptr; + } else { + (*out_vectors)[i] = new T[vectors[i].size()]; + std::copy(vectors[i].begin(), vectors[i].end(), (*out_vectors)[i]); + } + } + return PSR_OK; +} + +// Helper template for freeing numeric vectors +template +void free_vectors_impl(T** vectors, size_t* sizes, size_t count) { + (void)sizes; // unused for numeric types + if (!vectors) + return; + for (size_t i = 0; i < count; ++i) { + delete[] vectors[i]; + } + delete[] vectors; + delete[] sizes; +} + } // namespace struct psr_database { @@ -213,17 +265,8 @@ PSR_C_API psr_error_t psr_database_read_scalar_ints(psr_database_t* db, if (!db || !collection || !attribute || !out_values || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } - try { - auto values = db->db.read_scalar_ints(collection, attribute); - *out_count = values.size(); - if (values.empty()) { - *out_values = nullptr; - return PSR_OK; - } - *out_values = new int64_t[values.size()]; - std::copy(values.begin(), values.end(), *out_values); - return PSR_OK; + return read_scalars_impl(db->db.read_scalar_ints(collection, attribute), out_values, out_count); } catch (const std::exception&) { return PSR_ERROR_DATABASE; } @@ -237,17 +280,8 @@ PSR_C_API psr_error_t psr_database_read_scalar_doubles(psr_database_t* db, if (!db || !collection || !attribute || !out_values || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } - try { - auto values = db->db.read_scalar_doubles(collection, attribute); - *out_count = values.size(); - if (values.empty()) { - *out_values = nullptr; - return PSR_OK; - } - *out_values = new double[values.size()]; - std::copy(values.begin(), values.end(), *out_values); - return PSR_OK; + return read_scalars_impl(db->db.read_scalar_doubles(collection, attribute), out_values, out_count); } catch (const std::exception&) { return PSR_ERROR_DATABASE; } @@ -281,13 +315,9 @@ PSR_C_API psr_error_t psr_database_read_scalar_strings(psr_database_t* db, } } -PSR_C_API void psr_free_int_array(int64_t* values) { - delete[] values; -} +PSR_C_API void psr_free_int_array(int64_t* values) { delete[] values; } -PSR_C_API void psr_free_double_array(double* values) { - delete[] values; -} +PSR_C_API void psr_free_double_array(double* values) { delete[] values; } PSR_C_API void psr_free_string_array(char** values, size_t count) { if (!values) { @@ -308,27 +338,8 @@ PSR_C_API psr_error_t psr_database_read_vector_ints(psr_database_t* db, if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } - try { - auto vectors = db->db.read_vector_ints(collection, attribute); - *out_count = vectors.size(); - if (vectors.empty()) { - *out_vectors = nullptr; - *out_sizes = nullptr; - return PSR_OK; - } - *out_vectors = new int64_t*[vectors.size()]; - *out_sizes = new size_t[vectors.size()]; - for (size_t i = 0; i < vectors.size(); ++i) { - (*out_sizes)[i] = vectors[i].size(); - if (vectors[i].empty()) { - (*out_vectors)[i] = nullptr; - } else { - (*out_vectors)[i] = new int64_t[vectors[i].size()]; - std::copy(vectors[i].begin(), vectors[i].end(), (*out_vectors)[i]); - } - } - return PSR_OK; + return read_vectors_impl(db->db.read_vector_ints(collection, attribute), out_vectors, out_sizes, out_count); } catch (const std::exception&) { return PSR_ERROR_DATABASE; } @@ -343,27 +354,8 @@ PSR_C_API psr_error_t psr_database_read_vector_doubles(psr_database_t* db, if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } - try { - auto vectors = db->db.read_vector_doubles(collection, attribute); - *out_count = vectors.size(); - if (vectors.empty()) { - *out_vectors = nullptr; - *out_sizes = nullptr; - return PSR_OK; - } - *out_vectors = new double*[vectors.size()]; - *out_sizes = new size_t[vectors.size()]; - for (size_t i = 0; i < vectors.size(); ++i) { - (*out_sizes)[i] = vectors[i].size(); - if (vectors[i].empty()) { - (*out_vectors)[i] = nullptr; - } else { - (*out_vectors)[i] = new double[vectors[i].size()]; - std::copy(vectors[i].begin(), vectors[i].end(), (*out_vectors)[i]); - } - } - return PSR_OK; + return read_vectors_impl(db->db.read_vector_doubles(collection, attribute), out_vectors, out_sizes, out_count); } catch (const std::exception&) { return PSR_ERROR_DATABASE; } @@ -378,7 +370,6 @@ PSR_C_API psr_error_t psr_database_read_vector_strings(psr_database_t* db, if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } - try { auto vectors = db->db.read_vector_strings(collection, attribute); *out_count = vectors.size(); @@ -409,25 +400,11 @@ PSR_C_API psr_error_t psr_database_read_vector_strings(psr_database_t* db, } PSR_C_API void psr_free_int_vectors(int64_t** vectors, size_t* sizes, size_t count) { - if (!vectors) { - return; - } - for (size_t i = 0; i < count; ++i) { - delete[] vectors[i]; - } - delete[] vectors; - delete[] sizes; + free_vectors_impl(vectors, sizes, count); } PSR_C_API void psr_free_double_vectors(double** vectors, size_t* sizes, size_t count) { - if (!vectors) { - return; - } - for (size_t i = 0; i < count; ++i) { - delete[] vectors[i]; - } - delete[] vectors; - delete[] sizes; + free_vectors_impl(vectors, sizes, count); } PSR_C_API void psr_free_string_vectors(char*** vectors, size_t* sizes, size_t count) { From c8c2bd08106ce6996d9ed12e7b584dee30d38e38 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 13:56:59 -0300 Subject: [PATCH 04/10] [skip ci] update --- bindings/julia/generator/generator.jl | 5 ++++- bindings/julia/generator/prologue.jl | 21 ++++++++++++++++++++- bindings/julia/src/c_api.jl | 24 ++++++++++++------------ bindings/julia/test/fixture.jl | 2 +- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/bindings/julia/generator/generator.jl b/bindings/julia/generator/generator.jl index ba9a765..e9f81ff 100644 --- a/bindings/julia/generator/generator.jl +++ b/bindings/julia/generator/generator.jl @@ -12,7 +12,10 @@ include_dir = joinpath(database_dir, "include", "psr", "c") Libdl.dlopen(joinpath(database_dir, "build", "bin", "libpsr_database_c.dll")) -headers = [joinpath(include_dir, header) for header in readdir(include_dir) if endswith(header, ".h") && header != "platform.h"] +headers = [ + joinpath(include_dir, header) for + header in readdir(include_dir) if endswith(header, ".h") && header != "platform.h" +] args = get_default_args() options = load_options(joinpath(@__DIR__, "generator.toml")) ctx = create_context(headers, args, options) diff --git a/bindings/julia/generator/prologue.jl b/bindings/julia/generator/prologue.jl index fd43b29..4088ee4 100644 --- a/bindings/julia/generator/prologue.jl +++ b/bindings/julia/generator/prologue.jl @@ -3,4 +3,23 @@ using CEnum using Libdl -const libpsr_database_c = joinpath(@__DIR__, "..", "..", "..", "build", "bin", "libpsr_database_c.dll") +function library_name() + if Sys.iswindows() + return "libpsr_database_c.dll" + elseif Sys.isapple() + return "libpsr_database_c.dylib" + else + return "libpsr_database_c.so" + end +end + +# On Windows, DLLs go to bin/; on Linux/macOS, shared libs go to lib/ +function library_dir() + if Sys.iswindows() + return "bin" + else + return "lib" + end +end + +const libpsr_database_c = joinpath(@__DIR__, "..", "..", "..", "build", library_dir(), library_name()) diff --git a/bindings/julia/src/c_api.jl b/bindings/julia/src/c_api.jl index b06f182..a712651 100644 --- a/bindings/julia/src/c_api.jl +++ b/bindings/julia/src/c_api.jl @@ -134,18 +134,6 @@ function psr_database_read_scalar_strings(db, collection, attribute, out_values, @ccall libpsr_database_c.psr_database_read_scalar_strings(db::Ptr{psr_database_t}, collection::Ptr{Cchar}, attribute::Ptr{Cchar}, out_values::Ptr{Ptr{Ptr{Cchar}}}, out_count::Ptr{Csize_t})::psr_error_t end -function psr_free_int_array(values) - @ccall libpsr_database_c.psr_free_int_array(values::Ptr{Int64})::Cvoid -end - -function psr_free_double_array(values) - @ccall libpsr_database_c.psr_free_double_array(values::Ptr{Cdouble})::Cvoid -end - -function psr_free_string_array(values, count) - @ccall libpsr_database_c.psr_free_string_array(values::Ptr{Ptr{Cchar}}, count::Csize_t)::Cvoid -end - function psr_database_read_vector_ints(db, collection, attribute, out_vectors, out_sizes, out_count) @ccall libpsr_database_c.psr_database_read_vector_ints(db::Ptr{psr_database_t}, collection::Ptr{Cchar}, attribute::Ptr{Cchar}, out_vectors::Ptr{Ptr{Ptr{Int64}}}, out_sizes::Ptr{Ptr{Csize_t}}, out_count::Ptr{Csize_t})::psr_error_t end @@ -158,6 +146,18 @@ function psr_database_read_vector_strings(db, collection, attribute, out_vectors @ccall libpsr_database_c.psr_database_read_vector_strings(db::Ptr{psr_database_t}, collection::Ptr{Cchar}, attribute::Ptr{Cchar}, out_vectors::Ptr{Ptr{Ptr{Ptr{Cchar}}}}, out_sizes::Ptr{Ptr{Csize_t}}, out_count::Ptr{Csize_t})::psr_error_t end +function psr_free_int_array(values) + @ccall libpsr_database_c.psr_free_int_array(values::Ptr{Int64})::Cvoid +end + +function psr_free_double_array(values) + @ccall libpsr_database_c.psr_free_double_array(values::Ptr{Cdouble})::Cvoid +end + +function psr_free_string_array(values, count) + @ccall libpsr_database_c.psr_free_string_array(values::Ptr{Ptr{Cchar}}, count::Csize_t)::Cvoid +end + function psr_free_int_vectors(vectors, sizes, count) @ccall libpsr_database_c.psr_free_int_vectors(vectors::Ptr{Ptr{Int64}}, sizes::Ptr{Csize_t}, count::Csize_t)::Cvoid end diff --git a/bindings/julia/test/fixture.jl b/bindings/julia/test/fixture.jl index 03f4d3e..eec48f2 100644 --- a/bindings/julia/test/fixture.jl +++ b/bindings/julia/test/fixture.jl @@ -1,3 +1,3 @@ function tests_path() return joinpath(@__DIR__, "..", "..", "..", "tests") -end \ No newline at end of file +end From 0cfea97e0d7455f323eb02fdc235235b6d8f3b6f Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 13:57:44 -0300 Subject: [PATCH 05/10] update --- bindings/julia/src/c_api.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/julia/src/c_api.jl b/bindings/julia/src/c_api.jl index a712651..ebb26d0 100644 --- a/bindings/julia/src/c_api.jl +++ b/bindings/julia/src/c_api.jl @@ -5,7 +5,7 @@ module C using CEnum using Libdl -function _library_name() +function library_name() if Sys.iswindows() return "libpsr_database_c.dll" elseif Sys.isapple() @@ -15,8 +15,8 @@ function _library_name() end end -function _library_dir() - # On Windows, DLLs go to bin/; on Linux/macOS, shared libs go to lib/ +# On Windows, DLLs go to bin/; on Linux/macOS, shared libs go to lib/ +function library_dir() if Sys.iswindows() return "bin" else @@ -24,7 +24,7 @@ function _library_dir() end end -const libpsr_database_c = joinpath(@__DIR__, "..", "..", "..", "build", _library_dir(), _library_name()) +const libpsr_database_c = joinpath(@__DIR__, "..", "..", "..", "build", library_dir(), library_name()) @cenum psr_error_t::Int32 begin From d5e39d6f673ec2b5666ffda41d2d2ca315013bc2 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 14:05:00 -0300 Subject: [PATCH 06/10] update --- bindings/dart/test/read_test.dart | 102 ++++++++++++++++++++++++++++++ bindings/julia/test/test_read.jl | 60 ++++++++++++++++++ 2 files changed, 162 insertions(+) diff --git a/bindings/dart/test/read_test.dart b/bindings/dart/test/read_test.dart index 8fb07fe..0d368e3 100644 --- a/bindings/dart/test/read_test.dart +++ b/bindings/dart/test/read_test.dart @@ -143,4 +143,106 @@ void main() { } }); }); + + group('Read Vector Attributes', () { + test('reads int vectors from Collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + db.createElement('Collection', { + 'label': 'Item 1', + 'value_int': [1, 2, 3], + }); + db.createElement('Collection', { + 'label': 'Item 2', + 'value_int': [10, 20], + }); + + expect( + db.readVectorInts('Collection', 'value_int'), + equals([ + [1, 2, 3], + [10, 20], + ]), + ); + } finally { + db.close(); + } + }); + + test('reads double vectors from Collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + db.createElement('Collection', { + 'label': 'Item 1', + 'value_float': [1.5, 2.5, 3.5], + }); + db.createElement('Collection', { + 'label': 'Item 2', + 'value_float': [10.5, 20.5], + }); + + expect( + db.readVectorDoubles('Collection', 'value_float'), + equals([ + [1.5, 2.5, 3.5], + [10.5, 20.5], + ]), + ); + } finally { + db.close(); + } + }); + }); + + group('Read Vector Empty Result', () { + test('returns empty list when no elements', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + + expect(db.readVectorInts('Collection', 'value_int'), isEmpty); + expect(db.readVectorDoubles('Collection', 'value_float'), isEmpty); + } finally { + db.close(); + } + }); + }); + + group('Read Vector With Empty Vectors', () { + test('handles elements with and without vectors', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + db.createElement('Collection', { + 'label': 'Item 1', + 'value_int': [1, 2, 3], + }); + db.createElement('Collection', { + 'label': 'Item 2', + // No vector data + }); + + final result = db.readVectorInts('Collection', 'value_int'); + expect(result.length, equals(2)); + expect(result[0], equals([1, 2, 3])); + expect(result[1], isEmpty); + } finally { + db.close(); + } + }); + }); } diff --git a/bindings/julia/test/test_read.jl b/bindings/julia/test/test_read.jl index f3fe766..6fe1b64 100644 --- a/bindings/julia/test/test_read.jl +++ b/bindings/julia/test/test_read.jl @@ -68,4 +68,64 @@ end PSRDatabase.close!(db) end +@testset "Read Vector Attributes" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.create_empty_db_from_schema(":memory:", path_schema; force = true) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + PSRDatabase.create_element!(db, "Collection"; + label = "Item 1", + value_int = [1, 2, 3], + value_float = [1.5, 2.5, 3.5], + ) + PSRDatabase.create_element!(db, "Collection"; + label = "Item 2", + value_int = [10, 20], + value_float = [10.5, 20.5], + ) + + @test PSRDatabase.read_vector_ints(db, "Collection", "value_int") == [[1, 2, 3], [10, 20]] + @test PSRDatabase.read_vector_doubles(db, "Collection", "value_float") == [[1.5, 2.5, 3.5], [10.5, 20.5]] + + PSRDatabase.close!(db) +end + +@testset "Read Vector Empty Result" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.create_empty_db_from_schema(":memory:", path_schema; force = true) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # No Collection elements created + @test PSRDatabase.read_vector_ints(db, "Collection", "value_int") == Vector{Int64}[] + @test PSRDatabase.read_vector_doubles(db, "Collection", "value_float") == Vector{Float64}[] + + PSRDatabase.close!(db) +end + +@testset "Read Vector With Empty Vectors" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.create_empty_db_from_schema(":memory:", path_schema; force = true) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create element with vectors + PSRDatabase.create_element!(db, "Collection"; + label = "Item 1", + value_int = [1, 2, 3], + ) + # Create element without vectors (empty) + PSRDatabase.create_element!(db, "Collection"; + label = "Item 2", + ) + + result = PSRDatabase.read_vector_ints(db, "Collection", "value_int") + @test length(result) == 2 + @test result[1] == [1, 2, 3] + @test result[2] == Int64[] + + PSRDatabase.close!(db) +end + end From d62996deab74fa1934d0580e31b78efd6f9069b6 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 14:08:13 -0300 Subject: [PATCH 07/10] update --- bindings/dart/test/read_test.dart | 11 ++++++++--- bindings/julia/test/test_read.jl | 12 +++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/bindings/dart/test/read_test.dart b/bindings/dart/test/read_test.dart index 0d368e3..61858c7 100644 --- a/bindings/dart/test/read_test.dart +++ b/bindings/dart/test/read_test.dart @@ -219,8 +219,8 @@ void main() { }); }); - group('Read Vector With Empty Vectors', () { - test('handles elements with and without vectors', () { + group('Read Vector Only Returns Elements With Data', () { + test('only returns vectors for elements with data', () { final db = Database.fromSchema( ':memory:', path.join(testsPath, 'schemas', 'valid', 'collections.sql'), @@ -235,11 +235,16 @@ void main() { 'label': 'Item 2', // No vector data }); + db.createElement('Collection', { + 'label': 'Item 3', + 'value_int': [4, 5], + }); + // Only elements with vector data are returned final result = db.readVectorInts('Collection', 'value_int'); expect(result.length, equals(2)); expect(result[0], equals([1, 2, 3])); - expect(result[1], isEmpty); + expect(result[1], equals([4, 5])); } finally { db.close(); } diff --git a/bindings/julia/test/test_read.jl b/bindings/julia/test/test_read.jl index 6fe1b64..5ae0820 100644 --- a/bindings/julia/test/test_read.jl +++ b/bindings/julia/test/test_read.jl @@ -104,7 +104,7 @@ end PSRDatabase.close!(db) end -@testset "Read Vector With Empty Vectors" begin +@testset "Read Vector Only Returns Elements With Data" begin path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") db = PSRDatabase.create_empty_db_from_schema(":memory:", path_schema; force = true) @@ -115,15 +115,21 @@ end label = "Item 1", value_int = [1, 2, 3], ) - # Create element without vectors (empty) + # Create element without vectors (no vector data inserted) PSRDatabase.create_element!(db, "Collection"; label = "Item 2", ) + # Create another element with vectors + PSRDatabase.create_element!(db, "Collection"; + label = "Item 3", + value_int = [4, 5], + ) + # Only elements with vector data are returned result = PSRDatabase.read_vector_ints(db, "Collection", "value_int") @test length(result) == 2 @test result[1] == [1, 2, 3] - @test result[2] == Int64[] + @test result[2] == [4, 5] PSRDatabase.close!(db) end From 9cd80d5b8818a10fb76ee996795f950330953684 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 14:17:03 -0300 Subject: [PATCH 08/10] update --- include/psr/c/database.h | 30 ++-- include/psr/database.h | 8 +- src/c_api_database.cpp | 44 ++--- src/database.cpp | 10 +- tests/test_c_api_database.cpp | 295 ++++++++++++++++++++++++++++++++++ tests/test_database.cpp | 162 +++++++++++++++++++ 6 files changed, 503 insertions(+), 46 deletions(-) diff --git a/include/psr/c/database.h b/include/psr/c/database.h index d6b5cb4..5fc143c 100644 --- a/include/psr/c/database.h +++ b/include/psr/c/database.h @@ -73,25 +73,25 @@ PSR_C_API psr_error_t psr_database_read_scalar_strings(psr_database_t* db, // Read vector attributes PSR_C_API psr_error_t psr_database_read_vector_ints(psr_database_t* db, - const char* collection, - const char* attribute, - int64_t*** out_vectors, - size_t** out_sizes, - size_t* out_count); + const char* collection, + const char* attribute, + int64_t*** out_vectors, + size_t** out_sizes, + size_t* out_count); PSR_C_API psr_error_t psr_database_read_vector_doubles(psr_database_t* db, - const char* collection, - const char* attribute, - double*** out_vectors, - size_t** out_sizes, - size_t* out_count); + const char* collection, + const char* attribute, + double*** out_vectors, + size_t** out_sizes, + size_t* out_count); PSR_C_API psr_error_t psr_database_read_vector_strings(psr_database_t* db, - const char* collection, - const char* attribute, - char**** out_vectors, - size_t** out_sizes, - size_t* out_count); + const char* collection, + const char* attribute, + char**** out_vectors, + size_t** out_sizes, + size_t* out_count); // Memory cleanup for read results PSR_C_API void psr_free_int_array(int64_t* values); diff --git a/include/psr/database.h b/include/psr/database.h index 8ecde3c..70ef43f 100644 --- a/include/psr/database.h +++ b/include/psr/database.h @@ -56,12 +56,10 @@ class PSR_API Database { std::vector read_scalar_strings(const std::string& collection, const std::string& attribute); // Read vector attributes - std::vector> read_vector_ints(const std::string& collection, - const std::string& attribute); - std::vector> read_vector_doubles(const std::string& collection, - const std::string& attribute); + std::vector> read_vector_ints(const std::string& collection, const std::string& attribute); + std::vector> read_vector_doubles(const std::string& collection, const std::string& attribute); std::vector> read_vector_strings(const std::string& collection, - const std::string& attribute); + const std::string& attribute); // Transaction management void begin_transaction(); diff --git a/src/c_api_database.cpp b/src/c_api_database.cpp index 39f4098..04cc0eb 100644 --- a/src/c_api_database.cpp +++ b/src/c_api_database.cpp @@ -53,10 +53,8 @@ psr_error_t read_scalars_impl(const std::vector& values, T** out_values, size // Helper template for reading numeric vectors template -psr_error_t read_vectors_impl(const std::vector>& vectors, - T*** out_vectors, - size_t** out_sizes, - size_t* out_count) { +psr_error_t +read_vectors_impl(const std::vector>& vectors, T*** out_vectors, size_t** out_sizes, size_t* out_count) { *out_count = vectors.size(); if (vectors.empty()) { *out_vectors = nullptr; @@ -315,9 +313,13 @@ PSR_C_API psr_error_t psr_database_read_scalar_strings(psr_database_t* db, } } -PSR_C_API void psr_free_int_array(int64_t* values) { delete[] values; } +PSR_C_API void psr_free_int_array(int64_t* values) { + delete[] values; +} -PSR_C_API void psr_free_double_array(double* values) { delete[] values; } +PSR_C_API void psr_free_double_array(double* values) { + delete[] values; +} PSR_C_API void psr_free_string_array(char** values, size_t count) { if (!values) { @@ -330,11 +332,11 @@ PSR_C_API void psr_free_string_array(char** values, size_t count) { } PSR_C_API psr_error_t psr_database_read_vector_ints(psr_database_t* db, - const char* collection, - const char* attribute, - int64_t*** out_vectors, - size_t** out_sizes, - size_t* out_count) { + const char* collection, + const char* attribute, + int64_t*** out_vectors, + size_t** out_sizes, + size_t* out_count) { if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } @@ -346,11 +348,11 @@ PSR_C_API psr_error_t psr_database_read_vector_ints(psr_database_t* db, } PSR_C_API psr_error_t psr_database_read_vector_doubles(psr_database_t* db, - const char* collection, - const char* attribute, - double*** out_vectors, - size_t** out_sizes, - size_t* out_count) { + const char* collection, + const char* attribute, + double*** out_vectors, + size_t** out_sizes, + size_t* out_count) { if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } @@ -362,11 +364,11 @@ PSR_C_API psr_error_t psr_database_read_vector_doubles(psr_database_t* db, } PSR_C_API psr_error_t psr_database_read_vector_strings(psr_database_t* db, - const char* collection, - const char* attribute, - char**** out_vectors, - size_t** out_sizes, - size_t* out_count) { + const char* collection, + const char* attribute, + char**** out_vectors, + size_t** out_sizes, + size_t* out_count) { if (!db || !collection || !attribute || !out_vectors || !out_sizes || !out_count) { return PSR_ERROR_INVALID_ARGUMENT; } diff --git a/src/database.cpp b/src/database.cpp index 65411e9..c39841b 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -658,8 +658,8 @@ std::vector Database::read_scalar_strings(const std::string& collec } // Helper to find vector table for an attribute -static std::string find_vector_table(const Schema* schema, const std::string& collection, - const std::string& attribute) { +static std::string +find_vector_table(const Schema* schema, const std::string& collection, const std::string& attribute) { // First try: Collection_vector_attribute auto vector_table = Schema::vector_table_name(collection, attribute); if (schema->has_table(vector_table)) { @@ -683,7 +683,7 @@ static std::string find_vector_table(const Schema* schema, const std::string& co } std::vector> Database::read_vector_ints(const std::string& collection, - const std::string& attribute) { + const std::string& attribute) { auto vector_table = find_vector_table(impl_->schema.get(), collection, attribute); auto sql = "SELECT id, " + attribute + " FROM " + vector_table + " ORDER BY id, vector_index"; auto result = execute(sql); @@ -711,7 +711,7 @@ std::vector> Database::read_vector_ints(const std::string& } std::vector> Database::read_vector_doubles(const std::string& collection, - const std::string& attribute) { + const std::string& attribute) { auto vector_table = find_vector_table(impl_->schema.get(), collection, attribute); auto sql = "SELECT id, " + attribute + " FROM " + vector_table + " ORDER BY id, vector_index"; auto result = execute(sql); @@ -739,7 +739,7 @@ std::vector> Database::read_vector_doubles(const std::string } std::vector> Database::read_vector_strings(const std::string& collection, - const std::string& attribute) { + const std::string& attribute) { auto vector_table = find_vector_table(impl_->schema.get(), collection, attribute); auto sql = "SELECT id, " + attribute + " FROM " + vector_table + " ORDER BY id, vector_index"; auto result = execute(sql); diff --git a/tests/test_c_api_database.cpp b/tests/test_c_api_database.cpp index 86b6095..553ac1e 100644 --- a/tests/test_c_api_database.cpp +++ b/tests/test_c_api_database.cpp @@ -258,3 +258,298 @@ TEST_F(DatabaseFixture, CreateElementNullElement) { psr_database_close(db); } + +TEST_F(DatabaseFixture, ReadScalarInts) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/basic.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto e1 = psr_element_create(); + psr_element_set_string(e1, "label", "Config 1"); + psr_element_set_int(e1, "integer_attribute", 42); + psr_database_create_element(db, "Configuration", e1); + psr_element_destroy(e1); + + auto e2 = psr_element_create(); + psr_element_set_string(e2, "label", "Config 2"); + psr_element_set_int(e2, "integer_attribute", 100); + psr_database_create_element(db, "Configuration", e2); + psr_element_destroy(e2); + + int64_t* values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_ints(db, "Configuration", "integer_attribute", &values, &count); + + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 2); + EXPECT_EQ(values[0], 42); + EXPECT_EQ(values[1], 100); + + psr_free_int_array(values); + psr_database_close(db); +} + +TEST_F(DatabaseFixture, ReadScalarDoubles) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/basic.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto e1 = psr_element_create(); + psr_element_set_string(e1, "label", "Config 1"); + psr_element_set_double(e1, "float_attribute", 3.14); + psr_database_create_element(db, "Configuration", e1); + psr_element_destroy(e1); + + auto e2 = psr_element_create(); + psr_element_set_string(e2, "label", "Config 2"); + psr_element_set_double(e2, "float_attribute", 2.71); + psr_database_create_element(db, "Configuration", e2); + psr_element_destroy(e2); + + double* values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_doubles(db, "Configuration", "float_attribute", &values, &count); + + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 2); + EXPECT_DOUBLE_EQ(values[0], 3.14); + EXPECT_DOUBLE_EQ(values[1], 2.71); + + psr_free_double_array(values); + psr_database_close(db); +} + +TEST_F(DatabaseFixture, ReadScalarStrings) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/basic.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto e1 = psr_element_create(); + psr_element_set_string(e1, "label", "Config 1"); + psr_element_set_string(e1, "string_attribute", "hello"); + psr_database_create_element(db, "Configuration", e1); + psr_element_destroy(e1); + + auto e2 = psr_element_create(); + psr_element_set_string(e2, "label", "Config 2"); + psr_element_set_string(e2, "string_attribute", "world"); + psr_database_create_element(db, "Configuration", e2); + psr_element_destroy(e2); + + char** values = nullptr; + size_t count = 0; + auto err = psr_database_read_scalar_strings(db, "Configuration", "string_attribute", &values, &count); + + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 2); + EXPECT_STREQ(values[0], "hello"); + EXPECT_STREQ(values[1], "world"); + + psr_free_string_array(values, count); + psr_database_close(db); +} + +TEST_F(DatabaseFixture, ReadScalarEmpty) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto config = psr_element_create(); + psr_element_set_string(config, "label", "Test Config"); + psr_database_create_element(db, "Configuration", config); + psr_element_destroy(config); + + int64_t* int_values = nullptr; + size_t int_count = 0; + auto err = psr_database_read_scalar_ints(db, "Collection", "some_integer", &int_values, &int_count); + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(int_count, 0); + EXPECT_EQ(int_values, nullptr); + + double* double_values = nullptr; + size_t double_count = 0; + err = psr_database_read_scalar_doubles(db, "Collection", "some_float", &double_values, &double_count); + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(double_count, 0); + EXPECT_EQ(double_values, nullptr); + + psr_database_close(db); +} + +TEST_F(DatabaseFixture, ReadVectorInts) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto config = psr_element_create(); + psr_element_set_string(config, "label", "Test Config"); + psr_database_create_element(db, "Configuration", config); + psr_element_destroy(config); + + auto e1 = psr_element_create(); + psr_element_set_string(e1, "label", "Item 1"); + int64_t values1[] = {1, 2, 3}; + psr_element_set_array_int(e1, "value_int", values1, 3); + psr_database_create_element(db, "Collection", e1); + psr_element_destroy(e1); + + auto e2 = psr_element_create(); + psr_element_set_string(e2, "label", "Item 2"); + int64_t values2[] = {10, 20}; + psr_element_set_array_int(e2, "value_int", values2, 2); + psr_database_create_element(db, "Collection", e2); + psr_element_destroy(e2); + + int64_t** vectors = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_ints(db, "Collection", "value_int", &vectors, &sizes, &count); + + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 2); + EXPECT_EQ(sizes[0], 3); + EXPECT_EQ(sizes[1], 2); + EXPECT_EQ(vectors[0][0], 1); + EXPECT_EQ(vectors[0][1], 2); + EXPECT_EQ(vectors[0][2], 3); + EXPECT_EQ(vectors[1][0], 10); + EXPECT_EQ(vectors[1][1], 20); + + psr_free_int_vectors(vectors, sizes, count); + psr_database_close(db); +} + +TEST_F(DatabaseFixture, ReadVectorDoubles) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto config = psr_element_create(); + psr_element_set_string(config, "label", "Test Config"); + psr_database_create_element(db, "Configuration", config); + psr_element_destroy(config); + + auto e1 = psr_element_create(); + psr_element_set_string(e1, "label", "Item 1"); + double values1[] = {1.5, 2.5, 3.5}; + psr_element_set_array_double(e1, "value_float", values1, 3); + psr_database_create_element(db, "Collection", e1); + psr_element_destroy(e1); + + auto e2 = psr_element_create(); + psr_element_set_string(e2, "label", "Item 2"); + double values2[] = {10.5, 20.5}; + psr_element_set_array_double(e2, "value_float", values2, 2); + psr_database_create_element(db, "Collection", e2); + psr_element_destroy(e2); + + double** vectors = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_doubles(db, "Collection", "value_float", &vectors, &sizes, &count); + + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 2); + EXPECT_EQ(sizes[0], 3); + EXPECT_EQ(sizes[1], 2); + EXPECT_DOUBLE_EQ(vectors[0][0], 1.5); + EXPECT_DOUBLE_EQ(vectors[0][1], 2.5); + EXPECT_DOUBLE_EQ(vectors[0][2], 3.5); + EXPECT_DOUBLE_EQ(vectors[1][0], 10.5); + EXPECT_DOUBLE_EQ(vectors[1][1], 20.5); + + psr_free_double_vectors(vectors, sizes, count); + psr_database_close(db); +} + +TEST_F(DatabaseFixture, ReadVectorEmpty) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto config = psr_element_create(); + psr_element_set_string(config, "label", "Test Config"); + psr_database_create_element(db, "Configuration", config); + psr_element_destroy(config); + + int64_t** int_vectors = nullptr; + size_t* int_sizes = nullptr; + size_t int_count = 0; + auto err = psr_database_read_vector_ints(db, "Collection", "value_int", &int_vectors, &int_sizes, &int_count); + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(int_count, 0); + EXPECT_EQ(int_vectors, nullptr); + EXPECT_EQ(int_sizes, nullptr); + + double** double_vectors = nullptr; + size_t* double_sizes = nullptr; + size_t double_count = 0; + err = psr_database_read_vector_doubles( + db, "Collection", "value_float", &double_vectors, &double_sizes, &double_count); + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(double_count, 0); + EXPECT_EQ(double_vectors, nullptr); + EXPECT_EQ(double_sizes, nullptr); + + psr_database_close(db); +} + +TEST_F(DatabaseFixture, ReadVectorOnlyReturnsElementsWithData) { + auto options = psr_database_options_default(); + options.console_level = PSR_LOG_OFF; + auto db = psr_database_from_schema(":memory:", schema_path("schemas/valid/collections.sql").c_str(), &options); + ASSERT_NE(db, nullptr); + + auto config = psr_element_create(); + psr_element_set_string(config, "label", "Test Config"); + psr_database_create_element(db, "Configuration", config); + psr_element_destroy(config); + + // Element with vector data + auto e1 = psr_element_create(); + psr_element_set_string(e1, "label", "Item 1"); + int64_t values1[] = {1, 2, 3}; + psr_element_set_array_int(e1, "value_int", values1, 3); + psr_database_create_element(db, "Collection", e1); + psr_element_destroy(e1); + + // Element without vector data + auto e2 = psr_element_create(); + psr_element_set_string(e2, "label", "Item 2"); + psr_database_create_element(db, "Collection", e2); + psr_element_destroy(e2); + + // Another element with vector data + auto e3 = psr_element_create(); + psr_element_set_string(e3, "label", "Item 3"); + int64_t values3[] = {4, 5}; + psr_element_set_array_int(e3, "value_int", values3, 2); + psr_database_create_element(db, "Collection", e3); + psr_element_destroy(e3); + + int64_t** vectors = nullptr; + size_t* sizes = nullptr; + size_t count = 0; + auto err = psr_database_read_vector_ints(db, "Collection", "value_int", &vectors, &sizes, &count); + + // Only elements with vector data are returned + EXPECT_EQ(err, PSR_OK); + EXPECT_EQ(count, 2); + EXPECT_EQ(sizes[0], 3); + EXPECT_EQ(sizes[1], 2); + EXPECT_EQ(vectors[0][0], 1); + EXPECT_EQ(vectors[0][1], 2); + EXPECT_EQ(vectors[0][2], 3); + EXPECT_EQ(vectors[1][0], 4); + EXPECT_EQ(vectors[1][1], 5); + + psr_free_int_vectors(vectors, sizes, count); + psr_database_close(db); +} diff --git a/tests/test_database.cpp b/tests/test_database.cpp index e6ce0b2..e34d956 100644 --- a/tests/test_database.cpp +++ b/tests/test_database.cpp @@ -220,3 +220,165 @@ TEST_F(DatabaseFixture, CurrentVersion) { psr::Database db(":memory:", {.console_level = psr::LogLevel::off}); EXPECT_EQ(db.current_version(), 0); } + +TEST_F(DatabaseFixture, ReadScalarInts) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/basic.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element e1; + e1.set("label", std::string("Config 1")).set("integer_attribute", int64_t{42}); + db.create_element("Configuration", e1); + + psr::Element e2; + e2.set("label", std::string("Config 2")).set("integer_attribute", int64_t{100}); + db.create_element("Configuration", e2); + + auto values = db.read_scalar_ints("Configuration", "integer_attribute"); + EXPECT_EQ(values.size(), 2); + EXPECT_EQ(values[0], 42); + EXPECT_EQ(values[1], 100); +} + +TEST_F(DatabaseFixture, ReadScalarDoubles) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/basic.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element e1; + e1.set("label", std::string("Config 1")).set("float_attribute", 3.14); + db.create_element("Configuration", e1); + + psr::Element e2; + e2.set("label", std::string("Config 2")).set("float_attribute", 2.71); + db.create_element("Configuration", e2); + + auto values = db.read_scalar_doubles("Configuration", "float_attribute"); + EXPECT_EQ(values.size(), 2); + EXPECT_DOUBLE_EQ(values[0], 3.14); + EXPECT_DOUBLE_EQ(values[1], 2.71); +} + +TEST_F(DatabaseFixture, ReadScalarStrings) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/basic.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element e1; + e1.set("label", std::string("Config 1")).set("string_attribute", std::string("hello")); + db.create_element("Configuration", e1); + + psr::Element e2; + e2.set("label", std::string("Config 2")).set("string_attribute", std::string("world")); + db.create_element("Configuration", e2); + + auto values = db.read_scalar_strings("Configuration", "string_attribute"); + EXPECT_EQ(values.size(), 2); + EXPECT_EQ(values[0], "hello"); + EXPECT_EQ(values[1], "world"); +} + +TEST_F(DatabaseFixture, ReadScalarEmpty) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + // No Collection elements created + auto ints = db.read_scalar_ints("Collection", "some_integer"); + auto doubles = db.read_scalar_doubles("Collection", "some_float"); + auto strings = db.read_scalar_strings("Collection", "label"); + + EXPECT_TRUE(ints.empty()); + EXPECT_TRUE(doubles.empty()); + EXPECT_TRUE(strings.empty()); +} + +TEST_F(DatabaseFixture, ReadVectorInts) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + psr::Element e1; + e1.set("label", std::string("Item 1")).set("value_int", std::vector{1, 2, 3}); + db.create_element("Collection", e1); + + psr::Element e2; + e2.set("label", std::string("Item 2")).set("value_int", std::vector{10, 20}); + db.create_element("Collection", e2); + + auto vectors = db.read_vector_ints("Collection", "value_int"); + EXPECT_EQ(vectors.size(), 2); + EXPECT_EQ(vectors[0], (std::vector{1, 2, 3})); + EXPECT_EQ(vectors[1], (std::vector{10, 20})); +} + +TEST_F(DatabaseFixture, ReadVectorDoubles) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + psr::Element e1; + e1.set("label", std::string("Item 1")).set("value_float", std::vector{1.5, 2.5, 3.5}); + db.create_element("Collection", e1); + + psr::Element e2; + e2.set("label", std::string("Item 2")).set("value_float", std::vector{10.5, 20.5}); + db.create_element("Collection", e2); + + auto vectors = db.read_vector_doubles("Collection", "value_float"); + EXPECT_EQ(vectors.size(), 2); + EXPECT_EQ(vectors[0], (std::vector{1.5, 2.5, 3.5})); + EXPECT_EQ(vectors[1], (std::vector{10.5, 20.5})); +} + +TEST_F(DatabaseFixture, ReadVectorEmpty) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + // No Collection elements created + auto int_vectors = db.read_vector_ints("Collection", "value_int"); + auto double_vectors = db.read_vector_doubles("Collection", "value_float"); + + EXPECT_TRUE(int_vectors.empty()); + EXPECT_TRUE(double_vectors.empty()); +} + +TEST_F(DatabaseFixture, ReadVectorOnlyReturnsElementsWithData) { + auto db = psr::Database::from_schema( + ":memory:", schema_path("schemas/valid/collections.sql"), {.console_level = psr::LogLevel::off}); + + psr::Element config; + config.set("label", std::string("Test Config")); + db.create_element("Configuration", config); + + // Element with vector data + psr::Element e1; + e1.set("label", std::string("Item 1")).set("value_int", std::vector{1, 2, 3}); + db.create_element("Collection", e1); + + // Element without vector data + psr::Element e2; + e2.set("label", std::string("Item 2")); + db.create_element("Collection", e2); + + // Another element with vector data + psr::Element e3; + e3.set("label", std::string("Item 3")).set("value_int", std::vector{4, 5}); + db.create_element("Collection", e3); + + // Only elements with vector data are returned + auto vectors = db.read_vector_ints("Collection", "value_int"); + EXPECT_EQ(vectors.size(), 2); + EXPECT_EQ(vectors[0], (std::vector{1, 2, 3})); + EXPECT_EQ(vectors[1], (std::vector{4, 5})); +} From 68ddfdebf9c333d8c2c289f0e222141fe2752105 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 15:04:39 -0300 Subject: [PATCH 09/10] update --- include/psr/column_type.h | 10 +++------- src/database.cpp | 11 +++++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/include/psr/column_type.h b/include/psr/column_type.h index ee3f203..8c7fa00 100644 --- a/include/psr/column_type.h +++ b/include/psr/column_type.h @@ -6,17 +6,15 @@ namespace psr { -enum class ColumnType { Integer, Real, Text, Blob }; +enum class ColumnType { Integer, Real, Text }; inline ColumnType column_type_from_string(const std::string& type_str) { if (type_str == "INTEGER") return ColumnType::Integer; - if (type_str == "REAL") + else if (type_str == "REAL") return ColumnType::Real; - if (type_str == "TEXT") + else if (type_str == "TEXT") return ColumnType::Text; - if (type_str == "BLOB") - return ColumnType::Blob; throw std::runtime_error("Unknown column type: " + type_str); } @@ -28,8 +26,6 @@ inline const char* column_type_to_string(ColumnType type) { return "REAL"; case ColumnType::Text: return "TEXT"; - case ColumnType::Blob: - return "BLOB"; } return "UNKNOWN"; } diff --git a/src/database.cpp b/src/database.cpp index c39841b..c5b0e12 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -189,7 +189,7 @@ Result Database::execute(const std::string& sql, const std::vector& param values.reserve(col_count); for (int i = 0; i < col_count; ++i) { - int type = sqlite3_column_type(stmt, i); + auto type = sqlite3_column_type(stmt, i); switch (type) { case SQLITE_INTEGER: values.emplace_back(sqlite3_column_int64(stmt, i)); @@ -201,12 +201,15 @@ Result Database::execute(const std::string& sql, const std::vector& param const char* text = reinterpret_cast(sqlite3_column_text(stmt, i)); values.emplace_back(std::string(text ? text : "")); break; - } - case SQLITE_BLOB: + } case SQLITE_NULL: - default: values.emplace_back(nullptr); break; + case SQLITE_BLOB: + throw std::runtime_error("Blob not implemented"); + default: + throw std::runtime_error("Type not implemented"); + break; } } rows.emplace_back(std::move(values)); From 40fce614ed6754ab7c90ff2dbd52b4b4b9ad3696 Mon Sep 17 00:00:00 2001 From: raphasampaio Date: Thu, 15 Jan 2026 19:44:50 -0300 Subject: [PATCH 10/10] update --- src/database.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database.cpp b/src/database.cpp index c5b0e12..05e9fdc 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -201,7 +201,7 @@ Result Database::execute(const std::string& sql, const std::vector& param const char* text = reinterpret_cast(sqlite3_column_text(stmt, i)); values.emplace_back(std::string(text ? text : "")); break; - } + } case SQLITE_NULL: values.emplace_back(nullptr); break;