From 3cd9fbfa79800b12dfeda0089e4eeefe6740692b Mon Sep 17 00:00:00 2001 From: Marcin Lewandowski Date: Sat, 13 Jan 2024 22:48:07 +0100 Subject: [PATCH] sync with .net e73bbc2392f2e29ed3b6d9596c08aed0e08afef7..0111a936f92d652eeffdbd6f3a1928086b6a120a --- .../operations/PatchByQueryOperation.java | 4 + .../backups/BackupConfiguration.java | 9 + .../operations/backups/BackupUploadMode.java | 9 + .../SnapshotBackupCompressionAlgorithm.java | 9 + .../operations/backups/SnapshotSettings.java | 9 + .../documents/queries/HashCalculator.java | 3 + .../queries/QueryOperationOptions.java | 17 ++ .../IAdvancedDocumentSessionOperations.java | 7 + .../InMemoryDocumentSessionOperations.java | 33 +++ .../DatabaseSmugglerExportOptions.java | 11 +- .../smuggler/DatabaseSmugglerOptions.java | 16 ++ .../smuggler/ExportCompressionAlgorithm.java | 9 + .../IDatabaseSmugglerExportOptions.java | 3 + .../subscriptions/SubscriptionState.java | 9 + .../client/http/ClusterRequestExecutor.java | 4 + .../net/ravendb/client/http/NodeSelector.java | 105 +++++---- .../ravendb/client/http/RequestExecutor.java | 2 +- .../ravendb/client/json/JsonOperation.java | 2 +- .../serverwide/operations/BuildNumber.java | 9 + .../UpdateUnusedDatabasesOperation.java | 17 ++ .../PutClientCertificateOperation.java | 25 ++- .../logs/SetLogsConfigurationOperation.java | 9 + ...PutTrafficWatchConfigurationOperation.java | 15 ++ .../client/test/client/WhatChangedTest.java | 209 ++++++++++++++++++ 24 files changed, 500 insertions(+), 45 deletions(-) create mode 100644 src/main/java/net/ravendb/client/documents/operations/backups/BackupUploadMode.java create mode 100644 src/main/java/net/ravendb/client/documents/operations/backups/SnapshotBackupCompressionAlgorithm.java create mode 100644 src/main/java/net/ravendb/client/documents/smuggler/ExportCompressionAlgorithm.java diff --git a/src/main/java/net/ravendb/client/documents/operations/PatchByQueryOperation.java b/src/main/java/net/ravendb/client/documents/operations/PatchByQueryOperation.java index 6af0aca32..c8dc79186 100644 --- a/src/main/java/net/ravendb/client/documents/operations/PatchByQueryOperation.java +++ b/src/main/java/net/ravendb/client/documents/operations/PatchByQueryOperation.java @@ -73,6 +73,10 @@ public HttpRequestBase createRequest(ServerNode node, Reference url) { path += "&staleTimeout=" + TimeUtils.durationToTimeSpan(_options.getStaleTimeout()); } + if (_options.isIgnoreMaxStepsForScript()) { + path += "&ignoreMaxStepsForScript=" + _options.isIgnoreMaxStepsForScript(); + } + HttpPatch request = new HttpPatch(); request.setEntity(new ContentProviderHttpEntity(outputStream -> { try (JsonGenerator generator = createSafeJsonGenerator(outputStream)) { diff --git a/src/main/java/net/ravendb/client/documents/operations/backups/BackupConfiguration.java b/src/main/java/net/ravendb/client/documents/operations/backups/BackupConfiguration.java index 867135e62..9ef9f5053 100644 --- a/src/main/java/net/ravendb/client/documents/operations/backups/BackupConfiguration.java +++ b/src/main/java/net/ravendb/client/documents/operations/backups/BackupConfiguration.java @@ -3,6 +3,7 @@ public class BackupConfiguration { private BackupType backupType; + private BackupUploadMode backupUploadMode; private SnapshotSettings snapshotSettings; private BackupEncryptionSettings backupEncryptionSettings; @@ -21,6 +22,14 @@ public void setBackupType(BackupType backupType) { this.backupType = backupType; } + public BackupUploadMode getBackupUploadMode() { + return backupUploadMode; + } + + public void setBackupUploadMode(BackupUploadMode backupUploadMode) { + this.backupUploadMode = backupUploadMode; + } + public SnapshotSettings getSnapshotSettings() { return snapshotSettings; } diff --git a/src/main/java/net/ravendb/client/documents/operations/backups/BackupUploadMode.java b/src/main/java/net/ravendb/client/documents/operations/backups/BackupUploadMode.java new file mode 100644 index 000000000..61a026a90 --- /dev/null +++ b/src/main/java/net/ravendb/client/documents/operations/backups/BackupUploadMode.java @@ -0,0 +1,9 @@ +package net.ravendb.client.documents.operations.backups; + +import net.ravendb.client.primitives.UseSharpEnum; + +@UseSharpEnum +public enum BackupUploadMode { + DEFAULT, + DIRECT_UPLOAD +} diff --git a/src/main/java/net/ravendb/client/documents/operations/backups/SnapshotBackupCompressionAlgorithm.java b/src/main/java/net/ravendb/client/documents/operations/backups/SnapshotBackupCompressionAlgorithm.java new file mode 100644 index 000000000..ba7b3a10d --- /dev/null +++ b/src/main/java/net/ravendb/client/documents/operations/backups/SnapshotBackupCompressionAlgorithm.java @@ -0,0 +1,9 @@ +package net.ravendb.client.documents.operations.backups; + +import net.ravendb.client.primitives.UseSharpEnum; + +@UseSharpEnum +public enum SnapshotBackupCompressionAlgorithm { + Zstd, + Deflate +} diff --git a/src/main/java/net/ravendb/client/documents/operations/backups/SnapshotSettings.java b/src/main/java/net/ravendb/client/documents/operations/backups/SnapshotSettings.java index bdbfdc399..f7c3c6397 100644 --- a/src/main/java/net/ravendb/client/documents/operations/backups/SnapshotSettings.java +++ b/src/main/java/net/ravendb/client/documents/operations/backups/SnapshotSettings.java @@ -1,9 +1,18 @@ package net.ravendb.client.documents.operations.backups; public class SnapshotSettings { + private SnapshotBackupCompressionAlgorithm compressionAlgorithm; private CompressionLevel compressionLevel; private boolean excludeIndexes; + public SnapshotBackupCompressionAlgorithm getCompressionAlgorithm() { + return compressionAlgorithm; + } + + public void setCompressionAlgorithm(SnapshotBackupCompressionAlgorithm compressionAlgorithm) { + this.compressionAlgorithm = compressionAlgorithm; + } + public CompressionLevel getCompressionLevel() { return compressionLevel; } diff --git a/src/main/java/net/ravendb/client/documents/queries/HashCalculator.java b/src/main/java/net/ravendb/client/documents/queries/HashCalculator.java index 175c6ca25..7fc0f7f19 100644 --- a/src/main/java/net/ravendb/client/documents/queries/HashCalculator.java +++ b/src/main/java/net/ravendb/client/documents/queries/HashCalculator.java @@ -127,6 +127,7 @@ private void writeParameterValue(Object value, ObjectMapper mapper) throws IOExc for (Object o : ((Collection) value)) { writeParameterValue(o, mapper); } + write(((Collection) value).size()); } } else { write(mapper.writeValueAsString(value)); @@ -139,7 +140,9 @@ public void write(Map qp) throws IOException { } else { write(qp.size()); for (Map.Entry kvp : qp.entrySet()) { + write("key"); write(kvp.getKey()); + write("value"); write(kvp.getValue()); } } diff --git a/src/main/java/net/ravendb/client/documents/queries/QueryOperationOptions.java b/src/main/java/net/ravendb/client/documents/queries/QueryOperationOptions.java index 4c8c691ca..c49365428 100644 --- a/src/main/java/net/ravendb/client/documents/queries/QueryOperationOptions.java +++ b/src/main/java/net/ravendb/client/documents/queries/QueryOperationOptions.java @@ -11,6 +11,8 @@ public class QueryOperationOptions { private boolean allowStale; + private boolean ignoreMaxStepsForScript; + private Duration staleTimeout; private boolean retrieveDetails; @@ -31,6 +33,21 @@ public void setAllowStale(boolean allowStale) { this.allowStale = allowStale; } + /** + * Ignore the maximum number of statements a script can execute as defined in the server configuration. + */ + public boolean isIgnoreMaxStepsForScript() { + return ignoreMaxStepsForScript; + } + + /** + * Ignore the maximum number of statements a script can execute as defined in the server configuration. + * @param ignoreMaxStepsForScript steps + */ + public void setIgnoreMaxStepsForScript(boolean ignoreMaxStepsForScript) { + this.ignoreMaxStepsForScript = ignoreMaxStepsForScript; + } + /** * If AllowStale is set to false and index is stale, then this is the maximum timeout to wait for index to become non-stale. If timeout is exceeded then exception is thrown. * @return max time server can wait for stale results diff --git a/src/main/java/net/ravendb/client/documents/session/IAdvancedDocumentSessionOperations.java b/src/main/java/net/ravendb/client/documents/session/IAdvancedDocumentSessionOperations.java index 68d848260..61111a1dd 100644 --- a/src/main/java/net/ravendb/client/documents/session/IAdvancedDocumentSessionOperations.java +++ b/src/main/java/net/ravendb/client/documents/session/IAdvancedDocumentSessionOperations.java @@ -215,6 +215,13 @@ public interface IAdvancedDocumentSessionOperations { */ Map> whatChanged(); + /** + * Returns all changes for the specified entity. Including name of the field/property that changed, its old and new value and change type. + * @param entity Entity + * @return list of changes + */ + List whatChangedFor(Object entity); + /** * @return Returns all the tracked entities in this session. */ diff --git a/src/main/java/net/ravendb/client/documents/session/InMemoryDocumentSessionOperations.java b/src/main/java/net/ravendb/client/documents/session/InMemoryDocumentSessionOperations.java index c40eec3d6..87de2b8c0 100644 --- a/src/main/java/net/ravendb/client/documents/session/InMemoryDocumentSessionOperations.java +++ b/src/main/java/net/ravendb/client/documents/session/InMemoryDocumentSessionOperations.java @@ -1232,6 +1232,39 @@ public Map> whatChanged() { return changes; } + /** + * Returns all changes for the specified entity. Including name of the field/property that changed, its old and new value and change type. + * @param entity Entity + * @return list of changes + */ + public List whatChangedFor(Object entity) { + DocumentInfo documentInfo = documentsByEntity.get(entity); + if (documentInfo == null) { + return new ArrayList<>(); + } + + if (deletedEntities.contains(entity)) { + DocumentsChanges change = new DocumentsChanges(); + change.setFieldNewValue(""); + change.setFieldOldValue(""); + change.setChange(DocumentsChanges.ChangeType.DOCUMENT_DELETED); + + return Collections.singletonList(change); + } + + updateMetadataModifications(documentInfo.getMetadataInstance(), documentInfo.getMetadata()); + ObjectNode document = entityToJson.convertEntityToJson(documentInfo.getEntity(), documentInfo); + + Map> changes = new HashMap<>(); + + if (!entityChanged(document, documentInfo, changes)) { + return new ArrayList<>(); + } + + return changes.get(documentInfo.getId()); + } + + public Map getTrackedEntities() { Map tracked = documentsById.getTrackedEntities(this); diff --git a/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerExportOptions.java b/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerExportOptions.java index 07c3bdd56..0922e62ba 100644 --- a/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerExportOptions.java +++ b/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerExportOptions.java @@ -1,8 +1,13 @@ package net.ravendb.client.documents.smuggler; -import java.util.ArrayList; -import java.util.List; - public class DatabaseSmugglerExportOptions extends DatabaseSmugglerOptions implements IDatabaseSmugglerExportOptions { + private ExportCompressionAlgorithm compressionAlgorithm; + + public ExportCompressionAlgorithm getCompressionAlgorithm() { + return compressionAlgorithm; + } + public void setCompressionAlgorithm(ExportCompressionAlgorithm compressionAlgorithm) { + this.compressionAlgorithm = compressionAlgorithm; + } } diff --git a/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerOptions.java b/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerOptions.java index 9c5419715..3f9b033f3 100644 --- a/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerOptions.java +++ b/src/main/java/net/ravendb/client/documents/smuggler/DatabaseSmugglerOptions.java @@ -65,6 +65,8 @@ public class DatabaseSmugglerOptions implements IDatabaseSmugglerOptions { private String encryptionKey; private List collections; + private boolean skipCorruptedData; + public DatabaseSmugglerOptions() { this.operateOnTypes = DEFAULT_OPERATE_ON_TYPES.clone(); this.operateOnDatabaseRecordType = DEFAULT_OPERATE_ON_DATABASE_RECORD_TYPES.clone(); @@ -154,4 +156,18 @@ public List getCollections() { public void setCollections(List collections) { this.collections = collections; } + /** + * In case the database is corrupted (for example, Compression Dictionaries are lost), it is possible to export all the remaining data. + */ + public boolean isSkipCorruptedData() { + return skipCorruptedData; + } + + /** + * In case the database is corrupted (for example, Compression Dictionaries are lost), it is possible to export all the remaining data. + * @param skipCorruptedData skip corrupted data + */ + public void setSkipCorruptedData(boolean skipCorruptedData) { + this.skipCorruptedData = skipCorruptedData; + } } diff --git a/src/main/java/net/ravendb/client/documents/smuggler/ExportCompressionAlgorithm.java b/src/main/java/net/ravendb/client/documents/smuggler/ExportCompressionAlgorithm.java new file mode 100644 index 000000000..aa0025988 --- /dev/null +++ b/src/main/java/net/ravendb/client/documents/smuggler/ExportCompressionAlgorithm.java @@ -0,0 +1,9 @@ +package net.ravendb.client.documents.smuggler; + +import net.ravendb.client.primitives.UseSharpEnum; + +@UseSharpEnum +public enum ExportCompressionAlgorithm { + ZSTD, + GZIP +} diff --git a/src/main/java/net/ravendb/client/documents/smuggler/IDatabaseSmugglerExportOptions.java b/src/main/java/net/ravendb/client/documents/smuggler/IDatabaseSmugglerExportOptions.java index 652612f10..3ca3d7e2e 100644 --- a/src/main/java/net/ravendb/client/documents/smuggler/IDatabaseSmugglerExportOptions.java +++ b/src/main/java/net/ravendb/client/documents/smuggler/IDatabaseSmugglerExportOptions.java @@ -8,4 +8,7 @@ public interface IDatabaseSmugglerExportOptions extends IDatabaseSmugglerOptions void setCollections(List collections); + ExportCompressionAlgorithm getCompressionAlgorithm(); + + void setCompressionAlgorithm(ExportCompressionAlgorithm compressionAlgorithm); } diff --git a/src/main/java/net/ravendb/client/documents/subscriptions/SubscriptionState.java b/src/main/java/net/ravendb/client/documents/subscriptions/SubscriptionState.java index e0d3e8dae..5f6835ca8 100644 --- a/src/main/java/net/ravendb/client/documents/subscriptions/SubscriptionState.java +++ b/src/main/java/net/ravendb/client/documents/subscriptions/SubscriptionState.java @@ -14,6 +14,7 @@ public class SubscriptionState { private String nodeTag; private Date lastBatchAckTime; private Date lastClientConnectionTime; + private long raftCommandIndex; private boolean disabled; public String getQuery() { @@ -88,6 +89,14 @@ public void setLastClientConnectionTime(Date lastClientConnectionTime) { this.lastClientConnectionTime = lastClientConnectionTime; } + public long getRaftCommandIndex() { + return raftCommandIndex; + } + + public void setRaftCommandIndex(long raftCommandIndex) { + this.raftCommandIndex = raftCommandIndex; + } + public boolean isDisabled() { return disabled; } diff --git a/src/main/java/net/ravendb/client/http/ClusterRequestExecutor.java b/src/main/java/net/ravendb/client/http/ClusterRequestExecutor.java index a075b82dd..761e20d48 100644 --- a/src/main/java/net/ravendb/client/http/ClusterRequestExecutor.java +++ b/src/main/java/net/ravendb/client/http/ClusterRequestExecutor.java @@ -94,6 +94,10 @@ public CompletableFuture updateTopologyAsync(UpdateTopologyParameters p throw new IllegalArgumentException("Parameters cannot be null"); } + if (_disableTopologyUpdates) { + return CompletableFuture.completedFuture(false); + } + if (_disposed) { return CompletableFuture.completedFuture(false); } diff --git a/src/main/java/net/ravendb/client/http/NodeSelector.java b/src/main/java/net/ravendb/client/http/NodeSelector.java index 310c1339c..513f71d5f 100644 --- a/src/main/java/net/ravendb/client/http/NodeSelector.java +++ b/src/main/java/net/ravendb/client/http/NodeSelector.java @@ -17,7 +17,7 @@ public class NodeSelector implements CleanCloseable { private final ExecutorService executorService; private Timer _updateFastestNodeTimer; - private NodeSelectorState _state; + protected NodeSelectorState _state; public Topology getTopology() { return _state.topology; @@ -54,7 +54,7 @@ public boolean onUpdateTopology(Topology topology, boolean forceUpdate) { return false; } - NodeSelectorState state = new NodeSelectorState(topology); + NodeSelectorState state = new NodeSelectorState(topology, _state); _state = state; @@ -63,14 +63,14 @@ public boolean onUpdateTopology(Topology topology, boolean forceUpdate) { public CurrentIndexAndNode getRequestedNode(String nodeTag) { NodeSelectorState state = _state; - List serverNodes = state.nodes; + List serverNodes = state.getNodes(); for (int i = 0; i < serverNodes.size(); i++) { if (serverNodes.get(i).getClusterTag().equals(nodeTag)) { return new CurrentIndexAndNode(i, serverNodes.get(i)); } } - if (state.nodes.size() == 0) { + if (state.getNodes().isEmpty()) { throw new DatabaseDoesNotExistException("There are no nodes in the topology at all"); } throw new RequestedNodeUnavailableException("Could not find requested node " + nodeTag); @@ -87,7 +87,7 @@ public CurrentIndexAndNode getPreferredNode() { public static CurrentIndexAndNode getPreferredNodeInternal(NodeSelectorState state) { AtomicInteger[] stateFailures = state.failures; - List serverNodes = state.nodes; + List serverNodes = state.getNodes(); int len = Math.min(serverNodes.size(), stateFailures.length); for (int i = 0; i < len; i++) { if (stateFailures[i].get() == 0 && ServerNode.Role.MEMBER.equals(serverNodes.get(i).getServerRole())) { @@ -105,13 +105,13 @@ public AtomicInteger[] getNodeSelectorFailures() { private static CurrentIndexAndNode unlikelyEveryoneFaultedChoice(NodeSelectorState state) { // if there are all marked as failed, we'll chose the next (the one in CurrentNodeIndex) // one so the user will get an error (or recover :-) ); - if (state.nodes.size() == 0) { + if (state.getNodes().isEmpty()) { throw new DatabaseDoesNotExistException("There are no nodes in the topology at all"); } AtomicInteger[] stateFailures = state.failures; - List serverNodes = state.nodes; + List serverNodes = state.getNodes(); int len = Math.min(serverNodes.size(), stateFailures.length); for (int i = 0; i < len; i++) { @@ -133,14 +133,14 @@ public CurrentIndexAndNode getNodeBySessionId(int sessionId) { int index = Math.abs(sessionId % state.topology.getNodes().size()); for (int i = index; i < state.failures.length; i++) { - if (state.failures[i].get() == 0 && state.nodes.get(i).getServerRole() == ServerNode.Role.MEMBER) { - return new CurrentIndexAndNode(i, state.nodes.get(i)); + if (state.failures[i].get() == 0 && state.getNodes().get(i).getServerRole() == ServerNode.Role.MEMBER) { + return new CurrentIndexAndNode(i, state.getNodes().get(i)); } } for (int i = 0; i < index; i++) { - if (state.failures[i].get() == 0 && state.nodes.get(i).getServerRole() == ServerNode.Role.MEMBER) { - return new CurrentIndexAndNode(i, state.nodes.get(i)); + if (state.failures[i].get() == 0 && state.getNodes().get(i).getServerRole() == ServerNode.Role.MEMBER) { + return new CurrentIndexAndNode(i, state.getNodes().get(i)); } } @@ -149,21 +149,18 @@ public CurrentIndexAndNode getNodeBySessionId(int sessionId) { public CurrentIndexAndNode getFastestNode() { NodeSelectorState state = _state; - if (state.failures[state.fastest].get() == 0 && state.nodes.get(state.fastest).getServerRole() == ServerNode.Role.MEMBER) { - return new CurrentIndexAndNode(state.fastest, state.nodes.get(state.fastest)); + if (state.failures[state.fastest].get() == 0 && state.getNodes().get(state.fastest).getServerRole() == ServerNode.Role.MEMBER) { + return new CurrentIndexAndNode(state.fastest, state.getNodes().get(state.fastest)); } - // if the fastest node has failures, we'll immediately schedule - // another run of finding who the fastest node is, in the meantime - // we'll just use the server preferred node or failover as usual - - switchToSpeedTestPhase(); + // until new fastest node is selected, we'll just use the server preferred node or failover as usual + scheduleSpeedTest(); return getPreferredNode(); } public void restoreNodeIndex(ServerNode node) { NodeSelectorState state = _state; - int nodeIndex = state.nodes.indexOf(node); + int nodeIndex = state.getNodes().indexOf(node); if (nodeIndex == -1) { return; } @@ -171,10 +168,6 @@ public void restoreNodeIndex(ServerNode node) { state.failures[nodeIndex].set(0); } - protected static void throwEmptyTopology() { - throw new IllegalStateException("Empty database topology, this shouldn't happen."); - } - private void switchToSpeedTestPhase() { NodeSelectorState state = _state; @@ -202,15 +195,16 @@ public void recordFastest(int index, ServerNode node) { if (index < 0 || index >= stateFastest.length) return; - if (node != state.nodes.get(index)) { + if (node != state.getNodes().get(index)) { return; } if (++stateFastest[index] >= 10) { selectFastest(state, index); + return; } - if (state.speedTestMode.incrementAndGet() <= state.nodes.size() * 10) { + if (state.speedTestMode.incrementAndGet() <= state.getNodes().size() * 10) { return; } @@ -240,18 +234,24 @@ private void selectFastest(NodeSelectorState state, int index) { state.fastest = index; state.speedTestMode.set(0); - ensureFastestNodeTimerExists(); - _updateFastestNodeTimer.change(Duration.ofMinutes(1), null); + scheduleSpeedTest(); } + private final Object _timerCreationLocker = new Object(); + public void scheduleSpeedTest() { - ensureFastestNodeTimerExists(); - switchToSpeedTestPhase(); - } + if (_updateFastestNodeTimer != null) { + return; + } + + synchronized (_timerCreationLocker) { + if (_updateFastestNodeTimer != null) { + return ; + } - private void ensureFastestNodeTimerExists() { - if (_updateFastestNodeTimer == null) { - _updateFastestNodeTimer = new Timer(this::switchToSpeedTestPhase, null, null, executorService); + switchToSpeedTestPhase(); + + _updateFastestNodeTimer = new Timer(this::switchToSpeedTestPhase, Duration.ofMinutes(1), Duration.ofMinutes(1), executorService); } } @@ -264,7 +264,6 @@ public void close() { private static class NodeSelectorState { public final Topology topology; - public final List nodes; public final AtomicInteger[] failures; public final int[] fastestRecords; public int fastest; @@ -273,7 +272,6 @@ private static class NodeSelectorState { public NodeSelectorState(Topology topology) { this.topology = topology; - this.nodes = topology.getNodes(); this.failures = new AtomicInteger[topology.getNodes().size()]; for (int i = 0; i < this.failures.length; i++) { this.failures[i] = new AtomicInteger(0); @@ -282,11 +280,44 @@ public NodeSelectorState(Topology topology) { this.unlikelyEveryoneFaultedChoiceIndex = 0; } + public NodeSelectorState(Topology topology, NodeSelectorState prevState) { + this(topology); + + if (prevState.fastest < 0 || prevState.fastest >= prevState.getNodes().size()) { + return ; + } + + ServerNode fastestNode = prevState.getNodes().get(prevState.fastest); + int index = 0; + for (ServerNode node : topology.getNodes()) { + if (node.getClusterTag().equals(fastestNode.getClusterTag())) { + fastest = index; + break; + } + index++; + } + + // fastest node was not found in the new topology. enable speed tests + if (index >= topology.getNodes().size()) { + speedTestMode.set(2); + } else { + // we might be in the process of finding fastest node when we reorder the nodes, we don't want the tests to stop until we reach 10 + // otherwise, we want to stop the tests and they may be scheduled later on relevant topology change + if (fastest < prevState.fastestRecords.length && prevState.fastestRecords[fastest] < 10) { + speedTestMode.set(prevState.speedTestMode.get()); + } + } + } + + public List getNodes() { + return topology.getNodes(); + } + public CurrentIndexAndNode getNodeWhenEveryoneMarkedAsFaulted() { int index = unlikelyEveryoneFaultedChoiceIndex; - this.unlikelyEveryoneFaultedChoiceIndex = (unlikelyEveryoneFaultedChoiceIndex + 1) % nodes.size(); + this.unlikelyEveryoneFaultedChoiceIndex = (unlikelyEveryoneFaultedChoiceIndex + 1) % getNodes().size(); - return new CurrentIndexAndNode(index, nodes.get(index)); + return new CurrentIndexAndNode(index, getNodes().get(index)); } } diff --git a/src/main/java/net/ravendb/client/http/RequestExecutor.java b/src/main/java/net/ravendb/client/http/RequestExecutor.java index 1513fb07f..cc05ef091 100644 --- a/src/main/java/net/ravendb/client/http/RequestExecutor.java +++ b/src/main/java/net/ravendb/client/http/RequestExecutor.java @@ -1743,7 +1743,7 @@ public void close() { } private CloseableHttpClient createClient() { - HttpClientBuilder httpClientBuilder = HttpClients + final HttpClientBuilder httpClientBuilder = HttpClients .custom() .setMaxConnPerRoute(30) .setMaxConnTotal(40) diff --git a/src/main/java/net/ravendb/client/json/JsonOperation.java b/src/main/java/net/ravendb/client/json/JsonOperation.java index 866b2104c..7c9b31722 100644 --- a/src/main/java/net/ravendb/client/json/JsonOperation.java +++ b/src/main/java/net/ravendb/client/json/JsonOperation.java @@ -43,7 +43,7 @@ private static boolean compareJson(String fieldPath, String id, ObjectNode origi if (changes == null) { return true; } - newChange(fieldPath, field, null, null, docChanges, DocumentsChanges.ChangeType.REMOVED_FIELD); + newChange(fieldPath, field, null, originalJson.get(field), docChanges, DocumentsChanges.ChangeType.REMOVED_FIELD); } for (String prop : newJsonProps) { diff --git a/src/main/java/net/ravendb/client/serverwide/operations/BuildNumber.java b/src/main/java/net/ravendb/client/serverwide/operations/BuildNumber.java index 5a4ff3ec2..45fd1a79e 100644 --- a/src/main/java/net/ravendb/client/serverwide/operations/BuildNumber.java +++ b/src/main/java/net/ravendb/client/serverwide/operations/BuildNumber.java @@ -6,6 +6,7 @@ public class BuildNumber { private int buildVersion; private String commitHash; private String fullVersion; + private String assemblyVersion; public String getProductVersion() { return productVersion; @@ -38,4 +39,12 @@ public String getFullVersion() { public void setFullVersion(String fullVersion) { this.fullVersion = fullVersion; } + + public String getAssemblyVersion() { + return assemblyVersion; + } + + public void setAssemblyVersion(String assemblyVersion) { + this.assemblyVersion = assemblyVersion; + } } diff --git a/src/main/java/net/ravendb/client/serverwide/operations/UpdateUnusedDatabasesOperation.java b/src/main/java/net/ravendb/client/serverwide/operations/UpdateUnusedDatabasesOperation.java index 8a4a6c7d2..29490d243 100644 --- a/src/main/java/net/ravendb/client/serverwide/operations/UpdateUnusedDatabasesOperation.java +++ b/src/main/java/net/ravendb/client/serverwide/operations/UpdateUnusedDatabasesOperation.java @@ -22,6 +22,10 @@ public class UpdateUnusedDatabasesOperation implements IVoidServerOperation { private final Parameters _parameters; public UpdateUnusedDatabasesOperation(String database, Set unusedDatabaseIds) { + this(database, unusedDatabaseIds, false); + } + + public UpdateUnusedDatabasesOperation(String database, Set unusedDatabaseIds, boolean validate) { if (StringUtils.isEmpty(database)) { throw new IllegalArgumentException("Database cannot be null"); } @@ -29,6 +33,7 @@ public UpdateUnusedDatabasesOperation(String database, Set unusedDatabas _database = database; _parameters = new Parameters(); _parameters.setDatabaseIds(unusedDatabaseIds); + _parameters.setValidate(validate); } @Override @@ -48,6 +53,9 @@ public UpdateUnusedDatabasesCommand(String database, Parameters parameters) { @Override public HttpRequestBase createRequest(ServerNode node, Reference url) { url.value = node.getUrl() + "/admin/databases/unused-ids?name=" + _database; + if (_parameters.validate) { + url.value += "&validate=true"; + } HttpPost request = new HttpPost(); request.setEntity(new ContentProviderHttpEntity(outputStream -> { @@ -69,6 +77,7 @@ public String getRaftUniqueRequestId() { public static class Parameters { private Set databaseIds; + private boolean validate; public Set getDatabaseIds() { return databaseIds; @@ -77,5 +86,13 @@ public Set getDatabaseIds() { public void setDatabaseIds(Set databaseIds) { this.databaseIds = databaseIds; } + + public boolean isValidate() { + return validate; + } + + public void setValidate(boolean validate) { + this.validate = validate; + } } } diff --git a/src/main/java/net/ravendb/client/serverwide/operations/certificates/PutClientCertificateOperation.java b/src/main/java/net/ravendb/client/serverwide/operations/certificates/PutClientCertificateOperation.java index 0470288b0..f8af98717 100644 --- a/src/main/java/net/ravendb/client/serverwide/operations/certificates/PutClientCertificateOperation.java +++ b/src/main/java/net/ravendb/client/serverwide/operations/certificates/PutClientCertificateOperation.java @@ -23,8 +23,13 @@ public class PutClientCertificateOperation implements IVoidServerOperation { private final Map _permissions; private final String _name; private final SecurityClearance _clearance; + private final String _twoFactorAuthenticationKey; public PutClientCertificateOperation(String name, String certificate, Map permissions, SecurityClearance clearance) { + this(name, certificate, permissions, clearance, null); + } + + public PutClientCertificateOperation(String name, String certificate, Map permissions, SecurityClearance clearance, String twoFactorAuthenticationKey) { if (certificate == null) { throw new IllegalArgumentException("Certificate cannot be null"); } @@ -41,20 +46,30 @@ public PutClientCertificateOperation(String name, String certificate, Map _permissions; private final String _name; private final SecurityClearance _clearance; + private final String _twoFactorAuthenticationKey; - public PutClientCertificateCommand(String name, String certificate, Map permissions, SecurityClearance clearance) { + + public PutClientCertificateCommand(DocumentConventions conventions, + String name, + String certificate, + Map permissions, + SecurityClearance clearance, + String twoFactorAuthenticationKey) { if (certificate == null) { throw new IllegalArgumentException("Certificate cannot be null"); } @@ -62,10 +77,12 @@ public PutClientCertificateCommand(String name, String certificate, Map url) { generator.writeStringField("Name", _name); generator.writeStringField("Certificate", _certificate); generator.writeStringField("SecurityClearance", SharpEnum.value(_clearance)); + if (_twoFactorAuthenticationKey != null) { + generator.writeStringField("TwoFactorAuthenticationKey", _twoFactorAuthenticationKey); + } generator.writeFieldName("Permissions"); generator.writeStartObject(); @@ -95,7 +115,6 @@ public HttpRequestBase createRequest(ServerNode node, Reference url) { } generator.writeEndObject(); generator.writeEndObject(); - } catch (IOException e) { throw new RuntimeException(e); } diff --git a/src/main/java/net/ravendb/client/serverwide/operations/logs/SetLogsConfigurationOperation.java b/src/main/java/net/ravendb/client/serverwide/operations/logs/SetLogsConfigurationOperation.java index 862e17077..990e8a7fb 100644 --- a/src/main/java/net/ravendb/client/serverwide/operations/logs/SetLogsConfigurationOperation.java +++ b/src/main/java/net/ravendb/client/serverwide/operations/logs/SetLogsConfigurationOperation.java @@ -65,6 +65,7 @@ public static class Parameters { private Duration retentionTime; private Long retentionSize; private boolean compress; + private boolean persist; public Parameters(GetLogsConfigurationResult getLogs) { mode = getLogs.getMode(); @@ -107,5 +108,13 @@ public LogMode getMode() { public void setMode(LogMode mode) { this.mode = mode; } + + public boolean isPersist() { + return persist; + } + + public void setPersist(boolean persist) { + this.persist = persist; + } } } diff --git a/src/main/java/net/ravendb/client/serverwide/operations/trafficWatch/PutTrafficWatchConfigurationOperation.java b/src/main/java/net/ravendb/client/serverwide/operations/trafficWatch/PutTrafficWatchConfigurationOperation.java index f625cfdbf..3459fda25 100644 --- a/src/main/java/net/ravendb/client/serverwide/operations/trafficWatch/PutTrafficWatchConfigurationOperation.java +++ b/src/main/java/net/ravendb/client/serverwide/operations/trafficWatch/PutTrafficWatchConfigurationOperation.java @@ -69,6 +69,7 @@ public static class Parameters { private List httpMethods; private List changeTypes; private List certificateThumbprints; + private boolean persist; /** * @return Traffic Watch logging mode. @@ -195,5 +196,19 @@ public List getCertificateThumbprints() { public void setCertificateThumbprints(List certificateThumbprints) { this.certificateThumbprints = certificateThumbprints; } + + /** + * @return Indicates if the configuration should be persisted to the configuration file + */ + public boolean isPersist() { + return persist; + } + + /** + * @param persist Indicates if the configuration should be persisted to the configuration file + */ + public void setPersist(boolean persist) { + this.persist = persist; + } } } diff --git a/src/test/java/net/ravendb/client/test/client/WhatChangedTest.java b/src/test/java/net/ravendb/client/test/client/WhatChangedTest.java index 090487123..ad5cdcc8e 100644 --- a/src/test/java/net/ravendb/client/test/client/WhatChangedTest.java +++ b/src/test/java/net/ravendb/client/test/client/WhatChangedTest.java @@ -1,12 +1,16 @@ package net.ravendb.client.test.client; import net.ravendb.client.RemoteTestBase; +import net.ravendb.client.documents.DocumentStore; import net.ravendb.client.documents.IDocumentStore; import net.ravendb.client.documents.session.DocumentsChanges; import net.ravendb.client.documents.session.IDocumentSession; +import net.ravendb.client.documents.session.IMetadataDictionary; import net.ravendb.client.infrastructure.entities.User; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -350,6 +354,169 @@ public void hasChanges() throws Exception { } } + @Test + public void what_Changed_For_Delete_After_Change_Value() throws Exception { + //RavenDB-13501 + + try (DocumentStore store = getDocumentStore()) { + try (IDocumentSession session = store.openSession()) { + String id = "ABC"; + TestObject o = new TestObject(); + o.setId(id); + o.setA("A"); + o.setB("A"); + session.store(o); + session.saveChanges(); + + assertThat(session.advanced().hasChanges()) + .isFalse(); + + o = session.load(TestObject.class, id); + o.setA("B"); + o.setB("C"); + session.delete(o); + + List whatChangedFor = session.advanced().whatChangedFor(o); + assertThat(whatChangedFor.size() == 1 && whatChangedFor.get(0).getChange() == DocumentsChanges.ChangeType.DOCUMENT_DELETED) + .isTrue(); + + session.saveChanges(); + + o = session.load(TestObject.class, id); + assertThat(o) + .isNull(); + + } + } + } + + @Test + @Disabled("We don't support whatChanged for metadata") + public void what_Changed_For_RemovingAndAddingSameAmountOfFieldsToObjectShouldWork() throws Exception { + try (DocumentStore store = getDocumentStore()) { + String docId = "d/1"; + try (IDocumentSession session = store.openSession()) { + Doc d = new Doc(); + session.store(d, docId); + IMetadataDictionary meta = session.advanced().getMetadataFor(d); + + meta.put("Test-A", new String[] { "a", "a", "a" }); + meta.put("Test-C", new String[] { "c", "c", "c" }); + session.saveChanges(); + } + + try (IDocumentSession session = store.openSession()) { + Doc d = session.load(Doc.class, docId); + IMetadataDictionary meta = session.advanced().getMetadataFor(d); + + meta.put("Test-A", new String[] { "b", "a", "c" }); + + List changes = session.advanced().whatChangedFor(d); + assertThat(changes) + .hasSize(2); + assertThat(changes.get(0).getChange()) + .isEqualTo(DocumentsChanges.ChangeType.ARRAY_VALUE_CHANGED); + assertThat(changes.get(0).getFieldName()) + .isEqualTo("Test-A"); + assertThat(changes.get(1).getChange()) + .isEqualTo(DocumentsChanges.ChangeType.ARRAY_VALUE_CHANGED); + assertThat(changes.get(1).getFieldName()) + .isEqualTo("Test-A"); + session.saveChanges(); + } + + try (IDocumentSession session = store.openSession()) { + Doc d = session.load(Doc.class, docId); + IMetadataDictionary meta = session.advanced().getMetadataFor(d); + + meta.remove("Test-A"); + + List changes = session.advanced().whatChangedFor(d); + assertThat(changes) + .hasSize(1); + assertThat(changes.get(0).getChange()) + .isEqualTo(DocumentsChanges.ChangeType.REMOVED_FIELD); + } + + try (IDocumentSession session = store.openSession()) { + Doc d = session.load(Doc.class, docId); + IMetadataDictionary meta = session.advanced().getMetadataFor(d); + + meta.remove("Test-A"); + meta.remove("Test-C"); + meta.put("Test-B", new String[] { "b", "b", "b" }); + meta.put("Test-D", new String[] { "d", "d", "d" }); + List changes = session.advanced().whatChangedFor(d); + + assertThat(changes) + .hasSize(4); + assertThat(changes.stream().filter(x -> x.getFieldName().equals("Test-A")).findFirst().get().getChange()) + .isEqualTo(DocumentsChanges.ChangeType.REMOVED_FIELD); + assertThat(changes.stream().filter(x -> x.getFieldName().equals("Test-C")).findFirst().get().getChange()) + .isEqualTo(DocumentsChanges.ChangeType.REMOVED_FIELD); + assertThat(changes.stream().filter(x -> x.getFieldName().equals("Test-B")).findFirst().get().getChange()) + .isEqualTo(DocumentsChanges.ChangeType.NEW_FIELD); + assertThat(changes.stream().filter(x -> x.getFieldName().equals("Test-D")).findFirst().get().getChange()) + .isEqualTo(DocumentsChanges.ChangeType.NEW_FIELD); + } + + try (IDocumentSession session = store.openSession()) { + Doc d = session.load(Doc.class, docId); + IMetadataDictionary meta = session.advanced().getMetadataFor(d); + + meta.remove("Test-A"); + meta.put("Test-B", new String[] { "b", "b", "b" }); + + List changes = session.advanced().whatChangedFor(d); + assertThat(changes.stream().filter(x -> x.getFieldName().equals("Test-A")).findFirst().get().getChange()) + .isEqualTo(DocumentsChanges.ChangeType.REMOVED_FIELD); + assertThat(changes.stream().filter(x -> x.getFieldName().equals("Test-B")).findFirst().get().getChange()) + .isEqualTo(DocumentsChanges.ChangeType.NEW_FIELD); + } + } + } + + @Test + public void whatChanged_RemovedFieldFromDictionary() throws Exception { + try (DocumentStore store = getDocumentStore()) { + try (IDocumentSession session = store.openSession()) { + Entity entity = new Entity(); + entity.getSomeData().put("Key", "Value"); + session.store(entity, "entities/1"); + session.saveChanges(); + } + + try (IDocumentSession session = store.openSession()) { + Entity entity = session.load(Entity.class, "entities/1"); + entity.getSomeData().remove("Key"); + + List changes = session.advanced().whatChanged().get("entities/1"); + assertThat(changes) + .hasSize(1); + assertThat(changes.get(0).getChange()) + .isEqualTo(DocumentsChanges.ChangeType.REMOVED_FIELD); + assertThat(changes.get(0).getFieldName()) + .isEqualTo("Key"); + assertThat(changes.get(0).getFieldOldValue().toString()) + .isEqualTo("\"Value\""); + assertThat(changes.get(0).getFieldNewValue()) + .isNull(); + } + } + } + + public static class Entity { + private Map someData = new HashMap<>(); + + public Map getSomeData() { + return someData; + } + + public void setSomeData(Map someData) { + this.someData = someData; + } + } + public static class BasicName { private String name; @@ -432,4 +599,46 @@ public void setArray(Object[] array) { this.array = array; } } + + public static class TestObject { + private String id; + private String a; + private String b; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getA() { + return a; + } + + public void setA(String a) { + this.a = a; + } + + public String getB() { + return b; + } + + public void setB(String b) { + this.b = b; + } + } + + public static class Doc { + private String id; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + } }