com.swirlds.merkledb.MerkleDbDataSourceBuilder Maven / Gradle / Ivy
/*
* Copyright (C) 2022-2024 Hedera Hashgraph, LLC
*
* 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 com.swirlds.merkledb;
import com.swirlds.common.io.streams.SerializableDataInputStream;
import com.swirlds.common.io.streams.SerializableDataOutputStream;
import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder;
import com.swirlds.virtualmap.datasource.VirtualDataSource;
import com.swirlds.virtualmap.datasource.VirtualDataSourceBuilder;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.Objects;
/**
* Virtual data source builder that manages {@link MerkleDb} based data sources.
*
* One of the key MerkleDb builder config options is database directory. When a builder is
* requested to create a new data source, or restore an existing data sources from snapshot,
* the data source is hosted in the specified database. Full data source path is therefore
* databaseDir + "/" + dataSource.label. To make sure there are no folder name conflicts
* between data sources with the same label, e.g. on copy or snapshot, MerkleDb builders
* use different database directories, usually managed using {@link LegacyTemporaryFileBuilder}.
*/
public class MerkleDbDataSourceBuilder implements VirtualDataSourceBuilder {
private static final long CLASS_ID = 0x176ede0e1a69828L;
private static final class ClassVersion {
public static final int ORIGINAL = 1;
}
/**
* Default database folder used by this builder to create and restore data sources. If {@code null},
* the default {@link MerkleDb} database is used.
*/
private Path databaseDir;
/**
* Table configuration to use when this builder is requested to create a new data source.
*/
private MerkleDbTableConfig tableConfig;
/**
* Default constructor for deserialization purposes.
*/
public MerkleDbDataSourceBuilder() {
// for deserialization
}
/**
* Creates a new data source builder with the specified table configuration.
*
* @param tableConfig
* Table configuration to use to create new data sources
*/
public MerkleDbDataSourceBuilder(final MerkleDbTableConfig tableConfig) {
this(null, tableConfig);
}
/**
* Creates a new data source builder with the specified database dir and table configuration.
*
* @param databaseDir
* Default database folder. May be {@code null}
* @param tableConfig
* Table configuration to use to create new data sources
*/
public MerkleDbDataSourceBuilder(final Path databaseDir, final MerkleDbTableConfig tableConfig) {
this.databaseDir = databaseDir;
this.tableConfig = tableConfig;
}
/**
* {@inheritDoc}
*/
@Override
public VirtualDataSource build(final String label, final boolean withDbCompactionEnabled) {
if (tableConfig == null) {
throw new IllegalArgumentException("Table serialization config is missing");
}
// Creates a new data source in this builder's database dir or in the default MerkleDb instance
final MerkleDb database = MerkleDb.getInstance(databaseDir);
try {
return database.createDataSource(
label, // use VirtualMap name as the table name
tableConfig,
withDbCompactionEnabled);
} catch (final IOException ex) {
throw new UncheckedIOException(ex);
}
}
/**
* {@inheritDoc}
*/
@Override
public VirtualDataSource copy(final VirtualDataSource snapshotMe, final boolean makeCopyActive) {
if (!(snapshotMe instanceof MerkleDbDataSource source)) {
throw new IllegalArgumentException("The datasource must be compatible with the MerkleDb");
}
try {
return source.getDatabase().copyDataSource(source, makeCopyActive);
} catch (final IOException z) {
throw new UncheckedIOException(z);
}
}
/**
* {@inheritDoc}
*/
@Override
public void snapshot(final Path destination, final VirtualDataSource snapshotMe) {
if (!(snapshotMe instanceof MerkleDbDataSource source)) {
throw new IllegalArgumentException("The datasource must be compatible with the MerkleDb");
}
try {
// Snapshot all tables. When this snapshot() method is called for other data sources,
// the database will check if they are already present in the destination path. If so,
// the snapshot will be a no-op
source.getDatabase().snapshot(destination, source);
} catch (final IOException z) {
throw new UncheckedIOException(z);
}
}
/**
* {@inheritDoc}
*/
@Override
public VirtualDataSource restore(final String label, final Path source) {
try {
// Restore to the default database. Assuming the default database hasn't been initialized yet.
// Note that all database data, shared and per-table for all tables, will be restored.
final MerkleDb database = MerkleDb.restore(source, databaseDir);
return database.getDataSource(label, true);
} catch (final IOException z) {
throw new UncheckedIOException(z);
}
}
/**
* {@inheritDoc}
*/
@Override
public long getClassId() {
return CLASS_ID;
}
/**
* {@inheritDoc}
*/
@Override
public int getVersion() {
return ClassVersion.ORIGINAL;
}
/**
* {@inheritDoc}
*/
@Override
public void serialize(final SerializableDataOutputStream out) throws IOException {
// The order of the first 3 fields matches JasperDbBuilder serialization
out.writeSerializable(tableConfig, false);
}
/**
* {@inheritDoc}
*/
@Override
public void deserialize(final SerializableDataInputStream in, final int version) throws IOException {
tableConfig = in.readSerializable(false, MerkleDbTableConfig::new);
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return tableConfig.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof MerkleDbDataSourceBuilder that)) {
return false;
}
return Objects.equals(tableConfig, that.tableConfig);
}
}