Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions engine/src/main/java/com/arcadedb/database/BasicDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public interface BasicDatabase extends AutoCloseable {

String getDatabasePath();

/**
* Returns the total size of the database in bytes, including all database files
* (data files, indexes, transaction logs, metadata, etc.).
*
* @return The total size in bytes of all files in the database directory
*/
long getSize();

boolean isOpen();

Schema getSchema();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ default TransactionContext getTransaction() {
return tx;
}

long getSize();

TransactionContext getTransactionIfExists();

MutableEmbeddedDocument newEmbeddedDocument(EmbeddedModifier modifier, String typeName);
Expand Down
24 changes: 24 additions & 0 deletions engine/src/main/java/com/arcadedb/database/LocalDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
import java.util.concurrent.atomic.*;
import java.util.concurrent.locks.*;
import java.util.logging.*;
import java.util.stream.*;

/**
* Local implementation of {@link Database}. It is based on files opened on the local file system.
Expand Down Expand Up @@ -324,6 +325,29 @@ public String getDatabasePath() {
return databasePath;
}

@Override
public long getSize() {
return executeInReadLock(() -> {
checkDatabaseIsOpen();
try {
final Path dir = Path.of(databasePath);
if (!Files.exists(dir))
return 0L;
try (Stream<Path> stream = Files.walk(dir)) {
return stream.filter(Files::isRegularFile).mapToLong(p -> {
try {
return Files.size(p);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}).sum();
}
} catch (UncheckedIOException e) {
throw new DatabaseOperationException("Error calculating database size", e.getCause());
}
Comment on lines +332 to +347
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The exception handling in this block could be more consistent. An IOException thrown by Files.walk(dir) is not caught here and would be handled by the outer executeInReadLock method, leading to a generic error message. In contrast, an IOException from Files.size(p) is wrapped in UncheckedIOException and caught, resulting in a more specific error message.

To ensure all I/O errors during size calculation are handled uniformly with a clear message, I suggest catching both IOException and UncheckedIOException within this block.

      try {
        final Path dir = Path.of(databasePath);
        if (!Files.exists(dir))
          return 0L;

        try (Stream<Path> stream = Files.walk(dir)) {
          return stream.filter(Files::isRegularFile).mapToLong(p -> {
            try {
              return Files.size(p);
            } catch (IOException e) {
              throw new UncheckedIOException(e);
            }
          }).sum();
        }
      } catch (IOException e) {
        throw new DatabaseOperationException("Error calculating database size", e);
      } catch (UncheckedIOException e) {
        throw new DatabaseOperationException("Error calculating database size", e.getCause());
      }

});
}

@Override
public String getCurrentUserName() {
final DatabaseContext.DatabaseContextTL dbContext = DatabaseContext.INSTANCE.getContextIfExists(databasePath);
Expand Down
179 changes: 179 additions & 0 deletions engine/src/test/java/com/arcadedb/database/DatabaseGetSizeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* Copyright © 2021-present Arcade Data Ltd ([email protected])
*
* 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.
*
* SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
* SPDX-License-Identifier: Apache-2.0
*/
package com.arcadedb.database;

import com.arcadedb.TestHelper;
import com.arcadedb.graph.MutableVertex;
import com.arcadedb.schema.DocumentType;
import com.arcadedb.schema.Type;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Test cases for Database.getSize() API
*
* @author Luca Garulli ([email protected])
*/
class DatabaseGetSizeTest extends TestHelper {

@Test
void testGetSizeOnEmptyDatabase() {
// Even an empty database has metadata files (schema, configuration, etc.)
final long initialSize = database.getSize();
assertThat(initialSize).isGreaterThan(0L);
}

@Test
void testGetSizeIncreasesWithDocuments() {
database.transaction(() -> {
// Create a document type
final DocumentType personType = database.getSchema().createDocumentType("Person");
personType.createProperty("name", Type.STRING);
personType.createProperty("age", Type.INTEGER);
personType.createProperty("email", Type.STRING);
});

final long sizeBeforeInsert = database.getSize();

// Insert documents
database.transaction(() -> {
for (int i = 0; i < 1000; i++) {
final MutableDocument doc = database.newDocument("Person");
doc.set("name", "Person" + i);
doc.set("age", 20 + (i % 50));
doc.set("email", "person" + i + "@example.com");
doc.save();
}
});

final long sizeAfterInsert = database.getSize();

// Database size should increase after inserting documents
assertThat(sizeAfterInsert).isGreaterThan(sizeBeforeInsert);
}

@Test
void testGetSizeIncreasesWithVerticesAndEdges() {
database.transaction(() -> {
// Create vertex and edge types
database.getSchema().createVertexType("User");
database.getSchema().createEdgeType("Follows");
});

final long sizeBeforeData = database.getSize();

// Create vertices and edges
database.transaction(() -> {
MutableVertex user1 = database.newVertex("User");
user1.set("name", "Alice");
user1.save();

MutableVertex user2 = database.newVertex("User");
user2.set("name", "Bob");
user2.save();

MutableVertex user3 = database.newVertex("User");
user3.set("name", "Charlie");
user3.save();

// Create edges (using explicit method to avoid ambiguity)
user1.newEdge("Follows", user2, true, (Object[]) null);
user2.newEdge("Follows", user3, true, (Object[]) null);
user1.newEdge("Follows", user3, true, (Object[]) null);
});

final long sizeAfterData = database.getSize();

// Database size should increase after inserting vertices and edges
assertThat(sizeAfterData).isGreaterThan(sizeBeforeData);
}

@Test
void testGetSizeWithIndex() {
database.transaction(() -> {
// Create a document type with an indexed property
final DocumentType productType = database.getSchema().createDocumentType("Product");
productType.createProperty("sku", Type.STRING);
productType.createProperty("name", Type.STRING);
productType.createProperty("price", Type.DOUBLE);

// Create an index
database.getSchema().createTypeIndex(com.arcadedb.schema.Schema.INDEX_TYPE.LSM_TREE, true, "Product", "sku");
});

final long sizeBeforeInsert = database.getSize();

// Insert documents that will be indexed
database.transaction(() -> {
for (int i = 0; i < 500; i++) {
final MutableDocument doc = database.newDocument("Product");
doc.set("sku", "SKU-" + String.format("%05d", i));
doc.set("name", "Product " + i);
doc.set("price", 10.0 + i);
doc.save();
}
});

final long sizeAfterInsert = database.getSize();

// Database size should increase (includes both data and index files)
assertThat(sizeAfterInsert).isGreaterThan(sizeBeforeInsert);
}

@Test
void testGetSizeMultipleCalls() {
// Multiple calls to getSize() should return consistent results
final long size1 = database.getSize();
final long size2 = database.getSize();
final long size3 = database.getSize();

assertThat(size1).isEqualTo(size2);
assertThat(size2).isEqualTo(size3);
}

@Test
void testGetSizeAfterDelete() {
database.transaction(() -> {
database.getSchema().createDocumentType("TempDoc");
});

// Insert documents
database.transaction(() -> {
for (int i = 0; i < 100; i++) {
final MutableDocument doc = database.newDocument("TempDoc");
doc.set("value", "Data" + i);
doc.save();
}
});

final long sizeAfterInsert = database.getSize();

// Delete some documents
database.transaction(() -> {
database.command("sql", "DELETE FROM TempDoc WHERE value LIKE 'Data5%'").close();
});

final long sizeAfterDelete = database.getSize();

// Size might not decrease immediately due to space not being reclaimed until compaction
// But we can verify the size is still valid
assertThat(sizeAfterDelete).isGreaterThan(0L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.arcadedb.database.DocumentCallback;
import com.arcadedb.database.DocumentIndexer;
import com.arcadedb.database.EmbeddedModifier;
import com.arcadedb.database.LocalTransactionExplicitLock;
import com.arcadedb.database.MutableDocument;
import com.arcadedb.database.MutableEmbeddedDocument;
import com.arcadedb.database.RID;
Expand All @@ -33,7 +34,6 @@
import com.arcadedb.database.RecordEvents;
import com.arcadedb.database.RecordFactory;
import com.arcadedb.database.TransactionContext;
import com.arcadedb.database.LocalTransactionExplicitLock;
import com.arcadedb.database.async.DatabaseAsyncExecutor;
import com.arcadedb.database.async.ErrorCallback;
import com.arcadedb.database.async.OkCallback;
Expand Down Expand Up @@ -137,6 +137,11 @@ public Record invokeAfterReadEvents(final Record record) {
return record;
}

@Override
public long getSize() {
return 0L;
}

@Override
public TransactionContext getTransactionIfExists() {
return null;
Expand Down
5 changes: 5 additions & 0 deletions server/src/main/java/com/arcadedb/server/ServerDatabase.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ public String getDatabasePath() {
return wrapped.getDatabasePath();
}

@Override
public long getSize() {
return wrapped.getSize();
}

@Override
public String getCurrentUserName() {
return wrapped.getCurrentUserName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.arcadedb.database.DocumentIndexer;
import com.arcadedb.database.EmbeddedModifier;
import com.arcadedb.database.LocalDatabase;
import com.arcadedb.database.LocalTransactionExplicitLock;
import com.arcadedb.database.MutableDocument;
import com.arcadedb.database.MutableEmbeddedDocument;
import com.arcadedb.database.RID;
Expand All @@ -36,7 +37,6 @@
import com.arcadedb.database.RecordEvents;
import com.arcadedb.database.RecordFactory;
import com.arcadedb.database.TransactionContext;
import com.arcadedb.database.LocalTransactionExplicitLock;
import com.arcadedb.database.async.DatabaseAsyncExecutor;
import com.arcadedb.database.async.ErrorCallback;
import com.arcadedb.database.async.OkCallback;
Expand Down Expand Up @@ -347,6 +347,11 @@ public String getDatabasePath() {
return proxied.getDatabasePath();
}

@Override
public long getSize() {
return proxied.getSize();
}

@Override
public String getCurrentUserName() {
return proxied.getCurrentUserName();
Expand Down
Loading