diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 5705bdf..1f52101 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -23,7 +23,7 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew build + run: ./gradlew clean build - name: Coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} diff --git a/build.gradle b/build.gradle index 2cf56fd..b7d84c8 100644 --- a/build.gradle +++ b/build.gradle @@ -133,14 +133,7 @@ task immudbStart { pb = new ProcessBuilder() pb.command("/bin/bash", "immudb/start.sh") Process proc = pb.start() - - proc.waitFor(5, TimeUnit.SECONDS) - - if (proc.isAlive()) { - println "immudb has been started." - } else { - throw new GradleException("Process exit bad: " + proc.exitValue()) - } + proc.waitFor(15, TimeUnit.SECONDS) } task immudbStop(type: Exec) { @@ -167,6 +160,10 @@ test { } // Run the unit tests sequentially. maxParallelForks = 1 + + testLogging { + events "FAILED" + } } test.dependsOn immudbStart diff --git a/immudb/clean.sh b/immudb/clean.sh index 93583f0..2a16f6a 100755 --- a/immudb/clean.sh +++ b/immudb/clean.sh @@ -11,7 +11,6 @@ then fi rm -rf data -rm -rf states -echo "immudb's data and immudb4j's states folders were removed." +echo "immudb's data folder was removed." diff --git a/src/main/java/io/codenotary/immudb4j/Entry.java b/src/main/java/io/codenotary/immudb4j/Entry.java index 87b6e37..a89c5b7 100644 --- a/src/main/java/io/codenotary/immudb4j/Entry.java +++ b/src/main/java/io/codenotary/immudb4j/Entry.java @@ -28,8 +28,15 @@ public class Entry { private Reference referencedBy; + private long revision; + private Entry() {} + public Entry(byte[] key, byte[] value) { + this.key = key; + this.value = value; + } + public static Entry valueOf(ImmudbProto.Entry e) { final Entry entry = new Entry(); @@ -45,6 +52,8 @@ public static Entry valueOf(ImmudbProto.Entry e) { entry.referencedBy = Reference.valueOf(e.getReferencedBy()); } + entry.revision = e.getRevision(); + return entry; } @@ -68,6 +77,10 @@ public Reference getReferenceBy() { return referencedBy; } + public long getRevision() { + return revision; + } + public byte[] getEncodedKey() { if (referencedBy == null) { return Utils.wrapWithPrefix(key, Consts.SET_KEY_PREFIX); diff --git a/src/main/java/io/codenotary/immudb4j/ImmuClient.java b/src/main/java/io/codenotary/immudb4j/ImmuClient.java index 99ad078..ca00ac3 100644 --- a/src/main/java/io/codenotary/immudb4j/ImmuClient.java +++ b/src/main/java/io/codenotary/immudb4j/ImmuClient.java @@ -19,7 +19,10 @@ import com.google.protobuf.Empty; import io.codenotary.immudb.ImmuServiceGrpc; import io.codenotary.immudb.ImmudbProto; +import io.codenotary.immudb.ImmudbProto.Chunk; import io.codenotary.immudb.ImmudbProto.ScanRequest; +import io.codenotary.immudb.ImmudbProto.Score; +import io.codenotary.immudb4j.basics.LatchHolder; import io.codenotary.immudb4j.crypto.CryptoUtils; import io.codenotary.immudb4j.crypto.DualProof; import io.codenotary.immudb4j.crypto.InclusionProof; @@ -29,14 +32,19 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; +import io.grpc.stub.StreamObserver; import io.grpc.ConnectivityState; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.TimeUnit; @@ -53,35 +61,37 @@ public class ImmuClient { private final PublicKey serverSigningKey; private final ImmuStateHolder stateHolder; private long keepAlivePeriod; + private int chunkSize; private ManagedChannel channel; - private final ImmuServiceGrpc.ImmuServiceBlockingStub stub; + private final ImmuServiceGrpc.ImmuServiceBlockingStub blockingStub; + private final ImmuServiceGrpc.ImmuServiceStub nonBlockingStub; private Session session; private Timer sessionHeartBeat; - - public ImmuClient(Builder builder) { - this.stateHolder = builder.getStateHolder(); - this.serverSigningKey = builder.getServerSigningKey(); - this.keepAlivePeriod = builder.getKeepAlivePeriod(); - this.stub = createStubFrom(builder); - } - public static Builder newBuilder() { - return new Builder(); - } + public ImmuClient(Builder builder) { + stateHolder = builder.getStateHolder(); + serverSigningKey = builder.getServerSigningKey(); + keepAlivePeriod = builder.getKeepAlivePeriod(); + chunkSize = builder.getChunkSize(); - private ImmuServiceGrpc.ImmuServiceBlockingStub createStubFrom(Builder builder) { - channel = ManagedChannelBuilder.forAddress(builder.getServerUrl(), builder.getServerPort()) + channel = ManagedChannelBuilder + .forAddress(builder.getServerUrl(), builder.getServerPort()) .usePlaintext() .intercept(new ImmudbAuthRequestInterceptor(this)) .build(); - return ImmuServiceGrpc.newBlockingStub(channel); + blockingStub = ImmuServiceGrpc.newBlockingStub(channel); + nonBlockingStub = ImmuServiceGrpc.newStub(channel); + } + + public static Builder newBuilder() { + return new Builder(); } - public synchronized void shutdown() { + public synchronized void shutdown() throws InterruptedException { if (channel == null) { return; } @@ -91,46 +101,43 @@ public synchronized void shutdown() { } try { - channel.shutdown(); - if (!channel.isShutdown()) { - try { - channel.awaitTermination(1, TimeUnit.SECONDS); - } catch (InterruptedException e) { - channel.shutdownNow(); - } - } + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS); } finally { channel = null; } } - Session getSession() { - return this.session; + protected synchronized Session getSession() { + return session; } - public synchronized void openSession(String username, String password, String database) { - if (this.session != null) { + public synchronized void openSession(String database, String username, String password) { + if (session != null) { throw new IllegalStateException("session already opened"); } final ImmudbProto.OpenSessionRequest req = ImmudbProto.OpenSessionRequest .newBuilder() + .setDatabaseName(database) .setUsername(Utils.toByteString(username)) .setPassword(Utils.toByteString(password)) - .setDatabaseName(database) .build(); - final ImmudbProto.OpenSessionResponse resp = this.stub.openSession(req); + final ImmudbProto.OpenSessionResponse resp = this.blockingStub.openSession(req); - this.session = new Session(resp.getSessionID(), database); + session = new Session(resp.getSessionID(), database); - this.sessionHeartBeat = new Timer(); + sessionHeartBeat = new Timer(); - this.sessionHeartBeat.schedule(new TimerTask() { + sessionHeartBeat.schedule(new TimerTask() { @Override public void run() { try { - stub.keepAlive(Empty.getDefaultInstance()); + synchronized (ImmuClient.this) { + if (session != null) { + blockingStub.keepAlive(Empty.getDefaultInstance()); + } + } } catch (Exception e) { e.printStackTrace(); } @@ -139,16 +146,16 @@ public void run() { } public synchronized void closeSession() { - if (this.session == null) { + if (session == null) { throw new IllegalStateException("no open session"); } - this.sessionHeartBeat.cancel(); + sessionHeartBeat.cancel(); try { - this.stub.closeSession(Empty.getDefaultInstance()); + blockingStub.closeSession(Empty.getDefaultInstance()); } finally { - this.session = null; + session = null; } } @@ -157,14 +164,14 @@ public synchronized void closeSession() { * If nothing exists already, it is fetched from the server and save it locally. */ private ImmuState state() throws VerificationException { - if (this.session == null) { + if (session == null) { throw new IllegalStateException("no open session"); } - ImmuState state = this.stateHolder.getState(this.session.getDatabase()); + ImmuState state = stateHolder.getState(session.getDatabase()); if (state == null) { - state = this.currentState(); + state = currentState(); stateHolder.setState(state); } @@ -179,19 +186,19 @@ private ImmuState state() throws VerificationException { * Note: local state is not updated because this is not a verified operation */ public synchronized ImmuState currentState() throws VerificationException { - if (this.session == null) { + if (session == null) { throw new IllegalStateException("no open session"); } - final ImmudbProto.ImmutableState state = this.stub.currentState(Empty.getDefaultInstance()); + final ImmudbProto.ImmutableState state = blockingStub.currentState(Empty.getDefaultInstance()); final ImmuState immuState = ImmuState.valueOf(state); - if (!this.session.getDatabase().equals(immuState.getDatabase())) { + if (!session.getDatabase().equals(immuState.getDatabase())) { throw new VerificationException("database mismatch"); } - if (!immuState.checkSignature(this.serverSigningKey)) { + if (!immuState.checkSignature(serverSigningKey)) { throw new VerificationException("State signature verification failed"); } @@ -202,25 +209,84 @@ public synchronized ImmuState currentState() throws VerificationException { // ========== DATABASE ========== // - public synchronized void createDatabase(String database) { - if (this.session == null) { + public void createDatabase(String database) { + createDatabase(database, false); + } + + public synchronized void createDatabase(String database, boolean ifNotExists) { + if (session == null) { throw new IllegalStateException("no open session"); } final ImmudbProto.CreateDatabaseRequest req = ImmudbProto.CreateDatabaseRequest.newBuilder() .setName(database) + .setIfNotExists(ifNotExists) + .build(); + + blockingStub.createDatabaseV2(req); + } + + // LoadDatabase loads database on the server. A database is not loaded + // if it has AutoLoad setting set to false or if it failed to load during + // immudb startup. + // + // This call requires SysAdmin permission level or admin permission to the + // database. + public synchronized void loadDatabase(String database) { + if (session == null) { + throw new IllegalStateException("no open session"); + } + + final ImmudbProto.LoadDatabaseRequest req = ImmudbProto.LoadDatabaseRequest.newBuilder() + .setDatabase(database) + .build(); + + blockingStub.loadDatabase(req); + } + + // UnloadDatabase unloads database on the server. Such database becomes + // inaccessible + // by the client and server frees internal resources allocated for that + // database. + // + // This call requires SysAdmin permission level or admin permission to the + // database. + public synchronized void unloadDatabase(String database) { + if (session == null) { + throw new IllegalStateException("no open session"); + } + + final ImmudbProto.UnloadDatabaseRequest req = ImmudbProto.UnloadDatabaseRequest.newBuilder() + .setDatabase(database) + .build(); + + blockingStub.unloadDatabase(req); + } + + // DeleteDatabase removes an unloaded database. + // This also removes locally stored files used by the database. + // + // This call requires SysAdmin permission level or admin permission to the + // database. + public synchronized void deleteDatabase(String database) { + if (session == null) { + throw new IllegalStateException("no open session"); + } + + final ImmudbProto.DeleteDatabaseRequest req = ImmudbProto.DeleteDatabaseRequest.newBuilder() + .setDatabase(database) .build(); - this.stub.createDatabaseV2(req); + blockingStub.deleteDatabase(req); } public synchronized List databases() { - if (this.session == null) { + if (session == null) { throw new IllegalStateException("no open session"); } final ImmudbProto.DatabaseListRequestV2 req = ImmudbProto.DatabaseListRequestV2.newBuilder().build(); - final ImmudbProto.DatabaseListResponseV2 resp = this.stub.databaseListV2(req); + final ImmudbProto.DatabaseListResponseV2 resp = blockingStub.databaseListV2(req); final List list = new ArrayList<>(resp.getDatabasesCount()); @@ -254,7 +320,7 @@ public synchronized Entry getAtTx(byte[] key, long tx) throws KeyNotFoundExcepti .build(); try { - return Entry.valueOf(this.stub.get(req)); + return Entry.valueOf(blockingStub.get(req)); } catch (StatusRuntimeException e) { if (e.getMessage().contains("key not found")) { throw new KeyNotFoundException(); @@ -275,7 +341,7 @@ public synchronized Entry getSinceTx(byte[] key, long tx) throws KeyNotFoundExce .build(); try { - return Entry.valueOf(this.stub.get(req)); + return Entry.valueOf(blockingStub.get(req)); } catch (StatusRuntimeException e) { if (e.getMessage().contains("key not found")) { throw new KeyNotFoundException(); @@ -296,7 +362,7 @@ public synchronized Entry getAtRevision(byte[] key, long rev) throws KeyNotFound .build(); try { - return Entry.valueOf(this.stub.get(req)); + return Entry.valueOf(blockingStub.get(req)); } catch (StatusRuntimeException e) { if (e.getMessage().contains("key not found")) { throw new KeyNotFoundException(); @@ -314,7 +380,7 @@ public synchronized List getAll(List keys) { } final ImmudbProto.KeyListRequest req = ImmudbProto.KeyListRequest.newBuilder().addAllKeys(keysBS).build(); - final ImmudbProto.Entries entries = this.stub.getAll(req); + final ImmudbProto.Entries entries = blockingStub.getAll(req); final List result = new ArrayList<>(entries.getEntriesCount()); @@ -338,7 +404,7 @@ public Entry verifiedGetAtTx(String key, long tx) throws KeyNotFoundException, V } public synchronized Entry verifiedGetAtTx(byte[] key, long tx) throws KeyNotFoundException, VerificationException { - final ImmuState state = this.state(); + final ImmuState state = state(); final ImmudbProto.KeyRequest keyReq = ImmudbProto.KeyRequest.newBuilder() .setKey(Utils.toByteString(key)) @@ -355,7 +421,7 @@ public Entry verifiedGetSinceTx(String key, long tx) throws KeyNotFoundException public synchronized Entry verifiedGetSinceTx(byte[] key, long tx) throws KeyNotFoundException, VerificationException { - final ImmuState state = this.state(); + final ImmuState state = state(); final ImmudbProto.KeyRequest keyReq = ImmudbProto.KeyRequest.newBuilder() .setKey(Utils.toByteString(key)) @@ -372,7 +438,7 @@ public Entry verifiedGetAtRevision(String key, long rev) throws KeyNotFoundExcep public synchronized Entry verifiedGetAtRevision(byte[] key, long rev) throws KeyNotFoundException, VerificationException { - final ImmuState state = this.state(); + final ImmuState state = state(); final ImmudbProto.KeyRequest keyReq = ImmudbProto.KeyRequest.newBuilder() .setKey(Utils.toByteString(key)) @@ -392,7 +458,7 @@ private Entry verifiedGet(ImmudbProto.KeyRequest keyReq, ImmuState state) final ImmudbProto.VerifiableEntry vEntry; try { - vEntry = this.stub.verifiableGet(vGetReq); + vEntry = blockingStub.verifiableGet(vGetReq); } catch (StatusRuntimeException e) { if (e.getMessage().contains("key not found")) { throw new KeyNotFoundException(); @@ -464,7 +530,7 @@ private Entry verifiedGet(ImmudbProto.KeyRequest keyReq, ImmuState state) } final ImmuState newState = new ImmuState( - this.session.getDatabase(), + session.getDatabase(), targetId, targetAlh, vEntry.getVerifiableTx().getSignature().toByteArray()); @@ -473,7 +539,7 @@ private Entry verifiedGet(ImmudbProto.KeyRequest keyReq, ImmuState state) throw new VerificationException("State signature verification failed"); } - this.stateHolder.setState(newState); + stateHolder.setState(newState); return Entry.valueOf(vEntry.getEntry()); } @@ -492,7 +558,7 @@ public synchronized TxHeader delete(byte[] key) throws KeyNotFoundException { .addKeys(Utils.toByteString(key)) .build(); - return TxHeader.valueOf(this.stub.delete(req)); + return TxHeader.valueOf(blockingStub.delete(req)); } catch (StatusRuntimeException e) { if (e.getMessage().contains("key not found")) { throw new KeyNotFoundException(); @@ -506,18 +572,18 @@ public synchronized TxHeader delete(byte[] key) throws KeyNotFoundException { // ========== HISTORY ========== // - public List history(String key, int limit, long offset, boolean desc) throws KeyNotFoundException { - return history(Utils.toByteArray(key), limit, offset, desc); + public List historyAll(String key, long offset, boolean desc, int limit) throws KeyNotFoundException { + return historyAll(Utils.toByteArray(key), offset, desc, limit); } - public synchronized List history(byte[] key, int limit, long offset, boolean desc) + public synchronized List historyAll(byte[] key, long offset, boolean desc, int limit) throws KeyNotFoundException { try { - ImmudbProto.Entries entries = this.stub.history(ImmudbProto.HistoryRequest.newBuilder() + ImmudbProto.Entries entries = blockingStub.history(ImmudbProto.HistoryRequest.newBuilder() .setKey(Utils.toByteString(key)) - .setLimit(limit) - .setOffset(offset) .setDesc(desc) + .setOffset(offset) + .setLimit(limit) .build()); return buildList(entries); @@ -534,52 +600,44 @@ public synchronized List history(byte[] key, int limit, long offset, bool // ========== SCAN ========== // - public List scan(String prefix) { - return scan(Utils.toByteArray(prefix)); - } - - public List scan(byte[] prefix) { - return scan(prefix, 0, false); + public List scanAll(String prefix) { + return scanAll(Utils.toByteArray(prefix)); } - public List scan(String prefix, long limit, boolean desc) { - return scan(Utils.toByteArray(prefix), limit, desc); + public List scanAll(byte[] prefix) { + return scanAll(prefix, false, 0); } - public List scan(byte[] prefix, long limit, boolean desc) { - return scan(prefix, null, limit, desc); + public List scanAll(String prefix, boolean desc, long limit) { + return scanAll(Utils.toByteArray(prefix), null, desc, limit); } - public List scan(String prefix, String seekKey, long limit, boolean desc) { - return scan(Utils.toByteArray(prefix), Utils.toByteArray(seekKey), limit, desc); + public List scanAll(byte[] prefix, boolean desc, long limit) { + return scanAll(prefix, null, desc, limit); } - public List scan(String prefix, String seekKey, String endKey, long limit, boolean desc) { - return scan(Utils.toByteArray(prefix), Utils.toByteArray(seekKey), Utils.toByteArray(endKey), limit, desc); + public List scanAll(byte[] prefix, byte[] seekKey, boolean desc, long limit) { + return scanAll(prefix, seekKey, null, desc, limit); } - public List scan(byte[] prefix, byte[] seekKey, long limit, boolean desc) { - return scan(prefix, seekKey, null, limit, desc); + public List scanAll(byte[] prefix, byte[] seekKey, byte[] endKey, boolean desc, long limit) { + return scanAll(prefix, seekKey, endKey, false, false, desc, limit); } - public List scan(byte[] prefix, byte[] seekKey, byte[] endKey, long limit, boolean desc) { - return scan(prefix, seekKey, endKey, false, false, limit, desc); - } - - public synchronized List scan(byte[] prefix, byte[] seekKey, byte[] endKey, boolean inclusiveSeek, + public synchronized List scanAll(byte[] prefix, byte[] seekKey, byte[] endKey, boolean inclusiveSeek, boolean inclusiveEnd, - long limit, boolean desc) { + boolean desc, long limit) { final ImmudbProto.ScanRequest req = ScanRequest.newBuilder() .setPrefix(Utils.toByteString(prefix)) .setSeekKey(Utils.toByteString(seekKey)) .setEndKey(Utils.toByteString(endKey)) .setInclusiveSeek(inclusiveSeek) .setInclusiveEnd(inclusiveEnd) - .setLimit(limit) .setDesc(desc) + .setLimit(limit) .build(); - final ImmudbProto.Entries entries = this.stub.scan(req); + final ImmudbProto.Entries entries = blockingStub.scan(req); return buildList(entries); } @@ -598,7 +656,7 @@ public synchronized TxHeader set(byte[] key, byte[] value) throws CorruptedDataE .build(); final ImmudbProto.SetRequest req = ImmudbProto.SetRequest.newBuilder().addKVs(kv).build(); - final ImmudbProto.TxHeader txHdr = this.stub.set(req); + final ImmudbProto.TxHeader txHdr = blockingStub.set(req); if (txHdr.getNentries() != 1) { throw new CorruptedDataException(); @@ -619,7 +677,7 @@ public synchronized TxHeader setAll(List kvList) throws CorruptedDataExc reqBuilder.addKVs(kvBuilder.build()); } - final ImmudbProto.TxHeader txHdr = this.stub.set(reqBuilder.build()); + final ImmudbProto.TxHeader txHdr = blockingStub.set(reqBuilder.build()); if (txHdr.getNentries() != kvList.size()) { throw new CorruptedDataException(); @@ -649,7 +707,7 @@ public synchronized TxHeader setReference(byte[] key, byte[] referencedKey, long .setBoundRef(atTx > 0) .build(); - final ImmudbProto.TxHeader txHdr = this.stub.setReference(req); + final ImmudbProto.TxHeader txHdr = blockingStub.setReference(req); if (txHdr.getNentries() != 1) { throw new CorruptedDataException(); @@ -663,7 +721,7 @@ public TxHeader verifiedSet(String key, byte[] value) throws VerificationExcepti } public synchronized TxHeader verifiedSet(byte[] key, byte[] value) throws VerificationException { - final ImmuState state = this.state(); + final ImmuState state = state(); final ImmudbProto.KeyValue kv = ImmudbProto.KeyValue.newBuilder() .setKey(Utils.toByteString(key)) @@ -675,7 +733,7 @@ public synchronized TxHeader verifiedSet(byte[] key, byte[] value) throws Verifi .setProveSinceTx(state.getTxId()) .build(); - final ImmudbProto.VerifiableTx vtx = this.stub.verifiableSet(vSetReq); + final ImmudbProto.VerifiableTx vtx = blockingStub.verifiableSet(vSetReq); final int ne = vtx.getTx().getHeader().getNentries(); @@ -711,7 +769,7 @@ public synchronized TxHeader verifiedSet(byte[] key, byte[] value) throws Verifi throw new VerificationException("State signature verification failed"); } - this.stateHolder.setState(newState); + stateHolder.setState(newState); return TxHeader.valueOf(vtx.getTx().getHeader()); } @@ -723,7 +781,7 @@ public TxHeader verifiedSetReference(byte[] key, byte[] referencedKey) throws Ve public synchronized TxHeader verifiedSetReference(byte[] key, byte[] referencedKey, long atTx) throws VerificationException { - final ImmuState state = this.state(); + final ImmuState state = state(); final ImmudbProto.ReferenceRequest refReq = ImmudbProto.ReferenceRequest.newBuilder() .setKey(Utils.toByteString(key)) @@ -737,7 +795,7 @@ public synchronized TxHeader verifiedSetReference(byte[] key, byte[] referencedK .setProveSinceTx(state.getTxId()) .build(); - final ImmudbProto.VerifiableTx vtx = this.stub.verifiableSetReference(vRefReq); + final ImmudbProto.VerifiableTx vtx = blockingStub.verifiableSetReference(vRefReq); final int vtxNentries = vtx.getTx().getHeader().getNentries(); if (vtxNentries != 1) { @@ -779,7 +837,7 @@ public synchronized TxHeader verifiedSetReference(byte[] key, byte[] referencedK throw new VerificationException("State signature verification failed"); } - this.stateHolder.setState(newState); + stateHolder.setState(newState); return TxHeader.valueOf(vtx.getTx().getHeader()); } @@ -819,7 +877,7 @@ public TxHeader zAdd(byte[] set, byte[] key, double score) throws CorruptedDataE } public synchronized TxHeader zAdd(byte[] set, byte[] key, long atTx, double score) throws CorruptedDataException { - final ImmudbProto.TxHeader txHdr = this.stub.zAdd( + final ImmudbProto.TxHeader txHdr = blockingStub.zAdd( ImmudbProto.ZAddRequest.newBuilder() .setSet(Utils.toByteString(set)) .setKey(Utils.toByteString(key)) @@ -850,7 +908,7 @@ public TxHeader verifiedZAdd(String set, String key, long atTx, double score) th public synchronized TxHeader verifiedZAdd(byte[] set, byte[] key, long atTx, double score) throws VerificationException { - final ImmuState state = this.state(); + final ImmuState state = state(); final ImmudbProto.ZAddRequest zAddReq = ImmudbProto.ZAddRequest.newBuilder() .setSet(Utils.toByteString(set)) @@ -865,7 +923,7 @@ public synchronized TxHeader verifiedZAdd(byte[] set, byte[] key, long atTx, dou .setProveSinceTx(state.getTxId()) .build(); - final ImmudbProto.VerifiableTx vtx = this.stub.verifiableZAdd(vZAddReq); + final ImmudbProto.VerifiableTx vtx = blockingStub.verifiableZAdd(vZAddReq); if (vtx.getTx().getHeader().getNentries() != 1) { throw new VerificationException("Data is corrupted."); @@ -904,35 +962,61 @@ public synchronized TxHeader verifiedZAdd(byte[] set, byte[] key, long atTx, dou throw new VerificationException("State signature verification failed"); } - this.stateHolder.setState(newState); + stateHolder.setState(newState); return TxHeader.valueOf(vtx.getTx().getHeader()); } - public List zScan(String set, long limit, boolean reverse) { - return zScan(Utils.toByteArray(set), limit, reverse); + public List zScanAll(String set) { + return zScanAll(set, false, 0); } - public synchronized List zScan(byte[] set, long limit, boolean reverse) { - final ImmudbProto.ZScanRequest req = ImmudbProto.ZScanRequest - .newBuilder() - .setSet(Utils.toByteString(set)) - .setLimit(limit) + public List zScanAll(String set, boolean reverse, long limit) { + return pzScanAll(Utils.toByteArray(set), null, null, null, null, 0, false, reverse, limit); + } + + public List zScanAll(byte[] set, double minScore, double maxScore, boolean reverse, long limit) { + return pzScanAll(set, minScore, maxScore, null, null, 0, false, false, 0); + } + + public List zScanAll(byte[] set, double minScore, double maxScore, double seekScore, byte[] seekKey, + long seekAtTx, boolean inclusiveSeek, boolean reverse, long limit) { + return pzScanAll(set, minScore, maxScore, seekScore, seekKey, seekAtTx, inclusiveSeek, reverse, limit); + } + + private synchronized List pzScanAll(byte[] set, Double minScore, Double maxScore, Double seekScore, + byte[] seekKey, + long seekAtTx, boolean inclusiveSeek, boolean reverse, long limit) { + + final ImmudbProto.ZScanRequest.Builder reqBuilder = ImmudbProto.ZScanRequest.newBuilder(); + + reqBuilder.setSet(Utils.toByteString(set)) + .setSeekKey(Utils.toByteString(seekKey)) + .setSeekAtTx(seekAtTx) + .setInclusiveSeek(inclusiveSeek) .setDesc(reverse) - .build(); + .setLimit(limit); + + if (seekScore != null) { + reqBuilder.setSeekScore(seekScore); + } - final ImmudbProto.ZEntries zEntries = this.stub.zScan(req); + if (minScore != null) { + reqBuilder.setMinScore(Score.newBuilder().setScore(minScore).build()); + } + + if (maxScore != null) { + reqBuilder.setMaxScore(Score.newBuilder().setScore(maxScore).build()); + } + + final ImmudbProto.ZEntries zEntries = blockingStub.zScan(reqBuilder.build()); return buildList(zEntries); } - // - // ========== TX ========== - // - public synchronized Tx txById(long txId) throws TxNotFoundException, NoSuchAlgorithmException { try { - final ImmudbProto.Tx tx = this.stub.txById(ImmudbProto.TxRequest.newBuilder().setTx(txId).build()); + final ImmudbProto.Tx tx = blockingStub.txById(ImmudbProto.TxRequest.newBuilder().setTx(txId).build()); return Tx.valueOf(tx); } catch (StatusRuntimeException e) { if (e.getMessage().contains("tx not found")) { @@ -944,7 +1028,7 @@ public synchronized Tx txById(long txId) throws TxNotFoundException, NoSuchAlgor } public synchronized Tx verifiedTxById(long txId) throws TxNotFoundException, VerificationException { - final ImmuState state = this.state(); + final ImmuState state = state(); final ImmudbProto.VerifiableTxRequest vTxReq = ImmudbProto.VerifiableTxRequest.newBuilder() .setTx(txId) @@ -954,7 +1038,7 @@ public synchronized Tx verifiedTxById(long txId) throws TxNotFoundException, Ver final ImmudbProto.VerifiableTx vtx; try { - vtx = this.stub.verifiableTxById(vTxReq); + vtx = blockingStub.verifiableTxById(vTxReq); } catch (StatusRuntimeException e) { if (e.getMessage().contains("tx not found")) { throw new TxNotFoundException(); @@ -1012,23 +1096,360 @@ public synchronized Tx verifiedTxById(long txId) throws TxNotFoundException, Ver return tx; } - public synchronized List txScan(long initialTxId) { + public synchronized List txScanAll(long initialTxId) { final ImmudbProto.TxScanRequest req = ImmudbProto.TxScanRequest.newBuilder().setInitialTx(initialTxId).build(); - final ImmudbProto.TxList txList = this.stub.txScan(req); + final ImmudbProto.TxList txList = blockingStub.txScan(req); + return buildList(txList); } - public synchronized List txScan(long initialTxId, int limit, boolean desc) { + public synchronized List txScanAll(long initialTxId, int limit, boolean desc) { final ImmudbProto.TxScanRequest req = ImmudbProto.TxScanRequest .newBuilder() .setInitialTx(initialTxId) - .setLimit(limit) .setDesc(desc) + .setLimit(limit) .build(); - final ImmudbProto.TxList txList = this.stub.txScan(req); + + final ImmudbProto.TxList txList = blockingStub.txScan(req); return buildList(txList); } + // + // ========== STREAMS ========== + // + + private StreamObserver txHeaderStreamObserver(LatchHolder latchHolder) { + return new StreamObserver() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable cause) { + throw new RuntimeException(cause); + } + + @Override + public void onNext(ImmudbProto.TxHeader hdr) { + latchHolder.doneWith(hdr); + } + }; + } + + private void chunkIt(byte[] bs, StreamObserver streamObserver) { + final ByteBuffer buf = ByteBuffer.allocate(chunkSize).order(ByteOrder.BIG_ENDIAN); + + buf.putLong(bs.length); + + int i = 0; + + while (i < bs.length) { + final int chunkContentLen = Math.min(bs.length, buf.remaining()); + + buf.put(bs, i, chunkContentLen); + + buf.flip(); + + byte[] chunkContent = new byte[buf.limit()]; + buf.get(chunkContent); + + final Chunk chunk = Chunk.newBuilder().setContent(Utils.toByteString(chunkContent)).build(); + + streamObserver.onNext(chunk); + + buf.clear(); + + i += chunkContentLen; + } + } + + private byte[] dechunkIt(Iterator chunks) { + final Chunk firstChunk = chunks.next(); + final byte[] firstChunkContent = firstChunk.getContent().toByteArray(); + + if (firstChunkContent.length < Long.BYTES) { + throw new RuntimeException("invalid chunk"); + } + + final ByteBuffer b = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.BIG_ENDIAN); + b.put(firstChunkContent, 0, Long.BYTES); + + int payloadSize = (int) b.getLong(0); + + final ByteBuffer buf = ByteBuffer.allocate(payloadSize); + buf.put(firstChunkContent, Long.BYTES, firstChunkContent.length - Long.BYTES); + + while (buf.position() < payloadSize) { + Chunk chunk = chunks.next(); + buf.put(chunk.getContent().toByteArray()); + } + + return buf.array(); + } + + private Iterator entryIterator(Iterator chunks) { + return new Iterator() { + + @Override + public boolean hasNext() { + try { + return chunks.hasNext(); + } catch (StatusRuntimeException e) { + if (e.getMessage().contains("key not found")) { + return false; + } + + throw e; + } + } + + @Override + public Entry next() { + return new Entry(dechunkIt(chunks), dechunkIt(chunks)); + } + + }; + } + + private Iterator zentryIterator(Iterator chunks) { + return new Iterator() { + + @Override + public boolean hasNext() { + try { + return chunks.hasNext(); + } catch (StatusRuntimeException e) { + if (e.getMessage().contains("key not found")) { + return false; + } + + throw e; + } + } + + @Override + public ZEntry next() { + final byte[] set = dechunkIt(chunks); + final byte[] key = dechunkIt(chunks); + + final ByteBuffer b = ByteBuffer.allocate(Double.BYTES + Long.BYTES).order(ByteOrder.BIG_ENDIAN); + b.put(dechunkIt(chunks)); // score + b.put(dechunkIt(chunks)); // atTx + + double score = b.getDouble(0); + long atTx = b.getLong(Double.BYTES); + + final byte[] value = dechunkIt(chunks); + + final Entry entry = new Entry(key, value); + + return new ZEntry(set, key, score, atTx, entry); + } + + }; + } + + // + // ========== STREAM SET ========== + // + + public TxHeader streamSet(String key, byte[] value) throws InterruptedException, CorruptedDataException { + return streamSet(Utils.toByteArray(key), value); + } + + public synchronized TxHeader streamSet(byte[] key, byte[] value) + throws InterruptedException, CorruptedDataException { + final LatchHolder latchHolder = new LatchHolder<>(); + final StreamObserver streamObserver = nonBlockingStub.streamSet(txHeaderStreamObserver(latchHolder)); + + chunkIt(key, streamObserver); + chunkIt(value, streamObserver); + + streamObserver.onCompleted(); + + final ImmudbProto.TxHeader txHdr = latchHolder.awaitValue(); + + if (txHdr.getNentries() != 1) { + throw new CorruptedDataException(); + } + + return TxHeader.valueOf(txHdr); + } + + public synchronized TxHeader streamSetAll(List kvList) throws InterruptedException, CorruptedDataException { + final LatchHolder latchHolder = new LatchHolder<>(); + final StreamObserver streamObserver = nonBlockingStub.streamSet(txHeaderStreamObserver(latchHolder)); + + for (KVPair kv : kvList) { + chunkIt(kv.getKey(), streamObserver); + chunkIt(kv.getValue(), streamObserver); + } + + streamObserver.onCompleted(); + + final ImmudbProto.TxHeader txHdr = latchHolder.awaitValue(); + + if (txHdr.getNentries() != kvList.size()) { + throw new CorruptedDataException(); + } + + return TxHeader.valueOf(txHdr); + } + + // + // ========== STREAM GET ========== + // + + public Entry streamGet(String key) throws KeyNotFoundException { + return streamGet(Utils.toByteArray(key)); + } + + public synchronized Entry streamGet(byte[] key) throws KeyNotFoundException { + final ImmudbProto.KeyRequest req = ImmudbProto.KeyRequest.newBuilder() + .setKey(Utils.toByteString(key)) + .build(); + + try { + final Iterator chunks = blockingStub.streamGet(req); + return new Entry(dechunkIt(chunks), dechunkIt(chunks)); + } catch (StatusRuntimeException e) { + if (e.getMessage().contains("key not found")) { + throw new KeyNotFoundException(); + } + + throw e; + } + } + + // + // ========== STREAM SCAN ========== + // + + public Iterator scan(String prefix) { + return scan(Utils.toByteArray(prefix)); + } + + public Iterator scan(byte[] prefix) { + return scan(prefix, false, 0); + } + + public Iterator scan(String prefix, boolean desc, long limit) { + return scan(Utils.toByteArray(prefix), desc, limit); + } + + public Iterator scan(byte[] prefix, boolean desc, long limit) { + return scan(prefix, null, desc, limit); + } + + public Iterator scan(byte[] prefix, byte[] seekKey, boolean desc, long limit) { + return scan(prefix, seekKey, null, desc, limit); + } + + public Iterator scan(byte[] prefix, byte[] seekKey, byte[] endKey, boolean desc, long limit) { + return scan(prefix, seekKey, endKey, false, false, desc, limit); + } + + public synchronized Iterator scan(byte[] prefix, byte[] seekKey, byte[] endKey, boolean inclusiveSeek, + boolean inclusiveEnd, + boolean desc, long limit) { + + final ImmudbProto.ScanRequest req = ScanRequest.newBuilder() + .setPrefix(Utils.toByteString(prefix)) + .setSeekKey(Utils.toByteString(seekKey)) + .setEndKey(Utils.toByteString(endKey)) + .setInclusiveSeek(inclusiveSeek) + .setInclusiveEnd(inclusiveEnd) + .setDesc(desc) + .setLimit(limit) + .build(); + + final Iterator chunks = blockingStub.streamScan(req); + + return entryIterator(chunks); + } + + // + // ========== STREAM ZSCAN ========== + // + + public Iterator zScan(String set) { + return zScan(set, false, 0); + } + + public Iterator zScan(String set, boolean reverse, long limit) { + return pzScan(Utils.toByteArray(set), null, null, null, null, 0, false, reverse, limit); + } + + public Iterator zScan(byte[] set, double minScore, double maxScore, boolean reverse, long limit) { + return pzScan(set, minScore, maxScore, null, null, 0, false, false, 0); + } + + public Iterator zScan(byte[] set, double minScore, double maxScore, double seekScore, byte[] seekKey, + long seekAtTx, boolean inclusiveSeek, boolean reverse, long limit) { + return pzScan(set, minScore, maxScore, seekScore, seekKey, seekAtTx, inclusiveSeek, reverse, limit); + } + + private synchronized Iterator pzScan(byte[] set, Double minScore, Double maxScore, Double seekScore, + byte[] seekKey, + long seekAtTx, boolean inclusiveSeek, boolean reverse, long limit) { + + final ImmudbProto.ZScanRequest.Builder reqBuilder = ImmudbProto.ZScanRequest.newBuilder(); + + reqBuilder.setSet(Utils.toByteString(set)) + .setSeekKey(Utils.toByteString(seekKey)) + .setSeekAtTx(seekAtTx) + .setInclusiveSeek(inclusiveSeek) + .setDesc(reverse) + .setLimit(limit); + + if (seekScore != null) { + reqBuilder.setSeekScore(seekScore); + } + + if (minScore != null) { + reqBuilder.setMinScore(Score.newBuilder().setScore(minScore).build()); + } + + if (maxScore != null) { + reqBuilder.setMaxScore(Score.newBuilder().setScore(maxScore).build()); + } + + final Iterator chunks = blockingStub.streamZScan(reqBuilder.build()); + + return zentryIterator(chunks); + } + + // + // ========== STREAM HISTORY ========== + // + + public Iterator history(String key, long offset, boolean desc, int limit) throws KeyNotFoundException { + return history(Utils.toByteArray(key), offset, desc, limit); + } + + public synchronized Iterator history(byte[] key, long offset, boolean desc, int limit) + throws KeyNotFoundException { + try { + ImmudbProto.HistoryRequest req = ImmudbProto.HistoryRequest.newBuilder() + .setKey(Utils.toByteString(key)) + .setDesc(desc) + .setOffset(offset) + .setLimit(limit) + .build(); + + final Iterator chunks = blockingStub.streamHistory(req); + + return entryIterator(chunks); + } catch (StatusRuntimeException e) { + if (e.getMessage().contains("key not found")) { + throw new KeyNotFoundException(); + } + + throw e; + } + } + // // ========== HEALTH ========== // @@ -1042,7 +1463,7 @@ public boolean isShutdown() { } public synchronized boolean healthCheck() { - return this.stub.serverInfo(ImmudbProto.ServerInfoRequest.getDefaultInstance()) != null; + return blockingStub.serverInfo(ImmudbProto.ServerInfoRequest.getDefaultInstance()) != null; } // @@ -1050,7 +1471,7 @@ public synchronized boolean healthCheck() { // public synchronized List listUsers() { - final ImmudbProto.UserList userList = this.stub.listUsers(Empty.getDefaultInstance()); + final ImmudbProto.UserList userList = blockingStub.listUsers(Empty.getDefaultInstance()); return userList.getUsersList() .stream() @@ -1080,7 +1501,17 @@ public synchronized void createUser(String user, String password, Permission per .build(); // noinspection ResultOfMethodCallIgnored - this.stub.createUser(createUserRequest); + blockingStub.createUser(createUserRequest); + } + + public synchronized void activateUser(String user, boolean active) { + final ImmudbProto.SetActiveUserRequest req = ImmudbProto.SetActiveUserRequest.newBuilder() + .setUsername(user) + .setActive(active) + .build(); + + // noinspection ResultOfMethodCallIgnored + blockingStub.setActiveUser(req); } public synchronized void changePassword(String user, String oldPassword, String newPassword) { @@ -1091,24 +1522,48 @@ public synchronized void changePassword(String user, String oldPassword, String .build(); // noinspection ResultOfMethodCallIgnored - this.stub.changePassword(changePasswordRequest); + blockingStub.changePassword(changePasswordRequest); + } + + public synchronized void grantPermission(String user, String database, Permission permissions) { + final ImmudbProto.ChangePermissionRequest req = ImmudbProto.ChangePermissionRequest.newBuilder() + .setUsername(user) + .setAction(ImmudbProto.PermissionAction.GRANT) + .setDatabase(database) + .setPermission(permissions.permissionCode) + .build(); + + // noinspection ResultOfMethodCallIgnored + blockingStub.changePermission(req); + } + + public synchronized void revokePermission(String user, String database, Permission permissions) { + final ImmudbProto.ChangePermissionRequest req = ImmudbProto.ChangePermissionRequest.newBuilder() + .setUsername(user) + .setAction(ImmudbProto.PermissionAction.REVOKE) + .setDatabase(database) + .setPermission(permissions.permissionCode) + .build(); + + // noinspection ResultOfMethodCallIgnored + blockingStub.changePermission(req); } // // ========== INDEX MGMT ========== // - public synchronized void flushIndex(float cleanupPercentage, boolean synced) { + public synchronized void flushIndex(float cleanupPercentage) { ImmudbProto.FlushIndexRequest req = ImmudbProto.FlushIndexRequest.newBuilder() .setCleanupPercentage(cleanupPercentage) - .setSynced(synced) + .setSynced(true) .build(); - this.stub.flushIndex(req); + blockingStub.flushIndex(req); } public synchronized void compactIndex() { - this.stub.compactIndex(Empty.getDefaultInstance()); + blockingStub.compactIndex(Empty.getDefaultInstance()); } // @@ -1155,13 +1610,16 @@ public static class Builder { private long keepAlivePeriod; + private int chunkSize; + private ImmuStateHolder stateHolder; private Builder() { - this.serverUrl = "localhost"; - this.serverPort = 3322; - this.stateHolder = new SerializableImmuStateHolder(); - this.keepAlivePeriod = 60 * 1000; // 1 minute + serverUrl = "localhost"; + serverPort = 3322; + stateHolder = new SerializableImmuStateHolder(); + keepAlivePeriod = 60 * 1000; // 1 minute + chunkSize = 64 * 1024; // 64 * 1024 64 KiB } public ImmuClient build() { @@ -1169,7 +1627,7 @@ public ImmuClient build() { } public String getServerUrl() { - return this.serverUrl; + return serverUrl; } public Builder withServerUrl(String serverUrl) { @@ -1195,7 +1653,7 @@ public PublicKey getServerSigningKey() { * be used for verifying the state signature received from the server. */ public Builder withServerSigningKey(String publicKeyFilename) throws Exception { - this.serverSigningKey = CryptoUtils.getDERPublicKey(publicKeyFilename); + serverSigningKey = CryptoUtils.getDERPublicKey(publicKeyFilename); return this; } @@ -1217,6 +1675,18 @@ public Builder withStateHolder(ImmuStateHolder stateHolder) { return this; } + public Builder withChunkSize(int chunkSize) { + if (chunkSize < Long.BYTES) { + throw new RuntimeException("invalid chunk size"); + } + + this.chunkSize = chunkSize; + return this; + } + + public int getChunkSize() { + return chunkSize; + } } } diff --git a/src/main/java/io/codenotary/immudb4j/SerializableImmuStateHolder.java b/src/main/java/io/codenotary/immudb4j/SerializableImmuStateHolder.java index 772318a..1489e6f 100644 --- a/src/main/java/io/codenotary/immudb4j/SerializableImmuStateHolder.java +++ b/src/main/java/io/codenotary/immudb4j/SerializableImmuStateHolder.java @@ -18,7 +18,11 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.Reader; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; import java.util.HashMap; diff --git a/src/main/java/io/codenotary/immudb4j/TxHeader.java b/src/main/java/io/codenotary/immudb4j/TxHeader.java index f7cecf3..b982a34 100644 --- a/src/main/java/io/codenotary/immudb4j/TxHeader.java +++ b/src/main/java/io/codenotary/immudb4j/TxHeader.java @@ -20,7 +20,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.Base64; public class TxHeader { private final int version; diff --git a/src/main/java/io/codenotary/immudb4j/Utils.java b/src/main/java/io/codenotary/immudb4j/Utils.java index 2366b9d..2a74d37 100644 --- a/src/main/java/io/codenotary/immudb4j/Utils.java +++ b/src/main/java/io/codenotary/immudb4j/Utils.java @@ -119,5 +119,4 @@ public static void copy(byte[] src, int srcPos, int length, byte[] dest) { public static void copy(byte[] src, int srcPos, int length, byte[] dest, int destPos) { System.arraycopy(src, srcPos, dest, destPos, length); } - } diff --git a/src/main/java/io/codenotary/immudb4j/ZEntry.java b/src/main/java/io/codenotary/immudb4j/ZEntry.java index c13303e..6012d74 100644 --- a/src/main/java/io/codenotary/immudb4j/ZEntry.java +++ b/src/main/java/io/codenotary/immudb4j/ZEntry.java @@ -39,6 +39,14 @@ public class ZEntry { private ZEntry() {} + public ZEntry(byte[] set, byte[] key, double score, long atTx, Entry entry) { + this.set = set; + this.key = key; + this.score = score; + this.atTx = atTx; + this.entry = entry; + } + public static ZEntry valueOf(ImmudbProto.ZEntry e) { final ZEntry entry = new ZEntry(); @@ -98,5 +106,5 @@ public byte[] digestFor(int version) { return kv.digestFor(version); } - + } diff --git a/src/main/java/io/codenotary/immudb4j/basics/LatchHolder.java b/src/main/java/io/codenotary/immudb4j/basics/LatchHolder.java new file mode 100644 index 0000000..37ab10e --- /dev/null +++ b/src/main/java/io/codenotary/immudb4j/basics/LatchHolder.java @@ -0,0 +1,38 @@ +/* +Copyright 2022 CodeNotary, Inc. All rights reserved. + +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 io.codenotary.immudb4j.basics; + +import java.util.concurrent.CountDownLatch; + +public class LatchHolder { + + private T value; + private CountDownLatch doneLatch; + + public LatchHolder() { + doneLatch = new CountDownLatch(1); + } + + public T awaitValue() throws InterruptedException { + doneLatch.await(); + return value; + } + + public void doneWith(T value) { + this.value = value; + doneLatch.countDown(); + } +} \ No newline at end of file diff --git a/src/test/java/io/codenotary/immudb4j/BasicImmuClientTest.java b/src/test/java/io/codenotary/immudb4j/BasicImmuClientTest.java index eaca0a5..2b45252 100644 --- a/src/test/java/io/codenotary/immudb4j/BasicImmuClientTest.java +++ b/src/test/java/io/codenotary/immudb4j/BasicImmuClientTest.java @@ -17,7 +17,10 @@ import com.google.common.base.Charsets; import io.codenotary.immudb4j.exceptions.CorruptedDataException; +import io.codenotary.immudb4j.exceptions.KeyNotFoundException; import io.codenotary.immudb4j.exceptions.VerificationException; +import io.grpc.StatusRuntimeException; + import org.testng.Assert; import org.testng.annotations.Test; @@ -27,8 +30,8 @@ public class BasicImmuClientTest extends ImmuClientIntegrationTest { @Test(testName = "set, get") - public void t1() throws VerificationException, CorruptedDataException { - immuClient.openSession("immudb", "immudb", "defaultdb"); + public void t1() throws VerificationException, CorruptedDataException, InterruptedException { + immuClient.openSession("defaultdb", "immudb", "immudb"); byte[] v0 = new byte[] { 0, 1, 2, 3 }; byte[] v1 = new byte[] { 3, 2, 1, 0 }; @@ -63,12 +66,55 @@ public void t1() throws VerificationException, CorruptedDataException { Assert.assertNotNull(e); Assert.assertEquals(e.getValue(), v2); + Assert.assertEquals(v2, immuClient.getSinceTx("k2", ventry2.getRevision()).getValue()); + + immuClient.set("k0", v1); + + Assert.assertEquals(v0, immuClient.getAtRevision("k0", entry0.getRevision()).getValue()); + Assert.assertEquals(v1, immuClient.getAtRevision("k0", entry0.getRevision()+1).getValue()); + + Assert.assertEquals(v0, immuClient.verifiedGetAtRevision("k0", entry0.getRevision()).getValue()); + Assert.assertEquals(v1, immuClient.verifiedGetAtRevision("k0", entry0.getRevision()+1).getValue()); + + try { + immuClient.verifiedGet("non-existent-key"); + Assert.fail("Failed at verifiedGet."); + } catch (KeyNotFoundException _) { + } + + try { + immuClient.getSinceTx("non-existent-key", 1); + Assert.fail("Failed at getSinceTx."); + } catch (KeyNotFoundException _) { + } + + try { + immuClient.verifiedGetSinceTx("non-existent-key", 1); + Assert.fail("Failed at verifiedGetSinceTx."); + } catch (KeyNotFoundException _) { + } + + try { + immuClient.getAtRevision("k0", entry0.getRevision()+2); + Assert.fail("Failed at getSinceTx."); + } catch (StatusRuntimeException e1) { + Assert.assertTrue(e1.getMessage().contains("invalid key revision number")); + } + + try { + immuClient.verifiedGetAtRevision("k0", entry0.getRevision()+2); + Assert.fail("Failed at verifiedGetAtRevision."); + } catch (StatusRuntimeException e1) { + Assert.assertTrue(e1.getMessage().contains("invalid key revision number")); + } + + immuClient.closeSession(); } @Test(testName = "setAll, getAll") public void t2() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); List keys = new ArrayList<>(); keys.add("k0"); @@ -108,5 +154,4 @@ public void t2() { immuClient.closeSession(); } - } diff --git a/src/test/java/io/codenotary/immudb4j/BasicsTest.java b/src/test/java/io/codenotary/immudb4j/BasicsTest.java index e762002..d5ed5b9 100644 --- a/src/test/java/io/codenotary/immudb4j/BasicsTest.java +++ b/src/test/java/io/codenotary/immudb4j/BasicsTest.java @@ -15,6 +15,8 @@ */ package io.codenotary.immudb4j; +import io.codenotary.immudb4j.basics.LatchHolder; + // Note: This test is more for the sake of code coverage, as you may see. import io.codenotary.immudb4j.basics.Pair; @@ -58,4 +60,22 @@ public void t1() { Assert.assertNotEquals(triple, Triple.of("aDifferent", "", "")); } + @Test(testName = "LatchHolder test") + public void t2() throws InterruptedException { + final LatchHolder latchHolder = new LatchHolder<>(); + + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + + latchHolder.doneWith(true); + } + }).run(); + + Assert.assertTrue(latchHolder.awaitValue()); + } } diff --git a/src/test/java/io/codenotary/immudb4j/HealthCheckAndIndexCompactionTest.java b/src/test/java/io/codenotary/immudb4j/HealthCheckAndIndexCompactionTest.java new file mode 100644 index 0000000..9b57b5c --- /dev/null +++ b/src/test/java/io/codenotary/immudb4j/HealthCheckAndIndexCompactionTest.java @@ -0,0 +1,67 @@ +/* +Copyright 2022 CodeNotary, Inc. All rights reserved. + +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 io.codenotary.immudb4j; + +import io.grpc.StatusRuntimeException; + +import java.rmi.UnexpectedException; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class HealthCheckAndIndexCompactionTest extends ImmuClientIntegrationTest { + + @Test(testName = "openSession (with default credentials), healthCheck, logout") + public void t1() throws UnexpectedException { + try { + immuClient.openSession("defaultdb", "immudb", "immudb"); + } catch (Exception e) { + throw new UnexpectedException(e.getMessage()); + } + + Assert.assertTrue(immuClient.healthCheck()); + + immuClient.flushIndex(10.0f); + + immuClient.closeSession(); + } + + @Test(testName = "openSession (with wrong credentials)", expectedExceptions = StatusRuntimeException.class) + public void t2() { + immuClient.openSession("defaultdb", "immudb", "incorrect_password"); + } + + @Test(testName = "openSession with session already open", expectedExceptions = IllegalStateException.class) + public void t3() throws UnexpectedException { + try { + immuClient.openSession("defaultdb", "immudb", "immudb"); + } catch (Exception e) { + throw new UnexpectedException(e.getMessage()); + } + + try { + immuClient.openSession("defaultdb", "immudb", "immudb"); + } finally { + immuClient.closeSession(); + } + } + + @Test(testName = "openSession with no open session", expectedExceptions = IllegalStateException.class) + public void t4() { + immuClient.closeSession(); + } + +} diff --git a/src/test/java/io/codenotary/immudb4j/HistoryTest.java b/src/test/java/io/codenotary/immudb4j/HistoryTest.java index f0b0cce..b210cf4 100644 --- a/src/test/java/io/codenotary/immudb4j/HistoryTest.java +++ b/src/test/java/io/codenotary/immudb4j/HistoryTest.java @@ -21,13 +21,15 @@ import org.testng.annotations.Test; import java.nio.charset.StandardCharsets; +import java.util.Iterator; import java.util.List; +import java.util.NoSuchElementException; public class HistoryTest extends ImmuClientIntegrationTest { @Test(testName = "set, history", priority = 2) public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); byte[] value1 = {0, 1, 2, 3}; byte[] value2 = {4, 5, 6, 7}; @@ -43,7 +45,7 @@ public void t1() { Assert.fail("Failed at set.", e); } - List historyResponse1 = immuClient.history("history1", 10, 0, false); + List historyResponse1 = immuClient.historyAll("history1", 0, false, 2); Assert.assertEquals(historyResponse1.size(), 2); @@ -53,7 +55,7 @@ public void t1() { Assert.assertEquals(historyResponse1.get(1).getKey(), "history1".getBytes(StandardCharsets.UTF_8)); Assert.assertEquals(historyResponse1.get(1).getValue(), value2); - List historyResponse2 = immuClient.history("history2", 10, 0, false); + List historyResponse2 = immuClient.historyAll("history2", 0, false, 3); Assert.assertEquals(historyResponse2.size(), 3); @@ -66,17 +68,36 @@ public void t1() { Assert.assertEquals(historyResponse2.get(2).getKey(), "history2".getBytes(StandardCharsets.UTF_8)); Assert.assertEquals(historyResponse2.get(2).getValue(), value3); - historyResponse2 = immuClient.history("history2", 10, 2, false); + historyResponse2 = immuClient.historyAll("history2", 2, false, 1); Assert.assertNotNull(historyResponse2); Assert.assertEquals(historyResponse2.size(), 1); + Iterator entriesIt = immuClient.history("history2", 2, false, 1); + Assert.assertTrue(entriesIt.hasNext()); + + Entry entry = entriesIt.next(); + Assert.assertNotNull(entry); + + Assert.assertFalse(entriesIt.hasNext()); + + try { + entriesIt.next(); + Assert.fail("NoSuchElementException exception expected"); + } catch (NoSuchElementException e) { + // exception is expected here + } + try { - immuClient.history("nonExisting", 10, 0, false); + immuClient.historyAll("nonExisting", 0, false, 0); Assert.fail("key not found exception expected"); } catch (KeyNotFoundException e) { // exception is expected here } - + + Iterator entriesIt2 = immuClient.history("nonExisting", 0, false, 0); + + Assert.assertFalse(entriesIt2.hasNext()); + immuClient.closeSession(); } diff --git a/src/test/java/io/codenotary/immudb4j/ImmuClientIntegrationTest.java b/src/test/java/io/codenotary/immudb4j/ImmuClientIntegrationTest.java index 19e42bf..b7b0873 100644 --- a/src/test/java/io/codenotary/immudb4j/ImmuClientIntegrationTest.java +++ b/src/test/java/io/codenotary/immudb4j/ImmuClientIntegrationTest.java @@ -18,16 +18,22 @@ import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; public abstract class ImmuClientIntegrationTest { protected static ImmuClient immuClient; + protected static File statesDir; @BeforeClass public static void beforeClass() throws IOException { + statesDir = Files.createTempDirectory("immudb_states").toFile(); + statesDir.deleteOnExit(); + FileImmuStateHolder stateHolder = FileImmuStateHolder.newBuilder() - .withStatesFolder("immudb/states") + .withStatesFolder(statesDir.getAbsolutePath()) .build(); immuClient = ImmuClient.newBuilder() @@ -38,7 +44,7 @@ public static void beforeClass() throws IOException { } @AfterClass - public static void afterClass() { + public static void afterClass() throws InterruptedException { immuClient.shutdown(); } diff --git a/src/test/java/io/codenotary/immudb4j/ListDatabasesTest.java b/src/test/java/io/codenotary/immudb4j/ListDatabasesTest.java index fc19652..8a6bf72 100644 --- a/src/test/java/io/codenotary/immudb4j/ListDatabasesTest.java +++ b/src/test/java/io/codenotary/immudb4j/ListDatabasesTest.java @@ -21,9 +21,14 @@ public class ListDatabasesTest extends ImmuClientIntegrationTest { + @Test(testName = "databases without open session", expectedExceptions = IllegalStateException.class) + public void t1() { + immuClient.databases(); + } + @Test(testName = "databases") - public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + public void t2() { + immuClient.openSession("defaultdb", "immudb", "immudb"); List databases = immuClient.databases(); if (databases.size() > 0) { diff --git a/src/test/java/io/codenotary/immudb4j/ListUsersTest.java b/src/test/java/io/codenotary/immudb4j/ListUsersTest.java index 6e8f762..afe30c0 100644 --- a/src/test/java/io/codenotary/immudb4j/ListUsersTest.java +++ b/src/test/java/io/codenotary/immudb4j/ListUsersTest.java @@ -21,7 +21,7 @@ public class ListUsersTest extends ImmuClientIntegrationTest { @Test(testName = "listUsers") public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); immuClient.listUsers().forEach(user -> System.out.printf(">>> Got user %s", user)); diff --git a/src/test/java/io/codenotary/immudb4j/LoginAndHealthCheckAndCleanIndexTest.java b/src/test/java/io/codenotary/immudb4j/LoginAndHealthCheckAndCleanIndexTest.java deleted file mode 100644 index 4b46e30..0000000 --- a/src/test/java/io/codenotary/immudb4j/LoginAndHealthCheckAndCleanIndexTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2022 CodeNotary, Inc. All rights reserved. - -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 io.codenotary.immudb4j; - -import io.grpc.StatusRuntimeException; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class LoginAndHealthCheckAndCleanIndexTest extends ImmuClientIntegrationTest { - - @Test(testName = "openSession (with default credentials), healthCheck, logout") - public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); - - boolean isHealthy = immuClient.healthCheck(); - Assert.assertTrue(isHealthy); - - immuClient.flushIndex(10.0f, true); - - immuClient.closeSession(); - } - - @Test(testName = "openSession (with wrong credentials)", expectedExceptions = StatusRuntimeException.class) - public void t2() { - immuClient.openSession("immudb", "incorrect_password", "defaultdb"); - } - -} diff --git a/src/test/java/io/codenotary/immudb4j/MultidatabaseTest.java b/src/test/java/io/codenotary/immudb4j/MultidatabaseTest.java index 777e19e..8b9f1ef 100644 --- a/src/test/java/io/codenotary/immudb4j/MultidatabaseTest.java +++ b/src/test/java/io/codenotary/immudb4j/MultidatabaseTest.java @@ -17,6 +17,8 @@ import io.codenotary.immudb4j.exceptions.CorruptedDataException; import io.codenotary.immudb4j.exceptions.VerificationException; +import io.grpc.StatusRuntimeException; + import org.testng.Assert; import org.testng.annotations.Test; @@ -24,18 +26,38 @@ public class MultidatabaseTest extends ImmuClientIntegrationTest { + @Test(testName = "createDatabase without open session", expectedExceptions = IllegalStateException.class) + public void t1() { + immuClient.createDatabase("db1"); + } + + @Test(testName = "loadDatabase without open session", expectedExceptions = IllegalStateException.class) + public void t2() { + immuClient.loadDatabase("db1"); + } + + @Test(testName = "unloadDatabase without open session", expectedExceptions = IllegalStateException.class) + public void t3() { + immuClient.unloadDatabase("db1"); + } + + @Test(testName = "deleteDatabase without open session", expectedExceptions = IllegalStateException.class) + public void t4() { + immuClient.deleteDatabase("db1"); + } + @Test(testName = "Interacting with multiple databases (creating them, setting, and getting, listing)") - public void t1() throws VerificationException { - immuClient.openSession("immudb", "immudb", "defaultdb"); + public void t5() throws VerificationException { + immuClient.openSession("defaultdb", "immudb", "immudb"); - immuClient.createDatabase("db1"); - immuClient.createDatabase("db2"); + immuClient.createDatabase("db1", true); + immuClient.createDatabase("db2", true); immuClient.closeSession(); - immuClient.openSession("immudb", "immudb", "db1"); + immuClient.openSession("db1", "immudb", "immudb"); - byte[] v0 = new byte[]{0, 1, 2, 3}; + byte[] v0 = new byte[] { 0, 1, 2, 3 }; try { immuClient.set("k0", v0); } catch (CorruptedDataException e) { @@ -44,9 +66,9 @@ public void t1() throws VerificationException { immuClient.closeSession(); - immuClient.openSession("immudb", "immudb", "db2"); + immuClient.openSession("db2", "immudb", "immudb"); - byte[] v1 = new byte[]{3, 2, 1, 0}; + byte[] v1 = new byte[] { 3, 2, 1, 0 }; try { immuClient.set("k1", v1); } catch (CorruptedDataException e) { @@ -55,7 +77,7 @@ public void t1() throws VerificationException { immuClient.closeSession(); - immuClient.openSession("immudb", "immudb", "db1"); + immuClient.openSession("db1", "immudb", "immudb"); Entry entry1 = immuClient.get("k0"); Assert.assertNotNull(entry1); @@ -67,7 +89,7 @@ public void t1() throws VerificationException { immuClient.closeSession(); - immuClient.openSession("immudb", "immudb", "db2"); + immuClient.openSession("db2", "immudb", "immudb"); Entry entry2 = immuClient.get("k1"); Assert.assertEquals(entry2.getValue(), v1); @@ -85,4 +107,25 @@ public void t1() throws VerificationException { immuClient.closeSession(); } + @Test(testName = "create, unload and delete database") + public void t6() { + immuClient.openSession("defaultdb", "immudb", "immudb"); + + immuClient.createDatabase("manageddb"); + + immuClient.unloadDatabase("manageddb"); + + immuClient.deleteDatabase("manageddb"); + + /* + try { + immuClient.loadDatabase("manageddb"); + Assert.fail("exception expected"); + } catch (StatusRuntimeException e) { + Assert.assertTrue(e.getMessage().contains("database does not exist")); + } + */ + + immuClient.closeSession(); + } } diff --git a/src/test/java/io/codenotary/immudb4j/MultithreadTest.java b/src/test/java/io/codenotary/immudb4j/MultithreadTest.java index 95fbe19..412a1dc 100644 --- a/src/test/java/io/codenotary/immudb4j/MultithreadTest.java +++ b/src/test/java/io/codenotary/immudb4j/MultithreadTest.java @@ -29,10 +29,10 @@ public class MultithreadTest extends ImmuClientIntegrationTest { @Test(testName = "Multithread without key overlap") public void t1() throws InterruptedException, VerificationException { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); - final int threadCount = 10; - final int keyCount = 100; + final int threadCount = 5; + final int keyCount = 10; CountDownLatch latch = new CountDownLatch(threadCount); AtomicInteger succeeded = new AtomicInteger(0); @@ -75,10 +75,10 @@ public void t1() throws InterruptedException, VerificationException { @Test(testName = "Multithread with key overlap") public void t2() throws InterruptedException, VerificationException { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); - final int threadCount = 10; - final int keyCount = 100; + final int threadCount = 5; + final int keyCount = 10; CountDownLatch latch = new CountDownLatch(threadCount); AtomicInteger succeeded = new AtomicInteger(0); diff --git a/src/test/java/io/codenotary/immudb4j/ReferenceTest.java b/src/test/java/io/codenotary/immudb4j/ReferenceTest.java index 7bc4c9c..cd9b0b7 100644 --- a/src/test/java/io/codenotary/immudb4j/ReferenceTest.java +++ b/src/test/java/io/codenotary/immudb4j/ReferenceTest.java @@ -25,7 +25,7 @@ public class ReferenceTest extends ImmuClientIntegrationTest { @Test(testName = "set, setReference, setReferenceAt") public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); byte[] key = "testRef".getBytes(StandardCharsets.UTF_8); byte[] val = "abc".getBytes(StandardCharsets.UTF_8); diff --git a/src/test/java/io/codenotary/immudb4j/ScanTest.java b/src/test/java/io/codenotary/immudb4j/ScanTest.java index 3a66d8c..3fe658a 100644 --- a/src/test/java/io/codenotary/immudb4j/ScanTest.java +++ b/src/test/java/io/codenotary/immudb4j/ScanTest.java @@ -20,16 +20,17 @@ import org.testng.annotations.Test; import java.nio.charset.StandardCharsets; +import java.util.Iterator; import java.util.List; public class ScanTest extends ImmuClientIntegrationTest { - @Test(testName = "scan", priority = 2) + @Test(testName = "scan zscan") public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); - byte[] value1 = {0, 1, 2, 3}; - byte[] value2 = {4, 5, 6, 7}; + byte[] value1 = { 0, 1, 2, 3 }; + byte[] value2 = { 4, 5, 6, 7 }; try { immuClient.set("scan1", value1); @@ -38,29 +39,6 @@ public void t1() { Assert.fail("Failed at set.", e); } - List scanResult = immuClient.scan("scan", 5, false); - System.out.println(scanResult.size()); - - Assert.assertEquals(scanResult.size(), 2); - Assert.assertEquals(scanResult.get(0).getKey(), "scan1".getBytes(StandardCharsets.UTF_8)); - Assert.assertEquals(scanResult.get(0).getValue(), value1); - Assert.assertEquals(scanResult.get(1).getKey(), "scan2".getBytes(StandardCharsets.UTF_8)); - Assert.assertEquals(scanResult.get(1).getValue(), value2); - - Assert.assertTrue(immuClient.scan("scan").size() > 0); - - Assert.assertEquals(immuClient.scan("scan", "scan1", 1, false).size(), 1); - - immuClient.closeSession(); - } - - @Test(testName = "set, zAdd, zScan", priority = 3) - public void t2() { - immuClient.openSession("immudb", "immudb", "defaultdb"); - - byte[] value1 = {0, 1, 2, 3}; - byte[] value2 = {4, 5, 6, 7}; - try { immuClient.set("zadd1", value1); immuClient.set("zadd2", value2); @@ -78,15 +56,56 @@ public void t2() { Assert.fail("Failed to zAdd", e); } - List zScan1 = immuClient.zScan("set1", 5, false); + List scanResult = immuClient.scanAll("scan"); + System.out.println(scanResult.size()); + + Assert.assertEquals(scanResult.size(), 2); + Assert.assertEquals(scanResult.get(0).getKey(), "scan1".getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(scanResult.get(0).getValue(), value1); + Assert.assertEquals(scanResult.get(1).getKey(), "scan2".getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(scanResult.get(1).getValue(), value2); + + Assert.assertTrue(immuClient.scanAll("scan").size() > 0); + + Assert.assertEquals(immuClient.scanAll("scan".getBytes(), "scan1".getBytes(), false, 1).size(), 1); + + Iterator scanResult1 = immuClient.scan("scan", false, 5); + + int i = 0; + + while (scanResult1.hasNext()) { + Assert.assertEquals(scanResult1.next().getKey(), scanResult.get(i).getKey()); + i++; + } + + Assert.assertEquals(i, 2); + + Assert.assertFalse(immuClient.scan("nonexistent-prefix").hasNext()); + + List zScan1 = immuClient.zScanAll("set1", false, 5); Assert.assertEquals(zScan1.size(), 2); + Assert.assertEquals(zScan1.get(0).getSet(), "set1".getBytes(StandardCharsets.UTF_8)); Assert.assertEquals(zScan1.get(0).getKey(), "zadd1".getBytes(StandardCharsets.UTF_8)); + Assert.assertEquals(zScan1.get(0).getScore(), 1.0); + Assert.assertEquals(zScan1.get(0).getAtTx(), 0); Assert.assertEquals(zScan1.get(0).getEntry().getValue(), value1); - List zScan2 = immuClient.zScan("set2", 5, false); + List zScan2 = immuClient.zScanAll("set2"); Assert.assertEquals(zScan2.size(), 2); + Iterator zScan3 = immuClient.zScan("set2"); + i = 0; + + while (zScan3.hasNext()) { + Assert.assertEquals(zScan3.next().getKey(), zScan2.get(i).getKey()); + i++; + } + + Assert.assertEquals(i, 2); + + Assert.assertFalse(immuClient.zScan("nonexistent-set").hasNext()); + immuClient.closeSession(); } diff --git a/src/test/java/io/codenotary/immudb4j/SetAllAndGetAllTest.java b/src/test/java/io/codenotary/immudb4j/SetAllAndGetAllTest.java index 71d91aa..4c879b9 100644 --- a/src/test/java/io/codenotary/immudb4j/SetAllAndGetAllTest.java +++ b/src/test/java/io/codenotary/immudb4j/SetAllAndGetAllTest.java @@ -26,7 +26,7 @@ public class SetAllAndGetAllTest extends ImmuClientIntegrationTest { @Test(testName = "setAll & getAll") public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); String key1 = "sga-key1"; byte[] val1 = new byte[] { 1 }; @@ -42,8 +42,8 @@ public void t1() { .entries(); try { - TxHeader txMd = immuClient.setAll(kvs); - Assert.assertNotNull(txMd); + TxHeader txHdr = immuClient.setAll(kvs); + Assert.assertNotNull(txHdr); } catch (CorruptedDataException e) { Assert.fail("Failed at SetAll.", e); } diff --git a/src/test/java/io/codenotary/immudb4j/SetAndGetTest.java b/src/test/java/io/codenotary/immudb4j/SetAndGetTest.java index 6f39d81..4f711cd 100644 --- a/src/test/java/io/codenotary/immudb4j/SetAndGetTest.java +++ b/src/test/java/io/codenotary/immudb4j/SetAndGetTest.java @@ -20,13 +20,11 @@ import org.testng.Assert; import org.testng.annotations.Test; -import java.nio.charset.StandardCharsets; - public class SetAndGetTest extends ImmuClientIntegrationTest { @Test(testName = "set, get") public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); String key = "key1"; byte[] val = new byte[]{1, 2, 3, 4, 5}; diff --git a/src/test/java/io/codenotary/immudb4j/ShutdownTest.java b/src/test/java/io/codenotary/immudb4j/ShutdownTest.java index 6df992c..c3e5c65 100644 --- a/src/test/java/io/codenotary/immudb4j/ShutdownTest.java +++ b/src/test/java/io/codenotary/immudb4j/ShutdownTest.java @@ -22,13 +22,14 @@ public class ShutdownTest extends ImmuClientIntegrationTest { @Test(testName = "Login attempt after shutdown", expectedExceptions = StatusRuntimeException.class) - public void t1() { - + public void t1() throws InterruptedException { Assert.assertFalse(immuClient.isShutdown()); + immuClient.openSession("defaultdb", "immudb", "immudb"); + immuClient.shutdown(); - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); } } diff --git a/src/test/java/io/codenotary/immudb4j/StateTest.java b/src/test/java/io/codenotary/immudb4j/StateTest.java index 2e5ab96..5040c1c 100644 --- a/src/test/java/io/codenotary/immudb4j/StateTest.java +++ b/src/test/java/io/codenotary/immudb4j/StateTest.java @@ -28,9 +28,14 @@ public class StateTest extends ImmuClientIntegrationTest { private static final String publicKeyResource = "test_public_key.pem"; + @Test(testName = "currentState without open session", expectedExceptions = IllegalStateException.class) + public void t1() throws VerificationException { + immuClient.currentState(); + } + @Test(testName = "currentState") public void t2() throws VerificationException { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); ImmuState currState = immuClient.currentState(); @@ -52,11 +57,13 @@ public void t2() throws VerificationException { return; } - // The signature verification in this case should fail for the same aforementioned reason. + // The signature verification in this case should fail for the same + // aforementioned reason. Assert.assertFalse(currState.checkSignature(publicKey)); // Again, "covering" `checkSignature` when there is a `signature` attached. - ImmuState someState = new ImmuState(currState.getDatabase(), currState.getTxId(), currState.getTxHash(), new byte[1]); + ImmuState someState = new ImmuState(currState.getDatabase(), currState.getTxId(), currState.getTxHash(), + new byte[1]); Assert.assertFalse(someState.checkSignature(publicKey)); immuClient.closeSession(); @@ -82,27 +89,27 @@ public void t3() { return; } - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); try { immuClient.currentState(); - Assert.fail("Did not fail as it should in this case when the signingKey is provisioned only on the client side"); + Assert.fail( + "Did not fail as it should in this case when the signingKey is provisioned only on the client side"); } catch (VerificationException ignored) { - // Expected this since in the current tests setup, immudb does not have that state signature feature active. - // (this feature is active when starting it like: `immudb --signingKey test_private_key.pem`). + // Expected this since in the current tests setup, immudb does not have that + // state signature feature active. + // (this feature is active when starting it like: `immudb --signingKey + // test_private_key.pem`). } immuClient.closeSession(); } - - - @Test(testName = "currentState with server signature checking", - description = "Testing `checkSignature` (indirectly, through `currentState`), " + - "the (state signing) feature being set up on both server and client side. " + - "This could remain a manual test, that's why it is disabled." + - "Of course, it must be `enabled = true`, if you want to run it from IDE or cli.", - enabled = false) + @Test(testName = "currentState with server signature checking", description = "Testing `checkSignature` (indirectly, through `currentState`), " + + + "the (state signing) feature being set up on both server and client side. " + + "This could remain a manual test, that's why it is disabled." + + "Of course, it must be `enabled = true`, if you want to run it from IDE or cli.", enabled = false) public void t4() { // Provisioning the client side with the public key file. @@ -124,12 +131,14 @@ public void t4() { return; } - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); try { ImmuState state = immuClient.currentState(); - // In this case, it should be ok as long as the immudb server has been started accordingly - // from `immudb` directory (on this repo root) using: `./immudb --signingKey test_private_key.pem` + // In this case, it should be ok as long as the immudb server has been started + // accordingly + // from `immudb` directory (on this repo root) using: `./immudb --signingKey + // test_private_key.pem` Assert.assertNotNull(state); } catch (VerificationException e) { Assert.fail(e.getMessage(), e.getCause()); diff --git a/src/test/java/io/codenotary/immudb4j/StreamSetAllTest.java b/src/test/java/io/codenotary/immudb4j/StreamSetAllTest.java new file mode 100644 index 0000000..a08fe4a --- /dev/null +++ b/src/test/java/io/codenotary/immudb4j/StreamSetAllTest.java @@ -0,0 +1,63 @@ +/* +Copyright 2022 CodeNotary, Inc. All rights reserved. + +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 io.codenotary.immudb4j; + +import io.codenotary.immudb4j.exceptions.CorruptedDataException; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; + +public class StreamSetAllTest extends ImmuClientIntegrationTest { + + @Test(testName = "setAll & getAll") + public void t1() { + immuClient.openSession("defaultdb", "immudb", "immudb"); + + String key1 = "sga-key1"; + byte[] val1 = new byte[] { 1 }; + String key2 = "sga-key2"; + byte[] val2 = new byte[] { 2, 3 }; + String key3 = "sga-key3"; + byte[] val3 = new byte[] { 3, 4, 5 }; + + final List kvs = KVListBuilder.newBuilder() + .add(new KVPair(key1, val1)) + .add(new KVPair(key2, val2)) + .add(new KVPair(key3, val3)) + .entries(); + + try { + TxHeader txHdr = immuClient.streamSetAll(kvs); + Assert.assertNotNull(txHdr); + } catch (InterruptedException|CorruptedDataException e) { + Assert.fail("Failed at SetAll.", e); + } + + List keys = Arrays.asList(key1, key2, key3); + List got = immuClient.getAll(keys); + + Assert.assertEquals(kvs.size(), got.size()); + + for (int i = 0; i < kvs.size(); i++) { + Assert.assertEquals(got.get(i).getValue(), kvs.get(i).getValue(), String.format("Expected: %s got: %s", kvs.get(i), got.get(i))); + } + + immuClient.closeSession(); + } + +} diff --git a/src/test/java/io/codenotary/immudb4j/StreamSetAndGetTest.java b/src/test/java/io/codenotary/immudb4j/StreamSetAndGetTest.java new file mode 100644 index 0000000..c5ce019 --- /dev/null +++ b/src/test/java/io/codenotary/immudb4j/StreamSetAndGetTest.java @@ -0,0 +1,58 @@ +/* +Copyright 2022 CodeNotary, Inc. All rights reserved. + +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 io.codenotary.immudb4j; + +import io.codenotary.immudb4j.exceptions.CorruptedDataException; +import io.codenotary.immudb4j.exceptions.KeyNotFoundException; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class StreamSetAndGetTest extends ImmuClientIntegrationTest { + + @Test(testName = "stream set, get") + public void t1() { + immuClient.openSession("defaultdb", "immudb", "immudb"); + + String key = "key1"; + byte[] val = new byte[]{1, 2, 3, 4, 5}; + + TxHeader txHdr = null; + try { + txHdr = immuClient.streamSet(key, val); + } catch (CorruptedDataException|InterruptedException e) { + Assert.fail("Failed at set.", e); + } + Assert.assertNotNull(txHdr); + + Entry entry1 = immuClient.streamGet(key); + Assert.assertNotNull(entry1); + Assert.assertEquals(entry1.getValue(), val); + + immuClient.delete(key); + + try { + immuClient.streamGet(key); + Assert.fail("key not found exception expected"); + } catch (KeyNotFoundException e) { + // expected + } catch (Exception e) { + Assert.fail("key not found exception expected"); + } + + immuClient.closeSession(); + } + +} diff --git a/src/test/java/io/codenotary/immudb4j/TxTest.java b/src/test/java/io/codenotary/immudb4j/TxTest.java index 72b2493..11a26a5 100644 --- a/src/test/java/io/codenotary/immudb4j/TxTest.java +++ b/src/test/java/io/codenotary/immudb4j/TxTest.java @@ -16,6 +16,7 @@ package io.codenotary.immudb4j; import io.codenotary.immudb4j.exceptions.CorruptedDataException; +import io.codenotary.immudb4j.exceptions.TxNotFoundException; import io.codenotary.immudb4j.exceptions.VerificationException; import org.testng.Assert; import org.testng.annotations.Test; @@ -27,8 +28,8 @@ public class TxTest extends ImmuClientIntegrationTest { @Test(testName = "verifiedSet, txById, verifiedTxById") - public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + public void t1() throws NoSuchAlgorithmException, VerificationException{ + immuClient.openSession("defaultdb", "immudb", "immudb"); String key = "test-txid"; byte[] val = "test-txid-value".getBytes(StandardCharsets.UTF_8); @@ -40,12 +41,7 @@ public void t1() { Assert.fail("Failed at verifiedSet", e); } - Tx tx = null; - try { - tx = immuClient.txById(txHdr.getId()); - } catch (NoSuchAlgorithmException e) { - Assert.fail("Failed at txById", e); - } + Tx tx = immuClient.txById(txHdr.getId()); Assert.assertEquals(txHdr.getId(), tx.getHeader().getId()); @@ -57,12 +53,24 @@ public void t1() { Assert.assertEquals(txHdr.getId(), tx.getHeader().getId()); + try { + immuClient.txById(txHdr.getId()+1); + Assert.fail("Failed at txById."); + } catch (TxNotFoundException _) { + } + + try { + immuClient.verifiedTxById(txHdr.getId()+1); + Assert.fail("Failed at verifiedTxById."); + } catch (TxNotFoundException _) { + } + immuClient.closeSession(); } @Test(testName = "set, txScan") public void t2() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); String key = "txtest-t2"; byte[] val1 = "immuRocks!".getBytes(StandardCharsets.UTF_8); @@ -79,15 +87,15 @@ public void t2() { Assert.fail("Failed at set.", e); } - List txs = immuClient.txScan(initialTxId, 1, false); + List txs = immuClient.txScanAll(initialTxId, 1, false); Assert.assertNotNull(txs); Assert.assertEquals(txs.size(), 1); - txs = immuClient.txScan(initialTxId, 2, false); + txs = immuClient.txScanAll(initialTxId, 2, false); Assert.assertNotNull(txs); Assert.assertEquals(txs.size(), 2); - Assert.assertNotNull(immuClient.txScan(initialTxId)); + Assert.assertNotNull(immuClient.txScanAll(initialTxId)); immuClient.closeSession(); } diff --git a/src/test/java/io/codenotary/immudb4j/UserMgmtTest.java b/src/test/java/io/codenotary/immudb4j/UserMgmtTest.java index f338250..cd6e4a1 100644 --- a/src/test/java/io/codenotary/immudb4j/UserMgmtTest.java +++ b/src/test/java/io/codenotary/immudb4j/UserMgmtTest.java @@ -22,7 +22,6 @@ import org.testng.annotations.Test; import java.util.Collections; -import java.util.List; public class UserMgmtTest extends ImmuClientIntegrationTest { @@ -33,7 +32,7 @@ public void t1() { String password = "testTest123!"; Permission permission = Permission.PERMISSION_RW; - immuClient.openSession("immudb", "immudb", database); + immuClient.openSession(database, "immudb", "immudb"); // Should not contain testCreateUser. Skipping it as not valid for the current unit tests setup // (where a clean immudb server is started for each Test class). @@ -47,9 +46,9 @@ public void t1() { } // Should contain testCreateUser. - System.out.println(">>> listUsers:"); - List users = immuClient.listUsers(); - users.forEach(user -> System.out.println("\t- " + user)); + //System.out.println(">>> listUsers:"); + //List users = immuClient.listUsers(); + //users.forEach(user -> System.out.println("\t- " + user)); // TODO: Temporary commented since currently there's a bug on immudb's side. // The next release will include the fix of 'listUsers'. This commit includes the fix: @@ -69,15 +68,21 @@ public void t1() { @Test(testName = "createUser, changePassword", priority = 101) public void t2() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); try { - immuClient.createUser("testUser", "testTest123!", Permission.PERMISSION_ADMIN, "defaultdb"); + immuClient.createUser("testUser", "testTest123!", Permission.PERMISSION_R, "defaultdb"); } catch (StatusRuntimeException e) { // The user could already exist, ignoring this. System.out.println(">>> UserMgmtTest > t2 > createUser exception: " + e.getMessage()); } + immuClient.activateUser("testUser", true); + + immuClient.revokePermission("testUser", "defaultdb", Permission.PERMISSION_R); + + immuClient.grantPermission("testUser", "defaultdb", Permission.PERMISSION_RW); + immuClient.changePassword("testUser", "testTest123!", "newTestTest123!"); immuClient.closeSession(); @@ -90,7 +95,7 @@ public void t2() { // Login failed, everything's fine. } - immuClient.openSession("testUser", "newTestTest123!", "defaultdb"); + immuClient.openSession("defaultdb", "testUser", "newTestTest123!"); immuClient.closeSession(); diff --git a/src/test/java/io/codenotary/immudb4j/VerifiedSetAndGetTest.java b/src/test/java/io/codenotary/immudb4j/VerifiedSetAndGetTest.java index a0a812d..a74b9dc 100644 --- a/src/test/java/io/codenotary/immudb4j/VerifiedSetAndGetTest.java +++ b/src/test/java/io/codenotary/immudb4j/VerifiedSetAndGetTest.java @@ -20,13 +20,14 @@ import org.testng.Assert; import org.testng.annotations.Test; +import java.io.IOException; import java.nio.charset.StandardCharsets; public class VerifiedSetAndGetTest extends ImmuClientIntegrationTest { @Test(testName = "set, verifiedGet") public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); String key = "vsg"; byte[] val = "test-set-vget".getBytes(StandardCharsets.UTF_8); @@ -51,7 +52,7 @@ public void t1() { @Test(testName = "verifiedSet, verifiedGet, verifiedGetAt, verifiedGetSince") public void t2() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); byte[] key = "vsg".getBytes(StandardCharsets.UTF_8); byte[] val = "test-vset-vget".getBytes(StandardCharsets.UTF_8); @@ -95,6 +96,29 @@ public void t2() { immuClient.closeSession(); } + @Test(testName = "Login attempt after shutdown") + public void t3() throws InterruptedException, IllegalStateException, IOException, VerificationException { + immuClient.openSession("defaultdb", "immudb", "immudb"); + immuClient.verifiedSet("key1", "val1".getBytes()); + + immuClient.closeSession(); + + immuClient.shutdown(); + + FileImmuStateHolder stateHolder = FileImmuStateHolder.newBuilder() + .withStatesFolder(statesDir.getAbsolutePath()) + .build(); + + immuClient = ImmuClient.newBuilder() + .withStateHolder(stateHolder) + .withServerUrl("localhost") + .withServerPort(3322) + .build(); + + immuClient.openSession("defaultdb", "immudb", "immudb"); + + immuClient.verifiedGet("key1"); + } } diff --git a/src/test/java/io/codenotary/immudb4j/ZAddTest.java b/src/test/java/io/codenotary/immudb4j/ZAddTest.java index eb5be6d..c00cba1 100644 --- a/src/test/java/io/codenotary/immudb4j/ZAddTest.java +++ b/src/test/java/io/codenotary/immudb4j/ZAddTest.java @@ -26,7 +26,7 @@ public class ZAddTest extends ImmuClientIntegrationTest { @Test(testName = "zAdd, verifiedZAdd, verifiedZAddAt") public void t1() { - immuClient.openSession("immudb", "immudb", "defaultdb"); + immuClient.openSession("defaultdb", "immudb", "immudb"); String set = "test-zadd"; String key1 = "test-zadd-key1"; diff --git a/tests.sh b/tests.sh index 3095cc7..0399a7e 100755 --- a/tests.sh +++ b/tests.sh @@ -9,20 +9,29 @@ echo ## Unit Tests TESTS="${TESTS} BasicImmuClientTest" +TESTS="${TESTS} BasicsTest" +TESTS="${TESTS} CryptoUtilsTest" +TESTS="${TESTS} ExceptionsTest" +TESTS="${TESTS} FileImmuStateHolderTest" +TESTS="${TESTS} HealthCheckAndIndexCompactionTest" TESTS="${TESTS} HistoryTest" TESTS="${TESTS} HTreeTest" -TESTS="${TESTS} ListDatabasesTest ListUsersTest" -TESTS="${TESTS} LoginAndHealthCheckAndCleanIndexTest" -TESTS="${TESTS} MultidatabaseTest MultithreadTest" +TESTS="${TESTS} ListDatabasesTest" +TESTS="${TESTS} ListUsersTest" +TESTS="${TESTS} MultidatabaseTest" +TESTS="${TESTS} MultithreadTest" TESTS="${TESTS} ReferenceTest" TESTS="${TESTS} ScanTest" -TESTS="${TESTS} SetAllAndGetAllTest SetAndGetTest" +TESTS="${TESTS} SetAllAndGetAllTest" +TESTS="${TESTS} SetAndGetTest" +TESTS="${TESTS} StreamSetAndGetTest" +TESTS="${TESTS} StreamSetAllTest" +TESTS="${TESTS} ShutdownTest" TESTS="${TESTS} StateTest" TESTS="${TESTS} TxTest" TESTS="${TESTS} UserMgmtTest" TESTS="${TESTS} VerifiedSetAndGetTest" TESTS="${TESTS} ZAddTest" -TESTS="${TESTS} ShutdownTest" # -----------------------------------------------------------------------------