-
Notifications
You must be signed in to change notification settings - Fork 239
feat: add ls command to LittDB CLI #1809
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path" | ||
"path/filepath" | ||
"sort" | ||
"strings" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/litt/disktable/segment" | ||
"github.com/Layr-Labs/eigenda/litt/util" | ||
"github.com/Layr-Labs/eigensdk-go/logging" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
func lsCommand(ctx *cli.Context) error { | ||
logger, err := common.NewLogger(common.DefaultConsoleLoggerConfig()) | ||
if err != nil { | ||
return fmt.Errorf("failed to create logger: %w", err) | ||
} | ||
|
||
sources := ctx.StringSlice("src") | ||
if len(sources) == 0 { | ||
return fmt.Errorf("no sources provided") | ||
} | ||
for i, src := range sources { | ||
var err error | ||
sources[i], err = util.SanitizePath(src) | ||
if err != nil { | ||
return fmt.Errorf("invalid source path: %s", src) | ||
} | ||
} | ||
|
||
tables, err := lsPaths(logger, sources, true, true) | ||
if err != nil { | ||
return fmt.Errorf("failed to list tables in paths %v: %w", sources, err) | ||
} | ||
|
||
sb := &strings.Builder{} | ||
for _, table := range tables { | ||
sb.WriteString(table) | ||
sb.WriteString("\n") | ||
} | ||
|
||
logger.Infof("Tables found:\n%s", sb.String()) | ||
|
||
return nil | ||
} | ||
|
||
// Similar to ls, but searches for tables in multiple paths. | ||
func lsPaths(logger logging.Logger, rootPaths []string, lock bool, fsync bool) ([]string, error) { | ||
litt3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tableSet := make(map[string]struct{}) | ||
|
||
for _, rootPath := range rootPaths { | ||
tables, err := ls(logger, rootPath, lock, fsync) | ||
if err != nil { | ||
return nil, fmt.Errorf("error finding tables: %w", err) | ||
} | ||
for _, table := range tables { | ||
tableSet[table] = struct{}{} | ||
} | ||
} | ||
|
||
tableNames := make([]string, 0, len(tableSet)) | ||
for tableName := range tableSet { | ||
tableNames = append(tableNames, tableName) | ||
} | ||
|
||
sort.Strings(tableNames) | ||
|
||
return tableNames, nil | ||
} | ||
|
||
// Returns a list of LittDB tables at the specified LittDB path. Tables are alphabetically sorted by their names. | ||
// Returns an error if the path does not exist or if no tables are found. | ||
func ls(logger logging.Logger, rootPath string, lock bool, fsync bool) ([]string, error) { | ||
|
||
if lock { | ||
// Forbid touching tables in active use. | ||
lockPath := path.Join(rootPath, util.LockfileName) | ||
fLock, err := util.NewFileLock(logger, lockPath, fsync) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to acquire lock on %s: %w", rootPath, err) | ||
} | ||
defer fLock.Release() | ||
} | ||
|
||
// LittDB has one directory under the root directory per table, with the name | ||
// of the table being the name of the directory. | ||
possibleTables, err := os.ReadDir(rootPath) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to read dir %s: %w", rootPath, err) | ||
} | ||
|
||
// Each table directory will contain a "segments" directory. Infer that any directory containing this directory | ||
// is a table. If we are looking at a real LittDB instance, there shouldn't be any other directories, but | ||
// there is no need to enforce that here. | ||
tables := make([]string, 0, len(possibleTables)) | ||
for _, entry := range possibleTables { | ||
if !entry.IsDir() { | ||
continue | ||
} | ||
|
||
segmentPath := filepath.Join(rootPath, entry.Name(), segment.SegmentDirectory) | ||
isDirectory, err := util.IsDirectory(segmentPath) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to check if segment path %s is a directory: %w", segmentPath, err) | ||
} | ||
if isDirectory { | ||
tables = append(tables, entry.Name()) | ||
} | ||
} | ||
|
||
// Alphabetically sort the tables. | ||
sort.Strings(tables) | ||
|
||
return tables, nil | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
"testing" | ||
|
||
"github.com/Layr-Labs/eigenda/common" | ||
"github.com/Layr-Labs/eigenda/common/testutils/random" | ||
"github.com/Layr-Labs/eigenda/litt" | ||
"github.com/Layr-Labs/eigenda/litt/littbuilder" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestLs(t *testing.T) { | ||
t.Parallel() | ||
|
||
logger, err := common.NewLogger(common.DefaultConsoleLoggerConfig()) | ||
require.NoError(t, err) | ||
|
||
rand := random.NewTestRandom() | ||
directory := t.TempDir() | ||
|
||
// Spread data across several root directories. | ||
rootCount := rand.Uint32Range(2, 5) | ||
roots := make([]string, 0, rootCount) | ||
for i := 0; i < int(rootCount); i++ { | ||
roots = append(roots, fmt.Sprintf("%s/root-%d", directory, i)) | ||
} | ||
|
||
config, err := litt.DefaultConfig(roots...) | ||
require.NoError(t, err) | ||
|
||
// Make it so that we have at least as many shards as roots. | ||
config.ShardingFactor = rootCount * rand.Uint32Range(1, 4) | ||
|
||
// Settings that should be enabled for LittDB unit tests. | ||
config.DoubleWriteProtection = true | ||
config.Fsync = false | ||
|
||
// Use small segments to ensure that we create a few segments per table. | ||
config.TargetSegmentFileSize = 100 | ||
|
||
// Enable snapshotting. | ||
snapshotDir := t.TempDir() | ||
config.SnapshotDirectory = snapshotDir | ||
|
||
// Build the DB and a handful of tables. | ||
db, err := littbuilder.NewDB(config) | ||
require.NoError(t, err) | ||
|
||
tableCount := rand.Uint32Range(2, 5) | ||
tables := make([]litt.Table, 0, tableCount) | ||
expectedData := make(map[string]map[string][]byte) | ||
tableNames := make([]string, 0, tableCount) | ||
for i := 0; i < int(tableCount); i++ { | ||
tableName := fmt.Sprintf("table-%d-%s", i, rand.PrintableBytes(8)) | ||
table, err := db.GetTable(tableName) | ||
require.NoError(t, err) | ||
tables = append(tables, table) | ||
expectedData[table.Name()] = make(map[string][]byte) | ||
tableNames = append(tableNames, tableName) | ||
} | ||
|
||
// Alphabetize table names. ls should always return tables in this order. | ||
sort.Strings(tableNames) | ||
|
||
// Insert some data into the tables. | ||
for _, table := range tables { | ||
for i := 0; i < 100; i++ { | ||
key := rand.PrintableBytes(32) | ||
value := rand.PrintableVariableBytes(10, 200) | ||
expectedData[table.Name()][string(key)] = value | ||
err = table.Put(key, value) | ||
require.NoError(t, err, "Failed to put key-value pair in table %s", table.Name()) | ||
} | ||
err = table.Flush() | ||
require.NoError(t, err, "Failed to flush table %s", table.Name()) | ||
} | ||
|
||
// Verify that the data is correctly stored in the tables. | ||
for _, table := range tables { | ||
for key, expectedValue := range expectedData[table.Name()] { | ||
value, ok, err := table.Get([]byte(key)) | ||
require.NoError(t, err, "Failed to get value for key %s in table %s", key, table.Name()) | ||
require.True(t, ok, "Key %s not found in table %s", key, table.Name()) | ||
require.Equal(t, expectedValue, value, | ||
"Value mismatch for key %s in table %s", key, table.Name()) | ||
} | ||
} | ||
|
||
// We should not be able to call ls on the core directories while the table holds a lock. | ||
for _, root := range roots { | ||
_, err = ls(logger, root, true, false) | ||
require.Error(t, err) | ||
} | ||
_, err = lsPaths(logger, roots, true, false) | ||
require.Error(t, err) | ||
|
||
// Even when the DB is running, it should always be possible to ls the snapshot directory. | ||
lsResult, err := ls(logger, snapshotDir, true, false) | ||
require.NoError(t, err) | ||
require.Equal(t, tableNames, lsResult) | ||
|
||
lsResult, err = lsPaths(logger, []string{snapshotDir}, true, false) | ||
require.NoError(t, err) | ||
require.Equal(t, tableNames, lsResult) | ||
|
||
err = db.Close() | ||
require.NoError(t, err) | ||
|
||
// Now that the DB is closed, we should be able to ls it. We should find all tables defined regardless of which | ||
// root directory we peer into. | ||
for _, root := range roots { | ||
lsResult, err = ls(logger, root, true, false) | ||
require.NoError(t, err) | ||
require.Equal(t, tableNames, lsResult) | ||
} | ||
|
||
lsResult, err = lsPaths(logger, roots, true, true) | ||
require.NoError(t, err) | ||
require.Equal(t, tableNames, lsResult) | ||
|
||
// Data should still be present in the snapshot directory. | ||
lsResult, err = ls(logger, snapshotDir, true, false) | ||
require.NoError(t, err) | ||
require.Equal(t, tableNames, lsResult) | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.