diff --git a/bindings/dart/lib/src/database.dart b/bindings/dart/lib/src/database.dart index b47f9cd..632c6f8 100644 --- a/bindings/dart/lib/src/database.dart +++ b/bindings/dart/lib/src/database.dart @@ -889,16 +889,16 @@ class Database { } final dataStructure = switch (outDataStructure.value) { - 0 => 'scalar', - 1 => 'vector', - 2 => 'set', + psr_data_structure_t.PSR_DATA_STRUCTURE_SCALAR => 'scalar', + psr_data_structure_t.PSR_DATA_STRUCTURE_VECTOR => 'vector', + psr_data_structure_t.PSR_DATA_STRUCTURE_SET => 'set', _ => 'unknown', }; final dataType = switch (outDataType.value) { - 0 => 'integer', - 1 => 'real', - 2 => 'text', + psr_data_type_t.PSR_DATA_TYPE_INTEGER => 'integer', + psr_data_type_t.PSR_DATA_TYPE_REAL => 'real', + psr_data_type_t.PSR_DATA_TYPE_TEXT => 'text', _ => 'unknown', }; @@ -964,6 +964,246 @@ class Database { } } + // Update scalar attribute methods + + /// Updates an integer scalar attribute value by element ID. + void updateScalarInteger(String collection, String attribute, int id, int value) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final err = bindings.psr_database_update_scalar_integer( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + value, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update scalar integer '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + /// Updates a double scalar attribute value by element ID. + void updateScalarDouble(String collection, String attribute, int id, double value) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final err = bindings.psr_database_update_scalar_double( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + value, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update scalar double '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + /// Updates a string scalar attribute value by element ID. + void updateScalarString(String collection, String attribute, int id, String value) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final err = bindings.psr_database_update_scalar_string( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + value.toNativeUtf8(allocator: arena).cast(), + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update scalar string '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + // Update vector attribute methods + + /// Updates an integer vector attribute by element ID (replaces entire vector). + void updateVectorIntegers(String collection, String attribute, int id, List values) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final nativeValues = arena(values.length); + for (var i = 0; i < values.length; i++) { + nativeValues[i] = values[i]; + } + + final err = bindings.psr_database_update_vector_integers( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + nativeValues, + values.length, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update vector integers '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + /// Updates a double vector attribute by element ID (replaces entire vector). + void updateVectorDoubles(String collection, String attribute, int id, List values) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final nativeValues = arena(values.length); + for (var i = 0; i < values.length; i++) { + nativeValues[i] = values[i]; + } + + final err = bindings.psr_database_update_vector_doubles( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + nativeValues, + values.length, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update vector doubles '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + /// Updates a string vector attribute by element ID (replaces entire vector). + void updateVectorStrings(String collection, String attribute, int id, List values) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final nativePtrs = arena>(values.length); + for (var i = 0; i < values.length; i++) { + nativePtrs[i] = values[i].toNativeUtf8(allocator: arena).cast(); + } + + final err = bindings.psr_database_update_vector_strings( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + nativePtrs, + values.length, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update vector strings '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + // Update set attribute methods + + /// Updates an integer set attribute by element ID (replaces entire set). + void updateSetIntegers(String collection, String attribute, int id, List values) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final nativeValues = arena(values.length); + for (var i = 0; i < values.length; i++) { + nativeValues[i] = values[i]; + } + + final err = bindings.psr_database_update_set_integers( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + nativeValues, + values.length, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update set integers '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + /// Updates a double set attribute by element ID (replaces entire set). + void updateSetDoubles(String collection, String attribute, int id, List values) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final nativeValues = arena(values.length); + for (var i = 0; i < values.length; i++) { + nativeValues[i] = values[i]; + } + + final err = bindings.psr_database_update_set_doubles( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + nativeValues, + values.length, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update set doubles '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + + /// Updates a string set attribute by element ID (replaces entire set). + void updateSetStrings(String collection, String attribute, int id, List values) { + _ensureNotClosed(); + + final arena = Arena(); + try { + final nativePtrs = arena>(values.length); + for (var i = 0; i < values.length; i++) { + nativePtrs[i] = values[i].toNativeUtf8(allocator: arena).cast(); + } + + final err = bindings.psr_database_update_set_strings( + _ptr, + collection.toNativeUtf8(allocator: arena).cast(), + attribute.toNativeUtf8(allocator: arena).cast(), + id, + nativePtrs, + values.length, + ); + + if (err != psr_error_t.PSR_OK) { + throw DatabaseException.fromError(err, "Failed to update set strings '$collection.$attribute' for id $id"); + } + } finally { + arena.releaseAll(); + } + } + /// Closes the database and frees native resources. void close() { if (_isClosed) return; diff --git a/bindings/dart/test/update_test.dart b/bindings/dart/test/update_test.dart index 0743765..76e289a 100644 --- a/bindings/dart/test/update_test.dart +++ b/bindings/dart/test/update_test.dart @@ -274,4 +274,565 @@ void main() { } }); }); + + // ========================================================================== + // Scalar update functions tests + // ========================================================================== + + group('Update Scalar Integer', () { + test('basic update', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 42}); + + db.updateScalarInteger('Configuration', 'integer_attribute', 1, 100); + + final value = db.readScalarIntegerById('Configuration', 'integer_attribute', 1); + expect(value, equals(100)); + } finally { + db.close(); + } + }); + + test('update to zero', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 42}); + + db.updateScalarInteger('Configuration', 'integer_attribute', 1, 0); + + final value = db.readScalarIntegerById('Configuration', 'integer_attribute', 1); + expect(value, equals(0)); + } finally { + db.close(); + } + }); + + test('update to negative', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 42}); + + db.updateScalarInteger('Configuration', 'integer_attribute', 1, -999); + + final value = db.readScalarIntegerById('Configuration', 'integer_attribute', 1); + expect(value, equals(-999)); + } finally { + db.close(); + } + }); + + test('other elements unchanged', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 42}); + db.createElement('Configuration', {'label': 'Config 2', 'integer_attribute': 100}); + + db.updateScalarInteger('Configuration', 'integer_attribute', 1, 999); + + expect(db.readScalarIntegerById('Configuration', 'integer_attribute', 1), equals(999)); + expect(db.readScalarIntegerById('Configuration', 'integer_attribute', 2), equals(100)); + } finally { + db.close(); + } + }); + + test('throws on invalid collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + expect( + () => db.updateScalarInteger('NonexistentCollection', 'integer_attribute', 1, 42), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + + test('throws on invalid attribute', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'integer_attribute': 42}); + expect( + () => db.updateScalarInteger('Configuration', 'nonexistent_attribute', 1, 100), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Update Scalar Double', () { + test('basic update', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'float_attribute': 3.14}); + + db.updateScalarDouble('Configuration', 'float_attribute', 1, 2.71); + + final value = db.readScalarDoubleById('Configuration', 'float_attribute', 1); + expect(value, equals(2.71)); + } finally { + db.close(); + } + }); + + test('update to zero', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'float_attribute': 3.14}); + + db.updateScalarDouble('Configuration', 'float_attribute', 1, 0.0); + + final value = db.readScalarDoubleById('Configuration', 'float_attribute', 1); + expect(value, equals(0.0)); + } finally { + db.close(); + } + }); + + test('precision test', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'float_attribute': 1.0}); + + db.updateScalarDouble('Configuration', 'float_attribute', 1, 1.23456789012345); + + final value = db.readScalarDoubleById('Configuration', 'float_attribute', 1); + expect(value, closeTo(1.23456789012345, 1e-10)); + } finally { + db.close(); + } + }); + }); + + group('Update Scalar String', () { + test('basic update', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'string_attribute': 'hello'}); + + db.updateScalarString('Configuration', 'string_attribute', 1, 'world'); + + final value = db.readScalarStringById('Configuration', 'string_attribute', 1); + expect(value, equals('world')); + } finally { + db.close(); + } + }); + + test('update to empty string', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'string_attribute': 'hello'}); + + db.updateScalarString('Configuration', 'string_attribute', 1, ''); + + final value = db.readScalarStringById('Configuration', 'string_attribute', 1); + expect(value, equals('')); + } finally { + db.close(); + } + }); + + test('unicode support', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'basic.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Config 1', 'string_attribute': 'hello'}); + + db.updateScalarString('Configuration', 'string_attribute', 1, '日本語テスト'); + + final value = db.readScalarStringById('Configuration', 'string_attribute', 1); + expect(value, equals('日本語テスト')); + } finally { + db.close(); + } + }); + }); + + // ========================================================================== + // Vector update functions tests + // ========================================================================== + + group('Update Vector Integers', () { + test('replace existing vector', () { + 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.updateVectorIntegers('Collection', 'value_int', 1, [10, 20, 30, 40]); + + final values = db.readVectorIntegersById('Collection', 'value_int', 1); + expect(values, equals([10, 20, 30, 40])); + } finally { + db.close(); + } + }); + + test('update to smaller vector', () { + 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.updateVectorIntegers('Collection', 'value_int', 1, [100]); + + final values = db.readVectorIntegersById('Collection', 'value_int', 1); + expect(values, equals([100])); + } finally { + db.close(); + } + }); + + test('update to empty vector', () { + 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.updateVectorIntegers('Collection', 'value_int', 1, []); + + final values = db.readVectorIntegersById('Collection', 'value_int', 1); + expect(values, isEmpty); + } finally { + db.close(); + } + }); + + test('update from empty to non-empty', () { + 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'}); + + // Verify initially empty + expect(db.readVectorIntegersById('Collection', 'value_int', 1), isEmpty); + + db.updateVectorIntegers('Collection', 'value_int', 1, [1, 2, 3]); + + final values = db.readVectorIntegersById('Collection', 'value_int', 1); + expect(values, equals([1, 2, 3])); + } finally { + db.close(); + } + }); + + test('other elements unchanged', () { + 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] + }); + + db.updateVectorIntegers('Collection', 'value_int', 1, [100, 200]); + + expect(db.readVectorIntegersById('Collection', 'value_int', 1), equals([100, 200])); + expect(db.readVectorIntegersById('Collection', 'value_int', 2), equals([10, 20])); + } finally { + db.close(); + } + }); + + test('throws on invalid collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + expect( + () => db.updateVectorIntegers('NonexistentCollection', 'value_int', 1, [1, 2, 3]), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); + + group('Update Vector Doubles', () { + test('replace existing vector', () { + 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.updateVectorDoubles('Collection', 'value_float', 1, [10.5, 20.5]); + + final values = db.readVectorDoublesById('Collection', 'value_float', 1); + expect(values, equals([10.5, 20.5])); + } finally { + db.close(); + } + }); + + test('precision test', () { + 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.0] + }); + + db.updateVectorDoubles('Collection', 'value_float', 1, [1.23456789, 9.87654321]); + + final values = db.readVectorDoublesById('Collection', 'value_float', 1); + expect(values[0], closeTo(1.23456789, 1e-8)); + expect(values[1], closeTo(9.87654321, 1e-8)); + } finally { + db.close(); + } + }); + + test('update to empty vector', () { + 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.updateVectorDoubles('Collection', 'value_float', 1, []); + + final values = db.readVectorDoublesById('Collection', 'value_float', 1); + expect(values, isEmpty); + } finally { + db.close(); + } + }); + }); + + // ========================================================================== + // Set update functions tests + // ========================================================================== + + group('Update Set Strings', () { + test('replace existing set', () { + 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', + 'tag': ['important', 'urgent'] + }); + + db.updateSetStrings('Collection', 'tag', 1, ['new_tag1', 'new_tag2', 'new_tag3']); + + final values = db.readSetStringsById('Collection', 'tag', 1); + expect(values.toSet(), equals({'new_tag1', 'new_tag2', 'new_tag3'})); + } finally { + db.close(); + } + }); + + test('update to single element', () { + 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', + 'tag': ['important', 'urgent'] + }); + + db.updateSetStrings('Collection', 'tag', 1, ['single_tag']); + + final values = db.readSetStringsById('Collection', 'tag', 1); + expect(values, equals(['single_tag'])); + } finally { + db.close(); + } + }); + + test('update to empty set', () { + 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', + 'tag': ['important', 'urgent'] + }); + + db.updateSetStrings('Collection', 'tag', 1, []); + + final values = db.readSetStringsById('Collection', 'tag', 1); + expect(values, isEmpty); + } finally { + db.close(); + } + }); + + test('update from empty to non-empty', () { + 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'}); + + // Verify initially empty + expect(db.readSetStringsById('Collection', 'tag', 1), isEmpty); + + db.updateSetStrings('Collection', 'tag', 1, ['important', 'urgent']); + + final values = db.readSetStringsById('Collection', 'tag', 1); + expect(values.toSet(), equals({'important', 'urgent'})); + } finally { + db.close(); + } + }); + + test('other elements unchanged', () { + 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', + 'tag': ['important'] + }); + db.createElement('Collection', { + 'label': 'Item 2', + 'tag': ['urgent', 'review'] + }); + + db.updateSetStrings('Collection', 'tag', 1, ['updated']); + + expect(db.readSetStringsById('Collection', 'tag', 1), equals(['updated'])); + expect(db.readSetStringsById('Collection', 'tag', 2).toSet(), equals({'urgent', 'review'})); + } finally { + db.close(); + } + }); + + test('unicode support', () { + 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', + 'tag': ['tag1'] + }); + + db.updateSetStrings('Collection', 'tag', 1, ['日本語', '中文', '한국어']); + + final values = db.readSetStringsById('Collection', 'tag', 1); + expect(values.toSet(), equals({'日本語', '中文', '한국어'})); + } finally { + db.close(); + } + }); + + test('throws on invalid collection', () { + final db = Database.fromSchema( + ':memory:', + path.join(testsPath, 'schemas', 'valid', 'collections.sql'), + ); + try { + db.createElement('Configuration', {'label': 'Test Config'}); + expect( + () => db.updateSetStrings('NonexistentCollection', 'tag', 1, ['tag1']), + throwsA(isA()), + ); + } finally { + db.close(); + } + }); + }); } diff --git a/bindings/julia/src/database.jl b/bindings/julia/src/database.jl index 08fea7a..f84864d 100644 --- a/bindings/julia/src/database.jl +++ b/bindings/julia/src/database.jl @@ -1,5 +1,11 @@ -struct Database +mutable struct Database ptr::Ptr{C.psr_database} + + function Database(ptr::Ptr{C.psr_database}) + db = new(ptr) + finalizer(d -> d.ptr != C_NULL && C.psr_database_close(d.ptr), db) + return db + end end function from_schema(db_path, schema_path) @@ -35,13 +41,16 @@ function create_element!(db::Database, collection::String; kwargs...) try create_element!(db, collection, e) finally - C.psr_element_destroy(e.ptr) + destroy!(e) end return nothing end function close!(db::Database) - C.psr_database_close(db.ptr) + if db.ptr != C_NULL + C.psr_database_close(db.ptr) + db.ptr = C_NULL + end return nothing end @@ -545,7 +554,136 @@ function update_element!(db::Database, collection::String, id::Int64; kwargs...) try update_element!(db, collection, id, e) finally - C.psr_element_destroy(e.ptr) + destroy!(e) + end + return nothing +end + +# Update scalar attribute functions + +function update_scalar_integer!(db::Database, collection::String, attribute::String, id::Int64, value::Integer) + err = C.psr_database_update_scalar_integer(db.ptr, collection, attribute, id, Int64(value)) + if err != C.PSR_OK + throw(DatabaseException("Failed to update scalar integer '$collection.$attribute' for id $id")) + end + return nothing +end + +function update_scalar_double!(db::Database, collection::String, attribute::String, id::Int64, value::Real) + err = C.psr_database_update_scalar_double(db.ptr, collection, attribute, id, Float64(value)) + if err != C.PSR_OK + throw(DatabaseException("Failed to update scalar double '$collection.$attribute' for id $id")) + end + return nothing +end + +function update_scalar_string!(db::Database, collection::String, attribute::String, id::Int64, value::String) + err = C.psr_database_update_scalar_string(db.ptr, collection, attribute, id, value) + if err != C.PSR_OK + throw(DatabaseException("Failed to update scalar string '$collection.$attribute' for id $id")) + end + return nothing +end + +# Update vector attribute functions + +function update_vector_integers!( + db::Database, + collection::String, + attribute::String, + id::Int64, + values::Vector{<:Integer}, +) + int_values = Int64[Int64(v) for v in values] + err = C.psr_database_update_vector_integers( + db.ptr, + collection, + attribute, + id, + int_values, + Csize_t(length(int_values)), + ) + if err != C.PSR_OK + throw(DatabaseException("Failed to update vector integers '$collection.$attribute' for id $id")) + end + return nothing +end + +function update_vector_doubles!(db::Database, collection::String, attribute::String, id::Int64, values::Vector{<:Real}) + float_values = Float64[Float64(v) for v in values] + err = C.psr_database_update_vector_doubles( + db.ptr, + collection, + attribute, + id, + float_values, + Csize_t(length(float_values)), + ) + if err != C.PSR_OK + throw(DatabaseException("Failed to update vector doubles '$collection.$attribute' for id $id")) + end + return nothing +end + +function update_vector_strings!( + db::Database, + collection::String, + attribute::String, + id::Int64, + values::Vector{<:AbstractString}, +) + cstrings = [Base.cconvert(Cstring, s) for s in values] + ptrs = [Base.unsafe_convert(Cstring, cs) for cs in cstrings] + GC.@preserve cstrings begin + err = C.psr_database_update_vector_strings(db.ptr, collection, attribute, id, ptrs, Csize_t(length(values))) + end + if err != C.PSR_OK + throw(DatabaseException("Failed to update vector strings '$collection.$attribute' for id $id")) + end + return nothing +end + +# Update set attribute functions + +function update_set_integers!(db::Database, collection::String, attribute::String, id::Int64, values::Vector{<:Integer}) + int_values = Int64[Int64(v) for v in values] + err = C.psr_database_update_set_integers(db.ptr, collection, attribute, id, int_values, Csize_t(length(int_values))) + if err != C.PSR_OK + throw(DatabaseException("Failed to update set integers '$collection.$attribute' for id $id")) + end + return nothing +end + +function update_set_doubles!(db::Database, collection::String, attribute::String, id::Int64, values::Vector{<:Real}) + float_values = Float64[Float64(v) for v in values] + err = C.psr_database_update_set_doubles( + db.ptr, + collection, + attribute, + id, + float_values, + Csize_t(length(float_values)), + ) + if err != C.PSR_OK + throw(DatabaseException("Failed to update set doubles '$collection.$attribute' for id $id")) + end + return nothing +end + +function update_set_strings!( + db::Database, + collection::String, + attribute::String, + id::Int64, + values::Vector{<:AbstractString}, +) + cstrings = [Base.cconvert(Cstring, s) for s in values] + ptrs = [Base.unsafe_convert(Cstring, cs) for cs in cstrings] + GC.@preserve cstrings begin + err = C.psr_database_update_set_strings(db.ptr, collection, attribute, id, ptrs, Csize_t(length(values))) + end + if err != C.PSR_OK + throw(DatabaseException("Failed to update set strings '$collection.$attribute' for id $id")) end return nothing end diff --git a/bindings/julia/src/element.jl b/bindings/julia/src/element.jl index d8e4931..2d5ed57 100644 --- a/bindings/julia/src/element.jl +++ b/bindings/julia/src/element.jl @@ -1,4 +1,4 @@ -struct Element +mutable struct Element ptr::Ptr{C.psr_element} function Element() @@ -6,12 +6,18 @@ struct Element if ptr == C_NULL error("Failed to create Element") end - obj = new(ptr) - # finalizer(C.psr_element_destroy, obj) - return obj + return new(ptr) end end +function destroy!(el::Element) + if el.ptr != C_NULL + C.psr_element_destroy(el.ptr) + el.ptr = C_NULL + end + return nothing +end + function Base.setindex!(el::Element, value::Integer, name::String) cname = Base.cconvert(Cstring, name) err = C.psr_element_set_integer(el.ptr, cname, Int64(value)) diff --git a/bindings/julia/src/lua_runner.jl b/bindings/julia/src/lua_runner.jl index 758b925..d57248c 100644 --- a/bindings/julia/src/lua_runner.jl +++ b/bindings/julia/src/lua_runner.jl @@ -1,4 +1,4 @@ -struct LuaRunner +mutable struct LuaRunner ptr::Ptr{C.psr_lua_runner} db::Database # Keep reference to prevent GC end @@ -8,7 +8,9 @@ function LuaRunner(db::Database) if ptr == C_NULL throw(DatabaseException("Failed to create LuaRunner")) end - return LuaRunner(ptr, db) + runner = LuaRunner(ptr, db) + finalizer(r -> r.ptr != C_NULL && C.psr_lua_runner_free(r.ptr), runner) + return runner end function run!(runner::LuaRunner, script::String) @@ -26,6 +28,9 @@ function run!(runner::LuaRunner, script::String) end function close!(runner::LuaRunner) - C.psr_lua_runner_free(runner.ptr) + if runner.ptr != C_NULL + C.psr_lua_runner_free(runner.ptr) + runner.ptr = C_NULL + end return nothing end diff --git a/bindings/julia/test/test_create.jl b/bindings/julia/test/test_create.jl index 5992c2e..6ea9a14 100644 --- a/bindings/julia/test/test_create.jl +++ b/bindings/julia/test/test_create.jl @@ -5,216 +5,222 @@ using Test include("fixture.jl") -@testset "Create Scalar Attributes" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - # Create Configuration with various scalar types - PSRDatabase.create_element!(db, "Configuration"; - label = "Test Config", - integer_attribute = 42, - float_attribute = 3.14, - string_attribute = "hello", - date_attribute = "2024-01-01", - boolean_attribute = 1, - ) - - # Test default value is used - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2") - - PSRDatabase.close!(db) -end +@testset "Create" begin + @testset "Scalar Attributes" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + # Create Configuration with various scalar types + PSRDatabase.create_element!(db, "Configuration"; + label = "Test Config", + integer_attribute = 42, + float_attribute = 3.14, + string_attribute = "hello", + date_attribute = "2024-01-01", + boolean_attribute = 1, + ) + + # Test default value is used + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2") -@testset "Create Collections with Vectors" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - - # Create element with scalar and vector attributes - PSRDatabase.create_element!(db, "Collection"; - label = "Item 1", - some_integer = 10, - some_float = 1.5, - value_int = [1, 2, 3], - value_float = [0.1, 0.2, 0.3], - ) - - # Create element with only scalars - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 20) - - # Reject empty vector - @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!( - db, - "Collection"; - label = "Item 3", - value_int = Int64[], - ) - - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Create Collections with Sets" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Collections with Vectors" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - # Create element with set attribute - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["alpha", "beta", "gamma"]) + # Create element with scalar and vector attributes + PSRDatabase.create_element!(db, "Collection"; + label = "Item 1", + some_integer = 10, + some_float = 1.5, + value_int = [1, 2, 3], + value_float = [0.1, 0.2, 0.3], + ) - PSRDatabase.close!(db) -end + # Create element with only scalars + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 20) -@testset "Create Relations" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Reject empty vector + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!( + db, + "Collection"; + label = "Item 3", + value_int = Int64[], + ) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.close!(db) + end - # Create parent elements - PSRDatabase.create_element!(db, "Parent"; label = "Parent 1") - PSRDatabase.create_element!(db, "Parent"; label = "Parent 2") + @testset "Collections with Sets" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Create child with FK to parent - PSRDatabase.create_element!(db, "Child"; label = "Child 1", parent_id = 1) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - # Create child with self-reference - PSRDatabase.create_element!(db, "Child"; label = "Child 2", sibling_id = 1) + # Create element with set attribute + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["alpha", "beta", "gamma"]) - # Create child with vector containing FK refs - PSRDatabase.create_element!(db, "Child"; label = "Child 3", parent_ref = [1, 2]) + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Relations" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Reject Invalid Element" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - # Missing required label - @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!(db, "Configuration") - - # Wrong type for attribute - @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!( - db, - "Configuration"; - label = "Test", - integer_attribute = "not an int", - ) - - # Unknown attribute - @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!( - db, - "Configuration"; - label = "Test", - unknown_attr = 123, - ) - - PSRDatabase.close!(db) -end + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") -# Edge case tests + # Create parent elements + PSRDatabase.create_element!(db, "Parent"; label = "Parent 1") + PSRDatabase.create_element!(db, "Parent"; label = "Parent 2") -@testset "Create Single Element Vector" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Create child with FK to parent + PSRDatabase.create_element!(db, "Child"; label = "Child 1", parent_id = 1) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + # Create child with self-reference + PSRDatabase.create_element!(db, "Child"; label = "Child 2", sibling_id = 1) - # Create element with single element vector - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [42]) + # Create child with vector containing FK refs + PSRDatabase.create_element!(db, "Child"; label = "Child 3", parent_ref = [1, 2]) - result = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) - @test length(result) == 1 - @test result[1] == 42 + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Reject Invalid Element" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Create Large Vector" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Missing required label + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!(db, "Configuration") - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + # Wrong type for attribute + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!( + db, + "Configuration"; + label = "Test", + integer_attribute = "not an int", + ) - # Create element with 100+ element vector - large_vector = collect(1:150) - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = large_vector) + # Unknown attribute + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!( + db, + "Configuration"; + label = "Test", + unknown_attr = 123, + ) - result = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) - @test length(result) == 150 - @test result == large_vector + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + # Edge case tests -@testset "Create Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Single Element Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!(db, "NonexistentCollection"; label = "Test") + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.close!(db) -end + # Create element with single element vector + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [42]) -@testset "Create With Only Required Label" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + result = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test length(result) == 1 + @test result[1] == 42 - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.close!(db) + end - # Create with only required label, no optional attributes - PSRDatabase.create_element!(db, "Collection"; label = "Minimal Item") + @testset "Large Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - labels = PSRDatabase.read_scalar_strings(db, "Collection", "label") - @test length(labels) == 1 - @test labels[1] == "Minimal Item" + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.close!(db) -end + # Create element with 100+ element vector + large_vector = collect(1:150) + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = large_vector) -@testset "Create With Multiple Sets" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + result = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test length(result) == 150 + @test result == large_vector - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.close!(db) + end - # Create element with set attribute containing multiple values - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["a", "b", "c", "d", "e"]) + @testset "Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - result = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) - @test length(result) == 5 - @test sort(result) == ["a", "b", "c", "d", "e"] + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!( + db, + "NonexistentCollection"; + label = "Test", + ) - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Create Duplicate Label Rejected" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Only Required Label" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - # Trying to create with same label should fail - @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + # Create with only required label, no optional attributes + PSRDatabase.create_element!(db, "Collection"; label = "Minimal Item") - PSRDatabase.close!(db) -end + labels = PSRDatabase.read_scalar_strings(db, "Collection", "label") + @test length(labels) == 1 + @test labels[1] == "Minimal Item" + + PSRDatabase.close!(db) + end + + @testset "Multiple Sets" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create element with set attribute containing multiple values + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["a", "b", "c", "d", "e"]) + + result = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test length(result) == 5 + @test sort(result) == ["a", "b", "c", "d", "e"] + + PSRDatabase.close!(db) + end + + @testset "Duplicate Label Rejected" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + + # Trying to create with same label should fail + @test_throws PSRDatabase.DatabaseException PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + + PSRDatabase.close!(db) + end -@testset "Create With Float Vector" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Float Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_float = [1.1, 2.2, 3.3]) + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_float = [1.1, 2.2, 3.3]) - result = PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", Int64(1)) - @test length(result) == 3 - @test result == [1.1, 2.2, 3.3] + result = PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", Int64(1)) + @test length(result) == 3 + @test result == [1.1, 2.2, 3.3] - PSRDatabase.close!(db) + PSRDatabase.close!(db) + end end end diff --git a/bindings/julia/test/test_delete.jl b/bindings/julia/test/test_delete.jl index 2e09eda..5f6886f 100644 --- a/bindings/julia/test/test_delete.jl +++ b/bindings/julia/test/test_delete.jl @@ -5,145 +5,147 @@ using Test include("fixture.jl") -@testset "Delete Element By ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - # Create elements - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2") - PSRDatabase.create_element!(db, "Configuration"; label = "Config 3") - - # Verify elements exist - ids = PSRDatabase.read_element_ids(db, "Configuration") - @test length(ids) == 3 - - # Delete element with id 2 - PSRDatabase.delete_element_by_id!(db, "Configuration", Int64(2)) - - # Verify element is deleted - ids = PSRDatabase.read_element_ids(db, "Configuration") - @test length(ids) == 2 - @test 2 ∉ ids - @test 1 ∈ ids - @test 3 ∈ ids - - PSRDatabase.close!(db) -end - -@testset "Delete Element With Vector Data (CASCADE)" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - - # Create element with vector data - PSRDatabase.create_element!(db, "Collection"; - label = "Item 1", - some_integer = 10, - value_int = [1, 2, 3], - value_float = [0.1, 0.2, 0.3], - ) - - PSRDatabase.create_element!(db, "Collection"; - label = "Item 2", - some_integer = 20, - value_int = [4, 5, 6], - ) - - # Verify elements exist - ids = PSRDatabase.read_element_ids(db, "Collection") - @test length(ids) == 2 - - # Delete element with id 1 (CASCADE should delete vector data) - PSRDatabase.delete_element_by_id!(db, "Collection", Int64(1)) - - # Verify element is deleted - ids = PSRDatabase.read_element_ids(db, "Collection") - @test length(ids) == 1 - @test ids[1] == 2 - - # Verify remaining element's data is intact - values = PSRDatabase.read_vector_integers(db, "Collection", "value_int") - @test length(values) == 1 - @test values[1] == [4, 5, 6] - - PSRDatabase.close!(db) -end - -@testset "Delete Element With Set Data (CASCADE)" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - - # Create elements with set data - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["alpha", "beta"]) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", tag = ["gamma", "delta"]) - - # Verify elements exist - ids = PSRDatabase.read_element_ids(db, "Collection") - @test length(ids) == 2 - - # Delete element with id 1 (CASCADE should delete set data) - PSRDatabase.delete_element_by_id!(db, "Collection", Int64(1)) - - # Verify element is deleted - ids = PSRDatabase.read_element_ids(db, "Collection") - @test length(ids) == 1 - @test ids[1] == 2 - - # Verify remaining element's set data is intact - sets = PSRDatabase.read_set_strings(db, "Collection", "tag") - @test length(sets) == 1 - @test sort(sets[1]) == ["delta", "gamma"] - - PSRDatabase.close!(db) -end - -@testset "Delete Non-Existent Element (Idempotent)" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") - - # Delete non-existent element should succeed (idempotent) - PSRDatabase.delete_element_by_id!(db, "Configuration", Int64(999)) - - # Verify original element still exists - ids = PSRDatabase.read_element_ids(db, "Configuration") - @test length(ids) == 1 - @test ids[1] == 1 - - PSRDatabase.close!(db) -end - -@testset "Delete Does Not Affect Other Elements" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - # Create multiple elements with different values - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 3", integer_attribute = 300) +@testset "Delete" begin + @testset "Element By ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + # Create elements + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2") + PSRDatabase.create_element!(db, "Configuration"; label = "Config 3") + + # Verify elements exist + ids = PSRDatabase.read_element_ids(db, "Configuration") + @test length(ids) == 3 - # Delete middle element - PSRDatabase.delete_element_by_id!(db, "Configuration", Int64(2)) + # Delete element with id 2 + PSRDatabase.delete_element_by_id!(db, "Configuration", Int64(2)) + + # Verify element is deleted + ids = PSRDatabase.read_element_ids(db, "Configuration") + @test length(ids) == 2 + @test 2 ∉ ids + @test 1 ∈ ids + @test 3 ∈ ids + + PSRDatabase.close!(db) + end - # Verify other elements are unchanged - labels = PSRDatabase.read_scalar_strings(db, "Configuration", "label") - @test length(labels) == 2 - @test "Config 1" ∈ labels - @test "Config 3" ∈ labels - @test "Config 2" ∉ labels + @testset "Element With Vector Data (CASCADE)" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create element with vector data + PSRDatabase.create_element!(db, "Collection"; + label = "Item 1", + some_integer = 10, + value_int = [1, 2, 3], + value_float = [0.1, 0.2, 0.3], + ) + + PSRDatabase.create_element!(db, "Collection"; + label = "Item 2", + some_integer = 20, + value_int = [4, 5, 6], + ) + + # Verify elements exist + ids = PSRDatabase.read_element_ids(db, "Collection") + @test length(ids) == 2 + + # Delete element with id 1 (CASCADE should delete vector data) + PSRDatabase.delete_element_by_id!(db, "Collection", Int64(1)) + + # Verify element is deleted + ids = PSRDatabase.read_element_ids(db, "Collection") + @test length(ids) == 1 + @test ids[1] == 2 + + # Verify remaining element's data is intact + values = PSRDatabase.read_vector_integers(db, "Collection", "value_int") + @test length(values) == 1 + @test values[1] == [4, 5, 6] + + PSRDatabase.close!(db) + end + + @testset "Element With Set Data (CASCADE)" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # Create elements with set data + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["alpha", "beta"]) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", tag = ["gamma", "delta"]) + + # Verify elements exist + ids = PSRDatabase.read_element_ids(db, "Collection") + @test length(ids) == 2 + + # Delete element with id 1 (CASCADE should delete set data) + PSRDatabase.delete_element_by_id!(db, "Collection", Int64(1)) + + # Verify element is deleted + ids = PSRDatabase.read_element_ids(db, "Collection") + @test length(ids) == 1 + @test ids[1] == 2 + + # Verify remaining element's set data is intact + sets = PSRDatabase.read_set_strings(db, "Collection", "tag") + @test length(sets) == 1 + @test sort(sets[1]) == ["delta", "gamma"] + + PSRDatabase.close!(db) + end + + @testset "Non-Existent Element (Idempotent)" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1") + + # Delete non-existent element should succeed (idempotent) + PSRDatabase.delete_element_by_id!(db, "Configuration", Int64(999)) + + # Verify original element still exists + ids = PSRDatabase.read_element_ids(db, "Configuration") + @test length(ids) == 1 + @test ids[1] == 1 + + PSRDatabase.close!(db) + end + + @testset "Does Not Affect Other Elements" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + # Create multiple elements with different values + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 3", integer_attribute = 300) - values = PSRDatabase.read_scalar_integers(db, "Configuration", "integer_attribute") - @test length(values) == 2 - @test 100 ∈ values - @test 300 ∈ values - @test 200 ∉ values + # Delete middle element + PSRDatabase.delete_element_by_id!(db, "Configuration", Int64(2)) + + # Verify other elements are unchanged + labels = PSRDatabase.read_scalar_strings(db, "Configuration", "label") + @test length(labels) == 2 + @test "Config 1" ∈ labels + @test "Config 3" ∈ labels + @test "Config 2" ∉ labels + + values = PSRDatabase.read_scalar_integers(db, "Configuration", "integer_attribute") + @test length(values) == 2 + @test 100 ∈ values + @test 300 ∈ values + @test 200 ∉ values - PSRDatabase.close!(db) + PSRDatabase.close!(db) + end end end diff --git a/bindings/julia/test/test_invalid_database.jl b/bindings/julia/test/test_invalid_database.jl new file mode 100644 index 0000000..14e795b --- /dev/null +++ b/bindings/julia/test/test_invalid_database.jl @@ -0,0 +1,55 @@ +module TestValidDatabaseDefinitions + +using PSRDatabase +using Test + +include("fixture.jl") + +@testset "Invalid Schema" begin + @testset "No Configuration" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "no_configuration.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "Label Not Null" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "label_not_null.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "Label Not Unique" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "label_not_unique.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "Label Wrong Type" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "label_wrong_type.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "Duplicate Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "duplicate_attribute.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "Vector No Index" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "vector_no_index.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "Set No Unique" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "set_no_unique.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "FK Not Null Set Null" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "fk_not_null_set_null.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end + + @testset "FK Actions" begin + path_schema = joinpath(tests_path(), "schemas", "invalid", "fk_actions.sql") + @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) + end +end + +end diff --git a/bindings/julia/test/test_lua_runner.jl b/bindings/julia/test/test_lua_runner.jl index 8c85882..b87a53b 100644 --- a/bindings/julia/test/test_lua_runner.jl +++ b/bindings/julia/test/test_lua_runner.jl @@ -5,241 +5,243 @@ using Test include("fixture.jl") -@testset "LuaRunner Create Element" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - lua = PSRDatabase.LuaRunner(db) - - PSRDatabase.run!( - lua, - """ - db:create_element("Configuration", { label = "Test Config" }) - db:create_element("Collection", { label = "Item 1", some_integer = 42 }) -""", - ) - - labels = PSRDatabase.read_scalar_strings(db, "Collection", "label") - @test length(labels) == 1 - @test labels[1] == "Item 1" - - integers = PSRDatabase.read_scalar_integers(db, "Collection", "some_integer") - @test length(integers) == 1 - @test integers[1] == 42 - - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end - -@testset "LuaRunner Read from Lua" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_integer = 10) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 20) - - lua = PSRDatabase.LuaRunner(db) - - PSRDatabase.run!( - lua, - """ - local labels = db:read_scalar_strings("Collection", "label") - assert(#labels == 2, "Expected 2 labels") - assert(labels[1] == "Item 1", "First label mismatch") - assert(labels[2] == "Item 2", "Second label mismatch") -""", - ) - - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end - -@testset "LuaRunner Script Error" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - lua = PSRDatabase.LuaRunner(db) - - @test_throws PSRDatabase.DatabaseException PSRDatabase.run!(lua, "invalid syntax !!!") +@testset "LuaRunner" begin + @testset "Create Element" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + lua = PSRDatabase.LuaRunner(db) -@testset "LuaRunner Reuse Runner" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.run!( + lua, + """ + db:create_element("Configuration", { label = "Test Config" }) + db:create_element("Collection", { label = "Item 1", some_integer = 42 }) + """, + ) - lua = PSRDatabase.LuaRunner(db) + labels = PSRDatabase.read_scalar_strings(db, "Collection", "label") + @test length(labels) == 1 + @test labels[1] == "Item 1" - PSRDatabase.run!(lua, """db:create_element("Configuration", { label = "Config" })""") - PSRDatabase.run!(lua, """db:create_element("Collection", { label = "Item 1" })""") - PSRDatabase.run!(lua, """db:create_element("Collection", { label = "Item 2" })""") + integers = PSRDatabase.read_scalar_integers(db, "Collection", "some_integer") + @test length(integers) == 1 + @test integers[1] == 42 - labels = PSRDatabase.read_scalar_strings(db, "Collection", "label") - @test length(labels) == 2 - @test labels[1] == "Item 1" - @test labels[2] == "Item 2" + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + @testset "Read from Lua" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -# Error handling tests + PSRDatabase.create_element!(db, "Configuration"; label = "Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_integer = 10) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 20) -@testset "LuaRunner Undefined Variable" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + lua = PSRDatabase.LuaRunner(db) - lua = PSRDatabase.LuaRunner(db) + PSRDatabase.run!( + lua, + """ + local labels = db:read_scalar_strings("Collection", "label") + assert(#labels == 2, "Expected 2 labels") + assert(labels[1] == "Item 1", "First label mismatch") + assert(labels[2] == "Item 2", "Second label mismatch") + """, + ) - # Script that references undefined variable - @test_throws PSRDatabase.DatabaseException PSRDatabase.run!(lua, "print(undefined_variable.field)") + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + @testset "Script Error" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "LuaRunner Create Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + lua = PSRDatabase.LuaRunner(db) - lua = PSRDatabase.LuaRunner(db) + @test_throws PSRDatabase.DatabaseException PSRDatabase.run!(lua, "invalid syntax !!!") - PSRDatabase.run!(lua, """db:create_element("Configuration", { label = "Test Config" })""") + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end - # Script that creates element in nonexistent collection - @test_throws PSRDatabase.DatabaseException PSRDatabase.run!( - lua, - """db:create_element("NonexistentCollection", { label = "Item" })""", - ) - - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + @testset "Reuse Runner" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "LuaRunner Empty Script" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + lua = PSRDatabase.LuaRunner(db) - lua = PSRDatabase.LuaRunner(db) + PSRDatabase.run!(lua, """db:create_element("Configuration", { label = "Config" })""") + PSRDatabase.run!(lua, """db:create_element("Collection", { label = "Item 1" })""") + PSRDatabase.run!(lua, """db:create_element("Collection", { label = "Item 2" })""") - # Empty script should succeed without error - PSRDatabase.run!(lua, "") - @test true # If we get here, the empty script ran without error + labels = PSRDatabase.read_scalar_strings(db, "Collection", "label") + @test length(labels) == 2 + @test labels[1] == "Item 1" + @test labels[2] == "Item 2" - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end -@testset "LuaRunner Comment Only Script" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Error handling tests - lua = PSRDatabase.LuaRunner(db) + @testset "Undefined Variable" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Comment-only script should succeed - PSRDatabase.run!(lua, "-- this is just a comment") - @test true + lua = PSRDatabase.LuaRunner(db) - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + # Script that references undefined variable + @test_throws PSRDatabase.DatabaseException PSRDatabase.run!(lua, "print(undefined_variable.field)") -@testset "LuaRunner Read Integers" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end - PSRDatabase.create_element!(db, "Configuration"; label = "Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_integer = 100) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 200) + @testset "Create Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - lua = PSRDatabase.LuaRunner(db) + lua = PSRDatabase.LuaRunner(db) - PSRDatabase.run!( - lua, - """ - local ints = db:read_scalar_integers("Collection", "some_integer") - assert(#ints == 2, "Expected 2 integers") - assert(ints[1] == 100, "First integer mismatch") - assert(ints[2] == 200, "Second integer mismatch") -""", - ) + PSRDatabase.run!(lua, """db:create_element("Configuration", { label = "Test Config" })""") - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + # Script that creates element in nonexistent collection + @test_throws PSRDatabase.DatabaseException PSRDatabase.run!( + lua, + """db:create_element("NonexistentCollection", { label = "Item" })""", + ) + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end -@testset "LuaRunner Read Doubles" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Empty Script" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_float = 1.5) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_float = 2.5) + lua = PSRDatabase.LuaRunner(db) - lua = PSRDatabase.LuaRunner(db) + # Empty script should succeed without error + PSRDatabase.run!(lua, "") + @test true # If we get here, the empty script ran without error - PSRDatabase.run!( - lua, - """ - local floats = db:read_scalar_doubles("Collection", "some_float") - assert(#floats == 2, "Expected 2 doubles") - assert(floats[1] == 1.5, "First double mismatch") - assert(floats[2] == 2.5, "Second double mismatch") -""", - ) + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end + + @testset "Comment Only Script" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + lua = PSRDatabase.LuaRunner(db) - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + # Comment-only script should succeed + PSRDatabase.run!(lua, "-- this is just a comment") + @test true + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end -@testset "LuaRunner Read Vectors" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [1, 2, 3]) - - lua = PSRDatabase.LuaRunner(db) - - PSRDatabase.run!( - lua, - """ - local vectors = db:read_vector_integers("Collection", "value_int") - assert(#vectors == 1, "Expected 1 vector") - assert(#vectors[1] == 3, "Expected 3 elements in vector") - assert(vectors[1][1] == 1, "First element mismatch") - assert(vectors[1][2] == 2, "Second element mismatch") - assert(vectors[1][3] == 3, "Third element mismatch") -""", - ) - - PSRDatabase.close!(lua) - PSRDatabase.close!(db) -end + @testset "Read Integers" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "LuaRunner Create With Vector" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_integer = 100) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 200) - lua = PSRDatabase.LuaRunner(db) + lua = PSRDatabase.LuaRunner(db) - PSRDatabase.run!( - lua, - """ - db:create_element("Configuration", { label = "Config" }) - db:create_element("Collection", { label = "Item 1", value_int = {10, 20, 30} }) -""", - ) + PSRDatabase.run!( + lua, + """ + local ints = db:read_scalar_integers("Collection", "some_integer") + assert(#ints == 2, "Expected 2 integers") + assert(ints[1] == 100, "First integer mismatch") + assert(ints[2] == 200, "Second integer mismatch") + """, + ) - result = PSRDatabase.read_vector_integers(db, "Collection", "value_int") - @test length(result) == 1 - @test result[1] == [10, 20, 30] + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end - PSRDatabase.close!(lua) - PSRDatabase.close!(db) + @testset "Read Doubles" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_float = 1.5) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_float = 2.5) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!( + lua, + """ + local floats = db:read_scalar_doubles("Collection", "some_float") + assert(#floats == 2, "Expected 2 doubles") + assert(floats[1] == 1.5, "First double mismatch") + assert(floats[2] == 2.5, "Second double mismatch") + """, + ) + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end + + @testset "Read Vectors" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [1, 2, 3]) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!( + lua, + """ + local vectors = db:read_vector_integers("Collection", "value_int") + assert(#vectors == 1, "Expected 1 vector") + assert(#vectors[1] == 3, "Expected 3 elements in vector") + assert(vectors[1][1] == 1, "First element mismatch") + assert(vectors[1][2] == 2, "Second element mismatch") + assert(vectors[1][3] == 3, "Third element mismatch") + """, + ) + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end + + @testset "Create With Vector" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + lua = PSRDatabase.LuaRunner(db) + + PSRDatabase.run!( + lua, + """ + db:create_element("Configuration", { label = "Config" }) + db:create_element("Collection", { label = "Item 1", value_int = {10, 20, 30} }) + """, + ) + + result = PSRDatabase.read_vector_integers(db, "Collection", "value_int") + @test length(result) == 1 + @test result[1] == [10, 20, 30] + + PSRDatabase.close!(lua) + PSRDatabase.close!(db) + end end end diff --git a/bindings/julia/test/test_read.jl b/bindings/julia/test/test_read.jl index a437fcb..93b69fc 100644 --- a/bindings/julia/test/test_read.jl +++ b/bindings/julia/test/test_read.jl @@ -5,523 +5,547 @@ using Test include("fixture.jl") -@testset "Read Scalar Attributes" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; - label = "Config 1", - integer_attribute = 42, - float_attribute = 3.14, - string_attribute = "hello", - ) - PSRDatabase.create_element!(db, "Configuration"; - label = "Config 2", - integer_attribute = 100, - float_attribute = 2.71, - string_attribute = "world", - ) - - @test PSRDatabase.read_scalar_strings(db, "Configuration", "label") == ["Config 1", "Config 2"] - @test PSRDatabase.read_scalar_integers(db, "Configuration", "integer_attribute") == [42, 100] - @test PSRDatabase.read_scalar_doubles(db, "Configuration", "float_attribute") == [3.14, 2.71] - @test PSRDatabase.read_scalar_strings(db, "Configuration", "string_attribute") == ["hello", "world"] - - PSRDatabase.close!(db) -end - -@testset "Read From Collections" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_integer = 10, some_float = 1.5) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 20, some_float = 2.5) - - @test PSRDatabase.read_scalar_strings(db, "Collection", "label") == ["Item 1", "Item 2"] - @test PSRDatabase.read_scalar_integers(db, "Collection", "some_integer") == [10, 20] - @test PSRDatabase.read_scalar_doubles(db, "Collection", "some_float") == [1.5, 2.5] - - PSRDatabase.close!(db) -end - -@testset "Read Empty Result" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - - # No Collection elements created - @test PSRDatabase.read_scalar_strings(db, "Collection", "label") == String[] - @test PSRDatabase.read_scalar_integers(db, "Collection", "some_integer") == Int64[] - @test PSRDatabase.read_scalar_doubles(db, "Collection", "some_float") == Float64[] - - PSRDatabase.close!(db) -end - -@testset "Read Vector Attributes" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - 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_integers(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.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - - # No Collection elements created - @test PSRDatabase.read_vector_integers(db, "Collection", "value_int") == Vector{Int64}[] - @test PSRDatabase.read_vector_doubles(db, "Collection", "value_float") == Vector{Float64}[] - - PSRDatabase.close!(db) -end - -@testset "Read Vector Only Returns Elements With Data" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - 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 (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_integers(db, "Collection", "value_int") - @test length(result) == 2 - @test result[1] == [1, 2, 3] - @test result[2] == [4, 5] - - PSRDatabase.close!(db) -end - -@testset "Read Set Attributes" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important", "urgent"]) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", tag = ["review"]) - - result = PSRDatabase.read_set_strings(db, "Collection", "tag") - @test length(result) == 2 - # Sets are unordered, so sort before comparison - @test sort(result[1]) == ["important", "urgent"] - @test result[2] == ["review"] +@testset "Read" begin + @testset "Scalar Attributes" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; + label = "Config 1", + integer_attribute = 42, + float_attribute = 3.14, + string_attribute = "hello", + ) + PSRDatabase.create_element!(db, "Configuration"; + label = "Config 2", + integer_attribute = 100, + float_attribute = 2.71, + string_attribute = "world", + ) + + @test PSRDatabase.read_scalar_strings(db, "Configuration", "label") == ["Config 1", "Config 2"] + @test PSRDatabase.read_scalar_integers(db, "Configuration", "integer_attribute") == [42, 100] + @test PSRDatabase.read_scalar_doubles(db, "Configuration", "float_attribute") == [3.14, 2.71] + @test PSRDatabase.read_scalar_strings(db, "Configuration", "string_attribute") == ["hello", "world"] + + PSRDatabase.close!(db) + end + + @testset "Collections" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", some_integer = 10, some_float = 1.5) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", some_integer = 20, some_float = 2.5) + + @test PSRDatabase.read_scalar_strings(db, "Collection", "label") == ["Item 1", "Item 2"] + @test PSRDatabase.read_scalar_integers(db, "Collection", "some_integer") == [10, 20] + @test PSRDatabase.read_scalar_doubles(db, "Collection", "some_float") == [1.5, 2.5] + + PSRDatabase.close!(db) + end + + @testset "Empty Result" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # No Collection elements created + @test PSRDatabase.read_scalar_strings(db, "Collection", "label") == String[] + @test PSRDatabase.read_scalar_integers(db, "Collection", "some_integer") == Int64[] + @test PSRDatabase.read_scalar_doubles(db, "Collection", "some_float") == Float64[] + + PSRDatabase.close!(db) + end + + @testset "Vector Attributes" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + 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_integers(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 "Vector Empty Result" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + # No Collection elements created + @test PSRDatabase.read_vector_integers(db, "Collection", "value_int") == Vector{Int64}[] + @test PSRDatabase.read_vector_doubles(db, "Collection", "value_float") == Vector{Float64}[] + + PSRDatabase.close!(db) + end + + @testset "Vector Only Returns Elements With Data" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + 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 (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_integers(db, "Collection", "value_int") + @test length(result) == 2 + @test result[1] == [1, 2, 3] + @test result[2] == [4, 5] + + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Set Attributes" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Set Empty Result" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important", "urgent"]) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", tag = ["review"]) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + result = PSRDatabase.read_set_strings(db, "Collection", "tag") + @test length(result) == 2 + # Sets are unordered, so sort before comparison + @test sort(result[1]) == ["important", "urgent"] + @test result[2] == ["review"] - # No Collection elements created - @test PSRDatabase.read_set_strings(db, "Collection", "tag") == Vector{String}[] + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Set Empty Result" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Set Only Returns Elements With Data" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + # No Collection elements created + @test PSRDatabase.read_set_strings(db, "Collection", "tag") == Vector{String}[] - # Create element with set data - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important"]) - # Create element without set data - PSRDatabase.create_element!(db, "Collection"; label = "Item 2") - # Create another element with set data - PSRDatabase.create_element!(db, "Collection"; label = "Item 3", tag = ["urgent", "review"]) + PSRDatabase.close!(db) + end - # Only elements with set data are returned - result = PSRDatabase.read_set_strings(db, "Collection", "tag") - @test length(result) == 2 + @testset "Set Only Returns Elements With Data" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(db) -end + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") -@testset "Set and Read Scalar Relations" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Create element with set data + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important"]) + # Create element without set data + PSRDatabase.create_element!(db, "Collection"; label = "Item 2") + # Create another element with set data + PSRDatabase.create_element!(db, "Collection"; label = "Item 3", tag = ["urgent", "review"]) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + # Only elements with set data are returned + result = PSRDatabase.read_set_strings(db, "Collection", "tag") + @test length(result) == 2 - # Create parents - PSRDatabase.create_element!(db, "Parent"; label = "Parent 1") - PSRDatabase.create_element!(db, "Parent"; label = "Parent 2") + PSRDatabase.close!(db) + end - # Create children - PSRDatabase.create_element!(db, "Child"; label = "Child 1") - PSRDatabase.create_element!(db, "Child"; label = "Child 2") - PSRDatabase.create_element!(db, "Child"; label = "Child 3") + @testset "Set and Read Scalar Relations" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Set relations - PSRDatabase.set_scalar_relation!(db, "Child", "parent_id", "Child 1", "Parent 1") - PSRDatabase.set_scalar_relation!(db, "Child", "parent_id", "Child 3", "Parent 2") + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - # Read relations - labels = PSRDatabase.read_scalar_relation(db, "Child", "parent_id") - @test length(labels) == 3 - @test labels[1] == "Parent 1" - @test labels[2] === nothing # Child 2 has no parent - @test labels[3] == "Parent 2" + # Create parents + PSRDatabase.create_element!(db, "Parent"; label = "Parent 1") + PSRDatabase.create_element!(db, "Parent"; label = "Parent 2") - PSRDatabase.close!(db) -end + # Create children + PSRDatabase.create_element!(db, "Child"; label = "Child 1") + PSRDatabase.create_element!(db, "Child"; label = "Child 2") + PSRDatabase.create_element!(db, "Child"; label = "Child 3") -@testset "Read Scalar Relations Self-Reference" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Set relations + PSRDatabase.set_scalar_relation!(db, "Child", "parent_id", "Child 1", "Parent 1") + PSRDatabase.set_scalar_relation!(db, "Child", "parent_id", "Child 3", "Parent 2") - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + # Read relations + labels = PSRDatabase.read_scalar_relation(db, "Child", "parent_id") + @test length(labels) == 3 + @test labels[1] == "Parent 1" + @test labels[2] === nothing # Child 2 has no parent + @test labels[3] == "Parent 2" - # Create children (Child references itself via sibling_id) - PSRDatabase.create_element!(db, "Child"; label = "Child 1") - PSRDatabase.create_element!(db, "Child"; label = "Child 2") + PSRDatabase.close!(db) + end - # Set sibling relation (self-reference) - PSRDatabase.set_scalar_relation!(db, "Child", "sibling_id", "Child 1", "Child 2") + @testset "Scalar Relations Self-Reference" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Read sibling relations - labels = PSRDatabase.read_scalar_relation(db, "Child", "sibling_id") - @test length(labels) == 2 - @test labels[1] == "Child 2" # Child 1's sibling is Child 2 - @test labels[2] === nothing # Child 2 has no sibling set + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.close!(db) -end + # Create children (Child references itself via sibling_id) + PSRDatabase.create_element!(db, "Child"; label = "Child 1") + PSRDatabase.create_element!(db, "Child"; label = "Child 2") -@testset "Read Scalar Relations Empty Result" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Set sibling relation (self-reference) + PSRDatabase.set_scalar_relation!(db, "Child", "sibling_id", "Child 1", "Child 2") - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + # Read sibling relations + labels = PSRDatabase.read_scalar_relation(db, "Child", "sibling_id") + @test length(labels) == 2 + @test labels[1] == "Child 2" # Child 1's sibling is Child 2 + @test labels[2] === nothing # Child 2 has no sibling set - # No Child elements created - labels = PSRDatabase.read_scalar_relation(db, "Child", "parent_id") - @test labels == Union{String, Nothing}[] + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Scalar Relations Empty Result" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -# Read by ID tests + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") -@testset "Read Scalar Integers by ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # No Child elements created + labels = PSRDatabase.read_scalar_relation(db, "Child", "parent_id") + @test labels == Union{String, Nothing}[] - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 100) + PSRDatabase.close!(db) + end - @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 1) == 42 - @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 2) == 100 - @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 999) === nothing + # Read by ID tests - PSRDatabase.close!(db) -end + @testset "Scalar Integers by ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Scalar Doubles by ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 100) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", float_attribute = 3.14) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", float_attribute = 2.71) + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 1) == 42 + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 2) == 100 + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 999) === nothing - @test PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 1) == 3.14 - @test PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 2) == 2.71 + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Scalar Doubles by ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Scalar Strings by ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", float_attribute = 3.14) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", float_attribute = 2.71) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", string_attribute = "hello") - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", string_attribute = "world") + @test PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 1) == 3.14 + @test PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 2) == 2.71 - @test PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", 1) == "hello" - @test PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", 2) == "world" + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Scalar Strings by ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Vector Integers by ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", string_attribute = "hello") + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", string_attribute = "world") - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [1, 2, 3]) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", value_int = [10, 20]) + @test PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", 1) == "hello" + @test PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", 2) == "world" - @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", 1) == [1, 2, 3] - @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", 2) == [10, 20] + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Vector Integers by ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Vector Doubles by ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [1, 2, 3]) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", value_int = [10, 20]) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_float = [1.5, 2.5, 3.5]) + @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", 1) == [1, 2, 3] + @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", 2) == [10, 20] - @test PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", 1) == [1.5, 2.5, 3.5] + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Vector Doubles by ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Set Strings by ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_float = [1.5, 2.5, 3.5]) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important", "urgent"]) - PSRDatabase.create_element!(db, "Collection"; label = "Item 2", tag = ["review"]) + @test PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", 1) == [1.5, 2.5, 3.5] - result1 = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", 1) - @test sort(result1) == ["important", "urgent"] - @test PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", 2) == ["review"] + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Set Strings by ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read Vector by ID Empty" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important", "urgent"]) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", tag = ["review"]) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.create_element!(db, "Collection"; label = "Item 1") # No vector data - - @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", 1) == Int64[] - - PSRDatabase.close!(db) -end + result1 = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", 1) + @test sort(result1) == ["important", "urgent"] + @test PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", 2) == ["review"] -# Read element IDs tests + PSRDatabase.close!(db) + end -@testset "Read Element IDs" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Vector by ID Empty" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 100) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 3", integer_attribute = 200) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1") # No vector data - ids = PSRDatabase.read_element_ids(db, "Configuration") - @test length(ids) == 3 - @test ids[1] == 1 - @test ids[2] == 2 - @test ids[3] == 3 + @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", 1) == Int64[] - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Read Element IDs Empty" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + # Read element IDs tests - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + @testset "Element IDs" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # No Collection elements created - ids = PSRDatabase.read_element_ids(db, "Collection") - @test ids == Int64[] + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 3", integer_attribute = 200) - PSRDatabase.close!(db) -end + ids = PSRDatabase.read_element_ids(db, "Configuration") + @test length(ids) == 3 + @test ids[1] == 1 + @test ids[2] == 2 + @test ids[3] == 3 -# Get attribute type tests + PSRDatabase.close!(db) + end -@testset "Get Attribute Type - Scalar Integer" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Element IDs Empty" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - result = PSRDatabase.get_attribute_type(db, "Configuration", "integer_attribute") - @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SCALAR - @test result.data_type == PSRDatabase.PSR_DATA_TYPE_INTEGER + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.close!(db) -end + # No Collection elements created + ids = PSRDatabase.read_element_ids(db, "Collection") + @test ids == Int64[] -@testset "Get Attribute Type - Scalar Real" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + end - result = PSRDatabase.get_attribute_type(db, "Configuration", "float_attribute") - @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SCALAR - @test result.data_type == PSRDatabase.PSR_DATA_TYPE_REAL + # Get attribute type tests - PSRDatabase.close!(db) -end + @testset "Get Attribute Type - Scalar Integer" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Get Attribute Type - Scalar Text" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + result = PSRDatabase.get_attribute_type(db, "Configuration", "integer_attribute") + @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SCALAR + @test result.data_type == PSRDatabase.PSR_DATA_TYPE_INTEGER - result = PSRDatabase.get_attribute_type(db, "Configuration", "string_attribute") - @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SCALAR - @test result.data_type == PSRDatabase.PSR_DATA_TYPE_TEXT + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Get Attribute Type - Scalar Real" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Get Attribute Type - Vector Integer" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + result = PSRDatabase.get_attribute_type(db, "Configuration", "float_attribute") + @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SCALAR + @test result.data_type == PSRDatabase.PSR_DATA_TYPE_REAL - result = PSRDatabase.get_attribute_type(db, "Collection", "value_int") - @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_VECTOR - @test result.data_type == PSRDatabase.PSR_DATA_TYPE_INTEGER + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Get Attribute Type - Scalar Text" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Get Attribute Type - Vector Real" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + result = PSRDatabase.get_attribute_type(db, "Configuration", "string_attribute") + @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SCALAR + @test result.data_type == PSRDatabase.PSR_DATA_TYPE_TEXT - result = PSRDatabase.get_attribute_type(db, "Collection", "value_float") - @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_VECTOR - @test result.data_type == PSRDatabase.PSR_DATA_TYPE_REAL + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Get Attribute Type - Vector Integer" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Get Attribute Type - Set Text" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + result = PSRDatabase.get_attribute_type(db, "Collection", "value_int") + @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_VECTOR + @test result.data_type == PSRDatabase.PSR_DATA_TYPE_INTEGER - result = PSRDatabase.get_attribute_type(db, "Collection", "tag") - @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SET - @test result.data_type == PSRDatabase.PSR_DATA_TYPE_TEXT + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Get Attribute Type - Vector Real" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Get Attribute Type - Error on Nonexistent" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + result = PSRDatabase.get_attribute_type(db, "Collection", "value_float") + @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_VECTOR + @test result.data_type == PSRDatabase.PSR_DATA_TYPE_REAL - @test_throws PSRDatabase.DatabaseException PSRDatabase.get_attribute_type(db, "Configuration", "nonexistent") + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) -end + @testset "Get Attribute Type - Set Text" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -# Error handling tests + result = PSRDatabase.get_attribute_type(db, "Collection", "tag") + @test result.data_structure == PSRDatabase.PSR_DATA_STRUCTURE_SET + @test result.data_type == PSRDatabase.PSR_DATA_TYPE_TEXT -@testset "Read Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + end - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_strings(db, "NonexistentCollection", "label") - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_integers(db, "NonexistentCollection", "value") - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_doubles(db, "NonexistentCollection", "value") + @testset "Get Attribute Type - Error on Nonexistent" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(db) -end + @test_throws PSRDatabase.DatabaseException PSRDatabase.get_attribute_type(db, "Configuration", "nonexistent") -@testset "Read Invalid Attribute" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + end - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_strings(db, "Configuration", "nonexistent_attr") - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_integers(db, "Configuration", "nonexistent_attr") - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_doubles(db, "Configuration", "nonexistent_attr") + # Error handling tests - PSRDatabase.close!(db) -end + @testset "Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) -@testset "Read By ID Not Found" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_strings(db, "NonexistentCollection", "label") + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_integers( + db, + "NonexistentCollection", + "value", + ) + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_doubles(db, "NonexistentCollection", "value") - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) + PSRDatabase.close!(db) + end - # Read by ID that doesn't exist returns nothing - @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 999) === nothing - @test PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 999) === nothing - @test PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", 999) === nothing + @testset "Invalid Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(db) -end + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_strings( + db, + "Configuration", + "nonexistent_attr", + ) + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_integers( + db, + "Configuration", + "nonexistent_attr", + ) + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_doubles( + db, + "Configuration", + "nonexistent_attr", + ) -@testset "Read Vector Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + end - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + @testset "ID Not Found" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_vector_integers( - db, - "NonexistentCollection", - "value_int", - ) - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_vector_doubles( - db, - "NonexistentCollection", - "value_float", - ) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) - PSRDatabase.close!(db) -end + # Read by ID that doesn't exist returns nothing + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", 999) === nothing + @test PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", 999) === nothing + @test PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", 999) === nothing -@testset "Read Set Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + end - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + @testset "Vector Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_set_strings(db, "NonexistentCollection", "tag") + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - PSRDatabase.close!(db) -end + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_vector_integers( + db, + "NonexistentCollection", + "value_int", + ) + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_vector_doubles( + db, + "NonexistentCollection", + "value_float", + ) -@testset "Read Element IDs Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + end - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_element_ids(db, "NonexistentCollection") + @testset "Set Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(db) -end + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") -@testset "Read Scalar Relation Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_set_strings(db, "NonexistentCollection", "tag") - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.close!(db) + end + + @testset "Element IDs Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_element_ids(db, "NonexistentCollection") - @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_relation( - db, - "NonexistentCollection", - "parent_id", - ) + PSRDatabase.close!(db) + end - PSRDatabase.close!(db) + @testset "Scalar Relation Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + @test_throws PSRDatabase.DatabaseException PSRDatabase.read_scalar_relation( + db, + "NonexistentCollection", + "parent_id", + ) + + PSRDatabase.close!(db) + end end end diff --git a/bindings/julia/test/test_update.jl b/bindings/julia/test/test_update.jl index 5add682..e9750aa 100644 --- a/bindings/julia/test/test_update.jl +++ b/bindings/julia/test/test_update.jl @@ -5,217 +5,566 @@ using Test include("fixture.jl") -@testset "Update Element Single Scalar" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) +@testset "Update" begin + @testset "Element Single Scalar" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Create elements - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) + # Create elements + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) - # Update single attribute - PSRDatabase.update_element!(db, "Configuration", Int64(1); integer_attribute = 999) + # Update single attribute + PSRDatabase.update_element!(db, "Configuration", Int64(1); integer_attribute = 999) - # Verify update - value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) - @test value == 999 + # Verify update + value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value == 999 - # Verify label unchanged - label = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "label", Int64(1)) - @test label == "Config 1" + # Verify label unchanged + label = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "label", Int64(1)) + @test label == "Config 1" - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update Element Multiple Scalars" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Element Multiple Scalars" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Create element - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100, float_attribute = 1.5) + # Create element + PSRDatabase.create_element!( + db, + "Configuration"; + label = "Config 1", + integer_attribute = 100, + float_attribute = 1.5, + ) - # Update multiple attributes at once - PSRDatabase.update_element!(db, "Configuration", Int64(1); integer_attribute = 500, float_attribute = 9.9) + # Update multiple attributes at once + PSRDatabase.update_element!(db, "Configuration", Int64(1); integer_attribute = 500, float_attribute = 9.9) - # Verify updates - int_value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) - @test int_value == 500 + # Verify updates + int_value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test int_value == 500 - float_value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) - @test float_value == 9.9 + float_value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) + @test float_value == 9.9 - # Verify label unchanged - label = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "label", Int64(1)) - @test label == "Config 1" + # Verify label unchanged + label = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "label", Int64(1)) + @test label == "Config 1" - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update Element Other Elements Unchanged" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Element Other Elements Unchanged" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Create multiple elements - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 3", integer_attribute = 300) + # Create multiple elements + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 3", integer_attribute = 300) - # Update only element 2 - PSRDatabase.update_element!(db, "Configuration", Int64(2); integer_attribute = 999) + # Update only element 2 + PSRDatabase.update_element!(db, "Configuration", Int64(2); integer_attribute = 999) - # Verify element 2 updated - value2 = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(2)) - @test value2 == 999 + # Verify element 2 updated + value2 = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(2)) + @test value2 == 999 - # Verify elements 1 and 3 unchanged - value1 = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) - @test value1 == 100 + # Verify elements 1 and 3 unchanged + value1 = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value1 == 100 - value3 = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(3)) - @test value3 == 300 + value3 = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(3)) + @test value3 == 300 - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update Element Arrays Ignored" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Element Arrays Ignored" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - # Create element with vector data - PSRDatabase.create_element!(db, "Collection"; - label = "Item 1", - some_integer = 10, - value_int = [1, 2, 3], - ) + # Create element with vector data + PSRDatabase.create_element!(db, "Collection"; + label = "Item 1", + some_integer = 10, + value_int = [1, 2, 3], + ) - # Try to update with array values (should be ignored) - PSRDatabase.update_element!(db, "Collection", Int64(1); some_integer = 999, value_int = [7, 8, 9]) + # Try to update with array values (should be ignored) + PSRDatabase.update_element!(db, "Collection", Int64(1); some_integer = 999, value_int = [7, 8, 9]) - # Verify scalar was updated - int_value = PSRDatabase.read_scalar_integers_by_id(db, "Collection", "some_integer", Int64(1)) - @test int_value == 999 + # Verify scalar was updated + int_value = PSRDatabase.read_scalar_integers_by_id(db, "Collection", "some_integer", Int64(1)) + @test int_value == 999 - # Verify vector was NOT updated (arrays ignored in update_element) - vec_values = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) - @test vec_values == [1, 2, 3] + # Verify vector was NOT updated (arrays ignored in update_element) + vec_values = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test vec_values == [1, 2, 3] - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update Element Using Element Builder" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Element Using Element Builder" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Create element - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + # Create element + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - # Update using Element builder - e = PSRDatabase.Element() - e["integer_attribute"] = Int64(777) - PSRDatabase.update_element!(db, "Configuration", Int64(1), e) + # Update using Element builder + e = PSRDatabase.Element() + e["integer_attribute"] = Int64(777) + PSRDatabase.update_element!(db, "Configuration", Int64(1), e) - # Verify update - value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) - @test value == 777 + # Verify update + value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value == 777 - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -# Error handling tests + # Error handling tests -@testset "Update Invalid Collection" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - @test_throws PSRDatabase.DatabaseException PSRDatabase.update_element!( - db, - "NonexistentCollection", - Int64(1); - integer_attribute = 999, - ) + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_element!( + db, + "NonexistentCollection", + Int64(1); + integer_attribute = 999, + ) - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update Invalid Element ID" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Invalid Element ID" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - # Update element that doesn't exist - should not throw but also should not affect anything - PSRDatabase.update_element!(db, "Configuration", Int64(999); integer_attribute = 500) + # Update element that doesn't exist - should not throw but also should not affect anything + PSRDatabase.update_element!(db, "Configuration", Int64(999); integer_attribute = 500) - # Verify original element unchanged - value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) - @test value == 100 + # Verify original element unchanged + value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value == 100 - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update Invalid Attribute" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Invalid Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - @test_throws PSRDatabase.DatabaseException PSRDatabase.update_element!( - db, - "Configuration", - Int64(1); - nonexistent_attribute = 999, - ) + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_element!( + db, + "Configuration", + Int64(1); + nonexistent_attribute = 999, + ) - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update String Attribute" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "String Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", string_attribute = "original") + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", string_attribute = "original") - PSRDatabase.update_element!(db, "Configuration", Int64(1); string_attribute = "updated") + PSRDatabase.update_element!(db, "Configuration", Int64(1); string_attribute = "updated") - value = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", Int64(1)) - @test value == "updated" + value = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", Int64(1)) + @test value == "updated" - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end -@testset "Update Float Attribute" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + @testset "Float Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", float_attribute = 1.5) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", float_attribute = 1.5) - PSRDatabase.update_element!(db, "Configuration", Int64(1); float_attribute = 99.99) + PSRDatabase.update_element!(db, "Configuration", Int64(1); float_attribute = 99.99) - value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) - @test value == 99.99 + value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) + @test value == 99.99 - PSRDatabase.close!(db) -end + PSRDatabase.close!(db) + end + + @testset "Multiple Elements Sequentially" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) + + # Update both elements + PSRDatabase.update_element!(db, "Configuration", Int64(1); integer_attribute = 111) + PSRDatabase.update_element!(db, "Configuration", Int64(2); integer_attribute = 222) + + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) == 111 + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(2)) == 222 + + PSRDatabase.close!(db) + end + + # ============================================================================ + # Scalar update functions tests + # ============================================================================ + + @testset "Scalar Integer" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) + + # Basic update + PSRDatabase.update_scalar_integer!(db, "Configuration", "integer_attribute", Int64(1), 100) + value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value == 100 + + # Update to 0 + PSRDatabase.update_scalar_integer!(db, "Configuration", "integer_attribute", Int64(1), 0) + value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value == 0 + + # Update to negative + PSRDatabase.update_scalar_integer!(db, "Configuration", "integer_attribute", Int64(1), -999) + value = PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) + @test value == -999 + + PSRDatabase.close!(db) + end + + @testset "Scalar Integer Multiple Elements" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) + PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 100) + + # Update only first element + PSRDatabase.update_scalar_integer!(db, "Configuration", "integer_attribute", Int64(1), 999) + + # Verify first element changed + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) == 999 + + # Verify second element unchanged + @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(2)) == 100 + + PSRDatabase.close!(db) + end + + @testset "Scalar Double" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", float_attribute = 3.14) + + # Basic update + PSRDatabase.update_scalar_double!(db, "Configuration", "float_attribute", Int64(1), 2.71) + value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) + @test value == 2.71 + + # Update to 0.0 + PSRDatabase.update_scalar_double!(db, "Configuration", "float_attribute", Int64(1), 0.0) + value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) + @test value == 0.0 + + # Precision test + PSRDatabase.update_scalar_double!(db, "Configuration", "float_attribute", Int64(1), 1.23456789012345) + value = PSRDatabase.read_scalar_doubles_by_id(db, "Configuration", "float_attribute", Int64(1)) + @test value ≈ 1.23456789012345 + + PSRDatabase.close!(db) + end + + @testset "Scalar String" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", string_attribute = "hello") + + # Basic update + PSRDatabase.update_scalar_string!(db, "Configuration", "string_attribute", Int64(1), "world") + value = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", Int64(1)) + @test value == "world" + + # Update to empty string + PSRDatabase.update_scalar_string!(db, "Configuration", "string_attribute", Int64(1), "") + value = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", Int64(1)) + @test value == "" + + # Unicode support + PSRDatabase.update_scalar_string!(db, "Configuration", "string_attribute", Int64(1), "日本語テスト") + value = PSRDatabase.read_scalar_strings_by_id(db, "Configuration", "string_attribute", Int64(1)) + @test value == "日本語テスト" + + PSRDatabase.close!(db) + end + + # ============================================================================ + # Vector update functions tests + # ============================================================================ + + @testset "Vector Integers" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [1, 2, 3]) + + # Replace existing vector + PSRDatabase.update_vector_integers!(db, "Collection", "value_int", Int64(1), [10, 20, 30, 40]) + values = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test values == [10, 20, 30, 40] + + # Update to smaller vector + PSRDatabase.update_vector_integers!(db, "Collection", "value_int", Int64(1), [100]) + values = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test values == [100] + + # Update to empty vector + PSRDatabase.update_vector_integers!(db, "Collection", "value_int", Int64(1), Int64[]) + values = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test isempty(values) + + PSRDatabase.close!(db) + end + + @testset "Vector Integers From Empty" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1") + + # Verify initially empty + values = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test isempty(values) + + # Update to non-empty + PSRDatabase.update_vector_integers!(db, "Collection", "value_int", Int64(1), [1, 2, 3]) + values = PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) + @test values == [1, 2, 3] + + PSRDatabase.close!(db) + end + + @testset "Vector Integers Multiple Elements" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_int = [1, 2, 3]) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", value_int = [10, 20]) + + # Update only first element + PSRDatabase.update_vector_integers!(db, "Collection", "value_int", Int64(1), [100, 200]) + + # Verify first element changed + @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(1)) == [100, 200] + + # Verify second element unchanged + @test PSRDatabase.read_vector_integers_by_id(db, "Collection", "value_int", Int64(2)) == [10, 20] + + PSRDatabase.close!(db) + end + + @testset "Vector Doubles" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", value_float = [1.5, 2.5, 3.5]) + + # Replace existing vector + PSRDatabase.update_vector_doubles!(db, "Collection", "value_float", Int64(1), [10.5, 20.5]) + values = PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", Int64(1)) + @test values == [10.5, 20.5] + + # Precision test + PSRDatabase.update_vector_doubles!(db, "Collection", "value_float", Int64(1), [1.23456789, 9.87654321]) + values = PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", Int64(1)) + @test values ≈ [1.23456789, 9.87654321] + + # Update to empty vector + PSRDatabase.update_vector_doubles!(db, "Collection", "value_float", Int64(1), Float64[]) + values = PSRDatabase.read_vector_doubles_by_id(db, "Collection", "value_float", Int64(1)) + @test isempty(values) + + PSRDatabase.close!(db) + end + + # ============================================================================ + # Set update functions tests + # ============================================================================ + + @testset "Set Strings" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important", "urgent"]) + + # Replace existing set + PSRDatabase.update_set_strings!(db, "Collection", "tag", Int64(1), ["new_tag1", "new_tag2", "new_tag3"]) + values = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test sort(values) == sort(["new_tag1", "new_tag2", "new_tag3"]) + + # Update to single element + PSRDatabase.update_set_strings!(db, "Collection", "tag", Int64(1), ["single_tag"]) + values = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test values == ["single_tag"] + + # Update to empty set + PSRDatabase.update_set_strings!(db, "Collection", "tag", Int64(1), String[]) + values = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test isempty(values) + + PSRDatabase.close!(db) + end + + @testset "Set Strings From Empty" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1") + + # Verify initially empty + values = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test isempty(values) + + # Update to non-empty + PSRDatabase.update_set_strings!(db, "Collection", "tag", Int64(1), ["important", "urgent"]) + values = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test sort(values) == sort(["important", "urgent"]) + + PSRDatabase.close!(db) + end + + @testset "Set Strings Multiple Elements" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["important"]) + PSRDatabase.create_element!(db, "Collection"; label = "Item 2", tag = ["urgent", "review"]) + + # Update only first element + PSRDatabase.update_set_strings!(db, "Collection", "tag", Int64(1), ["updated"]) + + # Verify first element changed + @test PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) == ["updated"] + + # Verify second element unchanged + @test sort(PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(2))) == sort(["urgent", "review"]) + + PSRDatabase.close!(db) + end + + @testset "Set Strings Unicode" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + PSRDatabase.create_element!(db, "Collection"; label = "Item 1", tag = ["tag1"]) + + # Unicode support + PSRDatabase.update_set_strings!(db, "Collection", "tag", Int64(1), ["日本語", "中文", "한국어"]) + values = PSRDatabase.read_set_strings_by_id(db, "Collection", "tag", Int64(1)) + @test sort(values) == sort(["日本語", "中文", "한국어"]) + + PSRDatabase.close!(db) + end + + # ============================================================================ + # Error handling tests for new update functions + # ============================================================================ + + @testset "Scalar Integer Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_scalar_integer!( + db, + "NonexistentCollection", + "integer_attribute", + Int64(1), + 42, + ) + + PSRDatabase.close!(db) + end + + @testset "Scalar Integer Invalid Attribute" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 42) + + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_scalar_integer!( + db, + "Configuration", + "nonexistent_attribute", + Int64(1), + 100, + ) + + PSRDatabase.close!(db) + end + + @testset "Vector Integers Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") + + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_vector_integers!( + db, + "NonexistentCollection", + "value_int", + Int64(1), + [1, 2, 3], + ) -@testset "Update Multiple Elements Sequentially" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + end - PSRDatabase.create_element!(db, "Configuration"; label = "Config 1", integer_attribute = 100) - PSRDatabase.create_element!(db, "Configuration"; label = "Config 2", integer_attribute = 200) + @testset "Set Strings Invalid Collection" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) - # Update both elements - PSRDatabase.update_element!(db, "Configuration", Int64(1); integer_attribute = 111) - PSRDatabase.update_element!(db, "Configuration", Int64(2); integer_attribute = 222) + PSRDatabase.create_element!(db, "Configuration"; label = "Test Config") - @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(1)) == 111 - @test PSRDatabase.read_scalar_integers_by_id(db, "Configuration", "integer_attribute", Int64(2)) == 222 + @test_throws PSRDatabase.DatabaseException PSRDatabase.update_set_strings!( + db, + "NonexistentCollection", + "tag", + Int64(1), + ["tag1"], + ) - PSRDatabase.close!(db) + PSRDatabase.close!(db) + end end end diff --git a/bindings/julia/test/test_valid_database.jl b/bindings/julia/test/test_valid_database.jl new file mode 100644 index 0000000..334a954 --- /dev/null +++ b/bindings/julia/test/test_valid_database.jl @@ -0,0 +1,31 @@ +module TestValidDatabaseDefinitions + +using PSRDatabase +using Test + +include("fixture.jl") + +@testset "Valid Schema" begin + @testset "Basic" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + @test true + end + + @testset "Collections" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + @test true + end + + @testset "Relations" begin + path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") + db = PSRDatabase.from_schema(":memory:", path_schema) + PSRDatabase.close!(db) + @test true + end +end + +end diff --git a/bindings/julia/test/test_valid_database_definitions.jl b/bindings/julia/test/test_valid_database_definitions.jl deleted file mode 100644 index 0d673fa..0000000 --- a/bindings/julia/test/test_valid_database_definitions.jl +++ /dev/null @@ -1,74 +0,0 @@ -module TestValidDatabaseDefinitions - -using PSRDatabase -using Test - -include("fixture.jl") - -@testset "Valid Schema - Basic" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "basic.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(db) - @test true -end - -@testset "Valid Schema - Collections" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "collections.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(db) - @test true -end - -@testset "Valid Schema - Relations" begin - path_schema = joinpath(tests_path(), "schemas", "valid", "relations.sql") - db = PSRDatabase.from_schema(":memory:", path_schema) - PSRDatabase.close!(db) - @test true -end - -@testset "Invalid - No Configuration" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "no_configuration.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - Label Not Null" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "label_not_null.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - Label Not Unique" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "label_not_unique.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - Label Wrong Type" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "label_wrong_type.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - Duplicate Attribute" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "duplicate_attribute.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - Vector No Index" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "vector_no_index.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - Set No Unique" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "set_no_unique.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - FK Not Null Set Null" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "fk_not_null_set_null.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -@testset "Invalid - FK Actions" begin - path_schema = joinpath(tests_path(), "schemas", "invalid", "fk_actions.sql") - @test_throws PSRDatabase.DatabaseException PSRDatabase.from_schema(":memory:", path_schema) -end - -end diff --git a/src/database.cpp b/src/database.cpp index db7e1ca..a0d1a5a 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -95,6 +95,19 @@ struct Database::Impl { std::unique_ptr schema; std::unique_ptr type_validator; + void require_schema(const char* operation) const { + if (!schema) { + throw std::runtime_error(std::string("Cannot ") + operation + ": no schema loaded"); + } + } + + void require_collection(const std::string& collection, const char* operation) const { + require_schema(operation); + if (!schema->has_table(collection)) { + throw std::runtime_error("Collection not found in schema: " + collection); + } + } + ~Impl() { if (db) { sqlite3_close_v2(db); @@ -1119,14 +1132,7 @@ void Database::update_scalar_integer(const std::string& collection, int64_t id, int64_t value) { impl_->logger->debug("Updating {}.{} for id {} to {}", collection, attribute, id, value); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update scalar: no schema loaded"); - } - if (!impl_->schema->has_table(collection)) { - throw std::runtime_error("Collection not found in schema: " + collection); - } - + impl_->require_collection(collection, "update scalar"); impl_->type_validator->validate_scalar(collection, attribute, value); auto sql = "UPDATE " + collection + " SET " + attribute + " = ? WHERE id = ?"; @@ -1140,14 +1146,7 @@ void Database::update_scalar_double(const std::string& collection, int64_t id, double value) { impl_->logger->debug("Updating {}.{} for id {} to {}", collection, attribute, id, value); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update scalar: no schema loaded"); - } - if (!impl_->schema->has_table(collection)) { - throw std::runtime_error("Collection not found in schema: " + collection); - } - + impl_->require_collection(collection, "update scalar"); impl_->type_validator->validate_scalar(collection, attribute, value); auto sql = "UPDATE " + collection + " SET " + attribute + " = ? WHERE id = ?"; @@ -1161,14 +1160,7 @@ void Database::update_scalar_string(const std::string& collection, int64_t id, const std::string& value) { impl_->logger->debug("Updating {}.{} for id {} to '{}'", collection, attribute, id, value); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update scalar: no schema loaded"); - } - if (!impl_->schema->has_table(collection)) { - throw std::runtime_error("Collection not found in schema: " + collection); - } - + impl_->require_collection(collection, "update scalar"); impl_->type_validator->validate_scalar(collection, attribute, value); auto sql = "UPDATE " + collection + " SET " + attribute + " = ? WHERE id = ?"; @@ -1182,10 +1174,7 @@ void Database::update_vector_integers(const std::string& collection, int64_t id, const std::vector& values) { impl_->logger->debug("Updating vector {}.{} for id {} with {} values", collection, attribute, id, values.size()); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update vector: no schema loaded"); - } + impl_->require_schema("update vector"); auto vector_table = impl_->schema->find_vector_table(collection, attribute); @@ -1214,10 +1203,7 @@ void Database::update_vector_doubles(const std::string& collection, int64_t id, const std::vector& values) { impl_->logger->debug("Updating vector {}.{} for id {} with {} values", collection, attribute, id, values.size()); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update vector: no schema loaded"); - } + impl_->require_schema("update vector"); auto vector_table = impl_->schema->find_vector_table(collection, attribute); @@ -1246,10 +1232,7 @@ void Database::update_vector_strings(const std::string& collection, int64_t id, const std::vector& values) { impl_->logger->debug("Updating vector {}.{} for id {} with {} values", collection, attribute, id, values.size()); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update vector: no schema loaded"); - } + impl_->require_schema("update vector"); auto vector_table = impl_->schema->find_vector_table(collection, attribute); @@ -1278,10 +1261,7 @@ void Database::update_set_integers(const std::string& collection, int64_t id, const std::vector& values) { impl_->logger->debug("Updating set {}.{} for id {} with {} values", collection, attribute, id, values.size()); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update set: no schema loaded"); - } + impl_->require_schema("update set"); auto set_table = impl_->schema->find_set_table(collection, attribute); @@ -1309,10 +1289,7 @@ void Database::update_set_doubles(const std::string& collection, int64_t id, const std::vector& values) { impl_->logger->debug("Updating set {}.{} for id {} with {} values", collection, attribute, id, values.size()); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update set: no schema loaded"); - } + impl_->require_schema("update set"); auto set_table = impl_->schema->find_set_table(collection, attribute); @@ -1340,10 +1317,7 @@ void Database::update_set_strings(const std::string& collection, int64_t id, const std::vector& values) { impl_->logger->debug("Updating set {}.{} for id {} with {} values", collection, attribute, id, values.size()); - - if (!impl_->schema) { - throw std::runtime_error("Cannot update set: no schema loaded"); - } + impl_->require_schema("update set"); auto set_table = impl_->schema->find_set_table(collection, attribute);