Skip to content
Snippets Groups Projects

ENH: Implement queries and entity retrieval

Merged Florian Spreckelsen requested to merge f-extended-c into dev

Summary

See https://gitlab.indiscale.com/caosdb/customers/lfpb/management/-/issues/386.

Focus

Point the reviewer to the core of the code change. Where should they start
reading? What should they focus on (e.g. security, performance,
maintainability, user-friendliness, compliance with the specs, finding more
corner cases, concrete questions)?

Test Environment

How to set up a test environment for manual testing?

Check List for the Author

Please, prepare your MR for a review. Be sure to write a summary and a focus and create gitlab comments for the reviewer. They should guide the reviewer through the changes, explain your changes and also point out open questions. For further good practices have a look at our review guidelines

  • All automated tests pass
  • Reference related Issues
  • Up-to-date CHANGELOG.md
  • Annotations in code (Gitlab comments)
    • Intent of new code
    • Problems with old code
    • Why this implementation?

Check List for the Reviewer

  • I understand the intent of this MR
  • All automated tests pass
  • Up-to-date CHANGELOG.md
  • The test environment setup works and the intended behavior is reproducible in the test environment
  • In-code documentation and comments are up-to-date.
  • Check: Are there spezifications? Are they satisfied?

For further good practices have a look at our review guidelines.

Edited by Henrik tom Wörden

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
5 5 is meant for expert use only. Only use those if you know what you're
6 6 doing.
7 7
8 ```@index
9 Order = [:module, :function, :macro, :type, :constant]
  • Unfortunately, Documenter.jl doesn't support the Private argument in @index so we have to join everything into one index. I still like having one now that we have so many elements in our API documentation, though.

  • Please register or sign in to reply
  • 118 Add a sub-request to retrieve several entities by their `ids` to the
    119 given `transaction`.
    120
    121 !!! info
    122
    123 This does not execute the transaction.
    124 """
    125 function add_retrieve_by_id(
    126 transaction::Ref{_Transaction},
    127 ids::Vector{T},
    128 ) where {T<:AbstractString}
    129
    130 err_code = ccall(
    131 (:caosdb_transaction_transaction_retrieve_by_ids, CaosDB.library_name),
    132 Cint,
    133 (Ref{_Transaction}, Ptr{Ptr{Cchar}}),
  • added 1 commit

    Compare with previous version

  • added 6 commits

    • 45dabd84 - DRAFT: Begin implementation of entities
    • 5ef97a84 - ENH: Add Exception type for client exceptions
    • 475a378f - FIX: Correct arguments for retrieve_by_ids
    • 2b379aff - DRAFT: Begin implementation of entities
    • f5bd5529 - ENH: Add test for entities
    • 0be053a7 - DRAFT: Add a lot of setters, getters, and helper functions

    Compare with previous version

  • added 2 commits

    • 379a133b - ENH: Add convenience wrappers for Integer indices
    • 20939d95 - ENH: Add size checking to getters

    Compare with previous version

  • added 1 commit

    • b3e4ab79 - MAINT: Move remaining modules to separate files

    Compare with previous version

  • added 1 commit

    • bdcaceb1 - ENH: Finish retrieve and query transactions

    Compare with previous version

  • added 1 commit

    • 1e651982 - DOC: Add hint on COUNT queries

    Compare with previous version

  • 1 # ** header v3.0
  • 57 end
    58
    59 Base.showerror(io::IO, e::CaosDBException) =
    60 print(io, "CaosDBException: ", e.msg, " (Status code ", e.code, ")")
    61
    62 """
    63 Struct containing Messages and codes for status codes<0 that do not
    64 correspond to errors or success.
    65 """
    66 struct CaosDBMessage
    67 msg::String
    68 code::Cint
    69 end
    70
    71 Base.show(io::IO, m::CaosDBMessage) = print(io, m.msg, " (Status code ", m.code, ")")
    102 # include modules from other files
  • 34 34 """
    35 35 mutable struct _Connection
    36 36 wrapped_connection::Ptr{Cvoid}
    37 _deletable::Bool
    • The _deletable flag isn't actually needed on the Julia side, but we still have it in order to prevent any strange memory problems if the structs in Julia didn't exactly match the C structs.

    • Please register or sign in to reply
  • 34 34 """
    35 35 mutable struct _Connection
    36 36 wrapped_connection::Ptr{Cvoid}
    37 _deletable::Bool
    37 38
    38 39 function _Connection(managed_by_julia::Bool = false)
    39 40 conn = new()
    41 conn._deletable = false
  • src/Exceptions.jl 0 → 100644
    1 # ** header v3.0
  • src/Exceptions.jl 0 → 100644
    36
    37 """
    38 A generic exception that will be raised in case of non-zero return
    39 values of the calls to libccaosdb. May carry a message string and a
    40 code.
    41 """
    42 struct GenericCaosDBException <: CaosDBException
    43 msg::String
    44 code::Cint
    45 end
    46
    47 """
    48 Something went wrong on the client-side or the user is attempting to
    49 conduct an invalid operation.
    50 """
    51 struct ClientException <: CaosDBException
  • src/Exceptions.jl 0 → 100644
    42 struct GenericCaosDBException <: CaosDBException
    43 msg::String
    44 code::Cint
    45 end
    46
    47 """
    48 Something went wrong on the client-side or the user is attempting to
    49 conduct an invalid operation.
    50 """
    51 struct ClientException <: CaosDBException
    52 msg::String
    53 code::Cint
    54
    55 function ClientException(message::AbstractString)
    56 client_error_code =
    57 ccall((:caosdb_status_code_OTHER_CLIENT_ERROR, CaosDB.library_name), Cint, ())
  • src/Info.jl 0 → 100644
    1 # ** header v3.0
  • 61 end
    62 return trans
    63 end
    64 end
    65
    66 """
    67 Struct containing a pointer to the wrapped cpp result set of a
    68 transaction. The struct is meant for internal use only and shouldn't
    69 be created directly by the user but is returned by, e.g.,
    70 `get_result_set`.
    71 """
    72 mutable struct _ResultSet
    73 wrapped_result_set::Ptr{Cvoid}
    74 _deletable::Bool
    75
    76 function _ResultSet()
    • This is never created on its own, only as a part of a transaction. That's why we don't add a destructor (i.e., finalizer) here (there aren't even create_result_set and delete_result_set functions in ccaosdb).

    • Please register or sign in to reply
  • 74 _deletable::Bool
    75
    76 function _ResultSet()
    77 rs = new()
    78 rs._deletable = false
    79 return rs
    80 end
    81 end
    82
    83 """
    84 create_transaction(name::AbstractString = "default")
    85
    86 Return a transaction created with the connection of the given name. If
    87 none is given, the defult connection is used.
    88 """
    89 function create_transaction(name::AbstractString = "default")
    • Overload the creator s.th. users only have to type create_connection() and get one for the default connection which is probably what the users would want in most cases. They can also call this with the string name of the connection they want to use, or with the connection object itself if they already have created one.

    • Please register or sign in to reply
  • 87 none is given, the defult connection is used.
    88 """
    89 function create_transaction(name::AbstractString = "default")
    90 connection = CaosDB.Connection.get_connection(name)
    91
    92 return create_transaction(connection)
    93 end
    94
    95 """
    96 function create_transaction(connection::Ref{CaosDB.Connection._Connection})
    97
    98 Return a transactioncreated with the given connection object.
    99 """
    100 function create_transaction(connection::Ref{CaosDB.Connection._Connection})
    101
    102 transaction = Ref{_Transaction}(_Transaction(true))
    • This one is a little tricky on the C side. It is in principle owned by the connection object, but we release it so that it may continue existing after the connection goes out of scope. Afterwards we have to care for the deletion ourselves, hence managed_by_julia = true.

    • Please register or sign in to reply
  • 112 CaosDB.Exceptions.evaluate_return_code(err_code)
    113
    114 return transaction
    115 end
    116
    117 """
    118 function add_retrieve_by_id(transaction::Ref{_Transaction}, id::AbstractString)
    119
    120 Add a sub-request to retrieve a single entity by its `id` to the given
    121 `transaction`.
    122
    123 !!! info
    124
    125 This does not execute the transaction.
    126 """
    127 function add_retrieve_by_id(transaction::Ref{_Transaction}, id::AbstractString)
  • 141 function add_retrieve_by_id(
    142 transaction::Ref{_Transaction},
    143 ids::Vector{T},
    144 ) where {T<:AbstractString}
    145
    146 Add a sub-request to retrieve several entities by their `ids` to the
    147 given `transaction`.
    148
    149 !!! info
    150
    151 This does not execute the transaction.
    152 """
    153 function add_retrieve_by_id(
    154 transaction::Ref{_Transaction},
    155 ids::Vector{T},
    156 ) where {T<:AbstractString}
  • 147 given `transaction`.
    148
    149 !!! info
    150
    151 This does not execute the transaction.
    152 """
    153 function add_retrieve_by_id(
    154 transaction::Ref{_Transaction},
    155 ids::Vector{T},
    156 ) where {T<:AbstractString}
    157
    158 len = Cint(length(ids))
    159 err_code = ccall(
    160 (:caosdb_transaction_transaction_retrieve_by_ids, CaosDB.library_name),
    161 Cint,
    162 (Ref{_Transaction}, Ptr{Ptr{Cchar}}, Cint),
  • 188 err_code = ccall(
    189 (:caosdb_transaction_transaction_query, CaosDB.library_name),
    190 Cint,
    191 (Ref{_Transaction}, Cstring),
    192 transaction,
    193 query,
    194 )
    195
    196 CaosDB.Exceptions.evaluate_return_code(err_code)
    197 end
    198
    199 """
    200 function execute(transaction::Ref{_Transaction})
    201
    202 Send the given `transaction` to the CaosDB server for excecution and
    203 wait for it to finish.
  • 207 err_code = ccall(
    208 (:caosdb_transaction_transaction_execute, CaosDB.library_name),
    209 Cint,
    210 (Ref{_Transaction},),
    211 transaction,
    212 )
    213
    214 CaosDB.Exceptions.evaluate_return_code(err_code)
    215 end
    216
    217 """
    218 function get_result_set(transaction::Ref{_Transaction})
    219
    220 Return the result set of the given `transaction`.
    221 """
    222 function get_result_set(transaction::Ref{_Transaction})
  • 338
    339 Return al results of the given `result_set`.
    340 """
    341 function get_results(result_set::Ref{_ResultSet})
    342
    343 size = get_result_size(result_set)
    344
    345 return [get_result_at(result_set, Cint(ii)) for ii = 1:size]
    346 end
    347
    348 """
    349 function get_results(transaction::Ref{_Transaction})
    350
    351 Return all results of the given `transaction`.
    352 """
    353 function get_results(transaction::Ref{_Transaction})
    • This allows the users to omit the ResultSet alltogether in almost all cases which I expect to become the norm. get_results(transaction) returns the resulting entities directly without caring about a ResultSet.

    • Please register or sign in to reply
  • 363 name::AbstractString = "default"
    364 )
    365
    366 Execute the given `query` and return its results. Use the connection
    367 with the given `name`. If none is given, the default connection is
    368 used.
    369
    370 !!! info
    371
    372 Since only the resulting entities are returned, this only makes
    373 sense for FIND (and, in the future, SELECT) queries. To get the
    374 result of a `COUNT` query, you have to execute the transaction
    375 yourself using `create_transaction`, `add_query`, and `execute`,
    376 and get the result with `get_count_result`.
    377 """
    378 function execute_query(query::AbstractString, name::AbstractString = "default")
    • Convenience functions further simplifying queries and retrievals: The users don't have to care about connections (though they may still specify them by name or connection object if needed), transactions, adding subrequests, and getting the results after execution; they simply type execute_query() or retrieve() and are returned the entities directly.

    • Please register or sign in to reply
  • 394
    395 Execute the given `query` and return its results. Use the given
    396 `connection`.
    397
    398 !!! info
    399
    400 Since only the resulting entities are returned, this only makes
    401 sense for FIND (and, in the future, SELECT) queries. To get the
    402 result of a `COUNT` query, you have to execute the transaction
    403 yourself using `create_transaction`, `add_query`, and `execute`,
    404 and get the result with `get_count_result`.
    405 """
    406 function execute_query(
    407 query::AbstractString,
    408 connection::Ref{CaosDB.Connection._Connection},
    409 )
  • 412
    413 add_query(transaction, query)
    414
    415 execute(transaction)
    416
    417 return get_results(transaction)
    418 end
    419
    420 """
    421 function retrieve(id::AbstractString, name::AbstractString = "default")
    422
    423 Retrieve and return the entity with the given `id`. Use the connection
    424 with the given `name`. If none is provided, the default connection is
    425 used.
    426 """
    427 function retrieve(id::AbstractString, name::AbstractString = "default")
  • src/Utility.jl 0 → 100644
    1 # ** header v3.0
  • 63 63 Cint(-1),
    64 64 ),
    65 65 ) CaosDB.Exceptions.evaluate_return_code(Cint(-1))
    66
    67 # Check whether client errors can be built correctly and
    68 # return the correct code (Change it if this ever changes in
    69 # libcaosdb)
    70 client_err = CaosDB.Exceptions.ClientException("Client error")
    71 @test client_err.msg == "Client error"
    72 @test client_err.code == 9999
  • 75 @testset "TestTransaction" begin
    76 # transactions can be generated from the default connection,
    77 # from a connection name, or from a connection object
    78 @test CaosDB.Transaction.create_transaction() != nothing
    79 @test CaosDB.Transaction.create_transaction("default") != nothing
    80 conn = CaosDB.Connection.get_connection()
    81 @test CaosDB.Transaction.create_transaction(conn) != nothing
    82
    83 # Retrieval works by a single id, by a list of ids, or by querying
    84 trans1 = CaosDB.Transaction.create_transaction()
    85 @test CaosDB.Transaction.add_retrieve_by_id(trans1, "some_id") == nothing
    86 trans2 = CaosDB.Transaction.create_transaction()
    87 @test CaosDB.Transaction.add_retrieve_by_id(trans2, ["id1", "id2", "id3"]) ==
    88 nothing
    89 trans3 = CaosDB.Transaction.create_transaction()
    90 @test CaosDB.Transaction.add_query(trans3, "FIND ENTITY WITH id=123") == nothing
    • We're lacking the debugging means to create dummy connections, transactions, and result sets for unit testing, so everything that's related to the actual execution of the transactions and their results is part of the integration tests for now.

    • Please register or sign in to reply
  • 80 conn = CaosDB.Connection.get_connection()
    81 @test CaosDB.Transaction.create_transaction(conn) != nothing
    82
    83 # Retrieval works by a single id, by a list of ids, or by querying
    84 trans1 = CaosDB.Transaction.create_transaction()
    85 @test CaosDB.Transaction.add_retrieve_by_id(trans1, "some_id") == nothing
    86 trans2 = CaosDB.Transaction.create_transaction()
    87 @test CaosDB.Transaction.add_retrieve_by_id(trans2, ["id1", "id2", "id3"]) ==
    88 nothing
    89 trans3 = CaosDB.Transaction.create_transaction()
    90 @test CaosDB.Transaction.add_query(trans3, "FIND ENTITY WITH id=123") == nothing
    91
    92 end
    93
    94 @testset "TestEntity" begin
    95 ent_with_name = CaosDB.Entity.create_entity("TestEnt")
    • Test some setters and getters, also the appending and removal of parents and properties. Since the wrapped methods are already tested extensively in (c)caosdb, we're only testing some to verify the Julia code.

    • Please register or sign in to reply
  • src/Entity.jl 0 → 100644
    8 # it under the terms of the GNU Affero General Public License as
    9 # published by the Free Software Foundation, either version 3 of the
    10 # License, or (at your option) any later version.
    11 #
    12 # This program is distributed in the hope that it will be useful, but
    13 # WITHOUT ANY WARRANTY; without even the implied warranty of
    14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    15 # Affero General Public License for more details.
    16 #
    17 # You should have received a copy of the GNU Affero General Public
    18 # License along with this program. If not, see
    19 # <https://www.gnu.org/licenses/>.
    20 #
    21 # ** end header
    22 #
    23 module Entity
    • This one is quite long but also very repetitive (similar to the entity functions in ccaosdb). Even more so, since they are overloaded at many points to increase convenience (allow accessing members with Integers instead of Cints, getting all parents, properties, ... instead of getting them one-by-one, etc.).

    • Please register or sign in to reply
  • src/Entity.jl 0 → 100644
    11 #
    12 # This program is distributed in the hope that it will be useful, but
    13 # WITHOUT ANY WARRANTY; without even the implied warranty of
    14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    15 # Affero General Public License for more details.
    16 #
    17 # You should have received a copy of the GNU Affero General Public
    18 # License along with this program. If not, see
    19 # <https://www.gnu.org/licenses/>.
    20 #
    21 # ** end header
    22 #
    23 module Entity
    24
    25 # Creators
    26 export create_entity,
    • It's kind of ugly but for users to be able to type

      using Caosdb
      entity = create_entity()

      Instead of having to use CaosDB.Entity.create_entity() everywhere, this has to be exported by the Entity submodule first and then again by the CaosDB module itself.

      Note that this is the Julia way of extending the global namespace with the exported members of used modules in contrast to imported modules (import CaosDB instead of using CaosDB). In the latter case, nothing is forwarded to the global namespace even when exported.

    • Please register or sign in to reply
  • src/Entity.jl 0 → 100644
    197
    198 if name != ""
    199 set_name(entity, name)
    200 end
    201
    202 return entity
    203
    204 end
    205
    206 """
    207 function create_recordtype(name::AbstractString = "")
    208
    209 Return a new entity object with role RecordType. If a `name` was
    210 provided, its name is set accordingly.
    211 """
    212 function create_recordtype(name::AbstractString = "")
  • src/Entity.jl 0 → 100644
    233
    234 return record
    235
    236 end
    237
    238 """
    239 function create_property_entity(;
    240 name::AbstractString = "",
    241 datatype::AbstractString = "",
    242 unit::AbstractString = "",
    243 )
    244
    245 Return a new entity object with role Record. If `name`, `datatype`, or
    246 `unit` were provided, its name, datatype, or unit are set accordingly.
    247 """
    248 function create_property_entity(;
  • src/Entity.jl 0 → 100644
    268
    269 """
    270 function create_property(;
    271 name::AbstractString = "",
    272 id::AbstractString = "",
    273 value::AbstractString = "",
    274 )
    275
    276 Create a property object that can be appended to another `_Entity`. If
    277 `name`, `id`, or `value` are given, the property's name, id, or value
    278 are set accordingly.
    279
    280 !!! info
    281
    282 This is not an `_Entity` that could be inserted or updated by its
    283 own but a `_Property` that is to be appended to another `_Entity`.
  • src/Entity.jl 0 → 100644
    343 set_name(parent, name)
    344 end
    345
    346 if id != ""
    347 set_id(parent, id)
    348 end
    349
    350 return parent
    351 end
    352
    353 """
    354 function get_id(entity::Ref{_Entity})
    355
    356 Return the id of the given `entity`
    357 """
    358 function get_id(entity::Ref{_Entity})
  • src/Entity.jl 0 → 100644
    345
    346 if id != ""
    347 set_id(parent, id)
    348 end
    349
    350 return parent
    351 end
    352
    353 """
    354 function get_id(entity::Ref{_Entity})
    355
    356 Return the id of the given `entity`
    357 """
    358 function get_id(entity::Ref{_Entity})
    359
    360 out = Vector{UInt8}(undef, 256)
  • src/Entity.jl 0 → 100644
    357 """
    358 function get_id(entity::Ref{_Entity})
    359
    360 out = Vector{UInt8}(undef, 256)
    361
    362 err_code = ccall(
    363 (:caosdb_entity_entity_get_id, CaosDB.library_name),
    364 Cint,
    365 (Ref{_Entity}, Ptr{UInt8}),
    366 entity,
    367 out,
    368 )
    369
    370 CaosDB.Exceptions.evaluate_return_code(err_code)
    371
    372 return GC.@preserve out unsafe_string(pointer(out))
  • src/Entity.jl 0 → 100644
    798 """
    799 function get_errors_size(entity::Ref{_Entity})
    800
    801 size = Ref{Cint}(0)
    802
    803 err_code = ccall(
    804 (:caosdb_entity_entity_get_errors_size, CaosDB.library_name),
    805 Cint,
    806 (Ref{_Entity}, Ref{Cint}),
    807 entity,
    808 size,
    809 )
    810
    811 CaosDB.Exceptions.evaluate_return_code(err_code)
    812
    813 return size[]
  • src/Entity.jl 0 → 100644
    847
    848 message_text = get_description(error)
    849
    850 code = get_code(error)
    851
    852 return CaosDB.Exceptions.CaosDBMessage(message_text, code)
    853 end
    854
    855 """
    856 function get_error(entity::Ref{_Entity}, index::Integer)
    857
    858 Convenience wrapper for `get_error(::Ref{_Entity}, ::Cint)`. Convert
    859 `index` to Int32 and return the error at position `index` of the given
    860 `entity`.
    861 """
    862 function get_error(entity::Ref{_Entity}, index::Integer)
  • src/Entity.jl 0 → 100644
    828 throw(
    829 DomainError(
    830 index,
    831 "You tried to access the error at position $index but the entity only has $size errors.",
    832 ),
    833 )
    834 end
    835
    836 error = Ref{_Message}(_Message())
    837
    838 err_code = ccall(
    839 (:caosdb_entity_entity_get_error, CaosDB.library_name),
    840 Cint,
    841 (Ref{_Entity}, Cint),
    842 entity,
    843 index - Cint(1),
  • src/Entity.jl 0 → 100644
    1222 function get_properties(entity::Ref{_Entity})
    1223
    1224 size = get_properties_size(entity)
    1225
    1226 properties = [get_property(entity, Cint(ii)) for ii = 1:size]
    1227
    1228 return properties
    1229 end
    1230
    1231 """
    1232 function set_id(entity::Ref{_Entity}, id::AbstractString)
    1233
    1234 Throws a `CaosDB.Exceptions.ClientException` since you are not allowed
    1235 to set the id of entities.
    1236 """
    1237 function set_id(entity::Ref{_Entity}, id::AbstractString)
    • The only case in which an explicit CaosDB exception is thrown since this is new to the C(++)-based clients in contrast to the Python clients. You don't create an entity, set its id, and retrieve it, but you just execute a retrieval transaction.

    • Please register or sign in to reply
  • src/Entity.jl 0 → 100644
    1498 (:caosdb_entity_entity_append_parent, CaosDB.library_name),
    1499 Cint,
    1500 (Ref{_Entity}, Ref{_Parent}),
    1501 entity,
    1502 parent,
    1503 )
    1504
    1505 CaosDB.Exceptions.evaluate_return_code(err_code)
    1506 end
    1507
    1508 """
    1509 function append_parents(entity::Ref{_Entity}, parents::Vector{Base.RefValue{_Parent}})
    1510
    1511 Append all given `parents` to the parents of the given `entity`.
    1512 """
    1513 function append_parents(entity::Ref{_Entity}, parents::Vector{Base.RefValue{_Parent}})
  • src/Entity.jl 0 → 100644
    1581 (:caosdb_entity_entity_remove_property, CaosDB.library_name),
    1582 Cint,
    1583 (Ref{_Entity}, Cint),
    1584 entity,
    1585 index - Cint(1),
    1586 )
    1587
    1588 CaosDB.Exceptions.evaluate_return_code(err_code)
    1589 end
    1590
    1591 """
    1592 function has_errors(entity::Ref{_Entity})
    1593
    1594 Return true if the given `entity` has errors, false otherwise.
    1595 """
    1596 function has_errors(entity::Ref{_Entity})
  • added 1 commit

    Compare with previous version

  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Loading
  • Please register or sign in to reply
    Loading