[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/lmdbjava/lmdbjava/master/src/test/java/org/lmdbjava/TxnTest.java [Back]  [Original]

/*
 * Copyright  2016-2025 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.
 */

package org.lmdbjava;

import static java.nio.ByteBuffer.allocateDirect;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.lmdbjava.DbiFlags.MDB_CREATE;
import static org.lmdbjava.Env.create;
import static org.lmdbjava.EnvFlags.MDB_NOSUBDIR;
import static org.lmdbjava.EnvFlags.MDB_RDONLY_ENV;
import static org.lmdbjava.KeyRange.closed;
import static org.lmdbjava.TestUtils.DB_1;
import static org.lmdbjava.TestUtils.bb;
import static org.lmdbjava.Txn.State.DONE;
import static org.lmdbjava.Txn.State.READY;
import static org.lmdbjava.Txn.State.RELEASED;
import static org.lmdbjava.Txn.State.RESET;
import static org.lmdbjava.TxnFlags.MDB_RDONLY_TXN;

import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.lmdbjava.Dbi.BadValueSizeException;
import org.lmdbjava.Env.AlreadyClosedException;
import org.lmdbjava.Txn.EnvIsReadOnly;
import org.lmdbjava.Txn.IncompatibleParent;
import org.lmdbjava.Txn.NotReadyException;
import org.lmdbjava.Txn.NotResetException;
import org.lmdbjava.Txn.ReadOnlyRequiredException;
import org.lmdbjava.Txn.ReadWriteRequiredException;
import org.lmdbjava.Txn.ResetException;

/** Test {@link Txn}. */
public final class TxnTest {

  private Path file;
  private Env env;

  private TempDir tempDir;

  @BeforeEach
  void beforeEach() {
    tempDir = new TempDir();
    file = tempDir.createTempFile();
    env =
        create()
            .setMapSize(256, ByteUnit.KIBIBYTES)
            .setMaxReaders(1)
            .setMaxDbs(2)
            .setEnvFlags(MDB_NOSUBDIR)
            .open(file);
  }

  @AfterEach
  void afterEach() {
    env.close();
    tempDir.cleanup();
  }

  @Test
  void largeKeysRejected() {
    assertThatThrownBy(
            () -> {
              final Dbi dbi =
                  env.createDbi()
                      .setDbName(DB_1)
                      .withDefaultComparator()
                      .setDbiFlags(MDB_CREATE)
                      .open();
              final ByteBuffer key = allocateDirect(env.getMaxKeySize() + 1);
              key.limit(key.capacity());
              dbi.put(key, bb(2));
            })
        .isInstanceOf(BadValueSizeException.class);
  }

  @Test
  void rangeSearch() {
    final Dbi db =
        env.createDbi().setDbName(DB_1).withDefaultComparator().setDbiFlags(MDB_CREATE).open();

    final ByteBuffer key = allocateDirect(env.getMaxKeySize());
    key.put("cherry".getBytes(UTF_8)).flip();
    db.put(key, bb(1));

    key.clear();
    key.put("strawberry".getBytes(UTF_8)).flip();
    db.put(key, bb(3));

    key.clear();
    key.put("pineapple".getBytes(UTF_8)).flip();
    db.put(key, bb(2));

    try (Txn txn = env.txnRead()) {
      final ByteBuffer start = allocateDirect(env.getMaxKeySize());
      start.put("a".getBytes(UTF_8)).flip();

      final ByteBuffer end = allocateDirect(env.getMaxKeySize());
      end.put("z".getBytes(UTF_8)).flip();

      final List keysFound = new ArrayList();
      try (CursorIterable ckr = db.iterate(txn, closed(start, end))) {
        for (final CursorIterable.KeyVal kv : ckr) {
          keysFound.add(UTF_8.decode(kv.key()).toString());
        }
      }

      assertThat(keysFound.size()).isEqualTo(3);
    }
  }

  @Test
  void readOnlyTxnAllowedInReadOnlyEnv() {
    env.createDbi().setDbName(DB_1).withDefaultComparator().setDbiFlags(MDB_CREATE).open();
    try (Env roEnv =
        create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR, MDB_RDONLY_ENV).open(file)) {
      assertThat(roEnv.txnRead()).isNotNull();
    }
  }

  @Test
  void readWriteTxnDeniedInReadOnlyEnv() {
    assertThatThrownBy(
            () -> {
              env.createDbi()
                  .setDbName(DB_1)
                  .withDefaultComparator()
                  .setDbiFlags(MDB_CREATE)
                  .open();
              env.close();
              try (Env roEnv =
                  create().setMaxReaders(1).setEnvFlags(MDB_NOSUBDIR, MDB_RDONLY_ENV).open(file)) {
                roEnv.txnWrite(); // error
              }
            })
        .isInstanceOf(EnvIsReadOnly.class);
  }

  @Test
  void testCheckNotCommitted() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnRead()) {
                txn.commit();
                txn.checkReady();
              }
            })
        .isInstanceOf(NotReadyException.class);
  }

  @Test
  void testCheckReadOnly() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnWrite()) {
                txn.checkReadOnly();
              }
            })
        .isInstanceOf(ReadOnlyRequiredException.class);
  }

  @Test
  void testCheckWritesAllowed() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnRead()) {
                txn.checkWritesAllowed();
              }
            })
        .isInstanceOf(ReadWriteRequiredException.class);
  }

  @Test
  void testGetId() {
    final Dbi db =
        env.createDbi().setDbName(DB_1).withDefaultComparator().setDbiFlags(MDB_CREATE).open();
    final AtomicLong txId1 = new AtomicLong();
    final AtomicLong txId2 = new AtomicLong();

    try (Txn tx1 = env.txnRead()) {
      txId1.set(tx1.getId());
    }

    db.put(bb(1), bb(2));

    try (Txn tx2 = env.txnRead()) {
      txId2.set(tx2.getId());
    }
    // should not see the same snapshot
    assertThat(txId1.get()).isNotEqualTo(txId2.get());
  }

  @Test
  void txCanCommitThenCloseWithoutError() {
    try (Txn txn = env.txnRead()) {
      assertThat(txn.getState()).isEqualTo(READY);
      txn.commit();
      assertThat(txn.getState()).isEqualTo(DONE);
    }
  }

  @Test
  void txCannotAbortIfAlreadyCommitted() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnRead()) {
                assertThat(txn.getState()).isEqualTo(READY);
                txn.commit();
                assertThat(txn.getState()).isEqualTo(DONE);
                txn.abort();
              }
            })
        .isInstanceOf(NotReadyException.class);
  }

  @Test
  void txCannotCommitTwice() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnRead()) {
                txn.commit();
                txn.commit(); // error
              }
            })
        .isInstanceOf(NotReadyException.class);
  }

  @Test
  void txConstructionDeniedIfEnvClosed() {
    assertThatThrownBy(
            () -> {
              env.close();
              env.txnRead();
            })
        .isInstanceOf(AlreadyClosedException.class);
  }

  @Test
  void txRenewDeniedIfEnvClosed() {
    assertThatThrownBy(
            () -> {
              final Txn txnRead = env.txnRead();
              txnRead.close();
              env.close();
              txnRead.renew();
            })
        .isInstanceOf(AlreadyClosedException.class);
  }

  @Test
  void txCloseDeniedIfEnvClosed() {
    assertThatThrownBy(
            () -> {
              final Txn txnRead = env.txnRead();
              env.close();
              txnRead.close();
            })
        .isInstanceOf(AlreadyClosedException.class);
  }

  @Test
  void txCommitDeniedIfEnvClosed() {
    assertThatThrownBy(
            () -> {
              final Txn txnRead = env.txnRead();
              env.close();
              txnRead.commit();
            })
        .isInstanceOf(AlreadyClosedException.class);
  }

  @Test
  void txAbortDeniedIfEnvClosed() {
    assertThatThrownBy(
            () -> {
              final Txn txnRead = env.txnRead();
              env.close();
              txnRead.abort();
            })
        .isInstanceOf(AlreadyClosedException.class);
  }

  @Test
  void txResetDeniedIfEnvClosed() {
    assertThatThrownBy(
            () -> {
              final Txn txnRead = env.txnRead();
              env.close();
              txnRead.reset();
            })
        .isInstanceOf(AlreadyClosedException.class);
  }

  @Test
  public void txParent() {
    try (Txn txRoot = env.txnWrite();
        Txn txChild = env.txn(txRoot)) {
      assertThat(txRoot.getParent()).isNull();
      assertThat(txChild.getParent()).isEqualTo(txRoot);
    }
  }

  @Test
  public void txParent2() {
    try (Txn txRoot = env.txnWrite()) {
      assertThatThrownBy(
              () -> {
                env.txn(txRoot, (TxnFlagSet) null);
              })
          .isInstanceOf(NullPointerException.class);
    }
  }

  @Test
  public void txParent3() {
    try (Txn txRoot = env.txnWrite();
        Txn txChild = env.txn(txRoot, TxnFlagSet.EMPTY)) {
      assertThat(txRoot.getParent()).isNull();
      assertThat(txChild.getParent()).isEqualTo(txRoot);
    }
  }

  @Test
  void txParentDeniedIfEnvClosed() {
    assertThatThrownBy(
            () -> {
              try (Txn txRoot = env.txnWrite();
                  Txn txChild = env.txn(txRoot)) {
                env.close();
                assertThat(txChild.getParent()).isEqualTo(txRoot);
              }
            })
        .isInstanceOf(AlreadyClosedException.class);
  }

  @Test
  void txParentROChildRWIncompatible() {
    assertThatThrownBy(
            () -> {
              try (Txn txRoot = env.txnRead()) {
                env.txn(txRoot); // error
              }
            })
        .isInstanceOf(IncompatibleParent.class);
  }

  @Test
  void txParentRWChildROIncompatible() {
    assertThatThrownBy(
            () -> {
              try (Txn txRoot = env.txnWrite()) {
                env.txn(txRoot, MDB_RDONLY_TXN); // error
              }
            })
        .isInstanceOf(IncompatibleParent.class);
  }

  @Test
  void txReadOnly() {
    try (Txn txn = env.txnRead()) {
      assertThat(txn.getParent()).isNull();
      assertThat(txn.getState()).isEqualTo(READY);
      assertThat(txn.isReadOnly()).isTrue();
      txn.checkReady();
      txn.checkReadOnly();
      txn.reset();
      assertThat(txn.getState()).isEqualTo(RESET);
      txn.renew();
      assertThat(txn.getState()).isEqualTo(READY);
      txn.commit();
      assertThat(txn.getState()).isEqualTo(DONE);
      txn.close();
      assertThat(txn.getState()).isEqualTo(RELEASED);
    }
  }

  @Test
  void txReadWrite() {
    final Txn txn = env.txnWrite();
    assertThat(txn.getParent()).isNull();
    assertThat(txn.getState()).isEqualTo(READY);
    assertThat(txn.isReadOnly()).isFalse();
    txn.checkReady();
    txn.checkWritesAllowed();
    txn.commit();
    assertThat(txn.getState()).isEqualTo(DONE);
    txn.close();
    assertThat(txn.getState()).isEqualTo(RELEASED);
  }

  @Test
  void txRenewDeniedWithoutPriorReset() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnRead()) {
                txn.renew();
              }
            })
        .isInstanceOf(NotResetException.class);
  }

  @Test
  void txResetDeniedForAlreadyResetTransaction() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnRead()) {
                txn.reset();
                txn.renew();
                txn.reset();
                txn.reset();
              }
            })
        .isInstanceOf(ResetException.class);
  }

  @Test
  void txResetDeniedForReadWriteTransaction() {
    assertThatThrownBy(
            () -> {
              try (Txn txn = env.txnWrite()) {
                txn.reset();
              }
            })
        .isInstanceOf(ReadOnlyRequiredException.class);
  }

  @Test
  void zeroByteKeysRejected() {
    assertThatThrownBy(
            () -> {
              final Dbi dbi =
                  env.createDbi()
                      .setDbName(DB_1)
                      .withDefaultComparator()
                      .setDbiFlags(MDB_CREATE)
                      .open();
              final ByteBuffer key = allocateDirect(4);
              key.putInt(1);
              assertThat(key.remaining()).isEqualTo(0); // because key.flip() skipped
              dbi.put(key, bb(2));
            })
        .isInstanceOf(BadValueSizeException.class);
  }
}

Web Proxy Viewer  |  New URL  |  Original Page