org.lmdbjava.Env Maven / Gradle / Ivy
Show all versions of lmdbjava Show documentation
/*-
* #%L
* LmdbJava
* %%
* Copyright (C) 2016 - 2019 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 java.io.File;
import static java.lang.Boolean.getBoolean;
import java.nio.ByteBuffer;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static java.util.Objects.requireNonNull;
import jnr.ffi.Pointer;
import jnr.ffi.byref.PointerByReference;
import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL;
import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV;
import static org.lmdbjava.Library.LIB;
import org.lmdbjava.Library.MDB_envinfo;
import org.lmdbjava.Library.MDB_stat;
import static org.lmdbjava.Library.RUNTIME;
import static org.lmdbjava.MaskedFlag.isSet;
import static org.lmdbjava.MaskedFlag.mask;
import static org.lmdbjava.ResultCodeMapper.checkRc;
import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN;
/**
* LMDB environment.
*
* @param buffer type
*/
@SuppressWarnings("PMD.GodClass")
public final class Env implements AutoCloseable {
/**
* Java system property name that can be set to disable optional checks.
*/
public static final String DISABLE_CHECKS_PROP = "lmdbjava.disable.checks";
/**
* Indicates whether optional checks should be applied in LmdbJava. Optional
* checks are only disabled in critical paths (see package-level JavaDocs).
* Non-critical paths have optional checks performed at all times, regardless
* of this property.
*/
public static final boolean SHOULD_CHECK = !getBoolean(DISABLE_CHECKS_PROP);
private boolean closed;
private final int maxKeySize;
private final BufferProxy proxy;
private final Pointer ptr;
private final boolean readOnly;
private Env(final BufferProxy proxy, final Pointer ptr,
final boolean readOnly) {
this.proxy = proxy;
this.readOnly = readOnly;
this.ptr = ptr;
// cache max key size to avoid further JNI calls
this.maxKeySize = LIB.mdb_env_get_maxkeysize(ptr);
}
/**
* Create an {@link Env} using the {@link ByteBufferProxy#PROXY_OPTIMAL}.
*
* @return the environment (never null)
*/
public static Builder create() {
return new Builder<>(PROXY_OPTIMAL);
}
/**
* Create an {@link Env} using the passed {@link BufferProxy}.
*
* @param buffer type
* @param proxy the proxy to use (required)
* @return the environment (never null)
*/
public static Builder create(final BufferProxy proxy) {
return new Builder<>(proxy);
}
/**
* Opens an environment with a single default database in 0664 mode using the
* {@link ByteBufferProxy#PROXY_OPTIMAL}.
*
* @param path file system destination
* @param size size in megabytes
* @param flags the flags for this new environment
* @return env the environment (never null)
*/
public static Env open(final File path, final int size,
final EnvFlags... flags) {
return new Builder<>(PROXY_OPTIMAL)
.setMapSize(size * 1_024L * 1_024L)
.open(path, flags);
}
/**
* Close the handle.
*
*
* Will silently return if already closed or never opened.
*/
@Override
public void close() {
if (closed) {
return;
}
closed = true;
LIB.mdb_env_close(ptr);
}
/**
* Copies an LMDB environment to the specified destination path.
*
*
* This function may be used to make a backup of an existing environment. No
* lockfile is created, since it gets recreated at need.
*
*
* Note: This call can trigger significant file size growth if run in parallel
* with write transactions, because it employs a read-only transaction. See
* long-lived transactions under "Caveats" in the LMDB native documentation.
*
* @param path destination directory, which must exist, be writable and empty
* @param flags special options for this copy
*/
public void copy(final File path, final CopyFlags... flags) {
requireNonNull(path);
if (!path.exists()) {
throw new InvalidCopyDestination("Path must exist");
}
if (!path.isDirectory()) {
throw new InvalidCopyDestination("Path must be a directory");
}
final String[] files = path.list();
if (files != null && files.length > 0) {
throw new InvalidCopyDestination("Path must contain no files");
}
final int flagsMask = mask(flags);
checkRc(LIB.mdb_env_copy2(ptr, path.getAbsolutePath(), flagsMask));
}
/**
* Obtain the DBI names.
*
*
* This method is only compatible with {@link Env}s that use named databases.
* If an unnamed {@link Dbi} is being used to store data, this method will
* attempt to return all such keys from the unnamed database.
*
* @return a list of DBI names (never null)
*/
public List getDbiNames() {
final List result = new ArrayList<>();
final Dbi names = openDbi((byte[]) null);
try (Txn txn = txnRead();
Cursor cursor = names.openCursor(txn)) {
if (!cursor.first()) {
return Collections.emptyList();
}
do {
final byte[] name = proxy.getBytes(cursor.key());
result.add(name);
} while (cursor.next());
}
return result;
}
/**
* Set the size of the data memory map.
*
* @param mapSize the new size, in bytes
*/
public void setMapSize(final long mapSize) {
checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize));
}
/**
* Get the maximum size of keys and MDB_DUPSORT data we can write.
*
* @return the maximum size of keys.
*/
public int getMaxKeySize() {
return maxKeySize;
}
/**
* Return information about this environment.
*
* @return an immutable information object.
*/
public EnvInfo info() {
if (closed) {
throw new AlreadyClosedException();
}
final MDB_envinfo info = new MDB_envinfo(RUNTIME);
checkRc(LIB.mdb_env_info(ptr, info));
final long mapAddress;
if (info.f0_me_mapaddr.get() == null) {
mapAddress = 0;
} else {
mapAddress = info.f0_me_mapaddr.get().address();
}
return new EnvInfo(
mapAddress,
info.f1_me_mapsize.longValue(),
info.f2_me_last_pgno.longValue(),
info.f3_me_last_txnid.longValue(),
info.f4_me_maxreaders.intValue(),
info.f5_me_numreaders.intValue());
}
/**
* Indicates whether this environment has been closed.
*
* @return true if closed
*/
public boolean isClosed() {
return closed;
}
/**
* Indicates if this environment was opened with
* {@link EnvFlags#MDB_RDONLY_ENV}.
*
* @return true if read-only
*/
public boolean isReadOnly() {
return readOnly;
}
/**
* Convenience method that opens a {@link Dbi} with a UTF-8 database name.
*
* @param name name of the database (or null if no name is required)
* @param flags to open the database with
* @return a database that is ready to use
*/
public Dbi openDbi(final String name, final DbiFlags... flags) {
final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8);
return openDbi(nameBytes, flags);
}
/**
* Convenience method that opens a {@link Dbi} with a UTF-8 database name
* and custom comparator.
*
* @param name name of the database (or null if no name is required)
* @param comparator custom comparator callback (or null to use LMDB default)
* @param flags to open the database with
* @return a database that is ready to use
*/
public Dbi openDbi(final String name, final Comparator comparator,
final DbiFlags... flags) {
final byte[] nameBytes = name == null ? null : name.getBytes(UTF_8);
return openDbi(nameBytes, comparator, flags);
}
/**
* Open the {@link Dbi}.
*
* @param name name of the database (or null if no name is required)
* @param flags to open the database with
* @return a database that is ready to use
*/
public Dbi openDbi(final byte[] name, final DbiFlags... flags) {
try (Txn txn = readOnly ? txnRead() : txnWrite()) {
final Dbi dbi = new Dbi<>(this, txn, name, null, flags);
txn.commit(); // even RO Txns require a commit to retain Dbi in Env
return dbi;
}
}
/**
* Open the {@link Dbi}.
*
*
* If a custom comparator is specified, this comparator is called from LMDB
* any time it needs to compare two keys. The comparator must be used any time
* any time this database is opened, otherwise database corruption may occur.
* The custom comparator will also be used whenever a {@link CursorIterator}
* is created from the returned {@link Dbi}. If a custom comparator is not
* specified, LMDB's native default lexicographical order is used. The default
* comparator is typically more efficient (as there is no need for the native
* library to call back into Java for the comparator result).
*
* @param name name of the database (or null if no name is required)
* @param comparator custom comparator callback (or null to use LMDB default)
* @param flags to open the database with
* @return a database that is ready to use
*/
public Dbi openDbi(final byte[] name, final Comparator comparator,
final DbiFlags... flags) {
try (Txn txn = readOnly ? txnRead() : txnWrite()) {
final Dbi dbi = new Dbi<>(this, txn, name, comparator, flags);
txn.commit(); // even RO Txns require a commit to retain Dbi in Env
return dbi;
}
}
/**
* Return statistics about this environment.
*
* @return an immutable statistics object.
*/
public Stat stat() {
if (closed) {
throw new AlreadyClosedException();
}
final MDB_stat stat = new MDB_stat(RUNTIME);
checkRc(LIB.mdb_env_stat(ptr, stat));
return new Stat(
stat.f0_ms_psize.intValue(),
stat.f1_ms_depth.intValue(),
stat.f2_ms_branch_pages.longValue(),
stat.f3_ms_leaf_pages.longValue(),
stat.f4_ms_overflow_pages.longValue(),
stat.f5_ms_entries.longValue());
}
/**
* Flushes the data buffers to disk.
*
* @param force force a synchronous flush (otherwise if the environment has
* the MDB_NOSYNC flag set the flushes will be omitted, and with
* MDB_MAPASYNC they will be asynchronous)
*/
public void sync(final boolean force) {
if (closed) {
throw new AlreadyClosedException();
}
final int f = force ? 1 : 0;
checkRc(LIB.mdb_env_sync(ptr, f));
}
/**
* Obtain a transaction with the requested parent and flags.
*
* @param parent parent transaction (may be null if no parent)
* @param flags applicable flags (eg for a reusable, read-only transaction)
* @return a transaction (never null)
*/
public Txn txn(final Txn parent, final TxnFlags... flags) {
if (closed) {
throw new AlreadyClosedException();
}
return new Txn<>(this, parent, proxy, flags);
}
/**
* Obtain a read-only transaction.
*
* @return a read-only transaction
*/
public Txn txnRead() {
return txn(null, MDB_RDONLY_TXN);
}
/**
* Obtain a read-write transaction.
*
* @return a read-write transaction
*/
public Txn txnWrite() {
return txn(null);
}
Pointer pointer() {
return ptr;
}
/**
* Object has already been closed and the operation is therefore prohibited.
*/
public static final class AlreadyClosedException extends LmdbException {
private static final long serialVersionUID = 1L;
/**
* Creates a new instance.
*/
public AlreadyClosedException() {
super("Environment has already been closed");
}
}
/**
* Object has already been opened and the operation is therefore prohibited.
*/
public static final class AlreadyOpenException extends LmdbException {
private static final long serialVersionUID = 1L;
/**
* Creates a new instance.
*/
public AlreadyOpenException() {
super("Environment has already been opened");
}
}
/**
* Builder for configuring and opening Env.
*
* @param buffer type
*/
public static final class Builder {
static final int MAX_READERS_DEFAULT = 126;
private long mapSize = 1_024 * 1_024;
private int maxDbs = 1;
private int maxReaders = MAX_READERS_DEFAULT;
private boolean opened;
private final BufferProxy proxy;
Builder(final BufferProxy proxy) {
requireNonNull(proxy);
this.proxy = proxy;
}
/**
* Opens the environment.
*
* @param path file system destination
* @param mode Unix permissions to set on created files and semaphores
* @param flags the flags for this new environment
* @return an environment ready for use
*/
@SuppressWarnings("PMD.AccessorClassGeneration")
public Env open(final File path, final int mode,
final EnvFlags... flags) {
requireNonNull(path);
if (opened) {
throw new AlreadyOpenException();
}
opened = true;
final PointerByReference envPtr = new PointerByReference();
checkRc(LIB.mdb_env_create(envPtr));
final Pointer ptr = envPtr.getValue();
try {
checkRc(LIB.mdb_env_set_mapsize(ptr, mapSize));
checkRc(LIB.mdb_env_set_maxdbs(ptr, maxDbs));
checkRc(LIB.mdb_env_set_maxreaders(ptr, maxReaders));
final int flagsMask = mask(flags);
final boolean readOnly = isSet(flagsMask, MDB_RDONLY_ENV);
checkRc(LIB.mdb_env_open(ptr, path.getAbsolutePath(), flagsMask, mode));
return new Env<>(proxy, ptr, readOnly);
} catch (final LmdbNativeException e) {
LIB.mdb_env_close(ptr);
throw e;
}
}
/**
* Opens the environment with 0664 mode.
*
* @param path file system destination
* @param flags the flags for this new environment
* @return an environment ready for use
*/
@SuppressWarnings("PMD.AvoidUsingOctalValues")
public Env open(final File path, final EnvFlags... flags) {
return open(path, 0664, flags);
}
/**
* Sets the map size.
*
* @param mapSize new limit in bytes
* @return the builder
*/
public Builder setMapSize(final long mapSize) {
if (opened) {
throw new AlreadyOpenException();
}
if (mapSize < 0) {
throw new IllegalArgumentException("Negative value; overflow?");
}
this.mapSize = mapSize;
return this;
}
/**
* Sets the maximum number of databases (ie {@link Dbi}s permitted.
*
* @param dbs new limit
* @return the builder
*/
public Builder setMaxDbs(final int dbs) {
if (opened) {
throw new AlreadyOpenException();
}
this.maxDbs = dbs;
return this;
}
/**
* Sets the maximum number of databases permitted.
*
* @param readers new limit
* @return the builder
*/
public Builder setMaxReaders(final int readers) {
if (opened) {
throw new AlreadyOpenException();
}
this.maxReaders = readers;
return this;
}
}
/**
* File is not a valid LMDB file.
*/
public static final class FileInvalidException extends LmdbNativeException {
static final int MDB_INVALID = -30_793;
private static final long serialVersionUID = 1L;
FileInvalidException() {
super(MDB_INVALID, "File is not a valid LMDB file");
}
}
/**
* The specified copy destination is invalid.
*/
public static final class InvalidCopyDestination extends LmdbException {
private static final long serialVersionUID = 1L;
/**
* Creates a new instance.
*
* @param message the reason
*/
public InvalidCopyDestination(final String message) {
super(message);
}
}
/**
* Environment mapsize reached.
*/
public static final class MapFullException extends LmdbNativeException {
static final int MDB_MAP_FULL = -30_792;
private static final long serialVersionUID = 1L;
MapFullException() {
super(MDB_MAP_FULL, "Environment mapsize reached");
}
}
/**
* Environment maxreaders reached.
*/
public static final class ReadersFullException extends LmdbNativeException {
static final int MDB_READERS_FULL = -30_790;
private static final long serialVersionUID = 1L;
ReadersFullException() {
super(MDB_READERS_FULL, "Environment maxreaders reached");
}
}
/**
* Environment version mismatch.
*/
public static final class VersionMismatchException extends LmdbNativeException {
static final int MDB_VERSION_MISMATCH = -30_794;
private static final long serialVersionUID = 1L;
VersionMismatchException() {
super(MDB_VERSION_MISMATCH, "Environment version mismatch");
}
}
}