Surreal.java

package com.surrealdb;

import com.surrealdb.signin.*;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
import java.util.stream.Stream;

/**
 * The {@code Surreal} class provides methods to interact with a Surreal database.
 * It includes functionality to connect to the database, sign in with different scopes,
 * set the namespace and database, execute queries, and perform CRUD operations on records.
 */
public class Surreal extends Native implements AutoCloseable {

    static {
        Loader.loadNative();
    }

    /**
     * Constructs a new Surreal object.
     */
    public Surreal() {
        super(Surreal.newInstance());
    }

    private static native long newInstance();

    private static native boolean connect(long ptr, String connect);

    private static native String signinRoot(long ptr, String username, String password);

    private static native String signinNamespace(long ptr, String username, String password, String ns);

    private static native String signinDatabase(long ptr, String username, String password, String ns, String db);

    private static native boolean useNs(long ptr, String ns);

    private static native boolean useDb(long ptr, String ns);

    private static native long query(long ptr, String sql);

    private static native long queryBind(long ptr, String sql, String[] paramsKey, long[] valuePtrs);

    private static native long createThingValue(long ptr, long thingPtr, long valuePtr);

    private static native long[] createTargetValues(long ptr, String target, long[] valuePtrs);

    private static native long[] insertTargetValues(long ptr, String target, long[] valuePtrs);

    private static native long insertRelationTargetValue(long ptr, String target, long valuePtr);

    private static native long[] insertRelationTargetValues(long ptr, String target, long[] valuePtrs);

    private static native long relate(long ptr, long from, String table, long to);

    private static native long relateContent(long ptr, long from, String table, long to, long valuePtr);

    private static native long updateThingValue(long ptr, long thingPtr, int update, long valuePtr);

    private static native long updateTargetValue(long ptr, String target, int update, long valuePtr);

    private static native long updateTargetsValue(long ptr, String[] targets, int update, long valuePtr);

    private static native long updateTargetValueSync(long ptr, String target, int update, long valuePtr);

    private static native long updateTargetsValueSync(long ptr, String[] targets, int update, long valuePtr);

    private static native long upsertThingValue(long ptr, long thingPtr, int update, long valuePtr);

    private static native long upsertTargetValue(long ptr, String target, int update, long valuePtr);

    private static native long upsertTargetsValue(long ptr, String[] targets, int update, long valuePtr);

    private static native long upsertTargetValueSync(long ptr, String target, int update, long valuePtr);

    private static native long upsertTargetsValueSync(long ptr, String[] targets, int update, long valuePtr);

    private static native long selectThing(long ptr, long thing);

    private static native long[] selectThings(long ptr, long[] things);

    private static native long selectTargetsValues(long ptr, String... targets);

    private static native long selectTargetsValuesSync(long ptr, String... targets);

    private static native boolean deleteThing(long ptr, long thing);

    private static native boolean deleteThings(long ptr, long[] things);

    private static native boolean deleteTarget(long ptr, String target);


    @Override
    final String toString(long ptr) {
        return getClass().getName() + "[ptr=" + ptr + "]";
    }

    @Override
    final int hashCode(long ptr) {
        return Objects.hashCode(ptr);
    }

    @Override
    final boolean equals(long ptr1, long ptr2) {
        return ptr1 == ptr2;
    }

    @Override
    final native boolean deleteInstance(long ptr);

    /**
     * Establishes a connection to the Surreal database using the provided connection string.
     *
     * @param connect the connection string used to establish the connection
     * @return the current instance of the {@code Surreal} class
     */
    public Surreal connect(String connect) {
        connect(getPtr(), connect);
        return this;
    }

    /**
     * Attempts to sign in to the Surreal system using the provided credentials.
     * The type of signin object determines the scope of the sign-in (Root, Namespace, or Database).
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealdb/security/authentication">authentication documentation</a>.
     * <p>
     *
     * @param signin the credentials for signing in, which can be an instance of Root, Namespace, or Database
     * @return a Token representing the session token after a successful sign-in
     * @throws SurrealException if the signin type is unsupported
     */
    public Token signin(Signin signin) {
        if (signin instanceof Database) {
            final Database db = (Database) signin;
            return new Token(signinDatabase(getPtr(), db.getUsername(), db.getPassword(), db.getNamespace(), db.getDatabase()));
        } else if (signin instanceof Namespace) {
            final Namespace ns = (Namespace) signin;
            return new Token(signinNamespace(getPtr(), ns.getUsername(), ns.getPassword(), ns.getNamespace()));
        } else if (signin instanceof Root) {
            final Root r = (Root) signin;
            return new Token(signinRoot(getPtr(), r.getUsername(), r.getPassword()));
        }
        throw new SurrealException("Unsupported sign in");
    }

    /**
     * Sets the namespace for the Surreal instance.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/use">use statement documentation</a>.
     * <p>
     *
     * @param ns the namespace to use
     * @return the current instance of the {@code Surreal} class
     */
    public Surreal useNs(String ns) {
        useNs(getPtr(), ns);
        return this;
    }

    /**
     * Sets the database for the current instance of the Surreal class.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/use">use statement documentation</a>.
     * <p>
     *
     * @param db the database name to use
     * @return the current instance of the {@code Surreal} class
     */
    public Surreal useDb(String db) {
        useDb(getPtr(), db);
        return this;
    }

    /**
     * Executes a SurrealQL query on the database.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql">SurrealQL documentation</a>.
     * <p>
     *
     * @param sql the SurrealQL query to be executed
     * @return a Response object containing the results of the query
     */
    public Response query(String sql) {
        return new Response(query(getPtr(), sql));
    }

    /**
     * Executes a parameterized SurrealQL query on the database.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql">SurrealQL documentation</a>.
     * <p>
     *
     * @param sql    the SurrealQL query to be executed
     * @param params a map containing parameter values to be bound to the SQL query
     * @return a Response object containing the results of the query
     */
    public Response queryBind(String sql, Map<String, ?> params) {
        Map<String, ValueMut> valueMutMap = params.entrySet().stream()
            .collect(Collectors.toMap(
                Map.Entry::getKey,
                entry ->ValueBuilder.convert(entry.getValue())
            ));
        String[] keys = valueMutMap.keySet().toArray(new String[0]);
        long[] values = new long[keys.length];
        for (int i = 0; i < keys.length; i++) {
            values[i] = valueMutMap.get(keys[i]).getPtr();
        }
        return new Response(queryBind(getPtr(), sql,keys, values ));
    }

    /**
     * Creates a record in the database with the given `RecordID` as the key and the provided content as the value.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/create">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>     the type of the content
     * @param thg     the RecordId associated with the new record
     * @param content the content of the created record
     * @return a new Value object initialized with the provided RecordId and content
     */
    public <T> Value create(RecordId thg, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        final long valuePtr = createThingValue(getPtr(), thg.getPtr(), valueMut.getPtr());
        return new Value(valuePtr);
    }

    /**
     * Creates a record in the database with the given `RecordID` as the key and the provided content as the value.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/create">SurrealQL documentation</a>.
     * <p>
     *
     * @param type    The class type of the object to create
     * @param thg     The RecordId used with the new record
     * @param content The content of the created record
     * @return An instance of the specified type
     */
    public <T> T create(Class<T> type, RecordId thg, T content) {
        return create(thg, content).get(type);
    }

    /**
     * Creates records in the database with the given table and the provided contents as the values.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/create">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>      the type of the contents
     * @param target   the target for which the records are created
     * @param contents the contents of the created records
     * @return a list of Value objects created based on the target and contents
     */
    @SafeVarargs
    public final <T> List<Value> create(String target, T... contents) {
        final long[] valueMutPtrs = contents2longs(contents);
        final long[] valuePtrs = createTargetValues(getPtr(), target, valueMutPtrs);
        return Arrays.stream(valuePtrs).mapToObj(Value::new).collect(Collectors.toList());
    }

    /**
     * Creates records in the database with the given table and the provided contents as the values.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/create">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>      the type of objects to be created
     * @param type     the class of the type to be created
     * @param target   the target string used in the creation process
     * @param contents the contents to be used to create the objects
     * @return a list of objects of the specified type
     */
    @SafeVarargs
    public final <T> List<T> create(Class<T> type, String target, T... contents) {
        try (final Stream<Value> s = create(target, contents).stream()) {
            return s.map(v -> v.get(type)).collect(Collectors.toList());
        }
    }

    /**
     * Insert records in the database with the given table and the provided contents as the values.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/insert">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>      the type of the contents
     * @param target   the target for which the records are inserted
     * @param contents the contents of the inserted records
     * @return a list of Value objects inserted based on the target and contents
     */
    @SafeVarargs
    public final <T> List<Value> insert(String target, T... contents) {
        final long[] valueMutPtrs = contents2longs(contents);
        final long[] valuePtrs = insertTargetValues(getPtr(), target, valueMutPtrs);
        return Arrays.stream(valuePtrs).mapToObj(Value::new).collect(Collectors.toList());
    }

    /**
     * Insert records in the database with the given table and the provided contents as the values.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/insert">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>      the type of objects to be inserted
     * @param type     the class of the type to be inserted
     * @param target   the target string used in the insertion process
     * @param contents the contents to be used to insert the objects
     * @return a list of objects of the specified type
     */
    @SafeVarargs
    public final <T> List<T> insert(Class<T> type, String target, T... contents) {
        try (final Stream<Value> s = insert(target, contents).stream()) {
            return s.map(v -> v.get(type)).collect(Collectors.toList());
        }
    }

    /**
     * Inserts a relation to the specified table using the provided content.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/insert#insert-relation-tables">SurrealQL documentation</a>.
     * <p>
     *
     * @param target  the table where the relation is to be inserted
     * @param content the content to insert as the relation
     * @param <T>     a type that extends InsertRelation
     * @return a Value object representing the inserted relation
     */
    public <T extends InsertRelation> Value insertRelation(String target, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        final long valuePtr = insertRelationTargetValue(getPtr(), target, valueMut.getPtr());
        return new Value(valuePtr);
    }

    /**
     * Inserts a relation of the specified type and table with the provided content.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/insert#insert-relation-tables">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>     the type of the relation that extends InsertRelation
     * @param type    the class object of the type T
     * @param target  the target identifier for the relation
     * @param content the content to be inserted in the relation
     * @return the inserted relation of type T
     */
    public <T extends InsertRelation> T insertRelation(Class<T> type, String target, T content) {
        return insertRelation(target, content).get(type);
    }

    /**
     * Inserts relations into the specified table with the provided contents.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/insert#insert-relation-tables">SurrealQL documentation</a>.
     * <p>
     *
     * @param target   the table into which the relations will be inserted
     * @param contents the contents of the relations to be inserted
     * @param <T>      the type of the insert relation
     * @return a list of values representing the result of the insert operation
     */
    @SafeVarargs
    public final <T extends InsertRelation> List<Value> insertRelations(String target, T... contents) {
        final long[] valueMutPtrs = contents2longs(contents);
        final long[] valuePtrs = insertRelationTargetValues(getPtr(), target, valueMutPtrs);
        return Arrays.stream(valuePtrs).mapToObj(Value::new).collect(Collectors.toList());
    }

    /**
     * Inserts multiple relations of a specified type into the target.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/insert#insert-relation-tables">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>      the type of InsertRelation
     * @param type     the class type of the InsertRelation
     * @param target   the table in which the relations are to be inserted
     * @param contents the array of InsertRelation objects to be inserted
     * @return a list of inserted relations of the specified type
     */
    @SafeVarargs
    public final <T extends InsertRelation> List<T> insertRelations(Class<T> type, String target, T... contents) {
        try (final Stream<Value> s = insertRelations(target, contents).stream()) {
            return s.map(v -> v.get(type)).collect(Collectors.toList());
        }
    }

    /**
     * Establishes a relation between two records identified by `from` and `to` within a specified table.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/relate">SurrealQL documentation</a>.
     * <p>
     *
     * @param from  the record identifier from which the relation originates
     * @param table the name of the table where the relation will be established
     * @param to    the record identifier to which the relation points
     * @return a new {@code Value} instance representing the relation
     */
    public Value relate(RecordId from, String table, RecordId to) {
        final long valuePtr = relate(getPtr(), from.getPtr(), table, to.getPtr());
        return new Value(valuePtr);
    }

    /**
     * Establishes and retrieves a relation of a specified type between two records.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/relate">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>   The type of the relation extending Relation.
     * @param type  The class type of the relation.
     * @param from  The starting record of the relation.
     * @param table The name of the table that holds the relation.
     * @param to    The ending record of the relation.
     * @return The established relation of the specified type.
     */
    public <T extends Relation> T relate(Class<T> type, RecordId from, String table, RecordId to) {
        return relate(from, table, to).get(type);
    }

    /**
     * Establishes a relationship between two records within a specified table,
     * attaching the provided content to this relationship.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/relate">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the content associated with the relation
     * @param from    The record ID that the relationship starts from.
     * @param table   The table in which the relationship is being created.
     * @param to      The record ID that the relationship points to.
     * @param content The content to attach to the relationship.
     * @return A Value object representing the newly created relationship.
     */
    public <T> Value relate(RecordId from, String table, RecordId to, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        final long valuePtr = relateContent(getPtr(), from.getPtr(), table, to.getPtr(), valueMut.getPtr());
        return new Value(valuePtr);
    }

    /**
     * Establishes a relation between two records and retrieves it based on the specified relation type.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/relate">SurrealQL documentation</a>.
     *
     * @param <R>     the type of the relation
     * @param <T>     the type of the content associated with the relation
     * @param type    the class of the relation type
     * @param from    the record identifier of the source record
     * @param table   the name of the table where the relation is to be established
     * @param to      the record identifier of the target record
     * @param content the content to be associated with the relation
     * @return the established relation of the specified type
     */
    public <R extends Relation, T> R relate(Class<R> type, RecordId from, String table, RecordId to, T content) {
        return relate(from, table, to, content).get(type);
    }

    /**
     * Updates the value of a record with the specified content and update type.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param <T>     The type of the content to be updated.
     * @param thg     The RecordId of the thing to be updated.
     * @param upType  The type of update to be performed.
     * @param content The new content to set for the specified record.
     * @return A Value object representing the updated value.
     */
    public <T> Value update(RecordId thg, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        final long valuePtr = updateThingValue(getPtr(), thg.getPtr(), upType.code, valueMut.getPtr());
        return new Value(valuePtr);
    }

    /**
     * Updates a record of the specified type and returns the updated record.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param type    the class type of the record to be updated
     * @param thg     the identifier of the record to be updated
     * @param upType  the type of update operation to be performed
     * @param content the new content to update the record with
     * @param <T>     the type of the record
     * @return the updated record of the specified type
     */
    public <T> T update(Class<T> type, RecordId thg, UpType upType, T content) {
        return update(thg, upType, content).get(type);
    }

    /**
     * Updates the table with the given content based on the specified update type.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the content to be used for the update
     * @param target  the table to be updated
     * @param upType  the type of update operation to be performed
     * @param content the content to update the target with
     * @return an Iterator of Value objects reflecting the updated state of the target
     */
    public <T> Iterator<Value> update(String target, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new ValueIterator(updateTargetValue(getPtr(), target, upType.code, valueMut.getPtr()));
    }

    /**
     * Updates the specified tables with the given content.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param targets an array of strings representing the table identifiers to be updated
     * @param upType  the type of update operation to be performed
     * @param content the content to update the targets with; the content can be of any type
     * @param <T>     the type of the content
     * @return an Iterator of Value objects representing the updated values
     */
    public <T> Iterator<Value> update(String[] targets, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new ValueIterator(updateTargetsValue(getPtr(), targets, upType.code, valueMut.getPtr()));
    }

    /**
     * Updates the specified table with the provided content and returns an iterator
     * for the updated values.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the content and the type parameter for the iterator
     * @param type    the class type of the content
     * @param target  the table to be updated
     * @param upType  the type of update operation to be performed
     * @param content the content to update the target with
     * @return an iterator over the updated values
     */
    public <T> Iterator<T> update(Class<T> type, String target, UpType upType, T content) {
        return new ValueObjectIterator<>(type, update(target, upType, content));
    }

    /**
     * Updates the specified tables with the given content and returns an iterator
     * over the updated elements of the specified type.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param type    The class type of the elements to be updated.
     * @param targets An array of table identifiers to be updated.
     * @param upType  The type of update operation to be performed.
     * @param content The content to update the targets with.
     * @param <T>     The type of the elements being updated.
     * @return An iterator over the updated elements of the specified type.
     */
    public <T> Iterator<T> update(Class<T> type, String[] targets, UpType upType, T content) {
        return new ValueObjectIterator<>(type, update(targets, upType, content));
    }

    /**
     * Updates the specified table with the provided content.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param <T>     The type of the content to be synchronized.
     * @param target  The table identifier to be updated.
     * @param upType  The type of update to be performed, represented by an UpType object.
     * @param content The content to update the target with.
     * @return A thread-safe Iterator of Value objects that reflects the updated state.
     */
    public <T> Iterator<Value> updateSync(String target, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new SynchronizedValueIterator(updateTargetValueSync(getPtr(), target, upType.code, valueMut.getPtr()));
    }

    /**
     * Updates the tables using the provided content and update type.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the content being updated
     * @param targets an array of strings representing the tables to be updated
     * @param upType  an instance of {@code UpType} indicating the type of update to be performed
     * @param content the content to be used for the update, which will be converted to a {@code Value}
     * @return a thread-safe iterator over the updated {@code Value} objects
     */
    public <T> Iterator<Value> updateSync(String[] targets, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new SynchronizedValueIterator(updateTargetsValueSync(getPtr(), targets, upType.code, valueMut.getPtr()));
    }

    /**
     * Updates the table with the provided content.
     * The updated resource is then returned as a thread-safe iterator of the specified type.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the content being updated
     * @param type    the class type of the elements that the returned iterator will contain
     * @param target  the identifier of the table resource to be updated
     * @param upType  the type of update operation to be performed
     * @param content the data to update the target resource with
     * @return a thread-safe iterator of the specified type containing the updated resource
     */
    public <T> Iterator<T> updateSync(Class<T> type, String target, UpType upType, T content) {
        return new ValueObjectIterator<>(type, updateSync(target, upType, content));
    }

    /**
     * Updates the provided tables with the provided content and returns an iterator for the updated values.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/update">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the content being updated
     * @param type    the class type of the content
     * @param targets an array of target identifiers to be updated
     * @param upType  the type of update to be performed
     * @param content the content to be used for the update
     * @return a thread-safe iterator for the updated values
     */
    public <T> Iterator<T> updateSync(Class<T> type, String[] targets, UpType upType, T content) {
        return new ValueObjectIterator<>(type, updateSync(targets, upType, content));
    }

    /**
     * Inserts a new record or updates an existing record with the given content.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     The type of the content.
     * @param thg     The record identifier.
     * @param upType  The update type specifying how to handle the upsert.
     * @param content The content to be inserted or updated.
     * @return The resulting value after the upsert operation.
     */
    public <T> Value upsert(RecordId thg, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        final long valuePtr = upsertThingValue(getPtr(), thg.getPtr(), upType.code, valueMut.getPtr());
        return new Value(valuePtr);
    }

    /**
     * Upserts a record and returns the updated or inserted entity.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     The type of the entity to be upserted.
     * @param type    The class type of the entity.
     * @param thg     The record identifier.
     * @param upType  The type of the update.
     * @param content The content of the entity to be upserted.
     * @return The upserted entity of the specified type.
     */
    public <T> T upsert(Class<T> type, RecordId thg, UpType upType, T content) {
        return upsert(thg, upType, content).get(type);
    }

    /**
     * Performs an upsert operation on the specified table with the provided content.
     * The operation type is determined by the {@code UpType} enumeration.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     The type of the content to be upserted.
     * @param target  The target on which the upsert operation is to be performed.
     * @param upType  The type of upsert operation to be executed.
     * @param content The content to be upserted.
     * @return An iterator over the values resulting from the upsert operation.
     */
    public <T> Iterator<Value> upsert(String target, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new ValueIterator(upsertTargetValue(getPtr(), target, upType.code, valueMut.getPtr()));
    }

    /**
     * Inserts or updates values in the given tables.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param targets The array of tables to upsert values.
     * @param upType  The type specifying the upserting strategy to use.
     * @param content The content to be inserted or updated.
     * @param <T>     The type of the content to upsert.
     * @return An iterator over the upserted values.
     */
    public <T> Iterator<Value> upsert(String[] targets, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new ValueIterator(upsertTargetsValue(getPtr(), targets, upType.code, valueMut.getPtr()));
    }

    /**
     * Inserts or updates a record of the specified type with the given content.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the object to be upserted.
     * @param type    the Class object representing the type of the object.
     * @param target  the table identifier where the records should be upserted.
     * @param upType  the type of upsert operation to perform.
     * @param content the content of the object to be upserted.
     * @return an iterator of the type {@code T} containing the results of the upsert operation.
     */
    public <T> Iterator<T> upsert(Class<T> type, String target, UpType upType, T content) {
        return new ValueObjectIterator<>(type, upsert(target, upType, content));
    }

    /**
     * Updates or inserts the provided content based on the specified tables and update type.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     The type of the content to upsert.
     * @param type    the Class object representing the type of the object.
     * @param targets An array of target identifiers for the upsert operation.
     * @param upType  The type of the upsert operation specifying how to merge the content.
     * @param content The content to be upserted.
     * @return An iterator over the result of the upsert operation.
     */
    public <T> Iterator<T> upsert(Class<T> type, String[] targets, UpType upType, T content) {
        return new ValueObjectIterator<>(type, upsert(targets, upType, content));
    }

    /**
     * Inserts or updates the table with the provided content and
     * returns a thread-safe iterator over the resulting values.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>     the type of the content to be upserted
     * @param target  the target identifier where the content will be upserted
     * @param upType  the type of upsert operation
     * @param content the content to be upserted
     * @return a thread-safe iterator over the resulting values after the upsert operation
     */
    public <T> Iterator<Value> upsertSync(String target, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new SynchronizedValueIterator(upsertTargetValueSync(getPtr(), target, upType.code, valueMut.getPtr()));
    }

    /**
     * Performs an upsert (update or insert) operation on the specified tables.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the record to upsert
     * @param targets an array of target identifiers to perform the upsert operation on
     * @param upType  the type of upsert operation to perform
     * @param content the content to be upserted
     * @return a thread-safe Iterator of the resulting values from the upsert operation
     */
    public <T> Iterator<Value> upsertSync(String[] targets, UpType upType, T content) {
        final ValueMut valueMut = ValueBuilder.convert(content);
        return new SynchronizedValueIterator(upsertTargetsValueSync(getPtr(), targets, upType.code, valueMut.getPtr()));
    }

    /**
     * Inserts or updates a record and returns an iterator over the result.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     the type of the record to upsert
     * @param type    the class representing the type of the record
     * @param target  the target location for the upsert operation
     * @param upType  the type of the upsert operation
     * @param content the content of the record to be upserted
     * @return a thread-safe iterator over the upserted record
     */
    public <T> Iterator<T> upsertSync(Class<T> type, String target, UpType upType, T content) {
        return new ValueObjectIterator<>(type, upsertSync(target, upType, content));
    }

    /**
     * Performs an upsert operation with the specified content on the given tables.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/upsert">SurrealQL documentation</a>.
     *
     * @param <T>     The type of the content being upserted and returned iterator's elements.
     * @param type    The class type of the content.
     * @param targets The array of table identifiers on which to perform the upsert operation.
     * @param upType  The type of upsert operation to be performed.
     * @param content The content to be upserted.
     * @return A thread-safe iterator over the upserted content of the specified type.
     */
    public <T> Iterator<T> upsertSync(Class<T> type, String[] targets, UpType upType, T content) {
        return new ValueObjectIterator<>(type, upsertSync(targets, upType, content));
    }

    @SafeVarargs
    private final <T> long[] contents2longs(T... contents) {
        final long[] ptrs = new long[contents.length];
        int index = 0;
        for (final T c : contents) {
            ptrs[index++] = ValueBuilder.convert(c).getPtr();
        }
        return ptrs;
    }

    /**
     * Selects a record by its RecordId and retrieves the corresponding Value.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     *
     * @param recordId the unique identifier of the record to be selected
     * @return an Optional containing the Value if the record is found, or an empty Optional if not found
     */
    public Optional<Value> select(RecordId recordId) {
        final long valuePtr = selectThing(getPtr(), recordId.getPtr());
        if (valuePtr == 0) {
            return Optional.empty();
        }
        return Optional.of(new Value(valuePtr));
    }

    /**
     * Selects an instance of the specified type from a record identified by the given RecordId.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     *
     * @param <T>      the type of the instance to be selected
     * @param type     the class type of the instance to be selected
     * @param recordId the unique identifier of the record from which to select the instance
     * @return an Optional containing the selected instance of the specified type if present,
     * otherwise an empty Optional
     */
    public <T> Optional<T> select(Class<T> type, RecordId recordId) {
        return select(recordId).map(v -> v.get(type));
    }

    /**
     * Selects values based on the provided RecordIds.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     *
     * @param things an array of RecordId objects to be used in the selection.
     * @return a list of Value objects corresponding to the selected RecordIds.
     */
    public List<Value> select(RecordId... things) {
        final long[] thingsPtr = things2longs(things);
        final long[] valuePtrs = selectThings(getPtr(), thingsPtr);
        try (final LongStream s = Arrays.stream(valuePtrs)) {
            return s.mapToObj(Value::new).collect(Collectors.toList());
        }
    }

    private long[] things2longs(RecordId... things) {
        final long[] ptrs = new long[things.length];
        int index = 0;
        for (final RecordId t : things) {
            ptrs[index++] = t.getPtr();
        }
        return ptrs;
    }

    /**
     * Selects and retrieves a list of objects of the specified type based on the given record IDs.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     *
     * @param <T>    the type of objects to be retrieved
     * @param type   the Class object of the type to be retrieved
     * @param things an array of RecordId instances identifying the records to be selected
     * @return a list of objects of the specified type corresponding to the given record IDs
     */
    public <T> List<T> select(Class<T> type, RecordId... things) {
        try (final Stream<Value> s = select(things).stream()) {
            return s.map(v -> v.get(type)).collect(Collectors.toList());
        }
    }

    /**
     * Selects and returns an iterator over the values corresponding to the given targets.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     *
     * @param targets A string representing the targets to be selected.
     * @return An iterator over the values corresponding to the specified targets.
     */
    public Iterator<Value> select(String targets) {
        return new ValueIterator(selectTargetsValues(getPtr(), targets));
    }

    /**
     * Selects and returns a thread-safe iterator to traverse values associated with the given targets.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     *
     * @param targets The specified targets for which values need to be selected.
     * @return A thread-safe iterator to traverse the values associated with the specified targets.
     */
    public Iterator<Value> selectSync(String targets) {
        return new SynchronizedValueIterator(selectTargetsValuesSync(getPtr(), targets));
    }

    /**
     * Selects and retrieves an iterator of specified type for given targets.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     * <p>
     *
     * @param <T>     The type of objects to be selected.
     * @param type    The class type of the objects to be selected.
     * @param targets A string specifying the targets to select from.
     * @return An iterator of the specified type for the selected targets.
     */
    public <T> Iterator<T> select(Class<T> type, String targets) {
        return new ValueObjectIterator<>(type, select(targets));
    }

    /**
     * Selects and returns a thread-safe iterator over a collection of objects of the specified type
     * from the given targets.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/select">SurrealQL documentation</a>.
     *
     * @param <T>     the type of objects to be iterated over
     * @param type    the class of the type of objects to be selected
     * @param targets the targets from which to select objects
     * @return a thread-safe iterator over a collection of objects of the specified type
     */
    public <T> Iterator<T> selectSync(Class<T> type, String targets) {
        return new ValueObjectIterator<>(type, selectSync(targets));
    }

    /**
     * Deletes a record identified by the provided RecordId.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/delete">SurrealQL documentation</a>.
     *
     * @param recordId the identifier of the record to be deleted
     */
    public void delete(RecordId recordId) {
        deleteThing(getPtr(), recordId.getPtr());
    }

    /**
     * Deletes the specified records.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/delete">SurrealQL documentation</a>.
     *
     * @param things An array of RecordId objects representing the records to be deleted.
     */
    public void delete(RecordId... things) {
        final long[] thingsPtr = things2longs(things);
        deleteThings(getPtr(), thingsPtr);
    }

    /**
     * Deletes the specified target.
     * <p>
     * For more details, check the <a href="https://surrealdb.com/docs/surrealql/statements/delete">SurrealQL documentation</a>.
     *
     * @param target the name of the target to be deleted
     */
    public void delete(String target) {
        deleteTarget(getPtr(), target);
    }

    /**
     * Closes and releases any resources associated with this instance.
     * This method is typically called when the instance is no longer needed.
     * The underlying resources are safely cleaned up.
     */
    @Override
    public void close() {
        deleteInstance();
    }
}