Transaction.java

package com.surrealdb;

import java.util.Map;
import java.util.Objects;

/**
 * Client-side transaction. Use {@link Surreal#beginTransaction()} to start a
 * transaction. Operations (e.g. {@link #query(String)}) run within the
 * transaction until {@link #commit()} or {@link #cancel()} is called. With
 * try-with-resources, a transaction that was not committed is cancelled:
 *
 * <pre>{@code
 * try (Transaction txn = db.beginTransaction()) {
 * 	txn.query("CREATE person:tobie");
 * 	txn.commit();
 * }
 * }</pre>
 */
public class Transaction extends Native implements AutoCloseable {

	Transaction(long ptr) {
		super(ptr);
	}

	private static native void nativeDeleteInstance(long ptr);

	private native boolean commit(long ptr);

	private native boolean cancel(long ptr);

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

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

	/**
	 * Commits the transaction. After this call, the transaction is completed and
	 * must not be used. On failure the native layer throws; the return value is
	 * only true on success.
	 */
	public void commit() {
		commit(takePtr());
	}

	/**
	 * Cancels (rolls back) the transaction. After this call, the transaction is
	 * completed and must not be used. On failure the native layer throws; the
	 * return value is only true on success.
	 */
	public void cancel() {
		cancel(takePtr());
	}

	/**
	 * Cancels the transaction if it was neither committed nor cancelled, so a
	 * try-with-resources block rolls back unless {@link #commit()} was called.
	 * After {@link #commit()} or {@link #cancel()}, this does nothing.
	 */
	@Override
	public void close() {
		final long ptr = takePtr();
		if (ptr != 0) {
			cancel(ptr);
		}
	}

	/**
	 * Runs a SurrealQL query within this transaction.
	 *
	 * @param sql
	 *            the SurrealQL query
	 * @return the query response
	 */
	public Response query(String sql) {
		return new Response(query(getPtr(), sql));
	}

	/**
	 * Runs a parameterized SurrealQL query within this transaction.
	 *
	 * @param sql
	 *            the SurrealQL query
	 * @param params
	 *            a map containing parameter values to be bound to the SQL query
	 * @return the query response
	 */
	public Response query(String sql, Map<String, ?> params) {
		final Map<String, ValueMut> valueMuts = ValueBuilder.convertParams(params);
		try {
			final String[] keys = valueMuts.keySet().toArray(new String[0]);
			final long[] values = new long[keys.length];
			for (int i = 0; i < keys.length; i++) {
				values[i] = valueMuts.get(keys[i]).getPtr();
			}
			return new Response(queryWithBindings(getPtr(), sql, keys, values));
		} finally {
			ValueBuilder.releaseParams(valueMuts, params);
		}
	}

	@Override
	final NativeCleaner.Deleter deleter() {
		return Transaction::nativeDeleteInstance;
	}

	@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;
	}
}