Surreal.java

package com.surrealdb;

import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.LongStream;
import java.util.stream.Stream;

import com.surrealdb.signin.BearerCredential;
import com.surrealdb.signin.Credential;
import com.surrealdb.signin.DatabaseCredential;
import com.surrealdb.signin.NamespaceCredential;
import com.surrealdb.signin.RecordCredential;
import com.surrealdb.signin.RootCredential;
import com.surrealdb.signin.Token;

/**
 * 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();
	}

	// Current namespace and database set by useNs() / useDb() / useDefaults() (from
	// server return value).
	private String namespace;
	private String database;

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

	/**
	 * Constructor for a Surreal instance backed by an existing native pointer (e.g.
	 * from {@link #newSession()}).
	 */
	private Surreal(long ptr) {
		super(ptr);
		this.namespace = null;
		this.database = null;
	}

	private static native long newInstance();

	private static native long cloneSession(long ptr);

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

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

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

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

	private static native Token signup(long ptr, String namespace, String database, String access, long paramsValuePtr);

	private static native Token signinRecord(long ptr, String namespace, String database, String access,
			long paramsValuePtr);

	private static native boolean authenticate(long ptr, String token);

	private static native boolean invalidate(long ptr);

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

	private static native NsDb useDb(long ptr, String db);

	private static native NsDb useDefaults(long ptr);

	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 createRecordIdValue(long ptr, long recordIdPtr, 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 updateRecordIdValue(long ptr, long recordIdPtr, 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 upsertRecordIdValue(long ptr, long recordIdPtr, int update, long valuePtr);

	private static native long updateRecordIdRangeValue(long ptr, String table, long startIdPtr, long endIdPtr,
			int update, long valuePtr);

	private static native long upsertRecordIdRangeValue(long ptr, String table, long startIdPtr, long endIdPtr,
			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 selectRecordId(long ptr, long recordId);

	private static native long[] selectRecordIds(long ptr, long[] recordIds);

	private static native long[] selectRecordIdRange(long ptr, String table, long startIdPtr, long endIdPtr);

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

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

	private static native boolean deleteRecordId(long ptr, long recordId);

	private static native boolean deleteRecordIds(long ptr, long[] recordIds);

	private static native boolean deleteRecordIdRange(long ptr, String table, long startIdPtr, long endIdPtr);

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

	private static native String version(long ptr);

	private static native boolean health(long ptr);

	private static native long run(long ptr, String name, long[] argsValuePtrs);

	private static native boolean exportSql(long ptr, String path);

	private static native boolean importSql(long ptr, String path);

	private static native long selectLive(long ptr, String table);

	@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 void 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);
		namespace = null;
		database = null;
		return this;
	}

	/**
	 * Returns the server version (semantic version string, e.g. "1.0.0").
	 *
	 * @return the server version string
	 * @throws SurrealException
	 *             if the request fails
	 */
	public String version() {
		return version(getPtr());
	}

	/**
	 * Performs a health check against the server.
	 *
	 * @return true if the server is healthy
	 * @throws SurrealException
	 *             if the health check fails
	 */
	public boolean health() {
		return health(getPtr());
	}

	/**
	 * Runs a SurrealDB function by name with the given arguments (e.g.
	 * {@code array::add}, {@code math::mean}). Implemented via a SurrealQL RETURN
	 * query.
	 *
	 * @param name
	 *            the function name (e.g. "array::add" or "array::add<1.0.0>"
	 *            for a version)
	 * @param args
	 *            the arguments to pass to the function (converted to Surreal
	 *            values)
	 * @return the result Value
	 * @throws SurrealException
	 *             if the run fails
	 */
	public Value run(String name, java.lang.Object... args) {
		if (args == null || args.length == 0) {
			return new Value(run(getPtr(), name, new long[0]));
		}
		ValueMut[] valueMuts = new ValueMut[args.length];
		long[] ptrs = new long[args.length];
		for (int i = 0; i < args.length; i++) {
			valueMuts[i] = ValueBuilder.convert(args[i]);
			ptrs[i] = valueMuts[i].getPtr();
		}
		return new Value(run(getPtr(), name, ptrs));
	}

	/**
	 * Exports the database to a file. Supported by HTTP and local engines; not by
	 * WebSocket.
	 *
	 * @param path
	 *            file path to write the export
	 * @return true on success
	 * @throws SurrealException
	 *             if export fails or backups are not supported
	 */
	public boolean exportSql(String path) {
		return exportSql(getPtr(), path);
	}

	/**
	 * Imports the database from a file. Supported by HTTP and local engines; not by
	 * WebSocket.
	 *
	 * @param path
	 *            file path to read the import from
	 * @return true on success
	 * @throws SurrealException
	 *             if import fails or backups are not supported
	 */
	public boolean importSql(String path) {
		return importSql(getPtr(), path);
	}

	/**
	 * Starts a live query on the given table. Returns a blocking stream of
	 * notifications (CREATE, UPDATE, DELETE). Call {@link LiveStream#next()} in a
	 * loop and {@link LiveStream#close()} when done.
	 *
	 * @param table
	 *            table name to watch
	 * @return a LiveStream; must call {@link LiveStream#close()} when done
	 * @throws SurrealException
	 *             if live queries are not supported or the request fails
	 */
	public LiveStream selectLive(String table) {
		return new LiveStream(selectLive(getPtr(), table));
	}

	/**
	 * Signs in with the given credential. Supports RootCredential,
	 * NamespaceCredential, DatabaseCredential, RecordCredential, and
	 * BearerCredential.
	 * <p>
	 * For more details, check the <a href=
	 * "https://surrealdb.com/docs/surrealdb/security/authentication">authentication
	 * documentation</a>.
	 *
	 * @param credential
	 *            the credentials (RootCredential, NamespaceCredential,
	 *            DatabaseCredential, RecordCredential, or BearerCredential)
	 * @return a Token representing the session after a successful sign-in
	 * @throws SurrealException
	 *             if the credential type is unsupported or RecordCredential ns/db
	 *             cannot be resolved
	 */
	public Token signin(Credential credential) {
		if (credential instanceof DatabaseCredential) {
			final DatabaseCredential db = (DatabaseCredential) credential;
			return signinDatabase(getPtr(), db.getUsername(), db.getPassword(), db.getNamespace(), db.getDatabase());
		} else if (credential instanceof NamespaceCredential) {
			final NamespaceCredential ns = (NamespaceCredential) credential;
			return signinNamespace(getPtr(), ns.getUsername(), ns.getPassword(), ns.getNamespace());
		} else if (credential instanceof RootCredential) {
			final RootCredential r = (RootCredential) credential;
			return signinRoot(getPtr(), r.getUsername(), r.getPassword());
		} else if (credential instanceof RecordCredential) {
			final RecordCredential rec = (RecordCredential) credential;
			String ns = rec.getNamespace() != null ? rec.getNamespace() : this.namespace;
			String db = rec.getDatabase() != null ? rec.getDatabase() : this.database;
			if (ns == null || db == null) {
				throw new SurrealException(
						"RecordCredential signin requires namespace and database. Set them explicitly on RecordCredential or call useNs() and useDb() first.");
			}
			final ValueMut paramsValue = ValueBuilder.convert(rec.getParams());
			return signinRecord(getPtr(), ns, db, rec.getAccess(), paramsValue.getPtr());
		} else if (credential instanceof BearerCredential) {
			BearerCredential bearer = (BearerCredential) credential;
			authenticate(getPtr(), bearer.getToken());
			return new Token(bearer.getToken(), null);
		}
		throw new SurrealException(
				"Unsupported credential type: " + (credential != null ? credential.getClass().getName() : "null"));
	}

	/**
	 * Signs up a record user with the given record access credentials. When
	 * namespace or database are null on the record, the current session values from
	 * useNs/useDb are used.
	 *
	 * @param record
	 *            record signup credentials (namespace, database, access, params;
	 *            ns/db may be null to use session)
	 * @return tokens (access and optional refresh) returned by the server
	 * @throws SurrealException
	 *             if namespace or database cannot be resolved (call useNs/useDb
	 *             first when omitting)
	 */
	public Token signup(RecordCredential record) {
		String ns = record.getNamespace() != null ? record.getNamespace() : this.namespace;
		String db = record.getDatabase() != null ? record.getDatabase() : this.database;
		if (ns == null || db == null) {
			throw new SurrealException(
					"RecordCredential signup requires namespace and database. Set them explicitly on RecordCredential or call useNs() and useDb() first.");
		}
		final ValueMut paramsValue = ValueBuilder.convert(record.getParams());
		return signup(getPtr(), ns, db, record.getAccess(), paramsValue.getPtr());
	}

	/**
	 * Authenticates the current connection with a JWT token (e.g. the access token
	 * from signin).
	 *
	 * @param token
	 *            the access token string
	 * @return the current instance
	 */
	public Surreal authenticate(String token) {
		authenticate(getPtr(), token);
		return this;
	}

	/**
	 * Invalidates the authentication for the current connection.
	 *
	 * @return the current instance
	 */
	public Surreal invalidate() {
		invalidate(getPtr());
		return this;
	}

	/**
	 * Creates a new session that shares the same connection but has its own
	 * namespace, database, and authentication state. Use this for multi-session
	 * support.
	 *
	 * @return a new Surreal instance representing a separate session
	 */
	public Surreal newSession() {
		return new Surreal(cloneSession(getPtr()));
	}

	private static native long beginTransaction(long ptr);

	/**
	 * Starts a client-side transaction. Use {@link Transaction#query(String)} for
	 * operations and {@link Transaction#commit()} or {@link Transaction#cancel()}
	 * to complete it.
	 *
	 * @return a new Transaction instance
	 */
	public Transaction beginTransaction() {
		return new Transaction(beginTransaction(getPtr()));
	}

	/**
	 * Sets the namespace for the Surreal instance. The current namespace and
	 * database from the server are stored; use {@link #getNamespace()} and
	 * {@link #getDatabase()} to read them.
	 *
	 * @param ns
	 *            the namespace to use
	 * @return this instance for chaining
	 */
	public Surreal useNs(String ns) {
		NsDb result = useNs(getPtr(), ns);
		this.namespace = result.getNamespace();
		this.database = result.getDatabase();
		return this;
	}

	/**
	 * Sets the database for the current instance. The current namespace and
	 * database from the server are stored; use {@link #getNamespace()} and
	 * {@link #getDatabase()} to read them.
	 *
	 * @param db
	 *            the database name to use
	 * @return this instance for chaining
	 */
	public Surreal useDb(String db) {
		NsDb result = useDb(getPtr(), db);
		this.namespace = result.getNamespace();
		this.database = result.getDatabase();
		return this;
	}

	/**
	 * Sets the default namespace and database. The actual defaults from the server
	 * are stored; use {@link #getNamespace()} and {@link #getDatabase()} to read
	 * them.
	 *
	 * @return this instance for chaining
	 */
	public Surreal useDefaults() {
		NsDb result = useDefaults(getPtr());
		this.namespace = result.getNamespace();
		this.database = result.getDatabase();
		return this;
	}

	/**
	 * Returns the current namespace set by the last {@link #useNs(String)},
	 * {@link #useDb(String)}, or {@link #useDefaults()} call (from the server
	 * response). Null if none of those have been called since
	 * {@link #connect(String)} or for a new session.
	 *
	 * @return the current namespace, or null
	 */
	public String getNamespace() {
		return namespace;
	}

	/**
	 * Returns the current database set by the last {@link #useNs(String)},
	 * {@link #useDb(String)}, or {@link #useDefaults()} call (from the server
	 * response). Null if none of those have been called since
	 * {@link #connect(String)} or for a new session.
	 *
	 * @return the current database, or null
	 */
	public String getDatabase() {
		return database;
	}

	/**
	 * 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 recordId
	 *            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 recordId, T content) {
		final ValueMut valueMut = ValueBuilder.convert(content);
		final long valuePtr = createRecordIdValue(getPtr(), recordId.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 recordId
	 *            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 recordId, T content) {
		return create(recordId, 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 recordId
	 *            The RecordId of the record 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 recordId, UpType upType, T content) {
		final ValueMut valueMut = ValueBuilder.convert(content);
		final long valuePtr = updateRecordIdValue(getPtr(), recordId.getPtr(), upType.code, valueMut.getPtr());
		return new Value(valuePtr);
	}

	/**
	 * Updates all records in the given record ID range with the given content.
	 *
	 * @param range
	 *            the table and optional start/end bounds
	 * @param upType
	 *            CONTENT, MERGE, etc.
	 * @param content
	 *            the update content
	 * @return the first updated value (range updates return one result per updated
	 *         record; this returns the first)
	 */
	public <T> Value update(RecordIdRange range, UpType upType, T content) {
		final ValueMut valueMut = ValueBuilder.convert(content);
		final long startPtr = range.getStart() != null ? range.getStart().getPtr() : 0;
		final long endPtr = range.getEnd() != null ? range.getEnd().getPtr() : 0;
		final long valuePtr = updateRecordIdRangeValue(getPtr(), range.getTable(), startPtr, endPtr, 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 recordId
	 *            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 recordId, UpType upType, T content) {
		return update(recordId, 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 recordId
	 *            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 recordId, UpType upType, T content) {
		final ValueMut valueMut = ValueBuilder.convert(content);
		final long valuePtr = upsertRecordIdValue(getPtr(), recordId.getPtr(), upType.code, valueMut.getPtr());
		return new Value(valuePtr);
	}

	/**
	 * Upserts all records in the given record ID range with the given content.
	 *
	 * @param range
	 *            the table and optional start/end bounds
	 * @param upType
	 *            CONTENT, MERGE, etc.
	 * @param content
	 *            the upsert content
	 * @return the first upserted value
	 */
	public <T> Value upsert(RecordIdRange range, UpType upType, T content) {
		final ValueMut valueMut = ValueBuilder.convert(content);
		final long startPtr = range.getStart() != null ? range.getStart().getPtr() : 0;
		final long endPtr = range.getEnd() != null ? range.getEnd().getPtr() : 0;
		final long valuePtr = upsertRecordIdRangeValue(getPtr(), range.getTable(), startPtr, endPtr, 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 recordId
	 *            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 recordId, UpType upType, T content) {
		return upsert(recordId, 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 = selectRecordId(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 recordIds
	 *            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... recordIds) {
		final long[] recordIdsPtr = recordIds2longs(recordIds);
		final long[] valuePtrs = selectRecordIds(getPtr(), recordIdsPtr);
		try (final LongStream s = Arrays.stream(valuePtrs)) {
			return s.mapToObj(Value::new).collect(Collectors.toList());
		}
	}

	private long[] recordIds2longs(RecordId... recordIds) {
		final long[] ptrs = new long[recordIds.length];
		int index = 0;
		for (final RecordId r : recordIds) {
			ptrs[index++] = r.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 recordIds
	 *            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... recordIds) {
		try (final Stream<Value> s = select(recordIds).stream()) {
			return s.map(v -> v.get(type)).collect(Collectors.toList());
		}
	}

	/**
	 * Selects all records in the given record ID range.
	 *
	 * @param range
	 *            the table and optional start/end bounds (null = unbounded)
	 * @return list of values for records in the range
	 */
	public List<Value> select(RecordIdRange range) {
		final long startPtr = range.getStart() != null ? range.getStart().getPtr() : 0;
		final long endPtr = range.getEnd() != null ? range.getEnd().getPtr() : 0;
		final long[] valuePtrs = selectRecordIdRange(getPtr(), range.getTable(), startPtr, endPtr);
		try (final LongStream s = Arrays.stream(valuePtrs)) {
			return s.mapToObj(Value::new).collect(Collectors.toList());
		}
	}

	/**
	 * Selects records in the given range and maps them to the specified type.
	 *
	 * @param <T>
	 *            the type of objects to be retrieved
	 * @param type
	 *            the class of the type
	 * @param range
	 *            the table and optional start/end bounds
	 * @return list of objects of the specified type
	 */
	public <T> List<T> select(Class<T> type, RecordIdRange range) {
		try (final Stream<Value> s = select(range).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) {
		deleteRecordId(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 recordIds
	 *            An array of RecordId objects representing the records to be
	 *            deleted.
	 */
	public void delete(RecordId... recordIds) {
		final long[] recordIdsPtr = recordIds2longs(recordIds);
		deleteRecordIds(getPtr(), recordIdsPtr);
	}

	/**
	 * Deletes all records in the given record ID range.
	 *
	 * @param range
	 *            the table and optional start/end bounds
	 */
	public void delete(RecordIdRange range) {
		final long startPtr = range.getStart() != null ? range.getStart().getPtr() : 0;
		final long endPtr = range.getEnd() != null ? range.getEnd().getPtr() : 0;
		deleteRecordIdRange(getPtr(), range.getTable(), startPtr, endPtr);
	}

	/**
	 * 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();
	}
}