diff --git a/.docker/caosdb_client.json b/.docker/caosdb_client.json new file mode 100644 index 0000000000000000000000000000000000000000..8c63f71671910b425fbeb71a901ddc590ade9bb1 --- /dev/null +++ b/.docker/caosdb_client.json @@ -0,0 +1,27 @@ +{ + "connections": { + "default": "julialib-unittest", + "julialib-unittest": { + "host": "caosdb-server", + "port": 8443, + "authentication": { + "type": "plain", + "username": "admin", + "password": "caosdb" + } + }, + "local-caosdb": { + "host": "localhost", + "port": 8443, + "server_certificate_path": "some/path/cacert.pem", + "authentication": { + "type": "plain", + "username": "me", + "password": "secret!" + } + } + }, + "extension": { + "this is my": "special option" + } +} diff --git a/.docker/install_cpplib.sh b/.docker/install_cpplib.sh index 484938f13fde57c48d22cca03eec8af7641d73d9..35eb745a07e8f8cec176fb0bcb8531c634b54105 100755 --- a/.docker/install_cpplib.sh +++ b/.docker/install_cpplib.sh @@ -5,8 +5,10 @@ cd caosdb-cpplib # check if there is a crooesponding cpp branch. Use the default branch # if there isn't. if (git show-ref --verify --quiet refs/heads/$CI_COMMIT_REF_NAME) ; then + echo "git checkout $CI_COMMIT_REF_NAME" git checkout $CI_COMMIT_REF_NAME else + echo "git checkout $CPP_DEFAULT_BRANCH" git checkout $CPP_DEFAULT_BRANCH fi git submodule update --init --recursive diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2370f80dc8595d6828bb661edc6c4bc14322daf5..b58f19c15a34a7adc5ddc9d12859cf913b4e63e7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -25,6 +25,7 @@ variables: TRIGGERED_BY_HASH: $CI_COMMIT_SHORT_SHA # The defalt branch to use with caosdb-cpplib + # TODO: Change back to dev once f-consolidate-c has been merged. CPP_DEFAULT_BRANCH: dev image: $JULIALIB_REGISTRY_IMAGE @@ -100,6 +101,7 @@ test: script: - .docker/install_cpplib.sh - export LD_LIBRARY_PATH=/root/.local/lib:$LD_LIBRARY_PATH + - export CAOSDB_CLIENT_CONFIGURATION=$(pwd)/.docker/caosdb_client.json # Let's run the tests. Substitute `coverage = false` below, if you # do not want coverage results. - julia -e 'using Pkg; Pkg.add(path=pwd()); diff --git a/CHANGELOG.md b/CHANGELOG.md index b5fe7680ae1ec2f68631826fe246c539a1de036a..4fde278f4931ceae2d2e0f87d376c6d1b7f1e1dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Support for Windows * Support for config files * Minimal error handling +* Queries +* Retrieval by id(s) ### Changed diff --git a/Project.toml b/Project.toml index a17b1d50149fa14067e15ab9c8523b4b3cdbbafb..c62a453e14cf635f708a9b60c85da966b1d9cddc 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CaosDB" uuid = "091fdcf5-a163-4d8f-97f4-9adce40cd04e" authors = ["florian <f.spreckelsen@inidscale.com>"] -version = "0.0.2" +version = "0.0.3" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" diff --git a/README_SETUP.md b/README_SETUP.md index 801051111ad455b9b357bcfd51d936881e7dcb3a..e87f459387ba25d2b5e935fd66ac5386beaba916 100644 --- a/README_SETUP.md +++ b/README_SETUP.md @@ -35,6 +35,10 @@ the wrapped cpp library couldn't be loaded from the c interface) make sure you have the location of `libcaosdb.dylib` and `libccaosdb.dylib` in both, your `LD_LIBRARY_PATH` and your `DYLD_LIBRARY_PATH`. +## Configuration +The configuration is done the same way as the C++ client would do it. See +[caosdb-cpplib](https://gitlab.com/caosdb/caosdb-cpplib). + ## Tests After installing, the unit tests can be executed by @@ -44,6 +48,9 @@ julia> ]activate "/path/to/caosdb-julialib" julia> ]test ``` +Note that you must have a CaosDB client configuration (see above) with +a valid `"default"` configuration for all tests to succeed. + ## Code styling We use diff --git a/docs/src/api.md b/docs/src/api.md index 14cdb0cca131652c8f5b8ceeeb20edc3377a174a..c355666e6806bd2ad15b7b67f3dff008b985b4f0 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -5,16 +5,24 @@ interface. You also find the documentation of the internal API which is meant for expert use only. Only use those if you know what you're doing. +```@index +Order = [:module, :function, :macro, :type, :constant] +``` + ## Public API ```@autodocs -Modules=[CaosDB, CaosDB.Exceptions, CaosDB.Info, CaosDB.Utility, CaosDB.Connection, CaosDB.Authentication, CaosDB.Entity] +Modules=[CaosDB, CaosDB.Exceptions, CaosDB.Info, CaosDB.Utility, + CaosDB.Connection, CaosDB.Authentication, CaosDB.Entity, + CaosDB.Transaction] Private=false ``` ## Expert-use only API functions ```@autodocs -Modules=[CaosDB, CaosDB.Exceptions, CaosDB.Info, CaosDB.Utility, CaosDB.Connection, CaosDB.Authentication, CaosDB.Entity] +Modules=[CaosDB, CaosDB.Exceptions, CaosDB.Info, CaosDB.Utility, + CaosDB.Connection, CaosDB.Authentication, CaosDB.Entity, + CaosDB.Transaction] Public=false ``` diff --git a/docs/src/examples.md b/docs/src/examples.md index 248fb1ac5fce9af3ec103fd7eed508ecfd5afcc4..3998e66fb034746cefb443174657ccca0267f703 100644 --- a/docs/src/examples.md +++ b/docs/src/examples.md @@ -18,12 +18,128 @@ connection = CaosDB.Connection.connect() ``` which will establish a connection and print the version of the server -you are connected to. +you are connected to (since `connect` is exported, `connection = +connect()` works as well). ## Retrieve a Record +With CaosDB.jl you may use the same logic of creating a transaction +object, filling it with sub-requests, executing it, and retrieving the +result(s) as it is done in the [C++ +client](https://docs.indiscale.com/coasdb-cpplib) (see below). This is +handy when wanting to have several requests in the same +transaction. However, most of the time, e.g., when retrieving one or +multiple Records, this is not necessary. CaosDB.jl provides helper +functions so you don't have to worry about transactions and their +executions. Assume you want to retrieve an Entity with id=123. This is done via + +```julia +using CaosDB +entity = retrieve("123") +``` + +It is possible to specify the connection either by name or as a +connection object as created above in a second argument to +[`retrieve()`](@ref). + +You can then use the getter methods like [`get_name`](@ref), +[`get_parents`](@ref), or [`get_properties`](@ref) to get the name, +the parents, or the properties of the retrieved entity. + +Retrieving multiple entities works in the same way. Type + +```julia +results = retrieve(["123", "124", "125"]) +``` + +to retrieve the entities with ids 123, 124, and 125. They can then be +accessed as `results[1]`, `results[2]`, and `results[3]`, +respectively. Note that [`retrieve`](@ref) returns a single entity +when called with a single id whereas a vector of entities is returned +when called with a list of ids. + +The same (and more) can be achieved by building and executing the +transaction manually. This is done by + +```julia +transaction = create_transaction() +add_retrieve_by_id(transaction, "123") +add_retrieve_by_id(transaction, "124") +add_retrieve_by_id(transaction, "125") +execute_transaction(transaction) +results = get_results(transaction) +``` + +Again, a connection can be specified by name or object as an optional +argument in the call to [`create_transaction`](@ref). Optionally, and +in the same way as in the C++ client, a result set can be obtained via +[`get_result_set`](@ref) which contains the resulting entities and +can, e.g., be checked for length. However, since it is not necessary +to interact with the result set and one usually is interested in the +recieved entities proper, the result set can be omitted. As above, it +is also possible to add multiple id retrievals at once. + +```julia +transaction = create_transaction() +add_retrieve_by_id(transaction, ["123", "124", "125"]) +execute_transaction(transaction) +results = get_results(transaction) +``` + +is equivalent to the above Code. + ## Execute queries +Executing queries works very similar to the retrieval of entities via +ids. Again it is possible to create and execute a transaction manually +but most cases can be covered by the [`execute_query`](@ref) helper +function. + +### FIND and SELECT queries + +In general, entities can be found using CaosDB's query language like + +```julia +results = execute_query("FIND RECORD WITH name Like 'some_pattern' AND id>122") +``` + +`results` is a (possibly empty) vector of entities that can be +inspected as above. Similar to `retrieve`, a connection can be +specified by name or by a connection object in +[`execute_query`](@ref). If none is specified, the default connection +is used. The same result is achieved by constructing the transaction +manually: + +```julia +transaction = create_transaction() +add_query(transaction, "FIND RECORD WITH name Like 'some_pattern' AND id>122") +execute(transaction) +results = get_results(transaction) +``` + +!!! warning + + SELECT queries haven't been implemented in the C++ client yet and + thus cannot be executed from CaosDB.jl right now. SELECT queries + will be added in a future release. + +### COUNT queries + +COUNT queries are different from FIND or SELECT queries in two +ways. Firstly, they do not return entities but a single number which +is why, secondly, they do not have a result set that could be +returned. This is why they are currently not covered by the +`execute_query` function and have to be executed manually instead. The +result of the count is obtained using the [`get_count_result`](@ref) +function: + +```julia +transaction = create_transaction() +add_query(transaction, "COUNT RECORD person WITH NAME LIKE '*Baggins'") +execute(transaction) +count_result = get_count_result(transaction) +``` + ## Insert, update, and delete entities ## Download a file diff --git a/src/Authentication.jl b/src/Authentication.jl new file mode 100644 index 0000000000000000000000000000000000000000..9051a7ca7b71fdf88da2964ef14e273a58f64ad2 --- /dev/null +++ b/src/Authentication.jl @@ -0,0 +1,90 @@ +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# <https://www.gnu.org/licenses/>. +# +# ** end header +# +module Authentication + +using ..CaosDB + +""" +Struct containing a pointer to the wrapped cpp authenticator +class. Meant for internal use; call a +`CaosDB.Authentication.create_<authenticator>` function to create an +authenticator object from a configuration. +""" +mutable struct _Authenticator + wrapped_authenticator::Ptr{Cvoid} + _deletable::Bool + + function _Authenticator(managed_by_julia::Bool = false) + auth = new() + auth._deletable = false + if managed_by_julia + # Only append a finalizer for this if the object is + # actually managed by Julia and not created and destroyed + # internally by libcaosdb. + function f(t) + ccall( + (:caosdb_authentication_delete_authenticator, CaosDB.library_name), + Cint, + (Ref{_Authenticator},), + Ref{_Authenticator}(t), + ) + end + finalizer(f, auth) + end + return auth + end +end + +""" + create_plain_password_authenticator( + username::AbstractString, + password::AbstractString, + ) + +Return an authenticator object that contains a wrapped cpp +plain-password authenticator configured with `username` and +`password`. +""" +function create_plain_password_authenticator( + username::AbstractString, + password::AbstractString, +) + + auth = Ref{_Authenticator}(_Authenticator(true)) + + err_code = ccall( + (:caosdb_authentication_create_plain_password_authenticator, CaosDB.library_name), + Cint, + (Ref{_Authenticator}, Cstring, Cstring), + auth, + username, + password, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return auth + +end + +end # Authentication diff --git a/src/CaosDB.jl b/src/CaosDB.jl index 906400025ea1e789254dcb8539c1600be3aebf1a..b81fd4669dd3ea346f1819bf7bba98125ad3f03d 100644 --- a/src/CaosDB.jl +++ b/src/CaosDB.jl @@ -24,6 +24,74 @@ module CaosDB +# Exports from module Exceptions +export evaluate_return_code, + CaosDBException, ClientException, GenericCaosDBException, CaosDBMessage + +# Exports from module Utility +export get_env_fallback + +# Export from module Connection +export connect, connect_manually + +# Exports from module Entity +# Creators +export create_entity, + create_parent, create_property, create_property_entity, create_record, create_recordtype + +# getters +export get_id, + get_role, + get_name, + get_description, + get_datatype, + get_unit, + get_value, + get_version_id, + get_property, + get_properties, + get_parent, + get_parents, + get_error, + get_errors, + get_warning, + get_warnings, + get_info, + get_infos, + get_importance, + get_code + +# setters +export append_parent, + append_parents, + append_property, + append_properties, + remove_parent, + remove_property, + set_id, + set_role, + set_name, + set_description, + set_datatype, + set_unit, + set_value, + set_importance + +# helper functions +export has_errors, has_warnings + +# Exports from module Transaction +export create_transaction, + add_retrieve_by_id, + add_query, + execute, + get_result_set, + get_count_result, + get_result_at, + get_results, + execute_query, + retrieve + using Libdl """ @@ -34,186 +102,22 @@ if isempty(find_library(library_name)) @error "Could not find $library_name" end -module Exceptions - -export evaluate_return_code, CaosDBException, GenericCaosDBException, CaosDBMessage - -using Logging -using CaosDB - -""" -The parent type of all CaosDB errors that can also be used for testing. -""" -abstract type CaosDBException <: Exception end - -""" -A generic exception that will be raised in case of non-zero return -values of the calls to libccaosdb. May carry a message string and a -code. -""" -struct GenericCaosDBException <: CaosDBException - msg::String - code::Cint -end - -Base.showerror(io::IO, e::CaosDBException) = - print(io, "CaosDBException: ", e.msg, " (Status code ", e.code, ")") - -""" -Struct containing Messages and codes for status codes<0 that do not -correspond to errors or success. -""" -struct CaosDBMessage - msg::String - code::Cint -end - -Base.show(io::IO, m::CaosDBMessage) = print(io, m.msg, " (Status code ", m.code, ")") - -""" - function evaluate_return_code(code::Cint) - -Evaluate the return code of a libccaosdb ccall and raise a -`GenericCaosDBException` in case of a non-zero return code. -""" -function evaluate_return_code(code::Cint) - if code != 0 - msg = ccall( - (:caosdb_get_status_description, CaosDB.library_name), - Cstring, - (Cint,), - code, - ) - if code > 0 - throw(GenericCaosDBException(unsafe_string(msg), code)) - else - @info CaosDBMessage(unsafe_string(msg), code) - end - end -end - -end # Exceptions - -module Info +# include modules from other files -""" -Struct containing version information of the CaosDB server. Meant -mainly for internal usage; use `CaosDB.Connection.get_version_info` or -`CaosDB.Connection.print_version_info` to retrieve the version of the -connected CaosDB server. -""" -mutable struct _VersionInfo +include("Exceptions.jl") - major::Cint - minor::Cint - patch::Cint - pre_release::Cstring - build::Cstring - - _VersionInfo() = new() - -end +include("Info.jl") -end # Info +include("Utility.jl") -module Utility +include("Authentication.jl") -export get_env_var - -using ..CaosDB - -""" - get_env_var(var[, default]) - -Return the environmental variable `var` if it exists, `default` -otherwise. If no `default` is given an empty string is returned -instead. -""" -function get_env_var(var::AbstractString, default::AbstractString = "") - - ret = ccall( - (:caosdb_utility_get_env_var, CaosDB.library_name), - Cstring, - (Cstring, Cstring), - var, - default, - ) - - return unsafe_string(ret) - -end - -end # Utility - -module Authentication - -using ..CaosDB - -""" -Struct containing a pointer to the wrapped cpp authenticator -class. Meant for internal use; call a -`CaosDB.Authentication.create_<authenticator>` function to create an -authenticator object from a configuration. -""" -mutable struct _Authenticator - wrapped_authenticator::Ptr{Cvoid} - - function _Authenticator(managed_by_julia::Bool = false) - auth = new() - if managed_by_julia - # Only append a finalizer for this if the object is - # actually managed by Julia and not created and destroyed - # internally by libcaosdb. - function f(t) - ccall( - (:caosdb_authentication_delete_authenticator, CaosDB.library_name), - Cint, - (Ref{_Authenticator},), - Ref{_Authenticator}(t), - ) - end - finalizer(f, auth) - end - return auth - end -end - -""" - create_plain_password_authenticator( - username::AbstractString, - password::AbstractString, - ) - -Return an authenticator object that contains a wrapped cpp -plain-password authenticator configured with `username` and -`password`. -""" -function create_plain_password_authenticator( - username::AbstractString, - password::AbstractString, -) - - auth = Ref{_Authenticator}(_Authenticator(true)) - - err_code = ccall( - (:caosdb_authentication_create_plain_password_authenticator, CaosDB.library_name), - Cint, - (Ref{_Authenticator}, Cstring, Cstring), - auth, - username, - password, - ) - - CaosDB.Exceptions.evaluate_return_code(err_code) - - return auth - -end +include("Connection.jl") -end # Authentication +include("Entity.jl") -include("Connection.jl") +include("Transaction.jl") -module Entity end +using .Exceptions, .Info, .Authentication, .Connection, .Utility, .Entity, .Transaction end # CaosDB diff --git a/src/Connection.jl b/src/Connection.jl index 0f8e140a682e1e269b4ee395507b1ce66e54220a..cf106ef79d2aec145345853098fcac36d66a5c16 100644 --- a/src/Connection.jl +++ b/src/Connection.jl @@ -34,9 +34,11 @@ to create an connection object from a configuration. """ mutable struct _Connection wrapped_connection::Ptr{Cvoid} + _deletable::Bool function _Connection(managed_by_julia::Bool = false) conn = new() + conn._deletable = false if managed_by_julia # Only append a finalizer for this if the object is # actually managed by Julia and not created and destroyed @@ -63,9 +65,11 @@ an certificate-provider object from a configuration. """ mutable struct _CertificateProvider wrapped_certificate_provider::Ptr{Cvoid} + _deletable::Bool function _CertificateProvider(managed_by_julia::Bool = false) prov = new() + prov._deletable = false if managed_by_julia # Only append a finalizer for this if the object is # actually managed by Julia and not created and destroyed @@ -92,9 +96,11 @@ an connection-configuration object from a configuration. """ mutable struct _Configuration wrapped_connection_configuration::Ptr{Cvoid} + _deletable::Bool function _Configuration(managed_by_julia::Bool = false) config = new() + config._deletable = false if managed_by_julia # Only append a finalizer for this if the object is # actually managed by Julia and not created and destroyed @@ -358,13 +364,13 @@ function connect_manually(; if host == "" - host = CaosDB.Utility.get_env_var("CAOSDB_SERVER_HOST", "localhost") + host = CaosDB.Utility.get_env_fallback("CAOSDB_SERVER_HOST", "localhost") end if port_str == "undefined" - port_str = CaosDB.Utility.get_env_var("CAOSDB_SERVER_GRPC_PORT_HTTPS", "8443") + port_str = CaosDB.Utility.get_env_fallback("CAOSDB_SERVER_GRPC_PORT_HTTPS", "8443") end @@ -372,19 +378,19 @@ function connect_manually(; if cacert == "" - cacert = CaosDB.Utility.get_env_var("CAOSDB_SERVER_CERT") + cacert = CaosDB.Utility.get_env_fallback("CAOSDB_SERVER_CERT") end if username == "" - username = CaosDB.Utility.get_env_var("CAOSDB_USER", "admin") + username = CaosDB.Utility.get_env_fallback("CAOSDB_USER", "admin") end if password == "undefined" - password = CaosDB.Utility.get_env_var("CAOSDB_PASSWORD", "caosdb") + password = CaosDB.Utility.get_env_fallback("CAOSDB_PASSWORD", "caosdb") end diff --git a/src/Entity.jl b/src/Entity.jl new file mode 100644 index 0000000000000000000000000000000000000000..119729b789b6eca53d4ea0414b2c8f67d022d95c --- /dev/null +++ b/src/Entity.jl @@ -0,0 +1,1860 @@ +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# <https://www.gnu.org/licenses/>. +# +# ** end header +# +module Entity + +# Creators +export create_entity, + create_parent, create_property, create_property_entity, create_record, create_recordtype + +# getters +export get_id, + get_role, + get_name, + get_description, + get_datatype, + get_unit, + get_value, + get_version_id, + get_property, + get_properties, + get_parent, + get_parents, + get_error, + get_errors, + get_warning, + get_warnings, + get_info, + get_infos, + get_importance, + get_code + +# setters +export append_parent, + append_parents, + append_property, + append_properties, + remove_parent, + remove_property, + set_id, + set_role, + set_name, + set_description, + set_datatype, + set_unit, + set_value, + set_importance + +# helper functions +export has_errors, has_warnings + +using CaosDB + +# TODO(henrik, daniel) How much do we want to help the users when +# setting/getting the values? Should it only be allowed if the +# datatype of the entity/property is set? This might be too strong, +# especially in case of a property which I might want to specify by +# name, add a value, append it to a Record, and then insert it and +# might expect the server to care for the datatype. +# +# Also, how much do we want to support them with typos. Currently, C +# and C++ only understand all-capital strings for datatypes, +# importances, roles. What about user input like "record_type", +# "RecordType", "recordtype"? +""" +Struct containing a pointer to the wrapped cpp entity +object. Meant for internal use; use `CaosDB.Entity.create_entity` to +create an entity. +""" +mutable struct _Entity + wrapped_entity::Ptr{Cvoid} + _deletable::Bool + + function _Entity(managed_by_julia::Bool = false) + + entity = new() + entity._deletable = false + + if managed_by_julia + function f(t) + ccall( + (:caosdb_entity_delete_entity, CaosDB.library_name), + Cint, + (Ref{_Entity},), + Ref{_Entity}(t), + ) + end + finalizer(f, entity) + end + + return entity + end +end + + +""" +Struct containing a pointer to the wrapped cpp parent +object. Meant for internal use; use `CaosDB.Entity.create_parent` to +create an parent. +""" +mutable struct _Parent + wrapped_parent::Ptr{Cvoid} + _deletable::Bool + + function _Parent(managed_by_julia::Bool = false) + + parent = new() + parent._deletable = false + + if managed_by_julia + function f(t) + ccall( + (:caosdb_entity_delete_parent, CaosDB.library_name), + Cint, + (Ref{_Parent},), + Ref{_Parent}(t), + ) + end + finalizer(f, parent) + end + + return parent + end +end + + +""" +Struct containing a pointer to the wrapped cpp property +object. Meant for internal use; use `CaosDB.Entity.create_property` to +create an property. +""" +mutable struct _Property + wrapped_property::Ptr{Cvoid} + _deletable::Bool + + function _Property(managed_by_julia::Bool = false) + + property = new() + property._deletable = false + + if managed_by_julia + function f(t) + ccall( + (:caosdb_entity_delete_property, CaosDB.library_name), + Cint, + (Ref{_Property},), + Ref{_Property}(t), + ) + end + finalizer(f, property) + end + + return property + end +end + +""" +Struct containing a pointer to the wrapped cpp messsage object. Meant +for internal use only; don't create it by yourself. +""" +mutable struct _Message + wrapped_message::Ptr{Cvoid} + _deletable::Bool + + function _Message() + mess = new() + mess._deletable = false + return mess + end +end + +""" + function create_entity(name::AbstractString = "") + +Return a new entity object. If a `name` was provided, set the name of +the entity correspondingly. +""" +function create_entity(name::AbstractString = "") + + entity = Ref{_Entity}(_Entity(true)) + + err_code = ccall( + (:caosdb_entity_create_entity, CaosDB.library_name), + Cint, + (Ref{_Entity},), + entity, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + if name != "" + set_name(entity, name) + end + + return entity + +end + +""" + function create_recordtype(name::AbstractString = "") + +Return a new entity object with role RecordType. If a `name` was +provided, its name is set accordingly. +""" +function create_recordtype(name::AbstractString = "") + + record_type = create_entity(name) + + set_role(record_type, "RECORD_TYPE") + + return record_type + +end + +""" + function create_record(name::AbstractString = "") + +Return a new entity object with role Record. If a `name` was provided, +its name is set accordingly. +""" +function create_record(name::AbstractString = "") + + record = create_entity(name) + + set_role(record, "RECORD") + + return record + +end + +""" + function create_property_entity(; + name::AbstractString = "", + datatype::AbstractString = "", + unit::AbstractString = "", + is_reference::Bool = false, + is_list::Bool = false, + ) + +Return a new entity object with role Record. If `name`, `datatype`, or +`unit` were provided, its name, datatype (including whether it is a +reference or a list), or unit are set accordingly. +""" +function create_property_entity(; + name::AbstractString = "", + datatype::AbstractString = "", + unit::AbstractString = "", + is_reference::Bool = false, + is_list::Bool = false, +) + + property = create_entity(name) + + set_role(property, "PROPERTY") + + if datatype != "" + set_datatype(property, datatype, is_list = is_list, is_reference = is_reference) + end + + if unit != "" + set_unit(property, unit) + end + + return property +end + +""" + function create_property(; + name::AbstractString = "", + id::AbstractString = "", + value::Union{AbstractString, Number, Bool} = "", + datatype::AbstractString = "", + is_reference::Bool = false, + is_list::Bool = false, + ) + +Create a property object that can be appended to another `_Entity`. If +`name`, `id`, or `value` are given, the property's name, id, or value +are set accordingly. The datatype and whether it is a list or a +reference can be specified with `datatype`, `is_reference`, and +`is_list`, respectively. + +!!! info + + This is not an `_Entity` that could be inserted or updated by its + own but a `_Property` that is to be appended to another `_Entity`. +""" +function create_property(; + name::AbstractString = "", + id::AbstractString = "", + value::Union{AbstractString,Number,Bool} = "", + datatype::AbstractString = "", + is_reference::Bool = false, + is_list::Bool = false, +) + property = Ref{_Property}(_Property(true)) + + err_code = ccall( + (:caosdb_entity_create_property, CaosDB.library_name), + Cint, + (Ref{_Property},), + property, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + if name != "" + set_name(property, name) + end + + if id != "" + set_id(property, id) + end + + if datatype != "" + set_datatype(property, datatype, is_reference = is_reference, is_list = is_list) + end + + if value != "" + set_value(property, value) + end + + return property + +end + +""" + function create_parent(; name::AbstractString = "", id::AbstractString = "") + +Create a parent object that can be appended to another `_Entity`. If +`name`, `id`, or `value` are given, the parent's name, id, or value +are set accordingly. + +!!! info + + This is not an `_Entity` that could be inserted or updated by its + own but a `_Parent` that is to be appended to another `_Entity`. +""" +function create_parent(; name::AbstractString = "", id::AbstractString = "") + + parent = Ref{_Parent}(_Parent(true)) + + err_code = ccall( + (:caosdb_entity_create_parent, CaosDB.library_name), + Cint, + (Ref{_Parent},), + parent, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + if name != "" + set_name(parent, name) + end + + if id != "" + set_id(parent, id) + end + + return parent +end + +""" + function get_id(entity::Ref{_Entity}) + +Return the id of the given `entity` +""" +function get_id(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_entity_get_id, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + entity, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_id(entity::Ref{_Parent}) + +Return the id of the given `parent` +""" +function get_id(parent::Ref{_Parent}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_parent_get_id, CaosDB.library_name), + Cint, + (Ref{_Parent}, Ref{Ptr{UInt8}}), + parent, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_id(entity::Ref{_Property}) + +Return the id of the given `property` +""" +function get_id(property::Ref{_Property}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_property_get_id, CaosDB.library_name), + Cint, + (Ref{_Property}, Ref{Ptr{UInt8}}), + property, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_role(entity::Ref{_Entity}) + +Return the role of the given `entity`. +""" +function get_role(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_entity_get_role, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + entity, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_name(entity::Ref{_Entity}) + +Return the name of the given `entity` +""" +function get_name(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_entity_get_name, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + entity, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_name(entity::Ref{_Parent}) + +Return the name of the given `parent` +""" +function get_name(parent::Ref{_Parent}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_parent_get_name, CaosDB.library_name), + Cint, + (Ref{_Parent}, Ref{Ptr{UInt8}}), + parent, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_name(entity::Ref{_Property}) + +Return the name of the given `property` +""" +function get_name(property::Ref{_Property}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_property_get_name, CaosDB.library_name), + Cint, + (Ref{_Property}, Ref{Ptr{UInt8}}), + property, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_description(entity::Ref{_Entity}) + +Return the description of the given `entity` +""" +function get_description(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_entity_get_description, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + entity, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_description(entity::Ref{_Parent}) + +Return the description of the given `parent` +""" +function get_description(parent::Ref{_Parent}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_parent_get_description, CaosDB.library_name), + Cint, + (Ref{_Parent}, Ref{Ptr{UInt8}}), + parent, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_description(entity::Ref{_Property}) + +Return the description of the given `property` +""" +function get_description(property::Ref{_Property}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_property_get_description, CaosDB.library_name), + Cint, + (Ref{_Property}, Ref{Ptr{UInt8}}), + property, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_description(entity::Ref{_Message}) + +Return the description of the given `message` +""" +function get_description(message::Ref{_Message}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_message_get_description, CaosDB.library_name), + Cint, + (Ref{_Message}, Ref{Ptr{UInt8}}), + message, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +# TODO(henrik, daniel) Is this the best way to return name, is_ref, +# and is_list to the users? Should we split it up? +""" + function get_datatype(entity::Ref{_Entity}) + +Return a tuple that contains the name of the datatype of the given +`entity`, whether it is a reference, and whether it is a list. +""" +function get_datatype(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + is_ref = Ref{Bool}(false) + is_list = Ref{Bool}(false) + + err_code = ccall( + (:caosdb_entity_entity_get_datatype, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}, Ref{Bool}, Ref{Bool}), + entity, + out, + is_ref, + is_list, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]), is_ref[], is_list[] +end + +""" + function get_datatype(entity::Ref{_Property}) + +Return a tuple that contains the name of the datatype of the given +`property`, whether it is a reference, and whether it is a list. +""" +function get_datatype(property::Ref{_Property}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + is_ref = Ref{Bool}(false) + is_list = Ref{Bool}(false) + + err_code = ccall( + (:caosdb_entity_property_get_datatype, CaosDB.library_name), + Cint, + (Ref{_Property}, Ref{Ptr{UInt8}}, Ref{Bool}, Ref{Bool}), + property, + out, + is_ref, + is_list, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]), is_ref[], is_list[] +end + +""" + function get_unit(entity::Ref{_Entity}) + +Return the unit of the given `entity` +""" +function get_unit(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_entity_get_unit, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + entity, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_unit(entity::Ref{_Property}) + +Return the unit of the given `property` +""" +function get_unit(property::Ref{_Property}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_property_get_unit, CaosDB.library_name), + Cint, + (Ref{_Property}, Ref{Ptr{UInt8}}), + property, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +# TODO(henrik,daniel) Replace ccall within the `get_value` function by +# a check of the datatype and then call the correct function in case +# of scalar values or construct a vector and fill it with all values +# in case of list values. +""" + function get_value(entity::Ref{_Entity}) + +Return the value of the given `entity` +""" +function get_value(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_entity_get_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + entity, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_value(entity::Ref{_Property}) + +Return the value of the given `property` +""" +function get_value(property::Ref{_Property}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_property_get_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Ref{Ptr{UInt8}}), + property, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_version_id(entity::Ref{_Entity}) + +Return the version_id of the given `entity` +""" +function get_version_id(entity::Ref{_Entity}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_entity_get_version_id, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + entity, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_code(message::Ref{_Message}) + +Return the code of the given `message`. +""" +function get_code(message::Ref{_Message}) + + code = Ref{Cint}(0) + + err_code = ccall( + (:caosdb_entity_message_get_code, CaosDB.library_name), + Cint, + (Ref{_Message}, Ref{Cint}), + message, + code, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return code + +end + +""" + function get_importance(property::Ref{_Property}) + +Return the importance of the given `property` +""" +function get_importance(property::Ref{_Property}) + + out = Ref{Ptr{UInt8}}(Ptr{UInt8}()) + + err_code = ccall( + (:caosdb_entity_property_get_importance, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Ptr{UInt8}}), + property, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return unsafe_string(out[]) +end + +""" + function get_errors_size(entity::Ref{_Entity}) + +Return the number of error messages attached to the given `entity`. +""" +function get_errors_size(entity::Ref{_Entity}) + + size = Ref{Cint}(0) + + err_code = ccall( + (:caosdb_entity_entity_get_errors_size, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Cint}), + entity, + size, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return size[] + +end + +""" + function get_error(entity::Ref{_Entity}, index::Cint) + +Return the error message of the given `entity` with the provided +`index`. +""" +function get_error(entity::Ref{_Entity}, index::Cint) + + size = get_errors_size(entity) + + if index > size + throw( + DomainError( + index, + "You tried to access the error at position $index but the entity only has $size errors.", + ), + ) + end + + error = Ref{_Message}(_Message()) + + err_code = ccall( + (:caosdb_entity_entity_get_error, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cint), + entity, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + message_text = get_description(error) + + code = get_code(error) + + return CaosDB.Exceptions.CaosDBMessage(message_text, code) +end + +""" + function get_error(entity::Ref{_Entity}, index::Integer) + +Convenience wrapper for `get_error(::Ref{_Entity}, ::Cint)`. Convert +`index` to Int32 and return the error at position `index` of the given +`entity`. +""" +function get_error(entity::Ref{_Entity}, index::Integer) + + return get_error(entity, Cint(index)) + +end + +""" + function get_warnings_size(entity::Ref{_Entity}) + +Return the number of warning messages attached to the given `entity`. +""" +function get_warnings_size(entity::Ref{_Entity}) + + size = Ref{Cint}(0) + + err_code = ccall( + (:caosdb_entity_entity_get_warnings_size, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Cint}), + entity, + size, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return size[] + +end + +""" + function get_warning(entity::Ref{_Entity}, index::Cint) + +Return the warning message of the given `entity` with the provided +`index`. +""" +function get_warning(entity::Ref{_Entity}, index::Cint) + + size = get_warnings_size(entity) + + if index > size + throw( + DomainError( + index, + "You tried to access the warning at position $index but the entity only has $size warnings.", + ), + ) + end + + warning = Ref{_Message}(_Message()) + + err_code = ccall( + (:caosdb_entity_entity_get_warning, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cint), + entity, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + message_text = get_description(warning) + + code = get_code(warning) + + return CaosDB.Exceptions.CaosDBMessage(message_text, code) +end + +""" + function get_warning(entity::Ref{_Entity}, index::Integer) + +Convenience wrapper for `get_warning(::Ref{_Entity}, ::Cint)`. Convert +`index` to Int32 and return the warning at position `index` of the given +`entity`. +""" +function get_warning(entity::Ref{_Entity}, index::Integer) + + return get_warning(entity, Cint(index)) + +end + +""" + function get_infos_size(entity::Ref{_Entity}) + +Return the number of info messages attached to the given `entity`. +""" +function get_infos_size(entity::Ref{_Entity}) + + size = Ref{Cint}(0) + + err_code = ccall( + (:caosdb_entity_entity_get_infos_size, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Cint}), + entity, + size, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return size[] + +end + +""" + function get_info(entity::Ref{_Entity}, index::Cint) + +Return the info message of the given `entity` with the provided +`index`. +""" +function get_info(entity::Ref{_Entity}, index::Cint) + + size = get_infos_size(entity) + + if index > size + throw( + DomainError( + index, + "You tried to access the info at position $index but the entity only has $size infos.", + ), + ) + end + + info = Ref{_Message}(_Message()) + + err_code = ccall( + (:caosdb_entity_entity_get_info, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cint), + entity, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + message_text = get_description(info) + + code = get_code(info) + + return CaosDB.Exceptions.CaosDBMessage(message_text, code) +end + +""" + function get_info(entity::Ref{_Entity}, index::Integer) + +Convenience wrapper for `get_info(::Ref{_Entity}, ::Cint)`. Convert +`index` to Int32 and return the info at position `index` of the given +`entity`. +""" +function get_info(entity::Ref{_Entity}, index::Integer) + + return get_info(entity, Cint(index)) + +end + + +""" + function get_errors(entity::Ref{_Entity}) + +Return a Vector of all error messages attached to the given `entity`. +""" +function get_errors(entity::Ref{_Entity}) + + size = get_errors_size(entity) + + errors = [get_error(entity, Cint(ii)) for ii = 1:size] + + return errors +end + +""" + function get_warnings(entity::Ref{_Entity}) + +Return a Vector of all warning messages attached to the given `entity`. +""" +function get_warnings(entity::Ref{_Entity}) + + size = get_warnings_size(entity) + + warnings = [get_warning(entity, Cint(ii)) for ii = 1:size] + + return warnings +end + + +""" + function get_warnings(entity::Ref{_Entity}) + +Return a Vector of all info messages attached to the given `entity`. +""" +function get_infos(entity::Ref{_Entity}) + + size = get_infos_size(entity) + + infos = [get_info(entity, Cint(ii)) for ii = 1:size] + + return infos +end + +""" + function get_parents_size(entity::Ref{_Entity}) + +Return the number of parents attached to the given `entity`. +""" +function get_parents_size(entity::Ref{_Entity}) + + size = Ref{Cint}(0) + + err_code = ccall( + (:caosdb_entity_entity_get_parents_size, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Cint}), + entity, + size, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return size[] + +end + +""" + function get_parent(entity::Ref{_Entity}, index::Cint) + +Return the parent of the given `entity` at position `index`. +""" +function get_parent(entity::Ref{_Entity}, index::Cint) + + size = get_parents_size(entity) + + if index > size + throw( + DomainError( + index, + "You tried to access the parent at position $index but the entity only has $size parents.", + ), + ) + end + + parent = Ref{_Parent}(_Parent()) + + err_code = ccall( + (:caosdb_entity_entity_get_parent, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{_Parent}, Cint), + entity, + parent, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return parent +end + +""" + function get_parent(entity::Ref{_Entity}, index::Integer) + +Convenience wrapper for `get_parent(::Ref{_Entity}, ::Cint)`. Convert +`index` to Int32 and return the parent at position `index` of the given +`entity`. +""" +function get_parent(entity::Ref{_Entity}, index::Integer) + + return get_parent(entity, Cint(index)) + +end + + +""" + function get_parents(entity::Ref{_Entity}) + +Return the vector of all parents of the given `entity`. +""" +function get_parents(entity::Ref{_Entity}) + + size = get_parents_size(entity) + + parents = [get_parent(entity, Cint(ii)) for ii = 1:size] + + return parents +end + +""" + function get_properties_size(entity::Ref{_Entity}) + +Return the number of properties attached to the given `entity`. +""" +function get_properties_size(entity::Ref{_Entity}) + + size = Ref{Cint}(0) + + err_code = ccall( + (:caosdb_entity_entity_get_properties_size, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{Cint}), + entity, + size, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return size[] + +end + +""" + function get_property(entity::Ref{_Entity}, index::Cint) + +Return the property of the given `entity` at position `index`. +""" +function get_property(entity::Ref{_Entity}, index::Cint) + + size = get_properties_size(entity) + + if index > size + throw( + DomainError( + index, + "You tried to access the property at position $index but the entity only has $size properties.", + ), + ) + end + + property = Ref{_Property}(_Property()) + + err_code = ccall( + (:caosdb_entity_entity_get_property, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{_Property}, Cint), + entity, + property, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return property +end + + +""" + function get_property(entity::Ref{_Entity}, index::Integer) + +Convenience wrapper for `get_property(::Ref{_Entity}, ::Cint)`. Convert +`index` to Int32 and return the property at position `index` of the given +`entity`. +""" +function get_property(entity::Ref{_Entity}, index::Integer) + + return get_property(entity, Cint(index)) + +end + + +""" + function get_properties(entity::Ref{_Entity}) + +Return the vector of all properties of the given `entity`. +""" +function get_properties(entity::Ref{_Entity}) + + size = get_properties_size(entity) + + properties = [get_property(entity, Cint(ii)) for ii = 1:size] + + return properties +end + +""" + function set_id(entity::Ref{_Entity}, id::AbstractString) + +Throws a `CaosDB.Exceptions.ClientException` since you are not allowed +to set the id of entities. +""" +function set_id(entity::Ref{_Entity}, id::AbstractString) + throw( + CaosDB.Exceptions.ClientException( + "You cannot set the id of an entity object. +If you want to update an entity with this id, you have to retrieve it first.", + ), + ) +end + +""" + function set_id(parent::Ref{_Parent}, id::AbstractString) + +Set the id of the given `parent` object. +""" +function set_id(parent::Ref{_Parent}, id::AbstractString) + err_code = ccall( + (:caosdb_entity_parent_set_id, CaosDB.library_name), + Cint, + (Ref{_Parent}, Cstring), + parent, + id, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function set_id(property::Ref{_Property}, id::AbstractString) + +Set the id of the given `property` object. +""" +function set_id(property::Ref{_Property}, id::AbstractString) + err_code = ccall( + (:caosdb_entity_property_set_id, CaosDB.library_name), + Cint, + (Ref{_Property}, Cstring), + property, + id, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function set_role(entity::Ref{_Entity}, role::AbstractString) + +Set the role of the given `entity` object. +""" +function set_role(entity::Ref{_Entity}, role::AbstractString) + err_code = ccall( + (:caosdb_entity_entity_set_role, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cstring), + entity, + role, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + + +""" + function set_name(entity::Ref{_Entity}, name::AbstractString) + +Set the name of the given `entity` object. +""" +function set_name(entity::Ref{_Entity}, name::AbstractString) + err_code = ccall( + (:caosdb_entity_entity_set_name, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cstring), + entity, + name, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + + +""" + function set_name(property::Ref{_Property}, name::AbstractString) + +Set the name of the given `property` object. +""" +function set_name(property::Ref{_Property}, name::AbstractString) + err_code = ccall( + (:caosdb_entity_property_set_name, CaosDB.library_name), + Cint, + (Ref{_Property}, Cstring), + property, + name, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + + +""" + function set_name(parent::Ref{_Parent}, name::AbstractString) + +Set the name of the given `parent` object. +""" +function set_name(parent::Ref{_Parent}, name::AbstractString) + err_code = ccall( + (:caosdb_entity_parent_set_name, CaosDB.library_name), + Cint, + (Ref{_Parent}, Cstring), + parent, + name, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function set_description(entity::Ref{_Entity}, description::AbstractString) + +Set the description of the given `entity` object. +""" +function set_description(entity::Ref{_Entity}, description::AbstractString) + err_code = ccall( + (:caosdb_entity_entity_set_description, CaosDB.library_description), + Cint, + (Ref{_Entity}, Cstring), + entity, + description, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +# TODO(henrik, daniel) Convenience functions for lists and references, +# too? +""" + function set_datatype( + entity::Ref{_Entity}, + datatype::AbstractString; + is_list::Bool = false, + is_reference::Bool = false, + ) + +Set the datatype of the given `entity` object to the given `datatype` +name. Only possible if the role of the entity is "PROPERTY". Specify +whether it is a reference or a list with the optional `is_list` and +`is_reference` keyword arguments. If the datatype is not a reference, +it is checked against the Atomic Datatypes known to CaosDB and an +exception is thrown if it is not known. +""" +function set_datatype( + entity::Ref{_Entity}, + datatype::AbstractString; + is_list::Bool = false, + is_reference::Bool = false, +) + + if get_role(entity) != "PROPERTY" + throw( + CaosDB.Exceptions.ClientException( + "Only entities with role PROPERTY can be assigned a datatype.", + ), + ) + end + + err_code = ccall( + (:caosdb_entity_entity_set_datatype, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cstring, Bool, Bool), + entity, + datatype, + is_reference, + is_list, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + + +""" + function set_datatype( + property::Ref{_Property}, + datatype::AbstractString; + is_list::Bool = false, + is_reference::Bool = false, + ) + +Set the datatype of the given `property` object to the given +`datatype` name. Specify whether it is a reference or a list with the +optional `is_list` and `is_reference` keyword arguments. If the +datatype is not a reference, it is checked against the Atomic +Datatypes known to CaosDB and an exception is thrown if it is not +known. +""" +function set_datatype( + property::Ref{_Property}, + datatype::AbstractString; + is_list::Bool = false, + is_reference::Bool = false, +) + err_code = ccall( + (:caosdb_entity_property_set_datatype, CaosDB.library_name), + Cint, + (Ref{_Property}, Cstring, Bool, Bool), + property, + datatype, + is_reference, + is_list, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function set_unit(entity::Ref{_Entity}, unit::AbstractString) + +Set the unit of the given `entity` object. +""" +function set_unit(entity::Ref{_Entity}, unit::AbstractString) + err_code = ccall( + (:caosdb_entity_entity_set_unit, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cstring), + entity, + unit, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + + +""" + function set_unit(property::Ref{_Property}, unit::AbstractString) + +Set the unit of the given `property` object. +""" +function set_unit(property::Ref{_Property}, unit::AbstractString) + err_code = ccall( + (:caosdb_entity_property_set_unit, CaosDB.library_name), + Cint, + (Ref{_Property}, Cstring), + property, + unit, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +# TODO(henrik, daniel) There is no check for the correct datatype +# here. Should we add this? +""" + function set_value( + entity::Ref{_Entity}, + value::Union{AbstractString,Number,Bool,Vector{T}}, + ) where {T<:Union{AbstractString,Number,Bool}} + + +Set the value of the given `entity` object. Throw an error if it +doesn't have the correct role. The value must be either string, +Boolean, Integer, Float, or a vector thereof. +""" +function set_value( + entity::Ref{_Entity}, + value::Union{AbstractString,Number,Bool,Vector{T}}, +) where {T<:Union{AbstractString,Number,Bool}} + + if get_role(entity) != "PROPERTY" + throw( + CaosDB.Exceptions.ClientException( + "Only entites with role PROPERTY may be assigned a value.", + ), + ) + end + + in_type = typeof(value) + if in_type <: AbstractString + err_code = ccall( + (:caosdb_entity_entity_set_string_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cstring), + entity, + value, + ) + elseif in_type <: Bool + err_code = ccall( + (:caosdb_entity_entity_set_boolean_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Bool), + entity, + value, + ) + elseif in_type <: Integer + err_code = ccall( + (:caosdb_entity_entity_set_int_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Clong), + entity, + Clong(value), + ) + elseif in_type <: Number + err_code = ccall( + (:caosdb_entity_entity_set_int_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cdouble), + entity, + Cdouble(value), + ) + else + # Type is a vector now + length = Cint(length(value)) + if in_type <: Vector{T} where {T<:AbstractString} + err_code = ccall( + (:caosdb_entity_entity_set_string_list_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ptr{Ptr{Cchar}}, Cint), + entity, + value, + length, + ) + elseif in_type <: Vector{T} where {T<:Bool} + err_code = ccall( + (:caosdb_entity_entity_set_boolean_list_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ptr{Bool}, Cint), + entity, + value, + length, + ) + elseif in_type <: Vector{T} where {T<:Integer} + err_code = ccall( + (:caosdb_entity_entity_set_int_list_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ptr{Clong}, Cint), + entity, + Vector{Clong}(value), + length, + ) + elseif integer <: Vector{T} where {T<:Number} + err_code = ccall( + (:caosdb_entity_entity_set_int_list_value, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ptr{Cdouble}, Cint), + entity, + Vector{Cdouble}(value), + length, + ) + end + end + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + + +""" + function set_value(property::Ref{_Property}, value::AbstractString) + +Set the value of the given `property` object. +""" +function set_value( + property::Ref{_Property}, + value::Union{AbstractString,Number,Bool,Vector{T}}, +) where {T<:Union{AbstractString,Number,Bool}} + + in_type = typeof(value) + if in_type <: AbstractString + err_code = ccall( + (:caosdb_entity_property_set_string_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Cstring), + property, + value, + ) + elseif in_type <: Bool + err_code = ccall( + (:caosdb_entity_property_set_boolean_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Bool), + property, + value, + ) + elseif in_type <: Integer + err_code = ccall( + (:caosdb_entity_property_set_int_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Clong), + property, + Clong(value), + ) + elseif in_type <: Number + err_code = ccall( + (:caosdb_entity_property_set_int_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Cdouble), + property, + Cdouble(value), + ) + else + # Type is a vector now + length = Cint(length(value)) + if in_type <: Vector{T} where {T<:AbstractString} + err_code = ccall( + (:caosdb_entity_property_set_string_list_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Ptr{Ptr{Cchar}}, Cint), + property, + value, + length, + ) + elseif in_type <: Vector{T} where {T<:Bool} + err_code = ccall( + (:caosdb_entity_property_set_boolean_list_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Ptr{Bool}, Cint), + property, + value, + length, + ) + elseif in_type <: Vector{T} where {T<:Integer} + err_code = ccall( + (:caosdb_entity_property_set_int_list_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Ptr{Clong}, Cint), + property, + Vector{Clong}(value), + length, + ) + elseif integer <: Vector{T} where {T<:Number} + err_code = ccall( + (:caosdb_entity_property_set_int_list_value, CaosDB.library_name), + Cint, + (Ref{_Property}, Ptr{Cdouble}, Cint), + property, + Vector{Cdouble}(value), + length, + ) + end + end + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function set_importance(property::Ref{_Property}, importance::AbstractString) + +Set the importance of the given `property` object. +""" +function set_importance(property::Ref{_Property}, importance::AbstractString) + err_code = ccall( + (:caosdb_entity_property_set_importance, CaosDB.library_name), + Cint, + (Ref{_Property}, Cstring), + property, + importance, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function append_parent(entity::Ref{_Entity}, parent::Ref{_Parent}) + +Append the given `parent` to the parents of the given `entity`. +""" +function append_parent(entity::Ref{_Entity}, parent::Ref{_Parent}) + + err_code = ccall( + (:caosdb_entity_entity_append_parent, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{_Parent}), + entity, + parent, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function append_parents(entity::Ref{_Entity}, parents::Vector{Base.RefValue{_Parent}}) + +Append all given `parents` to the parents of the given `entity`. +""" +function append_parents(entity::Ref{_Entity}, parents::Vector{Base.RefValue{_Parent}}) + + for parent in parents + append_parent(entity, parent) + end +end + +""" + function remove_parent(entity::Ref{_Entity}, index::Cint) + +Remove the parent at position `index` from the parents of the given +entity. +""" +function remove_parent(entity::Ref{_Entity}, index::Cint) + + err_code = ccall( + (:caosdb_entity_entity_remove_parent, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cint), + entity, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function append_property(entity::Ref{_Entity}, property::Ref{_Property}) + +Append the given `property` to the properties of the given `entity`. +""" +function append_property(entity::Ref{_Entity}, property::Ref{_Property}) + + err_code = ccall( + (:caosdb_entity_entity_append_property, CaosDB.library_name), + Cint, + (Ref{_Entity}, Ref{_Property}), + entity, + property, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function append_properties(entity::Ref{_Entity}, properties::Vector{Base.RefValue{_Property}}) + +Append all given `properties` to the properties of the given `entity`. +""" +function append_properties( + entity::Ref{_Entity}, + properties::Vector{Base.RefValue{_Property}}, +) + + for property in properties + append_property(entity, property) + end +end + +""" + function remove_property(entity::Ref{_Entity}, index::Cint) + +Remove the property at position `index` from the properties of the given +entity. +""" +function remove_property(entity::Ref{_Entity}, index::Cint) + + err_code = ccall( + (:caosdb_entity_entity_remove_property, CaosDB.library_name), + Cint, + (Ref{_Entity}, Cint), + entity, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function has_errors(entity::Ref{_Entity}) + +Return true if the given `entity` has errors, false otherwise. +""" +function has_errors(entity::Ref{_Entity}) + + return get_errors_size(entity) > 0 + +end + +""" + function has_warnings(entity::Ref{_Entity}) + +Return true if the given `entity` has warnings, false otherwise. +""" +function has_warnings(entity::Ref{_Entity}) + + return get_warnings_size(entity) > 0 + +end + +end diff --git a/src/Exceptions.jl b/src/Exceptions.jl new file mode 100644 index 0000000000000000000000000000000000000000..3ffd0eeabd1947e67c03ac04e336587a94c6b2a4 --- /dev/null +++ b/src/Exceptions.jl @@ -0,0 +1,99 @@ +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> +# Copyright (C) 2021 Alexander Kreft +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# <https://www.gnu.org/licenses/>. +# +# ** end header +# +module Exceptions + +export evaluate_return_code, + CaosDBException, ClientException, GenericCaosDBException, CaosDBMessage + +using Logging +using CaosDB + +""" +The parent type of all CaosDB errors that can also be used for testing. +""" +abstract type CaosDBException <: Exception end + +""" +A generic exception that will be raised in case of non-zero return +values of the calls to libccaosdb. May carry a message string and a +code. +""" +struct GenericCaosDBException <: CaosDBException + msg::String + code::Cint +end + +""" +Something went wrong on the client-side or the user is attempting to +conduct an invalid operation. +""" +struct ClientException <: CaosDBException + msg::String + code::Cint + + function ClientException(message::AbstractString) + client_error_code = + ccall((:caosdb_status_code_OTHER_CLIENT_ERROR, CaosDB.library_name), Cint, ()) + new(message, client_error_code) + end +end + + +Base.showerror(io::IO, e::CaosDBException) = + print(io, "CaosDBException: ", e.msg, " (Status code ", e.code, ")") + +""" +Struct containing Messages and codes for status codes<0 that do not +correspond to errors or success. +""" +struct CaosDBMessage + msg::String + code::Cint +end + +Base.show(io::IO, m::CaosDBMessage) = print(io, m.msg, " (Status code ", m.code, ")") + +""" + function evaluate_return_code(code::Cint) + +Evaluate the return code of a libccaosdb ccall and raise a +`GenericCaosDBException` in case of a non-zero return code. +""" +function evaluate_return_code(code::Cint) + if code != 0 + msg = ccall( + (:caosdb_get_status_description, CaosDB.library_name), + Cstring, + (Cint,), + code, + ) + if code > 0 + throw(GenericCaosDBException(unsafe_string(msg), code)) + else + @info CaosDBMessage(unsafe_string(msg), code) + end + end +end + +end # Exceptions diff --git a/src/Info.jl b/src/Info.jl new file mode 100644 index 0000000000000000000000000000000000000000..8eaf328499d0fd027f5d02198e237953251efb18 --- /dev/null +++ b/src/Info.jl @@ -0,0 +1,43 @@ +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# <https://www.gnu.org/licenses/>. +# +# ** end header +# +module Info + +""" +Struct containing version information of the CaosDB server. Meant +mainly for internal usage; use `CaosDB.Connection.get_version_info` or +`CaosDB.Connection.print_version_info` to retrieve the version of the +connected CaosDB server. +""" +mutable struct _VersionInfo + + major::Cint + minor::Cint + patch::Cint + pre_release::Cstring + build::Cstring + + _VersionInfo() = new() + +end + +end # Info diff --git a/src/Transaction.jl b/src/Transaction.jl new file mode 100644 index 0000000000000000000000000000000000000000..2308048d9c5f5955d0ba933cb061dc9561c6d702 --- /dev/null +++ b/src/Transaction.jl @@ -0,0 +1,502 @@ +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# <https://www.gnu.org/licenses/>. +# +# ** end header +# + +module Transaction + +export create_transaction, + add_retrieve_by_id, + add_query, + execute, + get_result_set, + get_count_result, + get_result_at, + get_results, + execute_query, + retrieve + +using CaosDB + +""" +Struct containing a pointer to the wrapped cpp transaction +object. Meant for internal use; call +`CaosDB.Transaction.create_transaction` to create a transaction +object. +""" +mutable struct _Transaction + wrapped_transaction::Ptr{Cvoid} + _deletable::Bool + function _Transaction(managed_by_julia::Bool = false) + trans = new() + trans._deletable = false + if managed_by_julia + function f(t) + ccall( + (:caosdb_transaction_delete_transaction, CaosDB.library_name), + Cint, + (Ref{_Transaction},), + Ref{_Transaction}(t), + ) + end + finalizer(f, trans) + end + return trans + end +end + +""" +Struct containing a pointer to the wrapped cpp result set of a +transaction. The struct is meant for internal use only and shouldn't +be created directly by the user but is returned by, e.g., +`get_result_set`. +""" +mutable struct _ResultSet + wrapped_result_set::Ptr{Cvoid} + _deletable::Bool + + function _ResultSet() + rs = new() + rs._deletable = false + return rs + end +end + +""" + create_transaction(name::AbstractString = "default") + +Return a transaction created with the connection of the given name. If +none is given, the defult connection is used. +""" +function create_transaction(name::AbstractString = "default") + connection = CaosDB.Connection.get_connection(name) + + return create_transaction(connection) +end + +""" + function create_transaction(connection::Ref{CaosDB.Connection._Connection}) + +Return a transactioncreated with the given connection object. +""" +function create_transaction(connection::Ref{CaosDB.Connection._Connection}) + + transaction = Ref{_Transaction}(_Transaction(true)) + + err_code = ccall( + (:caosdb_connection_connection_create_transaction, CaosDB.library_name), + Cint, + (Ref{CaosDB.Connection._Connection}, Ref{_Transaction}), + connection, + transaction, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return transaction +end + +""" + function add_retrieve_by_id(transaction::Ref{_Transaction}, id::AbstractString) + +Add a sub-request to retrieve a single entity by its `id` to the given +`transaction`. + +!!! info + + This does not execute the transaction. +""" +function add_retrieve_by_id(transaction::Ref{_Transaction}, id::AbstractString) + + err_code = ccall( + (:caosdb_transaction_transaction_retrieve_by_id, CaosDB.library_name), + Cint, + (Ref{_Transaction}, Cstring), + transaction, + id, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function add_retrieve_by_id( + transaction::Ref{_Transaction}, + ids::Vector{T}, + ) where {T<:AbstractString} + +Add a sub-request to retrieve several entities by their `ids` to the +given `transaction`. + +!!! info + + This does not execute the transaction. +""" +function add_retrieve_by_id( + transaction::Ref{_Transaction}, + ids::Vector{T}, +) where {T<:AbstractString} + + len = Cint(length(ids)) + err_code = ccall( + (:caosdb_transaction_transaction_retrieve_by_ids, CaosDB.library_name), + Cint, + (Ref{_Transaction}, Ptr{Ptr{Cchar}}, Cint), + transaction, + ids, + len, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + + +""" + function add_query(transaction::Ref{_Transaction}, query::AbstractString) + +Add a query sub-request to the given `transaction`. + +!!! warning + + Only COUNT queris and FIND queries (and no SELECT queries) are + currently supported. + +!!! info + + This does not execute the transaction +""" +function add_query(transaction::Ref{_Transaction}, query::AbstractString) + + err_code = ccall( + (:caosdb_transaction_transaction_query, CaosDB.library_name), + Cint, + (Ref{_Transaction}, Cstring), + transaction, + query, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function execute(transaction::Ref{_Transaction}) + +Send the given `transaction` to the CaosDB server for excecution and +wait for it to finish. +""" +function execute(transaction::Ref{_Transaction}) + + err_code = ccall( + (:caosdb_transaction_transaction_execute, CaosDB.library_name), + Cint, + (Ref{_Transaction},), + transaction, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) +end + +""" + function get_result_set(transaction::Ref{_Transaction}) + +Return the result set of the given `transaction`. +""" +function get_result_set(transaction::Ref{_Transaction}) + + results = Ref{_ResultSet}(_ResultSet()) + + err_code = ccall( + (:caosdb_transaction_transaction_get_result_set, CaosDB.library_name), + Cint, + (Ref{_Transaction}, Ref{_ResultSet}), + transaction, + results, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return results +end + +""" + function get_count_result(transaction::Ref{_Transaction}) + +Return the number of results of the COUNT query in the given `transaction`. + +!!! info + + This is only a meaningful quantity if there actually was a COUNT + query in the `transaction`, and it already has been executed. In + all other cases the return value will be -1. +""" +function get_count_result(transaction::Ref{_Transaction}) + + out = Ref{Clong}(0) + + err_code = ccall( + (:caosdb_transaction_transaction_get_count_result, CaosDB.library_name), + Cint, + (Ref{_Transaction}, Ref{Clong}), + transaction, + out, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return out[] +end + +""" + function get_result_size(results::Ref{_ResultSet}) + +Return the size of the given `results`, i.e., the number of entities +or other responses returned in this result set. +""" +function get_result_size(results::Ref{_ResultSet}) + + size = Ref{Cint}(0) + + err_code = ccall( + (:caosdb_transaction_result_set_size, CaosDB.library_name), + Cint, + (Ref{_ResultSet}, Ref{Cint}), + results, + size, + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return size[] +end + +""" + function get_result_at(results::Ref{_ResultSet}, index::Cint) + +Return the entity at position `index` of the given `results`. +""" +function get_result_at(results::Ref{_ResultSet}, index::Cint) + + size = get_result_size(results) + + if index > size + throw( + DomainError( + index, + "You tried to access the result at position $index but the result set only has $size results.", + ), + ) + end + + entity = Ref{CaosDB.Entity._Entity}(CaosDB.Entity._Entity()) + + err_code = ccall( + (:caosdb_transaction_result_set_at, CaosDB.library_name), + Cint, + (Ref{_ResultSet}, Ref{CaosDB.Entity._Entity}, Cint), + results, + entity, + index - Cint(1), + ) + + CaosDB.Exceptions.evaluate_return_code(err_code) + + return entity +end + +""" + function get_result_at(results::Ref{_ResultSet}, index::Integer) + +Convenience wrapper for `get_result_at(::Ref{_ResultSet}, +::Cint)`. Convert the given `index` to Int32 and return the result at +position `index` of `results`. +""" +function get_result_at(results::Ref{_ResultSet}, index::Integer) + + return get_result_at(results, Cint(index)) +end + +""" + function get_results(result_set::Ref{_ResultSet}) + +Return al results of the given `result_set`. +""" +function get_results(result_set::Ref{_ResultSet}) + + size = get_result_size(result_set) + + return [get_result_at(result_set, Cint(ii)) for ii = 1:size] +end + +""" + function get_results(transaction::Ref{_Transaction}) + +Return all results of the given `transaction`. +""" +function get_results(transaction::Ref{_Transaction}) + + result_set = get_result_set(transaction) + + return get_results(result_set) +end + +""" + function execute_query( + query::AbstractString, + name::AbstractString = "default" + ) + +Execute the given `query` and return its results. Use the connection +with the given `name`. If none is given, the default connection is +used. + +!!! info + + Since only the resulting entities are returned, this only makes + sense for FIND (and, in the future, SELECT) queries. To get the + result of a `COUNT` query, you have to execute the transaction + yourself using `create_transaction`, `add_query`, and `execute`, + and get the result with `get_count_result`. +""" +function execute_query(query::AbstractString, name::AbstractString = "default") + + transaction = create_transaction(name) + + add_query(transaction, query) + + execute(transaction) + + return get_results(transaction) +end + +""" + function execute_query( + query::AbstractString, + connection::Ref{CaosDB.Connection._Connection} + ) + +Execute the given `query` and return its results. Use the given +`connection`. + +!!! info + + Since only the resulting entities are returned, this only makes + sense for FIND (and, in the future, SELECT) queries. To get the + result of a `COUNT` query, you have to execute the transaction + yourself using `create_transaction`, `add_query`, and `execute`, + and get the result with `get_count_result`. +""" +function execute_query( + query::AbstractString, + connection::Ref{CaosDB.Connection._Connection}, +) + + transaction = create_transaction(connection) + + add_query(transaction, query) + + execute(transaction) + + return get_results(transaction) +end + +""" + function retrieve(id::AbstractString, name::AbstractString = "default") + +Retrieve and return the entity with the given `id`. Use the connection +with the given `name`. If none is provided, the default connection is +used. +""" +function retrieve(id::AbstractString, name::AbstractString = "default") + + transaction = create_transaction(name) + + add_retrieve_by_id(transaction, id) + + execute(transaction) + + return get_results(transaction)[1] +end + +""" + function retrieve(id::AbstractString, connection::Ref{CaosDB.Connection._Connection}) + +Retrieve and return the entity with the given `id`. Use the given +`connection`. +""" +function retrieve(id::AbstractString, connection::Ref{CaosDB.Connection._Connection}) + + transaction = create_transaction(connection) + + add_retrieve_by_id(transaction, id) + + execute(transaction) + + return get_results(transaction)[1] +end + +""" + function retrieve( + ids::Vector{T}, + name::AbstractString = "default", + ) where {T<:AbstractString} + +Retrieve and return the entities with the given `ids`. Use the +connection of the given `name`. If none is provided, the default +connection is used. +""" +function retrieve( + ids::Vector{T}, + name::AbstractString = "default", +) where {T<:AbstractString} + + transaction = create_transaction(name) + + add_retrieve_by_id(transaction, ids) + + execute(transaction) + + return get_results(transaction) +end + +""" + function retrieve( + ids::Vector{T}, + connection::Ref{CaosDB.Connection._Connection}, + ) where {T<:AbstractString} + +Retrieve and return the entities with the given `ids`. Use the given +`connection`. +""" +function retrieve( + ids::Vector{T}, + connection::Ref{CaosDB.Connection._Connection}, +) where {T<:AbstractString} + + transaction = create_transaction(connection) + + add_retrieve_by_id(transaction, ids) + + execute(transaction) + + return get_results(transaction) +end + +end # Transaction diff --git a/src/Utility.jl b/src/Utility.jl new file mode 100644 index 0000000000000000000000000000000000000000..4f74dcdf06487fc7e5c4d1639294442fe1c02b1e --- /dev/null +++ b/src/Utility.jl @@ -0,0 +1,50 @@ +# ** header v3.0 +# This file is a part of the CaosDB Project. +# +# Copyright (C) 2021 Indiscale GmbH <info@indiscale.com> +# Copyright (C) 2021 Florian Spreckelsen <f.spreckelsen@indiscale.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public +# License along with this program. If not, see +# <https://www.gnu.org/licenses/>. +# +# ** end header +# +module Utility + +export get_env_fallback + +using ..CaosDB + +""" + get_env_fallback(var[, default]) + +Return the environmental variable `var` if it exists, `default` +otherwise. If no `default` is given an empty string is returned +instead. +""" +function get_env_fallback(var::AbstractString, default::AbstractString = "") + + ret = ccall( + (:caosdb_utility_get_env_fallback, CaosDB.library_name), + Cstring, + (Cstring, Cstring), + var, + default, + ) + + return unsafe_string(ret) + +end + +end # Utility diff --git a/test/runtests.jl b/test/runtests.jl index 5fa1db168735cfad9532752b8d9d3364074e447b..66b08bd87d55717710ead9fcf564f32a92abcdd7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -30,7 +30,7 @@ using CaosDB else shell_var = "default" end - @test CaosDB.Utility.get_env_var("SHELL", "default") == shell_var + @test CaosDB.Utility.get_env_fallback("SHELL", "default") == shell_var end @testset "TestExceptions" begin @@ -63,5 +63,114 @@ using CaosDB Cint(-1), ), ) CaosDB.Exceptions.evaluate_return_code(Cint(-1)) + + # Check whether client errors can be built correctly and + # return the correct code (Change it if this ever changes in + # libcaosdb) + client_err = CaosDB.Exceptions.ClientException("Client error") + @test client_err.msg == "Client error" + @test client_err.code == 9999 + end + + @testset "TestTransaction" begin + # transactions can be generated from the default connection, + # from a connection name, or from a connection object + @test CaosDB.Transaction.create_transaction() != nothing + @test CaosDB.Transaction.create_transaction("default") != nothing + conn = CaosDB.Connection.get_connection() + @test CaosDB.Transaction.create_transaction(conn) != nothing + + # Retrieval works by a single id, by a list of ids, or by querying + trans1 = CaosDB.Transaction.create_transaction() + @test CaosDB.Transaction.add_retrieve_by_id(trans1, "some_id") == nothing + trans2 = CaosDB.Transaction.create_transaction() + @test CaosDB.Transaction.add_retrieve_by_id(trans2, ["id1", "id2", "id3"]) == + nothing + trans3 = CaosDB.Transaction.create_transaction() + @test CaosDB.Transaction.add_query(trans3, "FIND ENTITY WITH id=123") == nothing + + end + + @testset "TestEntity" begin + ent_with_name = CaosDB.Entity.create_entity("TestEnt") + @test CaosDB.Entity.get_name(ent_with_name) == "TestEnt" + + rt_with_name = CaosDB.Entity.create_recordtype("TestRT") + @test CaosDB.Entity.get_name(rt_with_name) == "TestRT" + @test CaosDB.Entity.get_role(rt_with_name) == "RECORD_TYPE" + + prop_with_name_and_unit = + CaosDB.Entity.create_property_entity(name = "TestProp", unit = "m") + @test CaosDB.Entity.get_name(prop_with_name_and_unit) == "TestProp" + @test CaosDB.Entity.get_role(prop_with_name_and_unit) == "PROPERTY" + @test CaosDB.Entity.get_unit(prop_with_name_and_unit) == "m" + + rec_with_parent_and_props = CaosDB.Entity.create_record("TestRec") + # cannot set an ID + @test_throws CaosDB.Exceptions.ClientException CaosDB.Entity.set_id( + rec_with_parent_and_props, + "some_id", + ) + # cannot set the datatype of a record + @test_throws CaosDB.Exceptions.ClientException CaosDB.Entity.set_datatype( + rec_with_parent_and_props, + "INTEGER", + ) + + par1 = CaosDB.Entity.create_parent(name = "Parent1") + par2 = CaosDB.Entity.create_parent(name = "Parent2", id = "id_of_parent_2") + CaosDB.Entity.append_parents(rec_with_parent_and_props, [par1, par2]) + @test length(CaosDB.Entity.get_parents(rec_with_parent_and_props)) == 2 + # Getting has to work with all Integers that can be cast to Cint + @test CaosDB.Entity.get_name( + CaosDB.Entity.get_parent(rec_with_parent_and_props, Cint(1)), + ) == "Parent1" + @test CaosDB.Entity.get_name( + CaosDB.Entity.get_parent(rec_with_parent_and_props, 2), + ) == "Parent2" + @test_throws DomainError CaosDB.Entity.get_parent(rec_with_parent_and_props, 3) + @test CaosDB.Entity.get_id( + CaosDB.Entity.get_parent(rec_with_parent_and_props, Cint(2)), + ) == "id_of_parent_2" + CaosDB.Entity.remove_parent(rec_with_parent_and_props, Cint(1)) + @test length(CaosDB.Entity.get_parents(rec_with_parent_and_props)) == 1 + @test CaosDB.Entity.get_name( + CaosDB.Entity.get_parent(rec_with_parent_and_props, Cint(1)), + ) == "Parent2" + + prop1 = CaosDB.Entity.create_property(name = "Property1", value = "2") + prop2 = CaosDB.Entity.create_property(id = "id_of_property_2") + CaosDB.Entity.set_datatype(prop2, "TEXT") + prop3 = CaosDB.Entity.create_property(name = "Property3") + CaosDB.Entity.append_properties(rec_with_parent_and_props, [prop1, prop2, prop3]) + @test length(CaosDB.Entity.get_properties(rec_with_parent_and_props)) == 3 + # properties can be accessed as a list + + # TODO(henrik, daniel) + # @test CaosDB.Entity.get_value( + # CaosDB.Entity.get_properties(rec_with_parent_and_props)[1], + # ) == "2" + type, is_ref, is_list = CaosDB.Entity.get_datatype( + CaosDB.Entity.get_properties(rec_with_parent_and_props)[2], + ) + @test type == "TEXT" + @test is_ref == false + @test is_list == false + @test CaosDB.Entity.get_id( + CaosDB.Entity.get_properties(rec_with_parent_and_props)[2], + ) == "id_of_property_2" + CaosDB.Entity.remove_property(rec_with_parent_and_props, Cint(2)) + @test length(CaosDB.Entity.get_properties(rec_with_parent_and_props)) == 2 + @test CaosDB.Entity.get_name( + CaosDB.Entity.get_properties(rec_with_parent_and_props)[2], + ) == "Property3" + end + + @testset "Datatype and values" begin + # TODO(hernik, daniel) tests for some kinds of datatypes and + # values. We probably don't need all since the actual + # functions are already being tested in the C++ client but at + # least some strings, numbers, and lists thereof should be + # tested here, too end end