All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lmdbjava.Cursor Maven / Gradle / Ivy

/*-
 * #%L
 * LmdbJava
 * %%
 * Copyright (C) 2016 - 2023 The LmdbJava Open Source Project
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

package org.lmdbjava;

import static java.util.Objects.requireNonNull;
import static org.lmdbjava.Dbi.KeyExistsException.MDB_KEYEXIST;
import static org.lmdbjava.Dbi.KeyNotFoundException.MDB_NOTFOUND;
import static org.lmdbjava.Env.SHOULD_CHECK;
import static org.lmdbjava.Library.LIB;
import static org.lmdbjava.MaskedFlag.isSet;
import static org.lmdbjava.MaskedFlag.mask;
import static org.lmdbjava.PutFlags.MDB_MULTIPLE;
import static org.lmdbjava.PutFlags.MDB_NODUPDATA;
import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE;
import static org.lmdbjava.PutFlags.MDB_RESERVE;
import static org.lmdbjava.ResultCodeMapper.checkRc;
import static org.lmdbjava.SeekOp.MDB_FIRST;
import static org.lmdbjava.SeekOp.MDB_LAST;
import static org.lmdbjava.SeekOp.MDB_NEXT;
import static org.lmdbjava.SeekOp.MDB_PREV;

import jnr.ffi.Pointer;
import jnr.ffi.byref.NativeLongByReference;

/**
 * A cursor handle.
 *
 * @param  buffer type
 */
public final class Cursor implements AutoCloseable {

  private boolean closed;
  private final KeyVal kv;
  private final Pointer ptrCursor;
  private Txn txn;
  private final Env env;

  Cursor(final Pointer ptr, final Txn txn, final Env env) {
    requireNonNull(ptr);
    requireNonNull(txn);
    this.ptrCursor = ptr;
    this.txn = txn;
    this.kv = txn.newKeyVal();
    this.env = env;
  }

  /**
   * Close a cursor handle.
   *
   * 

* The cursor handle will be freed and must not be used again after this call. * Its transaction must still be live if it is a write-transaction. */ @Override public void close() { if (closed) { return; } kv.close(); if (SHOULD_CHECK) { env.checkNotClosed(); if (!txn.isReadOnly()) { txn.checkReady(); } } LIB.mdb_cursor_close(ptrCursor); closed = true; } /** * Return count of duplicates for current key. * *

* This call is only valid on databases that support sorted duplicate data * items {@link DbiFlags#MDB_DUPSORT}. * * @return count of duplicates for current key */ public long count() { if (SHOULD_CHECK) { env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } final NativeLongByReference longByReference = new NativeLongByReference(); checkRc(LIB.mdb_cursor_count(ptrCursor, longByReference)); return longByReference.longValue(); } /** * Delete current key/data pair. * *

* This function deletes the key/data pair to which the cursor refers. * * @param f flags (either null or {@link PutFlags#MDB_NODUPDATA} */ public void delete(final PutFlags... f) { if (SHOULD_CHECK) { env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } final int flags = mask(f); checkRc(LIB.mdb_cursor_del(ptrCursor, flags)); } /** * Position at first key/data item. * * @return false if requested position not found */ public boolean first() { return seek(MDB_FIRST); } /** * Reposition the key/value buffers based on the passed key and operation. * * @param key to search for * @param data to search for * @param op options for this operation * @return false if key not found */ public boolean get(final T key, final T data, final SeekOp op) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(op); env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } kv.keyIn(key); kv.valIn(data); final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv .pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; } checkRc(rc); kv.keyOut(); kv.valOut(); ReferenceUtil.reachabilityFence0(key); return true; } /** * Reposition the key/value buffers based on the passed key and operation. * * @param key to search for * @param op options for this operation * @return false if key not found */ public boolean get(final T key, final GetOp op) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(op); env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } kv.keyIn(key); final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv .pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; } checkRc(rc); kv.keyOut(); kv.valOut(); ReferenceUtil.reachabilityFence0(key); return true; } /** * Obtain the key. * * @return the key that the cursor is located at. */ public T key() { return kv.key(); } /** * Position at last key/data item. * * @return false if requested position not found */ public boolean last() { return seek(MDB_LAST); } /** * Position at next data item. * * @return false if requested position not found */ public boolean next() { return seek(MDB_NEXT); } /** * Position at previous data item. * * @return false if requested position not found */ public boolean prev() { return seek(MDB_PREV); } /** * Store by cursor. * *

* This function stores key/data pairs into the database. * * @param key key to store * @param val data to store * @param op options for this operation * @return true if the value was put, false if MDB_NOOVERWRITE or * MDB_NODUPDATA were set and the key/value existed already. */ public boolean put(final T key, final T val, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(key); requireNonNull(val); env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } kv.keyIn(key); kv.valIn(val); final int mask = mask(op); final int rc = LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), mask); if (rc == MDB_KEYEXIST) { if (isSet(mask, MDB_NOOVERWRITE)) { kv.valOut(); // marked as in,out in LMDB C docs } else if (!isSet(mask, MDB_NODUPDATA)) { checkRc(rc); } return false; } checkRc(rc); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); return true; } /** * Put multiple values into the database in one MDB_MULTIPLE * operation. * *

* The database must have been opened with {@link DbiFlags#MDB_DUPFIXED}. The * buffer must contain fixed-sized values to be inserted. The size of each * element is calculated from the buffer's size divided by the given element * count. For example, to populate 10 X 4 byte integers at once, present a * buffer of 40 bytes and specify the element as 10. * * @param key key to store in the database (not null) * @param val value to store in the database (not null) * @param elements number of elements contained in the passed value buffer * @param op options for operation (must set MDB_MULTIPLE) */ public void putMultiple(final T key, final T val, final int elements, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(txn); requireNonNull(key); requireNonNull(val); env.checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } final int mask = mask(op); if (SHOULD_CHECK && !isSet(mask, MDB_MULTIPLE)) { throw new IllegalArgumentException("Must set " + MDB_MULTIPLE + " flag"); } txn.kv().keyIn(key); final Pointer dataPtr = txn.kv().valInMulti(val, elements); final int rc = LIB.mdb_cursor_put(ptrCursor, txn.kv().pointerKey(), dataPtr, mask); checkRc(rc); ReferenceUtil.reachabilityFence0(key); ReferenceUtil.reachabilityFence0(val); } /** * Renew a cursor handle. * *

* A cursor is associated with a specific transaction and database. Cursors * that are only used in read-only transactions may be re-used, to avoid * unnecessary malloc/free overhead. The cursor may be associated with a new * read-only transaction, and referencing the same database handle as it was * created with. This may be done whether the previous transaction is live or * dead. * * @param newTxn transaction handle */ public void renew(final Txn newTxn) { if (SHOULD_CHECK) { requireNonNull(newTxn); env.checkNotClosed(); checkNotClosed(); this.txn.checkReadOnly(); // existing newTxn.checkReadOnly(); newTxn.checkReady(); } checkRc(LIB.mdb_cursor_renew(newTxn.pointer(), ptrCursor)); this.txn = newTxn; } /** * Reserve space for data of the given size, but don't copy the given val. * Instead, return a pointer to the reserved space, which the caller can fill * in later - before the next update operation or the transaction ends. This * saves an extra memcpy if the data is being generated later. LMDB does * nothing else with this memory, the caller is expected to modify all of the * space requested. * *

* This flag must not be specified if the database was opened with MDB_DUPSORT * * @param key key to store in the database (not null) * @param size size of the value to be stored in the database (not null) * @param op options for this operation * @return a buffer that can be used to modify the value */ public T reserve(final T key, final int size, final PutFlags... op) { if (SHOULD_CHECK) { requireNonNull(key); env.checkNotClosed(); checkNotClosed(); txn.checkReady(); txn.checkWritesAllowed(); } kv.keyIn(key); kv.valIn(size); final int flags = mask(op) | MDB_RESERVE.getMask(); checkRc(LIB.mdb_cursor_put(ptrCursor, kv.pointerKey(), kv.pointerVal(), flags)); kv.valOut(); ReferenceUtil.reachabilityFence0(key); return val(); } /** * Reposition the key/value buffers based on the passed operation. * * @param op options for this operation * @return false if requested position not found */ public boolean seek(final SeekOp op) { if (SHOULD_CHECK) { requireNonNull(op); env.checkNotClosed(); checkNotClosed(); txn.checkReady(); } final int rc = LIB.mdb_cursor_get(ptrCursor, kv.pointerKey(), kv .pointerVal(), op.getCode()); if (rc == MDB_NOTFOUND) { return false; } checkRc(rc); kv.keyOut(); kv.valOut(); return true; } /** * Obtain the value. * * @return the value that the cursor is located at. */ public T val() { return kv.val(); } private void checkNotClosed() { if (closed) { throw new ClosedException(); } } /** * Cursor has already been closed. */ public static final class ClosedException extends LmdbException { private static final long serialVersionUID = 1L; /** * Creates a new instance. */ public ClosedException() { super("Cursor has already been closed"); } } /** * Cursor stack too deep - internal error. */ public static final class FullException extends LmdbNativeException { static final int MDB_CURSOR_FULL = -30_787; private static final long serialVersionUID = 1L; FullException() { super(MDB_CURSOR_FULL, "Cursor stack too deep - internal error"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy