/*- * #%L * LmdbJava * %% * Copyright (C) 2016 - 2020 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 com.jakewharton.byteunits.BinaryByteUnit.KIBIBYTES; import static java.lang.Long.BYTES; import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsEmptyCollection.empty; import static org.lmdbjava.ByteArrayProxy.PROXY_BA; import static org.lmdbjava.ByteBufProxy.PROXY_NETTY; import static org.lmdbjava.ByteBufferProxy.PROXY_OPTIMAL; import static org.lmdbjava.ByteBufferProxy.PROXY_SAFE; import static org.lmdbjava.DbiFlags.MDB_CREATE; import static org.lmdbjava.DbiFlags.MDB_DUPSORT; import static org.lmdbjava.DirectBufferProxy.PROXY_DB; import static org.lmdbjava.Env.create; import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR; import static org.lmdbjava.GetOp.MDB_SET_KEY; import static org.lmdbjava.GetOp.MDB_SET_RANGE; import static org.lmdbjava.PutFlags.MDB_NOOVERWRITE; 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 static org.lmdbjava.TestUtils.DB_1; import static org.lmdbjava.TestUtils.POSIX_MODE; import static org.lmdbjava.TestUtils.bb; import static org.lmdbjava.TestUtils.mdb; import static org.lmdbjava.TestUtils.nb; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import io.netty.buffer.ByteBuf; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; /** * Test {@link Cursor} with different buffer implementations. */ @RunWith(Parameterized.class) public final class CursorParamTest { /** * Injected by {@link #data()} with appropriate runner. */ @Parameter public BufferRunner<?> runner; @Rule public final TemporaryFolder tmp = new TemporaryFolder(); @Parameters(name = "{index}: buffer adapter: {0}") public static Object[] data() { final BufferRunner<ByteBuffer> bb1 = new ByteBufferRunner(PROXY_OPTIMAL); final BufferRunner<ByteBuffer> bb2 = new ByteBufferRunner(PROXY_SAFE); final BufferRunner<byte[]> ba = new ByteArrayRunner(PROXY_BA); final BufferRunner<DirectBuffer> db = new DirectBufferRunner(); final BufferRunner<ByteBuf> netty = new NettyBufferRunner(); return new Object[]{bb1, bb2, ba, db, netty}; } @Test public void execute() { runner.execute(tmp); } /** * Abstract implementation of {@link BufferRunner}. * * @param <T> buffer type */ private abstract static class AbstractBufferRunner<T> implements BufferRunner<T> { final BufferProxy<T> proxy; protected AbstractBufferRunner(final BufferProxy<T> proxy) { this.proxy = proxy; } @Override public final void execute(final TemporaryFolder tmp) { try (Env<T> env = env(tmp)) { assertThat(env.getDbiNames(), empty()); final Dbi<T> db = env.openDbi(DB_1, MDB_CREATE, MDB_DUPSORT); assertThat(env.getDbiNames().get(0), is(DB_1.getBytes(UTF_8))); try (Txn<T> txn = env.txnWrite(); Cursor<T> c = db.openCursor(txn)) { // populate data c.put(set(1), set(2), MDB_NOOVERWRITE); c.put(set(3), set(4)); c.put(set(5), set(6)); // we cannot set the value for ByteArrayProxy // but the key is still valid. final T valForKey7 = c.reserve(set(7), BYTES); set(valForKey7, 8); // check MDB_SET operations final T key3 = set(3); assertThat(c.get(key3, MDB_SET_KEY), is(true)); assertThat(get(c.key()), is(3)); assertThat(get(c.val()), is(4)); final T key6 = set(6); assertThat(c.get(key6, MDB_SET_RANGE), is(true)); assertThat(get(c.key()), is(7)); if (!(this instanceof ByteArrayRunner)) { assertThat(get(c.val()), is(8)); } final T key999 = set(999); assertThat(c.get(key999, MDB_SET_KEY), is(false)); // check MDB navigation operations assertThat(c.seek(MDB_LAST), is(true)); final int mdb1 = get(c.key()); final int mdb2 = get(c.val()); assertThat(c.seek(MDB_PREV), is(true)); final int mdb3 = get(c.key()); final int mdb4 = get(c.val()); assertThat(c.seek(MDB_NEXT), is(true)); final int mdb5 = get(c.key()); final int mdb6 = get(c.val()); assertThat(c.seek(MDB_FIRST), is(true)); final int mdb7 = get(c.key()); final int mdb8 = get(c.val()); // assert afterwards to ensure memory address from LMDB // are valid within same txn and across cursor movement // MDB_LAST assertThat(mdb1, is(7)); if (!(this instanceof ByteArrayRunner)) { assertThat(mdb2, is(8)); } // MDB_PREV assertThat(mdb3, is(5)); assertThat(mdb4, is(6)); // MDB_NEXT assertThat(mdb5, is(7)); if (!(this instanceof ByteArrayRunner)) { assertThat(mdb6, is(8)); } // MDB_FIRST assertThat(mdb7, is(1)); assertThat(mdb8, is(2)); } } } private Env<T> env(final TemporaryFolder tmp) { try { final File path = tmp.newFile(); return create(proxy) .setMapSize(KIBIBYTES.toBytes(1_024)) .setMaxReaders(1) .setMaxDbs(1) .open(path, POSIX_MODE, MDB_NOSUBDIR); } catch (final IOException e) { throw new LmdbException("IO failure", e); } } } /** * {@link BufferRunner} for Java byte buffers. */ private static class ByteArrayRunner extends AbstractBufferRunner<byte[]> { ByteArrayRunner(final BufferProxy<byte[]> proxy) { super(proxy); } @Override public int get(final byte[] buff) { return (buff[0] & 0xFF) << 24 | (buff[1] & 0xFF) << 16 | (buff[2] & 0xFF) << 8 | (buff[3] & 0xFF); } @Override public byte[] set(final int val) { final byte[] buff = new byte[4]; buff[0] = (byte) (val >>> 24); buff[1] = (byte) (val >>> 16); buff[2] = (byte) (val >>> 8); buff[3] = (byte) val; return buff; } @Override public void set(final byte[] buff, final int val) { buff[0] = (byte) (val >>> 24); buff[1] = (byte) (val >>> 16); buff[2] = (byte) (val >>> 8); buff[3] = (byte) val; } } /** * {@link BufferRunner} for Java byte buffers. */ private static class ByteBufferRunner extends AbstractBufferRunner<ByteBuffer> { ByteBufferRunner(final BufferProxy<ByteBuffer> proxy) { super(proxy); } @Override public int get(final ByteBuffer buff) { return buff.getInt(0); } @Override public ByteBuffer set(final int val) { return bb(val); } @Override public void set(final ByteBuffer buff, final int val) { buff.putInt(val); } } /** * {@link BufferRunner} for Agrona direct buffer. */ private static class DirectBufferRunner extends AbstractBufferRunner<DirectBuffer> { DirectBufferRunner() { super(PROXY_DB); } @Override public int get(final DirectBuffer buff) { return buff.getInt(0); } @Override public DirectBuffer set(final int val) { return mdb(val); } @Override public void set(final DirectBuffer buff, final int val) { ((MutableDirectBuffer) buff).putInt(0, val); } } /** * {@link BufferRunner} for Netty byte buf. */ private static class NettyBufferRunner extends AbstractBufferRunner<ByteBuf> { NettyBufferRunner() { super(PROXY_NETTY); } @Override public int get(final ByteBuf buff) { return buff.getInt(0); } @Override public ByteBuf set(final int val) { return nb(val); } @Override public void set(final ByteBuf buff, final int val) { buff.setInt(0, val); } } /** * Adapter to allow different buffers to be tested with this class. * * @param <T> buffer type */ private interface BufferRunner<T> { void execute(TemporaryFolder tmp); T set(int val); void set(T buff, int val); int get(T buff); } }