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