Skip to content
Permalink
Browse files

Add new key/value implementation based on MVStore (issue #34).

  • Loading branch information...
archiecobbs committed Jun 18, 2019
1 parent 7a445d1 commit 54be5c281b2fee10d97d51244b85b802ad50adfb
Showing with 1,290 additions and 0 deletions.
  1. +4 −0 CHANGES.txt
  2. +57 −0 permazen-kv-mvstore/pom.xml
  3. +89 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/AbstractIterator.java
  4. +180 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/AbstractMVStoreKVStore.java
  5. +117 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/ByteArrayDataType.java
  6. +75 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/CursorIterator.java
  7. +132 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/MVMapKVStore.java
  8. +54 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/MVMapSnapshot.java
  9. +121 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/MVStoreAtomicKVStore.java
  10. +58 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/MVStoreKVDatabase.java
  11. +153 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/MVStoreKVImplementation.java
  12. +29 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/MVStoreKVTransaction.java
  13. +73 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/ReverseIterator.java
  14. +14 −0 permazen-kv-mvstore/src/main/java/io/permazen/kv/mvstore/package-info.java
  15. +5 −0 permazen-kv-mvstore/src/main/resources/META-INF/permazen/kv-implementations.xml
  16. +12 −0 permazen-kv-mvstore/src/spotbugs/spotbugs-exclude.xml
  17. +29 −0 permazen-kv-mvstore/src/test/java/io/permazen/kv/mvstore/MVStoreAtomicKVStoreTest.java
  18. +64 −0 permazen-kv-mvstore/src/test/java/io/permazen/kv/mvstore/MVStoreKVDatabaseTest.java
  19. +6 −0 permazen-kv-mvstore/src/test/java/io/permazen/kv/mvstore/package-info.java
  20. +18 −0 pom.xml
@@ -1,3 +1,7 @@
Version Next

- Added new key/value implementation based on MVStore (issue #34)

Version 4.1.5 Released May 21, 2019

- Fixed bug where snapshot tx could leak if commit() threw an error
@@ -0,0 +1,57 @@
<?xml version="1.0"?>

<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.permazen</groupId>
<artifactId>permazen</artifactId>
<version>4.1.5</version>
</parent>
<artifactId>permazen-kv-mvstore</artifactId>
<name>Permazen H2 MVStore Key/Value Store</name>
<description>Permazen key/value store implementation based on H2 Database MVStore.</description>
<distributionManagement>
<site>
<id>${project.artifactId}-site</id>
<url>file://${project.basedir}/../site/${project.artifactId}/</url>
</site>
</distributionManagement>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.parent.artifactId}-kv</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.parent.artifactId}-kv-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${project.parent.artifactId}-util</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,89 @@

/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/

package io.permazen.kv.mvstore;

import com.google.common.base.Preconditions;

import io.permazen.kv.KVPair;
import io.permazen.util.CloseableIterator;

import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.h2.mvstore.MVMap;

/**
* Support superclass for {@link KVPair} iterators reading from on an underlying {@link MVMap}.
*
* <p>
* The {@link #remove} method is implemented by invoking {@link MVMap#remove(Object)}.
*/
@ThreadSafe
abstract class AbstractIterator implements CloseableIterator<KVPair> {

protected final MVMap<byte[], byte[]> mvmap;

private final com.google.common.collect.AbstractIterator<KVPair> iter;

@GuardedBy("this")
private byte[] lastKey;

// Constructor

protected AbstractIterator(MVMap<byte[], byte[]> mvmap) {
this.mvmap = mvmap;
this.iter = new com.google.common.collect.AbstractIterator<KVPair>() {
@Override
protected KVPair computeNext() {
final KVPair kv = AbstractIterator.this.findNext();
return kv != null && AbstractIterator.this.boundsCheck(kv.getKey()) ? kv : this.endOfData();
}
};
}

protected abstract KVPair findNext();

protected abstract boolean boundsCheck(byte[] key);

/**
* Get the {@link MVMap} underlying this instance, if any.
*
* @return underlying {@link MVMap}, or null if none provided
*/
public MVMap<byte[], byte[]> getMVMap() {
return this.mvmap;
}

// Iterator

@Override
public synchronized KVPair next() {
final KVPair kv = this.iter.next();
this.lastKey = kv.getKey();
return kv;
}

@Override
public synchronized boolean hasNext() {
return this.iter.hasNext();
}

@Override
public synchronized void remove() {
if (this.mvmap == null)
throw new UnsupportedOperationException();
Preconditions.checkState(this.lastKey != null);
this.mvmap.remove(this.lastKey);
this.lastKey = null;
}

// Closeable

@Override
public void close() {
// nothing to do
}
}
@@ -0,0 +1,180 @@

/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/

package io.permazen.kv.mvstore;

import com.google.common.base.Preconditions;

import java.util.Optional;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import org.h2.mvstore.MVMap;
import org.h2.mvstore.MVStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Support superclass for {@link io.permazen.kv.KVStore} implementations based on an open {@link MVStore}.
*
* <p>
* Subclass must implement {@link #getMVMap}.
*/
@ThreadSafe
abstract class AbstractMVStoreKVStore extends MVMapKVStore {

protected final Logger log = LoggerFactory.getLogger(this.getClass());

// Configuration info
@GuardedBy("this")
MVStore.Builder builder;
@GuardedBy("this")
String mapName;

// Runtime info
@GuardedBy("this")
MVStore mvstore;

// Constructors

protected AbstractMVStoreKVStore() {
}

// Accessors

/**
* Configure the {@link MVStore.Builder} that will be used to construct the {@link MVStore} when {@link #start} is invoked.
*
* @param builder builder for the {@link MVStore}, or null to use a default builder
* @throws IllegalStateException if this instance is already {@link #start}ed
*/
public synchronized void setBuilder(MVStore.Builder builder) {
Preconditions.checkState(this.mvstore == null, "already started");
this.builder = builder;
}

/**
* Configure the {@link MVStore.Builder} that will be used to construct the {@link MVStore} when {@link #start} is invoked
* using the specified configuration string.
*
* @param builderConfig {@link MVStore.Builder} configuration string, or null to use a default builder
* @throws IllegalStateException if this instance is already {@link #start}ed
* @see MVStore.Builder#fromString
*/
public synchronized void setBuilderConfig(String builderConfig) {
Preconditions.checkState(this.mvstore == null, "already started");
this.builder = builderConfig != null ? MVStore.Builder.fromString(builderConfig) : null;
}

/**
* Configure the name of the {@link MVMap} to use.
*
* <p>
* Default value is {@link MVStoreKVImplementation#DEFAULT_MAP_NAME}.
*
* @param mapName map name, or null to use {@link MVStoreKVImplementation#DEFAULT_MAP_NAME}
* @throws IllegalStateException if this instance is already {@link #start}ed
*/
public synchronized void setMapName(String mapName) {
Preconditions.checkState(this.mvstore == null, "already started");
this.mapName = mapName;
}

/**
* Get the underlying {@link MVStore} associated with this instance.
*
* @return the associated underlying {@link MVStore}
* @throws IllegalStateException if this instance is not {@link #start}ed
*/
public synchronized MVStore getMVStore() {
Preconditions.checkState(this.mvstore != null, "not started");
return this.mvstore;
}

// MVMapKVStore

/**
* Get the underlying {@link MVMap} associated with this instance.
*
* @return the associated underlying {@link MVMap}
* @throws IllegalStateException if this instance is not {@link #start}ed
*/
@Override
public abstract MVMap<byte[], byte[]> getMVMap();

// Lifecycle

@PostConstruct
public synchronized void start() {

// Already started?
if (this.mvstore != null)
return;
this.log.info("starting " + this);

// Open MVStore and MVMap
boolean success = false;
try {
this.doOpen();
success = true;
} finally { // clean up if error occurred
if (!success && this.mvstore != null)
this.doCloseImmediately();
}
}

@PreDestroy
public synchronized void stop() {

// Check state
if (this.mvstore == null)
return;
this.log.info("stopping " + this);

// Close k/v store view
this.doClose();
this.mvstore = null;
}

protected void doOpen() {
this.mvstore = Optional.ofNullable(this.builder).orElse(new MVStore.Builder()).open();
}

protected void doCloseImmediately() {
this.mvstore.closeImmediately();
this.mvstore = null;
}

protected void doClose() {
this.mvstore.close();
this.mvstore = null;
}

// Object

/**
* Finalize this instance. Invokes {@link #stop} to close any unclosed iterators.
*/
@Override
protected void finalize() throws Throwable {
try {
if (this.mvstore != null)
this.log.warn(this + " leaked without invoking stop()");
this.stop();
} finally {
super.finalize();
}
}

@Override
public String toString() {
return this.getClass().getSimpleName()
+ "[map=\"" + this.mapName + "\""
+ "]";
}
}

0 comments on commit 54be5c2

Please sign in to comment.
You can’t perform that action at this time.