/**
 * @file   tiledb.h
 *
 * @section LICENSE
 *
 * The MIT License
 *
 * @copyright Copyright (c) 2017-2018 TileDB, Inc.
 * @copyright Copyright (c) 2016 MIT and Intel Corporation
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * This file declares the C API for TileDB.
 */

#ifndef TILEDB_H
#define TILEDB_H

#include "tiledb_version.h"

// Generated by CMake; defines TILEDB_EXPORT (etc) macros.
#include "tiledb_export.h"

#include <stdint.h>
#include <stdio.h>

/* ********************************* */
/*               MACROS              */
/* ********************************* */

#ifdef __cplusplus
extern "C" {
#endif

/* ****************************** */
/*          TILEDB ENUMS          */
/* ****************************** */

/** TileDB object type. */
typedef enum {
/** Helper macro for defining object type enums. */
#define TILEDB_OBJECT_TYPE_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_OBJECT_TYPE_ENUM
} tiledb_object_t;

/** TileDB query type. */
typedef enum {
/** Helper macro for defining query type enums. */
#define TILEDB_QUERY_TYPE_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_QUERY_TYPE_ENUM
} tiledb_query_type_t;

/** Query status. */
typedef enum {
/** Helper macro for defining query status enums. */
#define TILEDB_QUERY_STATUS_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_QUERY_STATUS_ENUM
} tiledb_query_status_t;

/** Filesystem type. */
typedef enum {
/** Helper macro for defining filesystem enums. */
#define TILEDB_FILESYSTEM_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_FILESYSTEM_ENUM
} tiledb_filesystem_t;

/** TileDB datatype. */
typedef enum {
/** Helper macro for defining datatype enums. */
#define TILEDB_DATATYPE_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_DATATYPE_ENUM
} tiledb_datatype_t;

/** Array type. */
typedef enum {
/** Helper macro for defining array type enums. */
#define TILEDB_ARRAY_TYPE_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_ARRAY_TYPE_ENUM
} tiledb_array_type_t;

/** Tile or cell layout. */
typedef enum {
/** Helper macro for defining layout type enums. */
#define TILEDB_LAYOUT_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_LAYOUT_ENUM
} tiledb_layout_t;

/** Filter type. */
typedef enum {
/** Helper macro for defining filter type enums. */
#define TILEDB_FILTER_TYPE_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_FILTER_TYPE_ENUM
} tiledb_filter_type_t;

/** Filter option. */
typedef enum {
/** Helper macro for defining filter option enums. */
#define TILEDB_FILTER_OPTION_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_FILTER_OPTION_ENUM
} tiledb_filter_option_t;

/** Encryption type. */
typedef enum {
/** Helper macro for defining encryption enums. */
#define TILEDB_ENCRYPTION_TYPE_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_ENCRYPTION_TYPE_ENUM
} tiledb_encryption_type_t;

/** Compression type. */
typedef enum {
/** Helper macro for defining compressor enums. */
#define TILEDB_COMPRESSOR_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_COMPRESSOR_ENUM
} tiledb_compressor_t;

/** Walk traversal order. */
typedef enum {
/** Helper macro for defining walk order enums. */
#define TILEDB_WALK_ORDER_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_WALK_ORDER_ENUM
} tiledb_walk_order_t;

/** VFS mode. */
typedef enum {
/** Helper macro for defining VFS mode enums. */
#define TILEDB_VFS_MODE_ENUM(id) TILEDB_##id
#include "tiledb_enum.h"
#undef TILEDB_VFS_MODE_ENUM
} tiledb_vfs_mode_t;

/* ****************************** */
/*            CONSTANTS           */
/* ****************************** */

/**
 * @name Return codes
 */
/**@{*/
/** Success */
#define TILEDB_OK 0
/** General error */
#define TILEDB_ERR (-1)
/** Out of memory */
#define TILEDB_OOM (-2)
/**@}*/

/** Returns a special name indicating the coordinates attribute. */
TILEDB_EXPORT const char* tiledb_coords();

/** Returns a special value indicating a variable number of elements. */
TILEDB_EXPORT uint32_t tiledb_var_num();

/** Returns the maximum path length on the current platform. */
TILEDB_EXPORT uint32_t tiledb_max_path();

/** Returns the input datatype size. */
TILEDB_EXPORT uint64_t tiledb_datatype_size(tiledb_datatype_t type);

/**
 * Returns the size (in bytes) of an offset (used in variable-sized
 * attributes).
 */
TILEDB_EXPORT uint64_t tiledb_offset_size();

/** Returns the current time in milliseconds. */
TILEDB_EXPORT uint64_t tiledb_timestamp_now_ms();

/**
 * @name Constants wrapping special functions
 */
/**@{*/
/** A special name indicating the coordinates attribute. */
#define TILEDB_COORDS tiledb_coords()
/** A special value indicating a variable number of elements. */
#define TILEDB_VAR_NUM tiledb_var_num()
/** The maximum path length on the current platform. */
#define TILEDB_MAX_PATH tiledb_max_path()
/** The size (in bytes) of an offset (used in variable-sized attributes). */
#define TILEDB_OFFSET_SIZE tiledb_offset_size()
/** The current time in milliseconds. */
#define TILEDB_TIMESTAMP_NOW_MS tiledb_timestamp_now_ms()
/**@}*/

/* ****************************** */
/*            VERSION             */
/* ****************************** */

/**
 *  Retrieves the version of the TileDB library currently being used.
 *
 *  @param major Will store the major version number.
 *  @param minor Will store the minor version number.
 *  @param rev Will store the revision (patch) number.
 */
TILEDB_EXPORT void tiledb_version(int32_t* major, int32_t* minor, int32_t* rev);

/* ********************************* */
/*           TILEDB TYPES            */
/* ********************************* */

/** An array object. */
typedef struct tiledb_array_t tiledb_array_t;

/** A config object. */
typedef struct tiledb_config_t tiledb_config_t;

/** A config iterator. */
typedef struct tiledb_config_iter_t tiledb_config_iter_t;

/** A TileDB context. */
typedef struct tiledb_ctx_t tiledb_ctx_t;

/** A TileDB error. **/
typedef struct tiledb_error_t tiledb_error_t;

/** A TileDB attribute. */
typedef struct tiledb_attribute_t tiledb_attribute_t;

/** A TileDB array schema. */
typedef struct tiledb_array_schema_t tiledb_array_schema_t;

/** A TileDB dimension. */
typedef struct tiledb_dimension_t tiledb_dimension_t;

/** A TileDB domain. */
typedef struct tiledb_domain_t tiledb_domain_t;

/** A TileDB filter. */
typedef struct tiledb_filter_t tiledb_filter_t;

/** A TileDB filter list. */
typedef struct tiledb_filter_list_t tiledb_filter_list_t;

/** A TileDB query. */
typedef struct tiledb_query_t tiledb_query_t;

/** A key-value store schema. */
typedef struct tiledb_kv_schema_t tiledb_kv_schema_t;

/** A key-value store object. */
typedef struct tiledb_kv_t tiledb_kv_t;

/** A key-value item. */
typedef struct tiledb_kv_item_t tiledb_kv_item_t;

/** A key-value store iterator. */
typedef struct tiledb_kv_iter_t tiledb_kv_iter_t;

/** A virtual filesystem object. */
typedef struct tiledb_vfs_t tiledb_vfs_t;

/** A virtual filesystem file handle. */
typedef struct tiledb_vfs_fh_t tiledb_vfs_fh_t;

/* ********************************* */
/*              ERROR                */
/* ********************************* */

/**
 * Returns the error message associated with a TileDB error object.
 *
 * **Example:**
 *
 * The following shows how to get the last error from a TileDB context.
 *
 * @code{.c}
 * tiledb_error_t* err = NULL;
 * tiledb_ctx_get_last_error(ctx, &err);
 * const char* msg;
 * tiledb_error_message(err, &msg);
 * printf("%s\n", msg);
 * @endcode
 *
 * @param err A TileDB error object.
 * @param errmsg A constant pointer to the error message.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_error_message(tiledb_error_t* err, const char** errmsg);

/**
 * Frees the resources associated with a TileDB error object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_error_t* err = NULL;
 * tiledb_ctx_get_last_error(ctx, &err);
 * const char* msg;
 * tiledb_error_message(err, &msg);
 * printf("%s\n", msg);
 * tiledb_error_free(&err);
 * @endcode
 *
 * @param err The TileDB error object.
 */
TILEDB_EXPORT void tiledb_error_free(tiledb_error_t** err);

/* ********************************* */
/*              CONFIG               */
/* ********************************* */

/**
 * Creates a TileDB config.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_config_t* config;
 * tiledb_error_t* error = NULL;
 * tiledb_config_alloc(&config, &error);
 * @endcode
 *
 * @param config The config to be created.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_config_alloc(tiledb_config_t** config, tiledb_error_t** error);

/**
 * Frees a TileDB config.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_config_t* config;
 * tiledb_error_t* error = NULL;
 * tiledb_config_alloc(&config, &error);
 * tiledb_config_free(&config);
 * @endcode
 *
 * @param config The config to be freed.
 */
TILEDB_EXPORT void tiledb_config_free(tiledb_config_t** config);

/**
 * Sets a config parameter.
 *
 * **Parameters**
 *
 * - `sm.dedup_coords` <br>
 *    If `true`, cells with duplicate coordinates will be removed during sparse
 *    array writes. Note that ties during deduplication are broken
 *    arbitrarily. <br>
 *    **Default**: false
 * - `sm.check_coord_dups` <br>
 *    This is applicable only if `sm.dedup_coords` is `false`.
 *    If `true`, an error will be thrown if there are cells with duplicate
 *    coordinates during sparse array writes. If `false` and there are
 *    duplicates, the duplicates will be written without errors, but the
 *    TileDB behavior could be unpredictable. <br>
 *    **Default**: true
 * - `sm.check_coord_oob` <br>
 *    If `true`, an error will be thrown if there are cells with coordinates
 *    lying outside the domain during sparse array writes.  <br>
 *    **Default**: true
 * - `sm.tile_cache_size` <br>
 *    The tile cache size in bytes. Any `uint64_t` value is acceptable. <br>
 *    **Default**: 10,000,000
 * - `sm.array_schema_cache_size` <br>
 *    The array schema cache size in bytes. Any `uint64_t` value is acceptable.
 * <br>
 *    **Default**: 10,000,000
 * - `sm.fragment_metadata_cache_size` <br>
 *    The fragment metadata cache size in bytes. Any `uint64_t` value is
 *    acceptable. <br>
 * - `sm.enable_signal_handlers` <br>
 *    Determines whether or not TileDB will install signal handlers. <br>
 *    **Default**: true
 * - `sm.num_async_threads` <br>
 *    The number of threads allocated for async queries. <br>
 *    **Default**: 1
 * - `sm.num_reader_threads` <br>
 *    The number of threads allocated for filesystem read operations. <br>
 *    **Default**: 1
 * - `sm.num_writer_threads` <br>
 *    The number of threads allocated for filesystem write operations. <br>
 *    **Default**: 1
 * - `sm.num_tbb_threads` <br>
 *    The number of threads allocated for the TBB thread pool (if TBB is
 *    enabled). Note: this is a whole-program setting. Usually this should not
 *    be modified from the default. See also the documentation for TBB's
 *    `task_scheduler_init` class.<br>
 *    **Default**: TBB automatic
 * - `vfs.num_threads` <br>
 *    The number of threads allocated for VFS operations (any backend), per VFS
 *    instance. <br>
 *    **Default**: number of cores
 * - `vfs.min_parallel_size` <br>
 *    The minimum number of bytes in a parallel VFS operation
 *    (except parallel S3 writes, which are controlled by
 *    `vfs.s3.multipart_part_size`). <br>
 *    **Default**: 10MB
 * - `vfs.file.max_parallel_ops` <br>
 *    The maximum number of parallel operations on objects with `file:///`
 *    URIs. <br>
 *    **Default**: `vfs.num_threads`
 * - `vfs.s3.region` <br>
 *    The S3 region, if S3 is enabled. <br>
 *    **Default**: us-east-1
 * - `vfs.s3.scheme` <br>
 *    The S3 scheme (`http` or `https`), if S3 is enabled. <br>
 *    **Default**: https
 * - `vfs.s3.endpoint_override` <br>
 *    The S3 endpoint, if S3 is enabled. <br>
 *    **Default**: ""
 * - `vfs.s3.use_virtual_addressing` <br>
 *    The S3 use of virtual addressing (`true` or `false`), if S3 is
 *    enabled. <br>
 *    **Default**: true
 * - `vfs.s3.max_parallel_ops` <br>
 *    The maximum number of S3 backend parallel operations. <br>
 *    **Default**: `vfs.num_threads`
 * - `vfs.s3.multipart_part_size` <br>
 *    The part size (in bytes) used in S3 multipart writes.
 *    Any `uint64_t` value is acceptable. Note: `vfs.s3.multipart_part_size *
 *    vfs.s3.max_parallel_ops` bytes will be buffered before issuing multipart
 *    uploads in parallel. <br>
 *    **Default**: 5MB
 * - `vfs.s3.connect_timeout_ms` <br>
 *    The connection timeout in ms. Any `long` value is acceptable. <br>
 *    **Default**: 3000
 * - `vfs.s3.connect_max_tries` <br>
 *    The maximum tries for a connection. Any `long` value is acceptable. <br>
 *    **Default**: 5
 * - `vfs.s3.connect_scale_factor` <br>
 *    The scale factor for exponential backoff when connecting to S3.
 *    Any `long` value is acceptable. <br>
 *    **Default**: 25
 * - `vfs.s3.request_timeout_ms` <br>
 *    The request timeout in ms. Any `long` value is acceptable. <br>
 *    **Default**: 3000
 * - `vfs.s3.proxy_host` <br>
 *    The S3 proxy host. <br>
 *    **Default**: ""
 * - `vfs.s3.proxy_port` <br>
 *    The S3 proxy port. <br>
 *    **Default**: 0
 * - `vfs.s3.proxy_scheme` <br>
 *    The S3 proxy scheme. <br>
 *    **Default**: "https"
 * - `vfs.s3.proxy_username` <br>
 *    The S3 proxy username. Note: this parameter is not serialized by
 *    `tiledb_config_save_to_file`. <br>
 *    **Default**: ""
 * - `vfs.s3.proxy_password` <br>
 *    The S3 proxy password. Note: this parameter is not serialized by
 *    `tiledb_config_save_to_file`. <br>
 *    **Default**: ""
 * - `vfs.hdfs.name_node"` <br>
 *    Name node for HDFS. <br>
 *    **Default**: ""
 * - `vfs.hdfs.username` <br>
 *    HDFS username. <br>
 *    **Default**: ""
 * - `vfs.hdfs.kerb_ticket_cache_path` <br>
 *    HDFS kerb ticket cache path. <br>
 *    **Default**: ""
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_error_t* error = NULL;
 * tiledb_config_set(config, "sm.tile_cache_size", "1000000", &error);
 * @endcode
 *
 * @param config The config object.
 * @param param The parameter to be set.
 * @param value The value of the parameter to be set.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_set(
    tiledb_config_t* config,
    const char* param,
    const char* value,
    tiledb_error_t** error);

/**
 * Gets a config parameter.
 *
 * **Example:**
 *
 * @code{.c}
 * const char* value;
 * tiledb_error_t* error = NULL;
 * tiledb_config_get(config, "sm.tile_cache_size", &value, &error);
 * @endcode
 *
 * @param config The config object.
 * @param param The parameter to be set.
 * @param value A pointer to the value of the parameter to be retrieved
 *    (`NULL` if it does not exist).
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_get(
    tiledb_config_t* config,
    const char* param,
    const char** value,
    tiledb_error_t** error);

/**
 * Loads config parameters from a (local) text file.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_error_t* error = NULL;
 * tiledb_config_load_from_file(config, "tiledb.conf", &error);
 * @endcode
 *
 * @param config The config object.
 * @param filename The name of the file.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_load_from_file(
    tiledb_config_t* config, const char* filename, tiledb_error_t** error);

/**
 * Unsets a config parameter. This will set the config parameter to its
 * default value.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_error_t* error = NULL;
 * tiledb_config_unset(config, "sm.tile_cache_size", &error);
 * @endcode
 *
 * @param config The config object.
 * @param param The parameter to be unset.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_unset(
    tiledb_config_t* config, const char* param, tiledb_error_t** error);

/**
 * Saves the config parameters to a (local) text file.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_error_t* error = NULL;
 * tiledb_config_save_to_file(config, "tiledb.conf", &error);
 * @endcode
 *
 * @param config The config object.
 * @param filename The name of the file.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_save_to_file(
    tiledb_config_t* config, const char* filename, tiledb_error_t** error);

/* ****************************** */
/*            CONFIG ITER         */
/* ****************************** */

/**
 * Creates an iterator on a config object.
 *
 * **Examples:**
 *
 * The following creates a config iterator without a prefix. This
 * will iterate over all config param/values.
 *
 * @code{.c}
 * tiledb_error_t* error = NULL;
 * tiledb_config_iter_t* config_iter;
 * tiledb_config_iter_alloc(config, NULL, &config_iter, &error);
 * @endcode
 *
 * The following creates a config iterator with a prefix. This
 * will iterate over all S3 config param/values, stripping out
 * `vfs.s3.`. For instance, instead of retrieving `vfs.s3.region`
 * as a parameter via `tiledb_config_iter_here`, it will retrieve
 * `region`.
 *
 * @code{.c}
 * tiledb_error_t* error = NULL;
 * tiledb_config_iter_t* config_iter;
 * tiledb_config_iter_alloc(config, "vfs.s3.", &config_iter, &error);
 * @endcode
 *
 * @param config A config object the iterator will operate on.
 * @param prefix If not `NULL`, only the config parameters starting
 *     with `prefix*` will be iterated on. Moreover, the prefix will
 *     be stripped from the parameters. Otherwise, all parameters will
 *     be iterated on and their full name will be retrieved.
 * @param config_iter The config iterator to be created.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_iter_alloc(
    tiledb_config_t* config,
    const char* prefix,
    tiledb_config_iter_t** config_iter,
    tiledb_error_t** error);

/**
 * Resets the iterator.
 *
 * **Examples:**
 *
 * Without a prefix:
 *
 * @code{.c}
 * tiledb_config_iter_reset(config, config_iter, NULL, &error);
 * @endcode
 *
 * With a prefix:
 *
 * @code{.c}
 * tiledb_config_iter_reset(config, config_iter, "vfs.s3.", &error);
 * @endcode
 *
 * @param config A config object the iterator will operate on.
 * @param config_iter The config iterator to be reset.
 * @param prefix If not `NULL`, only the config parameters starting
 *     with `prefix*` will be iterated on. Moreover, the prefix will
 *     be stripped from the parameters. Otherwise, all parameters will
 *     be iterated on and their full name will be retrieved.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_iter_reset(
    tiledb_config_t* config,
    tiledb_config_iter_t* config_iter,
    const char* prefix,
    tiledb_error_t** error);

/**
 * Frees a config iterator.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_config_iter_free(&config_iter);
 * @endcode
 *
 * @param config_iter The config iterator to be freed.
 */
TILEDB_EXPORT void tiledb_config_iter_free(tiledb_config_iter_t** config_iter);

/**
 * Retrieves the config param and value currently pointed by the iterator.
 *
 * **Example:**
 *
 * @code{.c}
 * const char *param, *value;
 * tiledb_config_iter_here(config_iter, &param, &value, &error);
 * @endcode
 *
 * @param config_iter The config iterator.
 * @param param The config parameter to be retrieved (`NULL` if the iterator
 *     is at the end).
 * @param value The config value to be retrieved (`NULL` if the iterator
 *     is at the end).
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_iter_here(
    tiledb_config_iter_t* config_iter,
    const char** param,
    const char** value,
    tiledb_error_t** error);

/**
 * Moves the iterator to the next param.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_config_iter_next(config_iter, &error);
 * @endcode
 *
 * @param config_iter The config iterator.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_iter_next(
    tiledb_config_iter_t* config_iter, tiledb_error_t** error);

/**
 * Checks if the iterator is done.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t done;
 * tiledb_config_iter_done(config_iter, &done, &error);
 * @endcode
 *
 * @param config_iter The config iterator.
 * @param done Sets this to `1` if the iterator is done, `0` otherwise.
 * @param error Error object returned upon error (`NULL` if there is
 *     no error).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_config_iter_done(
    tiledb_config_iter_t* config_iter, int32_t* done, tiledb_error_t** error);

/* ********************************* */
/*              CONTEXT              */
/* ********************************* */

/**
 * Creates a TileDB context, which contains the TileDB storage manager
 * that manages everything in the TileDB library.
 *
 * **Examples:**
 *
 * Without config (i.e., use default configuration):
 *
 * @code{.c}
 * tiledb_ctx_t* ctx;
 * tiledb_ctx_alloc(NULL, &ctx);
 * @endcode
 *
 * With some config:
 *
 * @code{.c}
 * tiledb_ctx_t* ctx;
 * tiledb_ctx_alloc(config, &ctx);
 * @endcode
 *
 * @param config The configuration parameters (`NULL` means default).
 * @param ctx The TileDB context to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_ctx_alloc(tiledb_config_t* config, tiledb_ctx_t** ctx);

/**
 * Destroys the TileDB context, freeing all associated memory and resources.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_ctx_t* ctx;
 * tiledb_ctx_alloc(NULL, &ctx);
 * tiledb_ctx_free(&ctx);
 * @endcode
 *
 * @param ctx The TileDB context to be freed.
 */
TILEDB_EXPORT void tiledb_ctx_free(tiledb_ctx_t** ctx);

/**
 * Retrieves the config from a TileDB context.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_config_t* config;
 * tiledb_ctx_get_config(ctx, &config);
 * // Make sure to free the retrieved config
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param config The config to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_ctx_get_config(tiledb_ctx_t* ctx, tiledb_config_t** config);

/**
 * Retrieves the last TileDB error associated with a TileDB context.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_error_t* error;
 * tiledb_ctx_get_last_error(ctx, &error);
 * // Make sure to free the retrieved error, checking first if it is NULL
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param err The last error, or `NULL` if no error has been raised.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_ctx_get_last_error(tiledb_ctx_t* ctx, tiledb_error_t** err);

/**
 * Checks if a given storage filesystem backend is supported.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t is_supported;
 * tiledb_ctx_is_supported_fs(ctx, TILEDB_HDFS, &is_supported);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param fs The filesystem to be checked.
 * @param is_supported Sets it to `1` if the filesystem is supported, and
 * `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_ctx_is_supported_fs(
    tiledb_ctx_t* ctx, tiledb_filesystem_t fs, int32_t* is_supported);

/**
 * Cancels all background or async tasks associated with the given context.
 *
 * @param ctx The TileDB context.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_ctx_cancel_tasks(tiledb_ctx_t* ctx);

/* ********************************* */
/*                GROUP              */
/* ********************************* */

/**
 * Creates a new TileDB group.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_group_create(ctx, "my_group");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param group_uri The group URI.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_group_create(tiledb_ctx_t* ctx, const char* group_uri);

/* ********************************* */
/*              FILTER               */
/* ********************************* */

/**
 * Creates a TileDB filter.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_t* filter;
 * tiledb_filter_alloc(ctx, TILEDB_FILTER_BZIP2, &filter);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param type The filter type.
 * @param filter The TileDB filter to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_alloc(
    tiledb_ctx_t* ctx, tiledb_filter_type_t type, tiledb_filter_t** filter);

/**
 * Destroys a TileDB filter, freeing associated memory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_t* filter;
 * tiledb_filter_alloc(ctx, TILEDB_FILTER_BZIP2, &filter);
 * tiledb_filter_free(&filter);
 * @endcode
 *
 * @param filter The filter to be destroyed.
 */
TILEDB_EXPORT void tiledb_filter_free(tiledb_filter_t** filter);

/**
 * Retrieves the type of a filter.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_t* filter;
 * tiledb_filter_alloc(ctx, TILEDB_FILTER_BZIP2, &filter);
 * tiledb_filter_type_t type;
 * tiledb_filter_get_type(ctx, filter, &type);
 * // type == TILEDB_FILTER_BZIP2
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param filter The TileDB filter.
 * @param type The filter type to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_get_type(
    tiledb_ctx_t* ctx, tiledb_filter_t* filter, tiledb_filter_type_t* type);

/**
 * Sets an option on a filter. Options are filter dependent; this function
 * returns an error if the given option is not valid for the given filter.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_t* filter;
 * tiledb_filter_alloc(ctx, TILEDB_FILTER_BZIP2, &filter);
 * int32_t level = 5;
 * tiledb_filter_set_option(ctx, filter, TILEDB_COMPRESSION_LEVEL, &level);
 * tiledb_filter_free(&filter);
 * @endcode
 *
 * @param ctx TileDB context.
 * @param filter The target filter.
 * @param option Filter option to set.
 * @param value Value of option to set.
 * @return `TILEDB_OK` for success or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_set_option(
    tiledb_ctx_t* ctx,
    tiledb_filter_t* filter,
    tiledb_filter_option_t option,
    const void* value);

/**
 * Gets an option value from a filter. Options are filter dependent; this
 * function returns an error if the given option is not valid for the given
 * filter.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_t* filter;
 * tiledb_filter_alloc(ctx, TILEDB_FILTER_BZIP2, &filter);
 * int32_t level;
 * tiledb_filter_get_option(ctx, filter, TILEDB_COMPRESSION_LEVEL, &level);
 * // level == -1 (the default)
 * tiledb_filter_free(&filter);
 * @endcode
 *
 * @note The buffer pointed to by `value` must be large enough to hold the
 * option value.
 *
 * @param ctx TileDB context.
 * @param filter The target filter.
 * @param option Filter option to get.
 * @param value Buffer that option value will be written to.
 * @return `TILEDB_OK` for success or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_get_option(
    tiledb_ctx_t* ctx,
    tiledb_filter_t* filter,
    tiledb_filter_option_t option,
    void* value);

/* ********************************* */
/*            FILTER LIST            */
/* ********************************* */

/**
 * Creates a TileDB filter list (pipeline of filters).
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_filter_list_alloc(ctx, &filter_list);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param filter_list The TileDB filter list to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_filter_list_alloc(tiledb_ctx_t* ctx, tiledb_filter_list_t** filter_list);

/**
 * Destroys a TileDB filter list, freeing associated memory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_filter_list_alloc(ctx, &filter_list);
 * tiledb_filter_list_free(&filter_list);
 * @endcode
 *
 * @param filter_list The filter list to be destroyed.
 */
TILEDB_EXPORT void tiledb_filter_list_free(tiledb_filter_list_t** filter_list);

/**
 * Appends a filter to a filter list. Data is processed through each filter in
 * the order the filters were added.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_t* filter;
 * tiledb_filter_alloc(ctx, TILEDB_FILTER_BZIP2, &filter);
 *
 * tiledb_filter_list_t* filter_list;
 * tiledb_filter_list_alloc(ctx, &filter_list);
 * tiledb_filter_list_add_filter(ctx, filter_list, filter);
 *
 * tiledb_filter_list_free(&filter_list);
 * tiledb_filter_free(&filter);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param filter_list The target filter list.
 * @param filter The filter to add.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_list_add_filter(
    tiledb_ctx_t* ctx,
    tiledb_filter_list_t* filter_list,
    tiledb_filter_t* filter);

/**
 * Sets the maximum tile chunk size for a filter list.
 *
 * **Example:**
 *
 * @code{.c}
 * // Set max chunk size to 16KB:
 * tiledb_filter_list_set_max_chunk_size(ctx, filter_list, 16384);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param filter_list The target filter list
 * @param max_chunk_size The max chunk size value to set
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_list_set_max_chunk_size(
    tiledb_ctx_t* ctx,
    const tiledb_filter_list_t* filter_list,
    uint32_t max_chunk_size);

/**
 * Retrieves the number of filters in a filter list.
 *
 * **Example:**
 *
 * @code{.c}
 * uint32_t num_filters;
 * tiledb_filter_list_get_nfilters(ctx, attr, &num_filters);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param filter_list The filter list
 * @param nfilters The number of filters on the filter list
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_list_get_nfilters(
    tiledb_ctx_t* ctx,
    const tiledb_filter_list_t* filter_list,
    uint32_t* nfilters);

/**
 * Retrieves a filter object from a filter list by index.
 *
 * **Example:**
 *
 * The following retrieves the first filter from a filter list.
 *
 * @code{.c}
 * tiledb_filter_t* filter;
 * tiledb_filter_list_get_filter_from_index(ctx, filter_list, 0, &filter);
 * tiledb_filter_free(&filter);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param filter_list The filter list to retrieve the filter from
 * @param index The index of the filter
 * @param filter The retrieved filter object.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_list_get_filter_from_index(
    tiledb_ctx_t* ctx,
    const tiledb_filter_list_t* filter_list,
    uint32_t index,
    tiledb_filter_t** filter);

/**
 * Gets the maximum tile chunk size for a filter list.
 *
 * **Example:**
 *
 * @code{.c}
 * uint32_t max_chunk_size;
 * tiledb_filter_list_get_max_chunk_size(ctx, filter_list, &max_chunk_size);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param filter_list The target filter list
 * @param max_chunk_size The retrieved max chunk size value
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_filter_list_get_max_chunk_size(
    tiledb_ctx_t* ctx,
    const tiledb_filter_list_t* filter_list,
    uint32_t* max_chunk_size);

/* ********************************* */
/*            ATTRIBUTE              */
/* ********************************* */

/**
 * Creates a TileDB attribute.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_attribute_alloc(ctx, "my_attr", TILEDB_INT32, &attr);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param name The attribute name. Providing an empty string for the name
 * creates an anonymous attribute.
 * @param type The attribute type.
 * @param attr The TileDB attribute to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 *
 * @note The default number of values per cell is 1.
 */
TILEDB_EXPORT int32_t tiledb_attribute_alloc(
    tiledb_ctx_t* ctx,
    const char* name,
    tiledb_datatype_t type,
    tiledb_attribute_t** attr);

/**
 * Destroys a TileDB attribute, freeing associated memory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_attribute_alloc(ctx, "my_attr", TILEDB_INT32, &attr);
 * tiledb_attribute_free(&attr);
 * @endcode
 *
 * @param attr The attribute to be destroyed.
 */
TILEDB_EXPORT void tiledb_attribute_free(tiledb_attribute_t** attr);

/**
 * Sets the filter list for an attribute.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_filter_list_alloc(ctx, &filter_list);
 * tiledb_filter_list_add_filter(ctx, filter_list, filter);
 * tiledb_attribute_set_filter_list(ctx, attr, filter_list);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The target attribute.
 * @param filter_list The filter_list to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_set_filter_list(
    tiledb_ctx_t* ctx,
    tiledb_attribute_t* attr,
    tiledb_filter_list_t* filter_list);

/**
 * Sets a compressor for an attribute.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_attribute_set_compressor(ctx, attr, TILEDB_ZSTD, -1);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The target attribute.
 * @param compressor The compressor to be set.
 * @param compression_level The compression level (use `-1` for default).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This function is deprecated and will be removed in a future version.
 *       The filter API should be used instead.
 */
TILEDB_EXPORT TILEDB_DEPRECATED int32_t tiledb_attribute_set_compressor(
    tiledb_ctx_t* ctx,
    tiledb_attribute_t* attr,
    tiledb_compressor_t compressor,
    int32_t compression_level);

/**
 * Sets the number of values per cell for an attribute. If this is not
 * used, the default is `1`.
 *
 * **Examples:**
 *
 * For a fixed-sized attribute:
 *
 * @code{.c}
 * tiledb_attribute_set_cell_val_num(ctx, attr, 3);
 * @endcode
 *
 * For a variable-sized attribute:
 *
 * @code{.c}
 * tiledb_attribute_set_cell_val_num(ctx, attr, TILEDB_VAR_NUM);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The target attribute.
 * @param cell_val_num The number of values per cell.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_set_cell_val_num(
    tiledb_ctx_t* ctx, tiledb_attribute_t* attr, uint32_t cell_val_num);

/**
 * Retrieves the attribute name.
 *
 * **Example:**
 *
 * @code{.c}
 * const char* attr_name;
 * tiledb_attribute_get_name(ctx, attr, &attr_name);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The attribute.
 * @param name The name to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_get_name(
    tiledb_ctx_t* ctx, const tiledb_attribute_t* attr, const char** name);

/**
 * Retrieves the attribute type.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_datatype_t attr_type;
 * tiledb_attribute_get_type(ctx, attr, &attr_type);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The attribute.
 * @param type The type to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_get_type(
    tiledb_ctx_t* ctx, const tiledb_attribute_t* attr, tiledb_datatype_t* type);

/**
 * Retrieves the filter list for an attribute.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_attribute_get_filter_list(ctx, attr, &filter_list);
 * tiledb_filter_list_free(&filter_list);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The target attribute.
 * @param filter_list The filter list to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_get_filter_list(
    tiledb_ctx_t* ctx,
    tiledb_attribute_t* attr,
    tiledb_filter_list_t** filter_list);

/**
 * Retrieves the attribute compressor and the compression level.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_compressor_t compressor;
 * int32_t level;
 * tiledb_attribute_get_compressor(ctx, attr, &compressor, &level);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The attribute.
 * @param compressor The compressor to be retrieved.
 * @param compression_level The compression level to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This function is deprecated and will be removed in a future version.
 *       The filter API should be used instead.
 */
TILEDB_EXPORT TILEDB_DEPRECATED int32_t tiledb_attribute_get_compressor(
    tiledb_ctx_t* ctx,
    const tiledb_attribute_t* attr,
    tiledb_compressor_t* compressor,
    int32_t* compression_level);

/**
 * Retrieves the number of values per cell for the attribute. For variable-sized
 * attributes result is TILEDB_VAR_NUM.
 *
 * **Example:**
 *
 * @code{.c}
 * uint32_t num;
 * tiledb_attribute_get_cell_val_num(ctx, attr, &num);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The attribute.
 * @param cell_val_num The number of values per cell to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_get_cell_val_num(
    tiledb_ctx_t* ctx, const tiledb_attribute_t* attr, uint32_t* cell_val_num);

/**
 * Retrieves the cell size for this attribute.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t cell_size;
 * tiledb_attribute_get_cell_size(ctx, attr, &cell_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The attribute.
 * @param cell_size The cell size to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_get_cell_size(
    tiledb_ctx_t* ctx, const tiledb_attribute_t* attr, uint64_t* cell_size);

/**
 * Dumps the contents of an attribute in ASCII form to some output (e.g.,
 * file or stdout).
 *
 * **Example:**
 *
 * The following prints the attribute dump to standard output.
 *
 * @code{.c}
 * tiledb_attribute_dump(ctx, attr, stdout);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param attr The attribute.
 * @param out The output.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_attribute_dump(
    tiledb_ctx_t* ctx, const tiledb_attribute_t* attr, FILE* out);

/* ********************************* */
/*               DOMAIN              */
/* ********************************* */

/**
 * Creates a TileDB domain.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_domain_t* domain;
 * tiledb_domain_alloc(ctx, &domain);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param domain The TileDB domain to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_domain_alloc(tiledb_ctx_t* ctx, tiledb_domain_t** domain);

/**
 * Destroys a TileDB domain, freeing associated memory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_domain_t* domain;
 * tiledb_domain_alloc(ctx, &domain);
 * tiledb_domain_free(&domain);
 * @endcode
 *
 * @param domain The domain to be destroyed.
 */
TILEDB_EXPORT void tiledb_domain_free(tiledb_domain_t** domain);

/**
 * Retrieves the domain's type.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_datatype_t type;
 * tiledb_domain_get_type(ctx, domain, &type);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param domain The domain.
 * @param type The type to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_domain_get_type(
    tiledb_ctx_t* ctx, const tiledb_domain_t* domain, tiledb_datatype_t* type);

/**
 * Retrieves the number of dimensions in a domain.
 *
 * **Example:**
 *
 * @code{.c}
 * uint32_t dim_num;
 * tiledb_domain_get_ndim(ctx, domain, &dim_num);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param domain The domain
 * @param ndim The number of dimensions in a domain.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_domain_get_ndim(
    tiledb_ctx_t* ctx, const tiledb_domain_t* domain, uint32_t* ndim);

/**
 * Adds a dimension to a TileDB domain.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_dimension_t* dim;
 * uint64_t dim_domain[] = {1, 10};
 * tiledb_dimension_alloc(ctx, "dim_0", TILEDB_INT64, dim_domain, 5, &dim);
 * tiledb_domain_add_dimension(ctx, domain, dim);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param domain The domain to add the dimension to.
 * @param dim The dimension to be added.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_domain_add_dimension(
    tiledb_ctx_t* ctx, tiledb_domain_t* domain, tiledb_dimension_t* dim);

/**
 * Retrieves a dimension object from a domain by index.
 *
 * **Example:**
 *
 * The following retrieves the first dimension from a domain.
 *
 * @code{.c}
 * tiledb_dimension_t* dim;
 * tiledb_domain_get_dimension_from_index(ctx, domain, 0, &dim);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param domain The domain to add the dimension to.
 * @param index The index of domain dimension
 * @param dim The retrieved dimension object.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_domain_get_dimension_from_index(
    tiledb_ctx_t* ctx,
    const tiledb_domain_t* domain,
    uint32_t index,
    tiledb_dimension_t** dim);

/**
 * Retrieves a dimension object from a domain by name (key).
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_dimension_t* dim;
 * tiledb_domain_get_dimension_from_name(ctx, domain, "dim_0", &dim);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param domain The domain to add the dimension to.
 * @param name The name (key) of the requested dimension
 * @param dim The retrieved dimension object.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_domain_get_dimension_from_name(
    tiledb_ctx_t* ctx,
    const tiledb_domain_t* domain,
    const char* name,
    tiledb_dimension_t** dim);

/**
 * Dumps the info of a domain in ASCII form to some output (e.g.,
 * file or `stdout`).
 *
 * **Example:**
 *
 * The following prints the domain dump to the standard output.
 *
 * @code{.c}
 * tiledb_domain_dump(ctx, domain, stdout);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param domain The domain.
 * @param out The output.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_domain_dump(tiledb_ctx_t* ctx, const tiledb_domain_t* domain, FILE* out);

/* ********************************* */
/*             DIMENSION             */
/* ********************************* */

/**
 * Creates a dimension.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_dimension_t* dim;
 * uint64_t dim_domain[] = {1, 10};
 * tiledb_dimension_alloc(ctx, "dim_0", TILEDB_INT64, dim_domain, 5, &dim);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param name The dimension name.
 * @param type The dimension type.
 * @param dim_domain The dimension domain.
 * @param tile_extent The dimension tile extent. This may be `NULL`, which is
 * equivalent to the entire domain.
 * @param dim The dimension to be created.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_dimension_alloc(
    tiledb_ctx_t* ctx,
    const char* name,
    tiledb_datatype_t type,
    const void* dim_domain,
    const void* tile_extent,
    tiledb_dimension_t** dim);

/**
 * Destroys a TileDB dimension, freeing associated memory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_dimension_free(&dim);
 * @endcode
 *
 * @param dim The dimension to be destroyed.
 */
TILEDB_EXPORT void tiledb_dimension_free(tiledb_dimension_t** dim);

/**
 * Retrieves the dimension name.
 *
 * **Example:**
 *
 * @code{.c}
 * const char* dim_name;
 * tiledb_dimension_get_name(ctx, dim, &dim_name);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param dim The dimension.
 * @param name The name to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_dimension_get_name(
    tiledb_ctx_t* ctx, const tiledb_dimension_t* dim, const char** name);

/**
 * Retrieves the dimension type.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_datatype_t dim_type;
 * tiledb_dimension_get_type(ctx, dim, &dim_type);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param dim The dimension.
 * @param type The type to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_dimension_get_type(
    tiledb_ctx_t* ctx, const tiledb_dimension_t* dim, tiledb_datatype_t* type);

/**
 * Retrieves the domain of the dimension.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t domain[2];
 * tiledb_dimension_get_domain(ctx, dim, &domain);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param dim The dimension.
 * @param domain The domain to be retrieved. Note that the defined type of
 *     input `domain` must be the same as the dimension type, otherwise the
 *     behavior is unpredictable (it will probably segfault).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_dimension_get_domain(
    tiledb_ctx_t* ctx, const tiledb_dimension_t* dim, void** domain);

/**
 * Retrieves the tile extent of the dimension.
 *
 * **Example:**
 *
 * @code{.c}
 * void* tile_extent;
 * tiledb_dimension_get_tile_extent(ctx, dim, &tile_extent);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param dim The dimension.
 * @param tile_extent The tile extent (pointer) to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_dimension_get_tile_extent(
    tiledb_ctx_t* ctx, const tiledb_dimension_t* dim, void** tile_extent);

/**
 * Dumps the contents of a dimension in ASCII form to some output (e.g.,
 * file or stdout).
 *
 * **Example:**
 *
 * The following prints the dimension dump to standard output.
 *
 * @code{.c}
 * tiledb_dimension_dump(ctx, dim, stdout);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param dim The dimension.
 * @param out The output.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_dimension_dump(
    tiledb_ctx_t* ctx, const tiledb_dimension_t* dim, FILE* out);

/* ********************************* */
/*            ARRAY SCHEMA           */
/* ********************************* */

/**
 * Creates a TileDB array schema object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_t* array_schema;
 * tiledb_array_schema_alloc(ctx, TILEDB_DENSE, &array_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_type The array type.
 * @param array_schema The TileDB array schema to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_alloc(
    tiledb_ctx_t* ctx,
    tiledb_array_type_t array_type,
    tiledb_array_schema_t** array_schema);

/**
 * Destroys an array schema, freeing associated memory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_free(&array_schema);
 * @endcode
 *
 * @param array_schema The array schema to be destroyed.
 */
TILEDB_EXPORT void tiledb_array_schema_free(
    tiledb_array_schema_t** array_schema);

/**
 * Adds an attribute to an array schema.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_attribute_alloc(ctx, "my_attr", TILEDB_INT32, &attr);
 * tiledb_array_schema_add_attribute(ctx, array_schema, attr);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param attr The attribute to be added.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_add_attribute(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_attribute_t* attr);

/**
 * Sets a domain for the array schema.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_domain_t* domain;
 * tiledb_domain_alloc(ctx, &domain);
 * // -- Add dimensions to the domain here -- //
 * tiledb_array_schema_set_domain(ctx, array_schema, domain);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param domain The domain to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_set_domain(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_domain_t* domain);

/**
 * Sets the tile capacity.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_set_capacity(ctx, array_schema, 10000);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param capacity The capacity to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_set_capacity(
    tiledb_ctx_t* ctx, tiledb_array_schema_t* array_schema, uint64_t capacity);

/**
 * Sets the cell order.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_set_cell_order(ctx, array_schema, TILEDB_ROW_MAJOR);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param cell_order The cell order to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_set_cell_order(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_layout_t cell_order);

/**
 * Sets the tile order.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_set_cell_order(ctx, array_schema, TILEDB_COL_MAJOR);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param tile_order The tile order to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_set_tile_order(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_layout_t tile_order);

/**
 * Sets the filter list to use for the coordinates.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_filter_list_alloc(ctx, &filter_list);
 * tiledb_filter_list_add_filter(ctx, filter_list, filter);
 * tiledb_array_schema_set_coords_filter_list(ctx, array_schema, filter_list);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param filter_list The filter list to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_set_coords_filter_list(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_filter_list_t* filter_list);

/**
 * Sets the compressor to use for the coordinates.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_set_coords_compressor(
 *     ctx, array_schema, TILEDB_GZIP, -1);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param compressor The coordinates compressor.
 * @param compression_level The coordinates compression level (use `-1` for
 *     default).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This function is deprecated and will be removed in a future version.
 *       The filter API should be used instead.
 */
TILEDB_EXPORT TILEDB_DEPRECATED int32_t
tiledb_array_schema_set_coords_compressor(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_compressor_t compressor,
    int32_t compression_level);

/**
 * Sets the filter list to use for the offsets of variable-sized attribute
 * values.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_filter_list_alloc(ctx, &filter_list);
 * tiledb_filter_list_add_filter(ctx, filter_list, filter);
 * tiledb_array_schema_set_offsets_filter_list(ctx, array_schema, filter_list);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param filter_list The filter list to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_set_offsets_filter_list(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_filter_list_t* filter_list);

/**
 * Sets the compressor to use for the offsets of variable-sized attribute
 * values.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_set_offsets_compressor(
 *     ctx, array_schema, TILEDB_GZIP, -1);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param compressor The offsets compressor.
 * @param compression_level The coordinates compression level (use `-1` for
 *     default).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This function is deprecated and will be removed in a future version.
 *       The filter API should be used instead.
 */
TILEDB_EXPORT TILEDB_DEPRECATED int32_t
tiledb_array_schema_set_offsets_compressor(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_compressor_t compressor,
    int32_t compression_level);

/**
 * Checks the correctness of the array schema.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_check(ctx, array_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @return `TILEDB_OK` if the array schema is correct and `TILEDB_ERR` upon any
 *     error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_check(
    tiledb_ctx_t* ctx, tiledb_array_schema_t* array_schema);

/**
 * Retrieves the schema of an array from the disk, creating an array schema
 * struct.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_t* array_schema;
 * tiledb_array_schema_load(ctx, "s3://tiledb_bucket/my_array", array_schema);
 * // Make sure to free the array schema in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_uri The array whose schema will be retrieved.
 * @param array_schema The array schema to be retrieved, or `NULL` upon error.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_load(
    tiledb_ctx_t* ctx,
    const char* array_uri,
    tiledb_array_schema_t** array_schema);

/**
 * Retrieves the schema of an encrypted array from the disk, creating an array
 * schema struct.
 *
 * **Example:**
 *
 * @code{.c}
 * // Load AES-256 key from disk, environment variable, etc.
 * uint8_t key[32] = ...;
 * tiledb_array_schema_t* array_schema;
 * tiledb_array_schema_load_with_key(
 *     ctx, "s3://tiledb_bucket/my_array", TILEDB_AES_256_GCM,
 *     key, sizeof(key), &array_schema);
 * // Make sure to free the array schema in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_uri The array whose schema will be retrieved.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @param array_schema The array schema to be retrieved, or `NULL` upon error.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_load_with_key(
    tiledb_ctx_t* ctx,
    const char* array_uri,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length,
    tiledb_array_schema_t** array_schema);

/**
 * Retrieves the array type.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_t* array_schema;
 * tiledb_array_schema_load(ctx, "s3://tiledb_bucket/my_array", array_schema);
 * tiledb_array_type_t* array_type;
 * tiledb_array_schema_get_array_type(ctx, array_schema, &array_type);
 * // Make sure to free the array schema in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param array_type The array type to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_array_type(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    tiledb_array_type_t* array_type);

/**
 * Retrieves the capacity.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t capacity;
 * tiledb_array_schema_get_capacity(ctx, array_schema, &capacity);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param capacity The capacity to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_capacity(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    uint64_t* capacity);

/**
 * Retrieves the cell order.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_layout_t cell_order;
 * tiledb_array_schema_get_cell_order(ctx, array_schema, &cell_order);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param cell_order The cell order to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_cell_order(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    tiledb_layout_t* cell_order);

/**
 * Retrieves the filter list used for the coordinates.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_array_schema_get_coords_filter_list(ctx, array_schema, &filter_list);
 * tiledb_filter_list_free(ctx, &filter_list);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param filter_list The filter list to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_coords_filter_list(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_filter_list_t** filter_list);

/**
 * Retrieves the compressor info of the coordinates.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_compressor_t compressor;
 * int32_t level;
 * tiledb_array_schema_get_coords_compressor(
 *     ctx, array_schema, &compressor, &level);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param compressor The compressor to be retrieved.
 * @param compression_level The compression level to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This function is deprecated and will be removed in a future version.
 *       The filter API should be used instead.
 */
TILEDB_EXPORT TILEDB_DEPRECATED int32_t
tiledb_array_schema_get_coords_compressor(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    tiledb_compressor_t* compressor,
    int32_t* compression_level);

/**
 * Retrieves the filter list used for the offsets.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_filter_list_t* filter_list;
 * tiledb_array_schema_get_offsets_filter_list(ctx, array_schema, &filter_list);
 * tiledb_filter_list_free(ctx, &filter_list);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param filter_list The filter list to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_offsets_filter_list(
    tiledb_ctx_t* ctx,
    tiledb_array_schema_t* array_schema,
    tiledb_filter_list_t** filter_list);

/**
 * Retrieves the compressor info of the offsets.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_compressor_t compressor;
 * int32_t level;
 * tiledb_array_schema_get_offsets_compressor(
 *     ctx, array_schema, &compressor, &level);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param compressor The compressor to be retrieved.
 * @param compression_level The compression level to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This function is deprecated and will be removed in a future version.
 *       The filter API should be used instead.
 */
TILEDB_EXPORT TILEDB_DEPRECATED int32_t
tiledb_array_schema_get_offsets_compressor(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    tiledb_compressor_t* compressor,
    int32_t* compression_level);

/**
 * Retrieves the array domain.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_domain_t* domain;
 * tiledb_array_schema_get_domain(ctx, array_schema, &domain);
 * // Make sure to delete domain in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param domain The array domain to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_domain(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    tiledb_domain_t** domain);

/**
 * Retrieves the tile order.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_layout_t tile_order;
 * tiledb_array_schema_get_tile_order(ctx, array_schema, &tile_order);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param tile_order The tile order to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_tile_order(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    tiledb_layout_t* tile_order);

/**
 * Retrieves the number of array attributes.
 *
 * **Example:**
 *
 * @code{.c}
 * uint32_t attr_num;
 * tiledb_array_schema_get_attribute_num(ctx, array_schema, &attr_num);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param attribute_num The number of attributes to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_attribute_num(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    uint32_t* attribute_num);

/**
 * Retrieves an attribute given its index.
 *
 * Attributes are ordered the same way they were defined
 * when constructing the array schema.
 *
 * **Example:**
 *
 * The following retrieves the first attribute in the schema.
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_array_schema_get_attribute_from_index(ctx, array_schema, 0, &attr);
 * // Make sure to delete the retrieved attribute in the end.
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param index The index of the attribute to retrieve.
 * @param attr The attribute object to retrieve.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_attribute_from_index(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    uint32_t index,
    tiledb_attribute_t** attr);

/**
 * Retrieves an attribute given its name (key).
 *
 * **Example:**
 *
 * The following retrieves the first attribute in the schema.
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_array_schema_get_attribute_from_index(ctx, array_schema, "attr_0",
 * &attr);
 * // Make sure to delete the retrieved attribute in the end.
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param name The name (key) of the attribute to retrieve.
 * @param attr THe attribute object to retrieve.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_get_attribute_from_name(
    tiledb_ctx_t* ctx,
    const tiledb_array_schema_t* array_schema,
    const char* name,
    tiledb_attribute_t** attr);

/**
 * Dumps the array schema in ASCII format in the selected output.
 *
 * **Example:**
 *
 * The following prints the array schema dump in standard output.
 *
 * @code{.c}
 * tiledb_array_schema_dump(ctx, array_schema, stdout);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_schema The array schema.
 * @param out The output.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_schema_dump(
    tiledb_ctx_t* ctx, const tiledb_array_schema_t* array_schema, FILE* out);

/* ********************************* */
/*               QUERY               */
/* ********************************* */

/**
 * Creates a TileDB query object. Note that the query object is associated
 * with a specific array object. The query type (read or write) is inferred
 * from the array object, which was opened with a specific query type.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "file:///my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_WRITE);
 * tiledb_query_t* query;
 * tiledb_query_alloc(ctx, array, TILEDB_WRITE, &query);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The query object to be created.
 * @param array An opened array object.
 * @param query_type The query type. This must comply with the query type
 *     `array` was opened.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_query_alloc(
    tiledb_ctx_t* ctx,
    tiledb_array_t* array,
    tiledb_query_type_t query_type,
    tiledb_query_t** query);

/**
 * Indicates that the query will write or read a subarray, and provides
 * the appropriate information.
 *
 * **Example:**
 *
 * The following sets a 2D subarray [0,10], [20, 30] to the query.
 *
 * @code{.c}
 * uint64_t subarray[] = { 0, 10, 20, 30};
 * tiledb_query_set_subarray(ctx, query, subarray);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The TileDB query.
 * @param subarray The subarray in which the array read/write will be
 *     constrained on. It should be a sequence of [low, high] pairs (one
 *     pair per dimension). For the case of writes, this is meaningful only
 *     for dense arrays, and specifically dense writes. Note that `subarray`
 *     must have the same type as the domain.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If you set the subarray of a completed, incomplete or in-progress
 *     query, this function will clear the internal state and render it
 *     as uninitialized. However, the potentially set layout and attribute
 *     buffers will be retained. This is useful when the user wishes to
 *     fix the attributes and layout, but explore different subarrays with
 *     the same `tiledb_query_t` object (i.e., without having to created
 *     a new object).
 *
 * @note Setting the subarray in sparse writes is meaningless and, thus,
 *     this function will error in the following two cases, provided that
 *     this is a write query:
 *     (i) the array is sparse, and (ii) the array is dense and the
 *     layout has been set to `TILEDB_UNORDERED`. In the second case,
 *     if the user sets the layout to `TILEDB_UNORDERED` **after**
 *     the subarray has been set, the subarray will simply be ignored.
 */
TILEDB_EXPORT int32_t tiledb_query_set_subarray(
    tiledb_ctx_t* ctx, tiledb_query_t* query, const void* subarray);

/**
 * Sets the buffer for a fixed-sized attribute to a query, which will
 * either hold the values to be written (if it is a write query), or will hold
 * the results from a read query.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t a1[100];
 * uint64_t a1_size = sizeof(a1);
 * tiledb_query_set_buffer(ctx, query, "a1", a1, &a1_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The TileDB query.
 * @param attribute The attribute to set the buffer for. Note that the
 *     coordinates have special attribute name `TILEDB_COORDS`. Also,
 *     if `attribute` is equal to the empty string, then a special default
 *     attribute name is set.
 * @param buffer The buffer that either have the input data to be written,
 *     or will hold the data to be read.
 * @param buffer_size In the case of writes, this is the size of `buffer`
 *     in bytes. In the case of reads, this initially contains the allocated
 *     size of `buffer`, but after the termination of the function
 *     it will contain the size of the useful (read) data in `buffer`.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_query_set_buffer(
    tiledb_ctx_t* ctx,
    tiledb_query_t* query,
    const char* attribute,
    void* buffer,
    uint64_t* buffer_size);

/**
 * Sets the buffer for a var-sized attribute to a query, which will
 * either hold the values to be written (if it is a write query), or will hold
 * the results from a read query.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t a2_off[10];
 * uint64_t a2_off_size = sizeof(a2_off);
 * char a2_val[100];
 * uint64_t a2_val_size = sizeof(a2_val);
 * tiledb_query_set_buffer_var(
 *     ctx, query, "a2", a2_off, &a2_off_size, a2_val, &a2_val_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The TileDB query.
 * @param attribute The attribute to set the buffer for.
 * @param buffer_off The buffer that either have the input data to be written,
 *     or will hold the data to be read. This buffer holds the starting offsets
 *     of each cell value in `buffer_val`.
 * @param buffer_off_size In the case of writes, it is the size of `buffer_off`
 *     in bytes. In the case of reads, this initially contains the allocated
 *     size of `buffer_off`, but after the termination of the function
 *     it will contain the size of the useful (read) data in `buffer_off`.
 * @param buffer_val The buffer that either have the input data to be written,
 *     or will hold the data to be read. This buffer holds the actual var-sized
 *     cell values.
 * @param buffer_val_size In the case of writes, it is the size of `buffer_val`
 *     in bytes. In the case of reads, this initially contains the allocated
 *     size of `buffer_val`, but after the termination of the function
 *     it will contain the size of the useful (read) data in `buffer_val`.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_query_set_buffer_var(
    tiledb_ctx_t* ctx,
    tiledb_query_t* query,
    const char* attribute,
    uint64_t* buffer_off,
    uint64_t* buffer_off_size,
    void* buffer_val,
    uint64_t* buffer_val_size);

/**
 * Gets the buffer of a fixed-sized attribute from a query. If the
 * buffer has not been set, then `buffer` is set to `nullptr`.
 *
 * **Example:**
 *
 * @code{.c}
 * int* a1;
 * uint64_t* a1_size;
 * tiledb_query_get_buffer(ctx, query, "a1", &a1, &a1_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The TileDB query.
 * @param attribute The attribute to get the buffer for. Note that the
 *     coordinates have special attribute name `TILEDB_COORDS`.
 * @param buffer The buffer to retrieve.
 * @param buffer_size A pointer to the size of the buffer.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_query_get_buffer(
    tiledb_ctx_t* ctx,
    tiledb_query_t* query,
    const char* attribute,
    void** buffer,
    uint64_t** buffer_size);

/**
 * Gets the values and offsets buffers for a var-sized attribute to a query.
 * If the buffers have not been set, then `buffer_off` and `buffer_val` are
 * set to `nullptr`.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t* a2_off;
 * uint64_t* a2_off_size;
 * char* a2_val;
 * uint64_t* a2_val_size;
 * tiledb_query_get_buffer_var(
 *     ctx, query, "a2", &a2_off, &a2_off_size, &a2_val, &a2_val_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The TileDB query.
 * @param attribute The attribute to set the buffer for.
 * @param buffer_off The offsets buffer to be retrieved.
 * @param buffer_off_size A pointer to the size of the offsets buffer.
 * @param buffer_val The values buffer to be retrieved.
 * @param buffer_val_size A pointer to the size of the values buffer.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_query_get_buffer_var(
    tiledb_ctx_t* ctx,
    tiledb_query_t* query,
    const char* attribute,
    uint64_t** buffer_off,
    uint64_t** buffer_off_size,
    void** buffer_val,
    uint64_t** buffer_val_size);

/**
 * Sets the layout of the cells to be written or read.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_query_set_layout(ctx, query, TILEDB_ROW_MAJOR);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The TileDB query.
 * @param layout For a write query, this specifies the order of the cells
 *     provided by the user in the buffers. For a read query, this specifies
 *     the order of the cells that will be retrieved as results and stored
 *     in the user buffers. The layout can be one of the following:
 *    - `TILEDB_COL_MAJOR`:
 *      This means column-major order with respect to the subarray.
 *    - `TILEDB_ROW_MAJOR`:
 *      This means row-major order with respect to the subarray.
 *    - `TILEDB_GLOBAL_ORDER`:
 *      This means that cells are stored or retrieved in the array global
 *      cell order.
 *    - `TILEDB_UNORDERED`:
 *      This is applicable only to writes for sparse arrays, or for sparse
 *      writes to dense arrays. It specifies that the cells are unordered and,
 *      hence, TileDB must sort the cells in the global cell order prior to
 *      writing.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_query_set_layout(
    tiledb_ctx_t* ctx, tiledb_query_t* query, tiledb_layout_t layout);

/**
 * Flushes all internal state of a query object and finalizes the query.
 * This is applicable only to global layout writes. It has no effect for
 * any other query type.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_query_t* query;
 * // ... Your code here ... //
 * tiledb_query_finalize(ctx, query);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The query object to be flushed.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_query_finalize(tiledb_ctx_t* ctx, tiledb_query_t* query);

/**
 * Frees a TileDB query object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_query_free(&query);
 * @endcode
 *
 * @param query The query object to be deleted.
 */
TILEDB_EXPORT void tiledb_query_free(tiledb_query_t** query);

/**
 * Submits a TileDB query.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_query_submit(ctx, &query);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The query to be submitted.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note `tiledb_query_finalize` must be invoked after finish writing in
 *     global layout (via repeated invocations of `tiledb_query_submit`),
 *     in order to flush any internal state.
 *
 * @note For the case of reads, if the returned status is `TILEDB_INCOMPLETE`,
 *    TileDB could not fit the entire result in the user's buffers. In this
 *    case, the user should consume the read results (if any), optionally
 *    reset the buffers with `tiledb_query_set_buffer`, and then resubmit the
 *    query until the status becomes `TILEDB_COMPLETED`. If all buffer sizes
 *    after the termination of this function become 0, then this means that
 *    **no** useful data was read into the buffers, implying that larger
 *    buffers are needed for the query to proceed. In this case, the users
 *    must reallocate their buffers (increasing their size), reset the buffers
 *    with `tiledb_query_set_buffer`, and resubmit the query.
 */
TILEDB_EXPORT int32_t
tiledb_query_submit(tiledb_ctx_t* ctx, tiledb_query_t* query);

/**
 * Submits a TileDB query in asynchronous mode.
 *
 * **Examples:**
 *
 * Submit without a callback.
 *
 * @code{.c}
 * tiledb_query_submit_async(ctx, &query, NULL, NULL);
 * @endcode
 *
 * Submit with a callback function `print` that takes as input message
 * `msg` and prints it upon completion of the query.
 *
 * @code{.c}
 * const char* msg = "Query completed";
 * tiledb_query_submit_async(ctx, &query, foo, msg);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The query to be submitted.
 * @param callback The function to be called when the query completes.
 * @param callback_data The data to be passed to the callback function.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 *
 * @note `tiledb_query_finalize` must be invoked after finish writing in
 *     global layout (via repeated invocations of `tiledb_query_submit`),
 *     in order to flush any internal state.
 *
 * @note For the case of reads, if the returned status is `TILEDB_INCOMPLETE`,
 *    TileDB could not fit the entire result in the user's buffers. In this
 *    case, the user should consume the read results (if any), optionally
 *    reset the buffers with `tiledb_query_set_buffer`, and then resubmit the
 *    query until the status becomes `TILEDB_COMPLETED`. If all buffer sizes
 *    after the termination of this function become 0, then this means that
 *    **no** useful data was read into the buffers, implying that larger
 *    buffers are needed for the query to proceed. In this case, the users
 *    must reallocate their buffers (increasing their size), reset the buffers
 *    with `tiledb_query_set_buffer`, and resubmit the query.
 */
TILEDB_EXPORT int32_t tiledb_query_submit_async(
    tiledb_ctx_t* ctx,
    tiledb_query_t* query,
    void (*callback)(void*),
    void* callback_data);

/**
 * Checks if the query has returned any results. Applicable only to
 * read queries; it sets `has_results` to `0 in the case of writes.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t has_results;
 * tiledb_query_has_results(ctx, query, &has_results);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The query.
 * @param has_results Set to `1` if the query returned results and `0`
 *     otherwise.
 * @return `TILEDB_OK` upon success, and `TILEDB_ERR` upon error.
 */
TILEDB_EXPORT int32_t tiledb_query_has_results(
    tiledb_ctx_t* ctx, tiledb_query_t* query, int32_t* has_results);

/**
 * Retrieves the status of a query.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_query_status_t status;
 * tiledb_query_get_status(ctx, query, &status);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The query.
 * @param status The query status to be retrieved.
 * @return `TILEDB_OK` upon success, and `TILEDB_ERR` upon error.
 */
TILEDB_EXPORT int32_t tiledb_query_get_status(
    tiledb_ctx_t* ctx, tiledb_query_t* query, tiledb_query_status_t* status);

/**
 * Retrieves the query type.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_query_type_t query_type;
 * tiledb_query_get_status(ctx, query, &query_type);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param query The query.
 * @param query_type The query type to be retrieved.
 * @return `TILEDB_OK` upon success, and `TILEDB_ERR` upon error.
 */
TILEDB_EXPORT int32_t tiledb_query_get_type(
    tiledb_ctx_t* ctx, tiledb_query_t* query, tiledb_query_type_t* query_type);

/* ********************************* */
/*               ARRAY               */
/* ********************************* */

/**
 * Allocates a TileDB array object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_uri The array URI.
 * @param array The array object to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_alloc(
    tiledb_ctx_t* ctx, const char* array_uri, tiledb_array_t** array);

/**
 * Opens a TileDB array. The array is opened using a query type as input.
 * This is to indicate that queries created for this `tiledb_array_t`
 * object will inherit the query type. In other words, `tiledb_array_t`
 * objects are opened to receive only one type of queries.
 * They can always be closed and be re-opened with another query type.
 * Also there may be many different `tiledb_array_t`
 * objects created and opened with different query types. For
 * instance, one may create and open an array object `array_read` for
 * reads and another one `array_write` for writes, and interleave
 * creation and submission of queries for both these array objects.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object to be opened.
 * @param query_type The type of queries the array object will be receiving.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the same array object is opened again without being closed,
 *     an error will be thrown.
 */
TILEDB_EXPORT int32_t tiledb_array_open(
    tiledb_ctx_t* ctx, tiledb_array_t* array, tiledb_query_type_t query_type);

/**
 * Similar to `tiledb_array_open`, but this function takes as input a
 * timestamp, representing time in milliseconds ellapsed since
 * 1970-01-01 00:00:00 +0000 (UTC). Opening the array at a
 * timestamp provides a view of the array with all writes/updates that
 * happened at or before `timestamp` (i.e., excluding those that
 * occurred after `timestamp`). This function is useful to ensure
 * consistency at a potential distributed setting, where machines
 * need to operate on the same view of the array.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * // Assuming `timestamp` is time represented in milliseconds:
 * tiledb_array_open_at(ctx, array, TILEDB_READ, timestamp);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object to be opened.
 * @param query_type The type of queries the array object will be receiving.
 * @param timestamp The timestamp to open the array at.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the same array object is opened again without being closed,
 *     an error will be thrown.
 * @note This function is applicable only to read queries.
 */
TILEDB_EXPORT int32_t tiledb_array_open_at(
    tiledb_ctx_t* ctx,
    tiledb_array_t* array,
    tiledb_query_type_t query_type,
    uint64_t timestamp);

/**
 * Opens an encrypted array using the given encryption key. This function has
 * the same semantics as `tiledb_array_open()` but is used for encrypted arrays.
 *
 * An encrypted array must be opened with this function before queries can be
 * issued to it.
 *
 * **Example:**
 *
 * @code{.c}
 * // Load AES-256 key from disk, environment variable, etc.
 * uint8_t key[32] = ...;
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * tiledb_array_open_with_key(ctx, array, TILEDB_READ,
 *     TILEDB_AES_256_GCM, key, sizeof(key));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object to be opened.
 * @param query_type The type of queries the array object will be receiving.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_open_with_key(
    tiledb_ctx_t* ctx,
    tiledb_array_t* array,
    tiledb_query_type_t query_type,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length);

/**
 * Similar to `tiledb_array_open_with_key`, but this function takes as
 * input a timestamp, representing time in milliseconds ellapsed since
 * 1970-01-01 00:00:00 +0000 (UTC). Opening the array at a
 * timestamp provides a view of the array with all writes/updates that
 * happened at or before `timestamp` (i.e., excluding those that
 * occurred after `timestamp`). This function is useful to ensure
 * consistency at a potential distributed setting, where machines
 * need to operate on the same view of the array.
 *
 * **Example:**
 *
 * @code{.c}
 * // Load AES-256 key from disk, environment variable, etc.
 * uint8_t key[32] = ...;
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * // Assuming `timestamp` is time represented in milliseconds:
 * tiledb_array_open_at_with_key(ctx, array, TILEDB_READ,
 *     TILEDB_AES_256_GCM, key, sizeof(key), timestamp);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object to be opened.
 * @param query_type The type of queries the array object will be receiving.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @param timestamp The timestamp to open the array at.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the same array object is opened again without being closed,
 *     an error will be thrown.
 * @note This function is applicable only to read queries.
 */
TILEDB_EXPORT int32_t tiledb_array_open_at_with_key(
    tiledb_ctx_t* ctx,
    tiledb_array_t* array,
    tiledb_query_type_t query_type,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length,
    uint64_t timestamp);

/**
 * Checks if the array is open.
 *
 * @param ctx The TileDB context.
 * @param array The array to be checked.
 * @param is_open `1` if the array is open and `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_is_open(
    tiledb_ctx_t* ctx, tiledb_array_t* array, int32_t* is_open);

/**
 * Reopens a TileDB array (the array must be already open). This is useful
 * when the array got updated after it got opened and the `tiledb_array_t`
 * object got created. To sync-up with the updates, the user must either
 * close the array and open with `tiledb_array_open`, or just use
 * `tiledb_array_reopen` without closing. This function will be generally
 * faster than the former alternative.
 *
 * Note: reopening encrypted arrays does not require the encryption key.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * tiledb_array_reopen(ctx, array);
 *
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object to be re-opened.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This is applicable only to arrays opened for reads.
 */
TILEDB_EXPORT int32_t
tiledb_array_reopen(tiledb_ctx_t* ctx, tiledb_array_t* array);

/**
 * Reopens a TileDB array (the array must be already open) at a specific
 * timestamp.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * uint64_t timestamp = tiledb_timestamp_now_ms();
 * tiledb_array_reopen_at(ctx, array, timestamp);
 *
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object to be re-opened.
 * @param timestamp Timestamp at which to reopen.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This is applicable only to arrays opened for reads.
 */
TILEDB_EXPORT int32_t tiledb_array_reopen_at(
    tiledb_ctx_t* ctx, tiledb_array_t* array, uint64_t timestamp);

/**
 * Returns the timestamp, representing time in milliseconds ellapsed since
 * 1970-01-01 00:00:00 +0000 (UTC), at which the array was opened. See also the
 * documentation of `tiledb_array_open_at`.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "s3://tiledb_bucket/my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * // Get the timestamp the array at which the array was opened.
 * uint64_t timestamp;
 * tiledb_array_get_timestamp(ctx, array, &timestamp);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array to retrieve the timestamp for.
 * @param timestamp Set to the timestamp at which the array was opened.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note The array does not need to be opened via `tiledb_array_open_at` to use
 *      this function.
 */
TILEDB_EXPORT int32_t tiledb_array_get_timestamp(
    tiledb_ctx_t* ctx, tiledb_array_t* array, uint64_t* timestamp);

/**
 * Closes a TileDB array.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * tiledb_array_close(ctx, array);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object to be closed.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the array object has already been closed, the function has
 *     no effect.
 */
TILEDB_EXPORT int32_t
tiledb_array_close(tiledb_ctx_t* ctx, tiledb_array_t* array);

/**
 * Frees a TileDB array object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * tiledb_array_close(ctx, array);
 * tiledb_array_free(&array);
 * @endcode
 *
 * @param array The array object to be freed.
 */
TILEDB_EXPORT void tiledb_array_free(tiledb_array_t** array);

/**
 * Retrieves the schema of an array.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_schema_t* array_schema;
 * tiledb_array_get_schema(ctx, array, &array_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The open array.
 * @param array_schema The array schema to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 *
 * @note The user must free the array schema with `tiledb_array_schema_free`.
 */
TILEDB_EXPORT int32_t tiledb_array_get_schema(
    tiledb_ctx_t* ctx,
    tiledb_array_t* array,
    tiledb_array_schema_t** array_schema);

/**
 * Retrieves the query type with which the array was opened.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "hdfs:///tiledb_arrays/my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * tiledb_query_type_t query_type;
 * tiledb_array_get_type(ctx, array, &query_type);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array.
 * @param query_type The query type to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_get_query_type(
    tiledb_ctx_t* ctx, tiledb_array_t* array, tiledb_query_type_t* query_type);

/**
 * Creates a new TileDB array given an input schema.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_create(ctx, "hdfs:///tiledb_arrays/my_array", array_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_uri The array name.
 * @param array_schema The array schema.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_create(
    tiledb_ctx_t* ctx,
    const char* array_uri,
    const tiledb_array_schema_t* array_schema);

/**
 * Creates a new encrypted TileDB array given an input schema.
 *
 * Encrypted arrays can only be created through this function.
 *
 * **Example:**
 *
 * @code{.c}
 * uint8_t key[32] = ...;
 * tiledb_array_create_with_key(
 *     ctx, "hdfs:///tiledb_arrays/my_array", array_schema,
 *     TILEDB_AES_256_GCM, key, sizeof(key));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_uri The array name.
 * @param array_schema The array schema.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_create_with_key(
    tiledb_ctx_t* ctx,
    const char* array_uri,
    const tiledb_array_schema_t* array_schema,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length);

/**
 * Consolidates the fragments of an array into a single fragment.
 *
 * You must first finalize all queries to the array before consolidation can
 * begin (as consolidation temporarily acquires an exclusive lock on the array).
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_consolidate(ctx, "hdfs:///tiledb_arrays/my_array");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_uri The name of the TileDB array to be consolidated.
 * @return `TILEDB_OK` on success, and `TILEDB_ERR` on error.
 */
TILEDB_EXPORT int32_t
tiledb_array_consolidate(tiledb_ctx_t* ctx, const char* array_uri);

/**
 * Consolidates the fragments of an encrypted array into a single fragment.
 *
 * You must first finalize all queries to the array before consolidation can
 * begin (as consolidation temporarily acquires an exclusive lock on the array).
 *
 * **Example:**
 *
 * @code{.c}
 * uint8_t key[32] = ...;
 * tiledb_array_consolidate_with_key(ctx, "hdfs:///tiledb_arrays/my_array",
 *     TILEDB_AES_256_GCM, key, sizeof(key));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array_uri The name of the TileDB array to be consolidated.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @return `TILEDB_OK` on success, and `TILEDB_ERR` on error.
 */
TILEDB_EXPORT int32_t tiledb_array_consolidate_with_key(
    tiledb_ctx_t* ctx,
    const char* array_uri,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length);

/**
 * Retrieves the non-empty domain from an array. This is the union of the
 * non-empty domains of the array fragments.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t domain[4]; // Assuming a 2D array, 2 [low, high] pairs
 * int32_t is_empty;
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * tiledb_array_get_non_empty_domain(ctx, array, domain, &is_empty);
 * @endcode
 *
 * @param ctx The TileDB context
 * @param array The array object (must be opened beforehand).
 * @param domain The domain to be retrieved.
 * @param is_empty The function sets it to `1` if the non-empty domain is
 *     empty (i.e., the array does not contain any data yet), and `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_get_non_empty_domain(
    tiledb_ctx_t* ctx, tiledb_array_t* array, void* domain, int32_t* is_empty);

/**
 * Computes an upper bound on the buffer size (in bytes) required for a read
 * query, for a given **fixed-sized* attribute and subarray.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * uint64_t buffer_size;
 * const char* attribute = "attr_1";
 * uint64_t subarray[] = {10, 20, 10, 100};
 * tiledb_array_max_buffer_size(ctx, array, attribute, subarray, buffer_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object (must be opened beforehand).
 * @param attribute The fixed-sized attribute to focus on.
 * @param subarray The subarray to focus on. Note that it must have the same
 *     underlying type as the array domain.
 * @param buffer_size The buffer size (in bytes) to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_max_buffer_size(
    tiledb_ctx_t* ctx,
    tiledb_array_t* array,
    const char* attribute,
    const void* subarray,
    uint64_t* buffer_size);

/**
 * Computes an upper bound on the buffer size (in bytes) required for a read
 * query, for a given **var-sized* attribute and subarray.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_array_t* array;
 * tiledb_array_alloc(ctx, "my_array", &array);
 * tiledb_array_open(ctx, array, TILEDB_READ);
 * uint64_t buffer_off_size, buffer_val_size;
 * const char* attribute = "attr_2";
 * uint64_t subarray[] = {10, 20, 10, 100};
 * tiledb_array_max_buffer_size_var(
 *     ctx, array, attribute, subarray, buffer_off_size, buffer_val_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param array The array object (must be opened beforehand).
 * @param attribute The var-sized attribute to focus on.
 * @param subarray The subarray to focus on. Note that it must have the same
 *     underlying type as the array domain.
 * @param buffer_off_size The offsets buffer size (in bytes) to be retrieved.
 * @param buffer_val_size The values buffer size (in bytes) to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_max_buffer_size_var(
    tiledb_ctx_t* ctx,
    tiledb_array_t* array,
    const char* attribute,
    const void* subarray,
    uint64_t* buffer_off_size,
    uint64_t* buffer_val_size);

/**
 * Retrieves the URI the array was opened with. It outputs an error
 * if the array is not open.
 *
 * @param ctx The TileDB context.
 * @param array The input array.
 * @param array_uri The array URI to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_array_get_uri(
    tiledb_ctx_t* ctx, tiledb_array_t* array, const char** array_uri);

/* ********************************* */
/*          OBJECT MANAGEMENT        */
/* ********************************* */

/**
 * Returns the TileDB object type for a given resource path.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_object_t type;
 * tiledb_object_type(ctx, "arrays/my_array", &type);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param path The URI path to the TileDB resource.
 * @param type The type to be retrieved.
 * @return `TILEDB_OK` on success, `TILEDB_ERR` on error.
 */
TILEDB_EXPORT int32_t
tiledb_object_type(tiledb_ctx_t* ctx, const char* path, tiledb_object_t* type);

/**
 * Deletes a TileDB resource (group, array, key-value).
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_object_remove(ctx, "arrays/my_array");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param path The URI path to the tiledb resource.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_object_remove(tiledb_ctx_t* ctx, const char* path);

/**
 * Moves a TileDB resource (group, array, key-value).
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_object_move(ctx, "arrays/my_array", "arrays/my_array_2");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param old_path The old TileDB directory.
 * @param new_path The new TileDB directory.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_object_move(
    tiledb_ctx_t* ctx, const char* old_path, const char* new_path);

/**
 * Walks (iterates) over the TileDB objects contained in *path*. The traversal
 * is done recursively in the order defined by the user. The user provides
 * a callback function which is applied on each of the visited TileDB objects.
 * The iteration continues for as long the callback returns non-zero, and stops
 * when the callback returns 0. Note that this function ignores any object
 * (e.g., file or directory) that is not TileDB-related.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_object_walk(ctx, "arrays", TILEDB_PREORDER, NULL, NULL);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param path The path in which the traversal will occur.
 * @param order The order of the recursive traversal (e.g., pre-order or
 *     post-order.
 * @param callback The callback function to be applied on every visited object.
 *     The callback should return `0` if the iteration must stop, and `1`
 *     if the iteration must continue. It takes as input the currently visited
 *     path, the type of that path (e.g., array or group), and the data
 *     provided by the user for the callback. The callback returns `-1` upon
 *     error. Note that `path` in the callback will be an **absolute** path.
 * @param data The data passed in the callback as the last argument.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_object_walk(
    tiledb_ctx_t* ctx,
    const char* path,
    tiledb_walk_order_t order,
    int32_t (*callback)(const char*, tiledb_object_t, void*),
    void* data);

/**
 * Similar to `tiledb_walk`, but now the function visits only the children of
 * `path` (i.e., it does not recursively continue to the children directories.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_object_ls(ctx, "arrays", NULL, NULL);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param path The path in which the traversal will occur.
 * @param callback The callback function to be applied on every visited object.
 *     The callback should return `0` if the iteration must stop, and `1`
 *     if the iteration must continue. It takes as input the currently visited
 *     path, the type of that path (e.g., array or group), and the data
 *     provided by the user for the callback. The callback returns `-1` upon
 *     error. Note that `path` in the callback will be an **absolute** path.
 * @param data The data passed in the callback as the last argument.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_object_ls(
    tiledb_ctx_t* ctx,
    const char* path,
    int32_t (*callback)(const char*, tiledb_object_t, void*),
    void* data);

/* ****************************** */
/*         KEY-VALUE SCHEMA       */
/* ****************************** */

/**
 * Creates a TileDB key-value schema object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_schema_t* kv_schema;
 * tiledb_kv_schema_alloc(ctx, &kv_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The TileDB key-value schema to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_schema_alloc(tiledb_ctx_t* ctx, tiledb_kv_schema_t** kv_schema);

/**
 * Destroys a key-value schema, freeing associated memory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_schema_free(&kv_schema);
 * @endcode
 *
 * @param kv_schema The key-value schema to be destroyed.
 */
TILEDB_EXPORT void tiledb_kv_schema_free(tiledb_kv_schema_t** kv_schema);

/**
 * Adds an attribute to a key-value schema.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_attribute_alloc(ctx, "my_attr", TILEDB_INT32, &attr);
 * tiledb_kv_schema_add_attribute(ctx, kv_schema, attr);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The key-value schema.
 * @param attr The attribute to be added.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_add_attribute(
    tiledb_ctx_t* ctx, tiledb_kv_schema_t* kv_schema, tiledb_attribute_t* attr);

/**
 * Sets the tile capacity.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_schema_set_capacity(ctx, kv_schema, 10000);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The kv schema.
 * @param capacity The capacity to be set.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_set_capacity(
    tiledb_ctx_t* ctx, tiledb_kv_schema_t* kv_schema, uint64_t capacity);

/**
 * Checks the correctness of the key-value schema.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_schema_check(ctx, kv_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The key-value schema.
 * @return `TILEDB_OK` if the key-value schema is correct and `TILEDB_ERR`
 *     upon any error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_schema_check(tiledb_ctx_t* ctx, tiledb_kv_schema_t* kv_schema);

/**
 * Retrieves the schema of a key-value store from the disk, creating a
 * key-value schema struct.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_schema_t* kv_schema;
 * tiledb_kv_schema_load(ctx, "file:///my_kv", &kv_schema);
 * // Make sure to delete the kv schema in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_uri The key-value store whose schema will be retrieved.
 * @param kv_schema The key-value schema to be retrieved, or NULL upon error.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_load(
    tiledb_ctx_t* ctx, const char* kv_uri, tiledb_kv_schema_t** kv_schema);

/**
 * Retrieves the schema of an encrypted key-value store from the disk, creating
 * a key-value schema struct.
 *
 * **Example:**
 *
 * @code{.c}
 * // Load AES-256 key from disk, environment variable, etc.
 * uint8_t key[32] = ...;
 * tiledb_kv_schema_t* kv_schema;
 * tiledb_kv_schema_load_with_key(
 *     ctx, "file:///my_kv", TILEDB_AES_256_GCM,
 *     key, sizeof(key), &kv_schema);
 * // Make sure to free the kv schema in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_uri The key-value store whose schema will be retrieved.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @param kv_schema The key-value schema to be retrieved, or NULL upon error.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_load_with_key(
    tiledb_ctx_t* ctx,
    const char* kv_uri,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length,
    tiledb_kv_schema_t** kv_schema);

/**
 * Retrieves the capacity.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t capacity;
 * tiledb_kv_schema_get_capacity(ctx, kv_schema, &capacity);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The kv schema.
 * @param capacity The capacity to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_get_capacity(
    tiledb_ctx_t* ctx, const tiledb_kv_schema_t* kv_schema, uint64_t* capacity);

/**
 * Retrieves the number of array attributes.
 *
 * **Example:**
 *
 * @code{.c}
 * uint32_t attribute_num;
 * tiledb_kv_schema_get_attribute_num(ctx, kv_schema, &attribute_num);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The key-value schema.
 * @param attribute_num The number of attributes to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_get_attribute_num(
    tiledb_ctx_t* ctx,
    const tiledb_kv_schema_t* kv_schema,
    uint32_t* attribute_num);

/**
 * Retrieves an attribute given its index.
 *
 * Attributes are ordered the same way they were defined
 * when constructing the key-value schema.
 *
 * **Example:**
 *
 * The following retrieves the first attribute in the kv schema.
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_kv_schema_get_attribute_from_index(ctx, kv_schema, 0, &attr);
 * // Make sure to delete the attribute in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The key-value schema.
 * @param index The index of the attribute to retrieve.
 * @param attr The attribute object to retrieve.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_get_attribute_from_index(
    tiledb_ctx_t* ctx,
    const tiledb_kv_schema_t* kv_schema,
    uint32_t index,
    tiledb_attribute_t** attr);

/**
 * Retrieves an attribute given its name (key).
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_attribute_t* attr;
 * tiledb_kv_schema_get_attribute_from_name(ctx, kv_schema, "my_attr", &attr);
 * // Make sure to delete the attribute in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The key-value schema.
 * @param name The name (key) of the attribute to retrieve.
 * @param attr THe attribute object to retrieve.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_get_attribute_from_name(
    tiledb_ctx_t* ctx,
    const tiledb_kv_schema_t* kv_schema,
    const char* name,
    tiledb_attribute_t** attr);

/**
 * Dumps the key-value schema in ASCII format in the selected output.
 *
 * **Example:**
 *
 * The following prints the kv schema dump to standard output.
 *
 * @code{.c}
 * tiledb_kv_schema_dump(ctx, kv_schema, stdout);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_schema The key-value schema.
 * @param out The output.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_schema_dump(
    tiledb_ctx_t* ctx, const tiledb_kv_schema_t* kv_schema, FILE* out);

/* ****************************** */
/*          KEY-VALUE ITEM        */
/* ****************************** */

/**
 * Creates a key-value item.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_item_t* kv_item;
 * tiledb_kv_item_alloc(ctx, &kv_item);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_item The key-value item to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_item_alloc(tiledb_ctx_t* ctx, tiledb_kv_item_t** kv_item);

/**
 * Frees a key-value item.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_item_free(&kv_item);
 * @endcode
 *
 * @param kv_item The key-value item to be freed.
 */
TILEDB_EXPORT void tiledb_kv_item_free(tiledb_kv_item_t** kv_item);

/**
 * Set the key in the key-value item.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_item_set_key(ctx, kv_item, "my_key", TILEDB_CHAR, strlen(my_key));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_item The key-value item.
 * @param key The key to be set.
 * @param key_type The key type to be set.
 * @param key_size The key size (in bytes).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_item_set_key(
    tiledb_ctx_t* ctx,
    tiledb_kv_item_t* kv_item,
    const void* key,
    tiledb_datatype_t key_type,
    uint64_t key_size);

/**
 * Sets a value for a particular attribute to the key-value item.
 * This function works for both fixed- and variable-sized
 * attributes.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t v = 10;
 * tiledb_kv_item_set_value(ctx, kv_item, "attr_1", v, TILEDB_INT32, sizeof(v));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_item The key-value item.
 * @param attribute The attribute name.
 * @param value The value to be added.
 * @param value_type The value type.
 * @param value_size The value size in bytes.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_item_set_value(
    tiledb_ctx_t* ctx,
    tiledb_kv_item_t* kv_item,
    const char* attribute,
    const void* value,
    tiledb_datatype_t value_type,
    uint64_t value_size);

/**
 * Gets the key in the key-value item.
 *
 * **Example:**
 *
 * @code{.c}
 * const char* key; // A pointer will be retrieved - not a new char object
 * uint64_t key_size;
 * tiledb_datatype_t key_type;
 * tiledb_kv_item_get_key(ctx, kv_item, &key, &key_type, &key_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_item The key-value item.
 * @param key The key to be retrieved.
 * @param key_type The key type to be retrieved.
 * @param key_size The key size (in bytes).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_item_get_key(
    tiledb_ctx_t* ctx,
    tiledb_kv_item_t* kv_item,
    const void** key,
    tiledb_datatype_t* key_type,
    uint64_t* key_size);

/**
 * Gets the value and value size on a given attribute from a key-value item.
 *
 * **Example:**
 *
 * @code{.c}
 * const int32_t* v; // A pointer will be retrieved - not a new int object
 * uint64_t v_size;
 * tiledb_datatype_t v_type;
 * tiledb_kv_item_get_value(ctx, kv_item, &v, &v_type, &v_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_item The key-value item.
 * @param attribute The attribute name.
 * @param value The value to be retrieved.
 * @param value_type The value type to be retrieved.
 * @param value_size The value size to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_item_get_value(
    tiledb_ctx_t* ctx,
    tiledb_kv_item_t* kv_item,
    const char* attribute,
    const void** value,
    tiledb_datatype_t* value_type,
    uint64_t* value_size);

/* ****************************** */
/*             KEY-VALUE          */
/* ****************************** */

/**
 * Creates a key-value store from the input key-value schema.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_create(ctx, "my_kv", kv_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_uri The URI of the key-value store.
 * @param kv_schema The key-value store schema.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_create(
    tiledb_ctx_t* ctx, const char* kv_uri, const tiledb_kv_schema_t* kv_schema);

/**
 * Creates an encrypted key-value store from the input key-value schema.
 *
 * Encrypted key-value stores can only be created through this function.
 *
 * **Example:**
 *
 * @code{.c}
 * uint8_t key[32] = ...;
 * tiledb_kv_create_with_key(
 *     ctx, "my_kv", kv_schema,
 *     TILEDB_AES_256_GCM, key, sizeof(key));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_uri The URI of the key-value store.
 * @param kv_schema The key-value store schema.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_create_with_key(
    tiledb_ctx_t* ctx,
    const char* kv_uri,
    const tiledb_kv_schema_t* kv_schema,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length);

/**
 * Consolidates the fragments of a key-value store into a single fragment.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_consolidate(ctx, "my_kv");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_uri The name of the TileDB key-value store to be consolidated.
 * @return `TILEDB_OK` on success, and `TILEDB_ERR` on error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_consolidate(tiledb_ctx_t* ctx, const char* kv_uri);

/**
 * Consolidates the fragments of an encrypted key-value store into a single
 * fragment.
 *
 * **Example:**
 *
 * @code{.c}
 * uint8_t key[32] = ...;
 * tiledb_kv_consolidate_with_key(ctx, "my_kv",
 *     TILEDB_AES_256_GCM, key, sizeof(key));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_uri The name of the TileDB key-value store to be consolidated.
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @return `TILEDB_OK` on success, and `TILEDB_ERR` on error.
 */
TILEDB_EXPORT int32_t tiledb_kv_consolidate_with_key(
    tiledb_ctx_t* ctx,
    const char* kv_uri,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length);

/**
 * Creates a key-value store object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_uri The URI of the key-value store.
 * @param kv The key-value store object to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_alloc(tiledb_ctx_t* ctx, const char* kv_uri, tiledb_kv_t** kv);

/**
 * Prepares a key-value store for reading/writing.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * tiledb_kv_open(ctx, kv, TILEDB_READ);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store object to be opened.
 * @param query_type The mode in which the key-value store is opened
 *     (read or write).
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the key-value store is already open, the function throws
 *     an error.
 */
TILEDB_EXPORT int32_t tiledb_kv_open(
    tiledb_ctx_t* ctx, tiledb_kv_t* kv, tiledb_query_type_t query_type);

/**
 * Similar to `tiledb_kv_open`, but this function takes as input a
 * timestamp, representing time in milliseconds ellapsed since
 * 1970-01-01 00:00:00 +0000 (UTC). Opening the array at a
 * timestamp provides a view of the array with all writes/updates that
 * happened at or before `timestamp` (i.e., excluding those that
 * occurred after `timestamp`). This function is useful to ensure
 * consistency at a potential distributed setting, where machines
 * need to operate on the same view of the array.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * // Assuming `timestamp` is time represented in milliseconds:
 * tiledb_kv_open_at(ctx, kv, TILEDB_READ, timestamp);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store object to be opened.
 * @param query_type The mode in which the key-value store is opened
 *     (read or write).
 * @param timestamp The timestamp to open the key-value store at.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the key-value store is already open, the function throws
 *     an error.
 * @note This function is applicable only to read queries.
 */
TILEDB_EXPORT int32_t tiledb_kv_open_at(
    tiledb_ctx_t* ctx,
    tiledb_kv_t* kv,
    tiledb_query_type_t query_type,
    uint64_t timestamp);

/**
 * Prepares an encrypted key-value store for reading/writing.
 *
 * An encrypted key-value store must be opened with this function before queries
 * can be issued to it.
 *
 * **Example:**
 *
 * @code{.c}
 * // Load AES-256 key from disk, environment variable, etc.
 * uint8_t key[32] = ...;
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * tiledb_kv_open_with_key(ctx, kv, TILEDB_READ,
 *     TILEDB_AES_256_GCM, key, sizeof(key));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store object to be opened.
 * @param query_type The mode in which the key-value store is opened
 *     (read or write).
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_open_with_key(
    tiledb_ctx_t* ctx,
    tiledb_kv_t* kv,
    tiledb_query_type_t query_type,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length);

/**
 * Similar to `tiledb_kv_open_with_key`, but this function takes as input a
 * timestamp, representing time in milliseconds ellapsed since
 * 1970-01-01 00:00:00 +0000 (UTC). Opening the array at a
 * timestamp provides a view of the array with all writes/updates that
 * happened at or before `timestamp` (i.e., excluding those that
 * occurred after `timestamp`). This function is useful to ensure
 * consistency at a potential distributed setting, where machines
 * need to operate on the same view of the array.
 *
 * **Example:**
 *
 * @code{.c}
 * // Load AES-256 key from disk, environment variable, etc.
 * uint8_t key[32] = ...;
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * // Assuming `timestamp` is time represented in milliseconds:
 * tiledb_kv_open_at_with_key(ctx, kv, TILEDB_READ,
 *     TILEDB_AES_256_GCM, key, sizeof(key), timestamp);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store object to be opened.
 * @param query_type The mode in which the key-value store is opened
 *     (read or write).
 * @param encryption_type The encryption type to use.
 * @param encryption_key The encryption key to use.
 * @param key_length Length in bytes of the encryption key.
 * @param timestamp The timestamp to open the key-value store at.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the key-value store is already open, the function throws
 *     an error.
 * @note This function is applicable only to read queries.
 */
TILEDB_EXPORT int32_t tiledb_kv_open_at_with_key(
    tiledb_ctx_t* ctx,
    tiledb_kv_t* kv,
    tiledb_query_type_t query_type,
    tiledb_encryption_type_t encryption_type,
    const void* encryption_key,
    uint32_t key_length,
    uint64_t timestamp);

/**
 * Checks if the key-value store is open.
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store to be checked.
 * @param is_open `1` if the array is open and `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_is_open(tiledb_ctx_t* ctx, tiledb_kv_t* kv, int32_t* is_open);

/**
 * Reopens a key-value store. This is useful when there were updates
 * to the key-value store after it got opened. This function reopens
 * the key-value store so that it can "see" the new fragments.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * tiledb_kv_open(ctx, kv, TILEDB_READ);
 * // Some code here
 * tiledb_kv_reopen(ctx, kv);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv An opened key-value store object.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This is applicable only to arrays opened for reads.
 */
TILEDB_EXPORT int32_t tiledb_kv_reopen(tiledb_ctx_t* ctx, tiledb_kv_t* kv);

/**
 * Reopens a key-value store at a specific timestamp.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * tiledb_kv_open(ctx, kv, TILEDB_READ);
 * // Some code here
 * uint64_t timestamp = tiledb_timestamp_now_ms();
 * tiledb_kv_reopen_at(ctx, kv, timestamp);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv An opened key-value store object.
 * @param timestamp Timestamp at which to reopen.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This is applicable only to arrays opened for reads.
 */
TILEDB_EXPORT int32_t
tiledb_kv_reopen_at(tiledb_ctx_t* ctx, tiledb_kv_t* kv, uint64_t timestamp);

/**
 * Returns the timestamp, representing time in milliseconds ellapsed since
 * 1970-01-01 00:00:00 +0000 (UTC), at which the KV was opened. See also the
 * documentation of `tiledb_kv_open_at`.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "s3://tiledb_bucket/my_kv", &kv);
 * tiledb_kv_open(ctx, kv, TILEDB_READ);
 * // Get the timestamp at which the kv was opened.
 * uint64_t timestamp;
 * tiledb_kv_get_timestamp(ctx, kv, &timestamp);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The KV to retrieve the timestamp for.
 * @param timestamp Set to the timestamp at which the KV was opened.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note The KV does not need to be opened via `tiledb_kv_open_at` to use
 *      this function.
 */
TILEDB_EXPORT int32_t tiledb_kv_get_timestamp(
    tiledb_ctx_t* ctx, tiledb_kv_t* kv, uint64_t* timestamp);

/**
 * Closes a key-value store and frees all memory. All buffered written items
 * will be flushed to persistent storage.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_close(ctx, kv);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store to be closed.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note If the kv object is already closed, this function has no effect.
 */
TILEDB_EXPORT int32_t tiledb_kv_close(tiledb_ctx_t* ctx, tiledb_kv_t* kv);

/**
 * Frees the key-value store object.
 *
 * @param kv The key-value store object to be freed.
 */
TILEDB_EXPORT void tiledb_kv_free(tiledb_kv_t** kv);

/**
 * Retrieves the schema of a kv object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_schema_t* kv_schema;
 * tiledb_kv_get_schema(ctx, kv, &kv_schema);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The open kv.
 * @param kv_schema The kv schema to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 *
 * @note The user must free the kv schema with `tiledb_kv_schema_free`.
 */
TILEDB_EXPORT int32_t tiledb_kv_get_schema(
    tiledb_ctx_t* ctx, tiledb_kv_t* kv, tiledb_kv_schema_t** kv_schema);

/**
 * Checks if the key-value store is dirty, i.e., if the user added items
 * to the key-value store that are kept in main-memory and have not
 * been flushed to persistent storage.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t is_dirty;
 * tiledb_kv_is_dirty(ctx, kv, &is_dirty);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The open kv.
 * @param is_dirty Set to `1` or `0` based on whether the key-value store
 *     is dirty or not, respectively.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_is_dirty(tiledb_ctx_t* ctx, tiledb_kv_t* kv, int32_t* is_dirty);

/**
 * Adds a key-value item to a key-value store. The item is buffered
 * internally and periodically flushed to persistent storage.
 * `tiledb_kv_flush` forces flushing the buffered items to storage.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_item_t* kv_item;
 * tiledb_kv_item_alloc(ctx, &kv_item);
 * // Add a key and values to the kv item...
 * tiledb_kv_add_item(ctx, kv, kv_item);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store.
 * @param kv_item The key-value item to be added to the store.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_add_item(
    tiledb_ctx_t* ctx, tiledb_kv_t* kv, tiledb_kv_item_t* kv_item);

/**
 * Flushes the buffered items to persistent storage.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_flush(ctx, kv);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_flush(tiledb_ctx_t* ctx, tiledb_kv_t* kv);

/**
 * Retrieves a key-value item based on the input key. If the item with
 * the input key does not exist, `kv_item` is set to `NULL`.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_item_t* kv_item;
 * tiledb_kv_get_item(ctx, kv, "my_key", TILEDB_CHAR, strlen(my_key), &kv_item);
 * // Make sure to delete the kv item in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store.
 * @param key The key.
 * @param key_type The key type.
 * @param key_size The key size.
 * @param kv_item The key-value item to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_get_item(
    tiledb_ctx_t* ctx,
    tiledb_kv_t* kv,
    const void* key,
    tiledb_datatype_t key_type,
    uint64_t key_size,
    tiledb_kv_item_t** kv_item);

/**
 * Checks if a key exists in the key-value store.
 *
 * @param ctx The TileDB context.
 * @param kv The key-value store.
 * @param key The key to check.
 * @param key_type The key type.
 * @param key_size The key size.
 * @param has_key Set to `1` if the key exists and `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_has_key(
    tiledb_ctx_t* ctx,
    tiledb_kv_t* kv,
    const void* key,
    tiledb_datatype_t key_type,
    uint64_t key_size,
    int32_t* has_key);

/* ****************************** */
/*          KEY-VALUE ITER        */
/* ****************************** */

/**
 * Creates an iterator for a key-value store. This can be used only
 * for reading. This sets the pointer to the first key-value item.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_t* kv;
 * tiledb_kv_alloc(ctx, "my_kv", &kv);
 * tiledb_kv_open(ctx, kv);
 * tiledb_kv_iter_t* kv_iter;
 * tiledb_kv_iter_alloc(ctx, kv, &kv_iter);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv The kv the iterator is associated with.
 * @param kv_iter The kv iterator to be created.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_iter_alloc(
    tiledb_ctx_t* ctx, tiledb_kv_t* kv, tiledb_kv_iter_t** kv_iter);

/**
 * Frees a key-value store iterator.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_iter_free(&kv_iter);
 * @endcode
 *
 * @param kv_iter The key-value store iterator to be freed.
 */
TILEDB_EXPORT void tiledb_kv_iter_free(tiledb_kv_iter_t** kv_iter);

/**
 * Retrieves the key-value item currently pointed by the iterator.
 * Note that this function creates a new key-value item.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_item_t* kv_item;
 * tiledb_kv_iter_here(ctx, kv_iter, &kv_iter);
 * // Make sure to delete kv item in the end
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_iter The key-value store iterator.
 * @param kv_item The current key-value item to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_iter_here(
    tiledb_ctx_t* ctx, tiledb_kv_iter_t* kv_iter, tiledb_kv_item_t** kv_item);

/**
 * Moves the iterator to the next item.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_iter_next(ctx, kv_iter);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_iter The key-value store iterator.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_iter_next(tiledb_ctx_t* ctx, tiledb_kv_iter_t* kv_iter);

/**
 * Checks if the iterator is done.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t done;
 * tiledb_kv_iter_next(ctx, kv_iter, &done);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_iter The key-value store iterator.
 * @param done Sets this to `1` if the iterator is done, and to `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_kv_iter_done(
    tiledb_ctx_t* ctx, tiledb_kv_iter_t* kv_iter, int32_t* done);

/**
 * Resets a key-value store iterator.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_kv_iter_reset(ctx, kv_iter);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param kv_iter The key-value store iterator to be reset.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_kv_iter_reset(tiledb_ctx_t* ctx, tiledb_kv_iter_t* kv_iter);

/* ****************************** */
/*        VIRTUAL FILESYSTEM      */
/* ****************************** */

/**
 * Creates a virtual filesystem object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_t* vfs;
 * tiledb_vfs_alloc(ctx, config, &vfs);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object to be created.
 * @param config Configuration parameters.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_alloc(
    tiledb_ctx_t* ctx, tiledb_config_t* config, tiledb_vfs_t** vfs);

/**
 * Frees a virtual filesystem object.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_free(&vfs);
 * @endcode
 *
 * @param vfs The virtual filesystem object to be freed.
 */
TILEDB_EXPORT void tiledb_vfs_free(tiledb_vfs_t** vfs);

/**
 * Retrieves the config from a VFS context.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_config_t* config;
 * tiledb_vfs_get_config(ctx, vfs, &config);
 * // Make sure to free the retrieved config
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The VFS object.
 * @param config The config to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_OOM` or `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_get_config(
    tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, tiledb_config_t** config);

/**
 * Creates an object-store bucket.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_create_bucket(ctx, vfs, "s3://tiledb");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the bucket to be created.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_vfs_create_bucket(tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri);

/**
 * Deletes an object-store bucket.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_delete_bucket(ctx, vfs, "s3://tiledb");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the bucket to be deleted.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_vfs_remove_bucket(tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri);

/**
 * Deletes the contents of an object-store bucket.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_empty_bucket(ctx, vfs, "s3://tiledb");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the bucket to be emptied.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_vfs_empty_bucket(tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri);

/**
 * Checks if an object-store bucket is empty.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t is_empty;
 * tiledb_vfs_is_empty_bucket(ctx, vfs, "s3://tiledb", &empty);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the bucket.
 * @param is_empty Sets it to `1` if the input bucket is empty,
 *     and `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_is_empty_bucket(
    tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri, int32_t* is_empty);

/**
 * Checks if an object-store bucket exists.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t exists;
 * tiledb_vfs_is_bucket(ctx, vfs, "s3://tiledb", &exists);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the bucket.
 * @param is_bucket Sets it to `1` if the input URI is a bucket, and `0`
 *     otherwise.
 * @return TILEDB_OK for success and TILEDB_ERR for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_is_bucket(
    tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri, int32_t* is_bucket);

/**
 * Creates a directory.
 *
 * - On S3, this is a noop.
 * - On all other backends, if the directory exists, the function
 *   just succeeds without doing anything.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_create_dir(ctx, vfs, "hdfs:///temp/my_dir");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the directory to be created.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_vfs_create_dir(tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri);

/**
 * Checks if a directory exists.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t exists;
 * tiledb_vfs_is_dir(ctx, vfs, "hdfs:///temp/my_dir", &exists);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the directory.
 * @param is_dir Sets it to `1` if the directory exists and `0`
 *     otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note For S3, this function will return `true` if there is an object
 *     with prefix `uri/` (TileDB will append `/` internally to `uri`
 *     only if it does not exist), and `false` othewise.
 */
TILEDB_EXPORT int32_t tiledb_vfs_is_dir(
    tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri, int32_t* is_dir);

/**
 * Removes a directory (recursively).
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_remove_dir(ctx, vfs, "hdfs:///temp/my_dir");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The uri of the directory to be removed
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_vfs_remove_dir(tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri);

/**
 * Checks if a file exists.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t exists;
 * tiledb_vfs_is_file(ctx, vfs, "hdfs:///temp/my_file", &is_file);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the file.
 * @param is_file Sets it to `1` if the file exists and `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_is_file(
    tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri, int32_t* is_file);

/**
 * Deletes a file.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_remove_file(ctx, vfs, "hdfs:///temp/my_file");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the file.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_vfs_remove_file(tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri);

/**
 * Retrieves the size of a file.
 *
 * **Example:**
 *
 * @code{.c}
 * uint64_t file_size;
 * tiledb_vfs_file_size(ctx, vfs, "hdfs:///temp/my_file", &file_size);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the file.
 * @param size The file size to be retrieved.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_file_size(
    tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri, uint64_t* size);

/**
 * Renames a file. If the destination file exists, it will be overwritten.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_move_file(
 * ctx, vfs, "hdfs:///temp/my_file", "hdfs::///new_file");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param old_uri The old URI.
 * @param new_uri The new URI.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_move_file(
    tiledb_ctx_t* ctx,
    tiledb_vfs_t* vfs,
    const char* old_uri,
    const char* new_uri);

/**
 * Renames a directory.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_move_dir(ctx, vfs, "hdfs:///temp/my_dir", "hdfs::///new_dir");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param old_uri The old URI.
 * @param new_uri The new URI.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_move_dir(
    tiledb_ctx_t* ctx,
    tiledb_vfs_t* vfs,
    const char* old_uri,
    const char* new_uri);

/**
 * Prepares a file for reading/writing.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_fh_t* fh;
 * tiledb_vfs_open(ctx, vfs, "some_file", TILEDB_VFS_READ, &fh);
 * // Make sure to close and delete the created file handle
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the file.
 * @param mode The mode in which the file is opened:
 *     - `TILEDB_VFS_READ`:
 *       The file is opened for reading. An error is returned if the file
 *       does not exist.
 *     - `TILEDB_VFS_WRITE`:
 *       The file is opened for writing. If the file exists, it will be
 *       overwritten.
 *     - `TILEDB_VFS_APPEND`:
 *       The file is opened for writing. If the file exists, the write
 *       will start from the end of the file. Note that S3 does not
 *       support this operation and, thus, an error will be thrown in
 *       that case.
 * @param fh The file handle that is created. This will be used in
 *     `tiledb_vfs_read`, `tiledb_vfs_write` and `tiledb_vfs_sync`.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` or `TILEDB_OOM` for error.
 *
 * @note If the file is closed after being opened, without having
 *     written any data to it, the file will not be created. If you
 *     wish to create an empty file, use `tiledb_vfs_touch`
 *     instead.
 */
TILEDB_EXPORT int32_t tiledb_vfs_open(
    tiledb_ctx_t* ctx,
    tiledb_vfs_t* vfs,
    const char* uri,
    tiledb_vfs_mode_t mode,
    tiledb_vfs_fh_t** fh);

/**
 * Closes a file. This is flushes the buffered data into the file
 * when the file was opened in write (or append) mode. It is particularly
 * important to be called after S3 writes, as otherwise the writes will
 * not take effect.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_close(ctx, vfs, fh);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param fh The file handle.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_close(tiledb_ctx_t* ctx, tiledb_vfs_fh_t* fh);

/**
 * Reads from a file.
 *
 * **Example:**
 *
 * @code{.c}
 * char buffer[10000];
 * tiledb_vfs_read(ctx, fh, 100, buffer, 10000);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param fh The URI file handle.
 * @param offset The offset in the file where the read begins.
 * @param buffer The buffer to read into.
 * @param nbytes Number of bytes to read.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_read(
    tiledb_ctx_t* ctx,
    tiledb_vfs_fh_t* fh,
    uint64_t offset,
    void* buffer,
    uint64_t nbytes);

/**
 * Writes the contents of a buffer into a file. Note that this
 * function only **appends** data at the end of the file. If the
 * file does not exist, it will be created.
 *
 * **Example:**
 *
 * @code{.c}
 * const char* msg = "This will be written to the file";
 * tiledb_vfs_write(ctx, fh, buffer, strlen(msg));
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param fh The URI file handle.
 * @param buffer The buffer to write from.
 * @param nbytes Number of bytes to write.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_write(
    tiledb_ctx_t* ctx,
    tiledb_vfs_fh_t* fh,
    const void* buffer,
    uint64_t nbytes);

/**
 * Syncs (flushes) a file.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_sync(ctx, fh);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param fh The URI file handle.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note This has no effect for S3.
 */
TILEDB_EXPORT int32_t tiledb_vfs_sync(tiledb_ctx_t* ctx, tiledb_vfs_fh_t* fh);

/**
 * Frees a file handle.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_fh_free(&fh);
 * @endcode
 *
 * @param fh The URI file handle.
 */
TILEDB_EXPORT void tiledb_vfs_fh_free(tiledb_vfs_fh_t** fh);

/**
 * Checks if a file handle is closed.
 *
 * **Example:**
 *
 * @code{.c}
 * int32_t is_closed;
 * tiledb_vfs_fh_is_closed(ctx, fh, &is_closed);
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param fh The URI file handle.
 * @param is_closed Set to `1` if the file handle is closed, and `0` otherwise.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_vfs_fh_is_closed(
    tiledb_ctx_t* ctx, tiledb_vfs_fh_t* fh, int32_t* is_closed);

/**
 * Touches a file, i.e., creates a new empty file.
 *
 * **Example:**
 *
 * @code{.c}
 * tiledb_vfs_touch(ctx, vfs, "my_empty_file");
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param vfs The virtual filesystem object.
 * @param uri The URI of the file to be created.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t
tiledb_vfs_touch(tiledb_ctx_t* ctx, tiledb_vfs_t* vfs, const char* uri);

/* ****************************** */
/*              URI               */
/* ****************************** */

/**
 * Converts the given file URI to a null-terminated platform-native file path.
 *
 * **Example:**
 *
 * @code{.c}
 * char path[TILEDB_MAX_PATH];
 * uint32_t length;
 * tiledb_uri_to_path(ctx, "file:///my_array", path, &length);
 * // This will set "my_array" to `path`
 * @endcode
 *
 * @param ctx The TileDB context.
 * @param uri The URI to be converted.
 * @param path_out The buffer where the converted path string is stored.
 * @param path_length The length of the path buffer. On return, this is set to
 * the length of the converted path string, or 0 on error.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 *
 * @note The path_out buffer must be allocated according to the platform's
 * maximum path length (e.g. `TILEDB_MAX_PATH), which includes space for the
 * terminating null character.
 */
TILEDB_EXPORT int32_t tiledb_uri_to_path(
    tiledb_ctx_t* ctx, const char* uri, char* path_out, uint32_t* path_length);

/* ****************************** */
/*             Stats              */
/* ****************************** */

/**
 * Enable internal statistics gathering.
 *
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_stats_enable();

/**
 * Disable internal statistics gathering.
 *
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_stats_disable();

/**
 * Reset all internal statistics counters to 0.
 *
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_stats_reset();

/**
 * Dump all internal statistics counters to to some output (e.g.,
 * file or stdout).
 *
 * @param out The output.
 * @return `TILEDB_OK` for success and `TILEDB_ERR` for error.
 */
TILEDB_EXPORT int32_t tiledb_stats_dump(FILE* out);

#ifdef __cplusplus
}
#endif

#endif  // TILEDB_H
