Skip to content

Commit 85b7f39

Browse files
h3n4lclaude
andauthored
feat: add utility commands support (show dbs, show collections, getCollectionNames) (#4)
Add support for MongoDB shell utility commands: - `show dbs` / `show databases` - list all databases - `show collections` - list collections in current database - `db.getCollectionNames()` - list collection names in current database 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 11f935e commit 85b7f39

3 files changed

Lines changed: 157 additions & 10 deletions

File tree

executor.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ func executeOperation(ctx context.Context, client *mongo.Client, database string
6161
switch op.opType {
6262
case opFind:
6363
return executeFind(ctx, client, database, op)
64+
case opShowDatabases:
65+
return executeShowDatabases(ctx, client)
66+
case opShowCollections:
67+
return executeShowCollections(ctx, client, database)
68+
case opGetCollectionNames:
69+
return executeGetCollectionNames(ctx, client, database)
6470
default:
6571
return nil, &UnsupportedOperationError{
6672
Operation: statement,
@@ -122,3 +128,40 @@ func executeFind(ctx context.Context, client *mongo.Client, database string, op
122128
RowCount: len(rows),
123129
}, nil
124130
}
131+
132+
// executeShowDatabases executes a show dbs/databases command.
133+
func executeShowDatabases(ctx context.Context, client *mongo.Client) (*Result, error) {
134+
names, err := client.ListDatabaseNames(ctx, bson.D{})
135+
if err != nil {
136+
return nil, fmt.Errorf("list databases failed: %w", err)
137+
}
138+
139+
rows := make([]string, len(names))
140+
copy(rows, names)
141+
142+
return &Result{
143+
Rows: rows,
144+
RowCount: len(rows),
145+
}, nil
146+
}
147+
148+
// executeShowCollections executes a show collections command.
149+
func executeShowCollections(ctx context.Context, client *mongo.Client, database string) (*Result, error) {
150+
names, err := client.Database(database).ListCollectionNames(ctx, bson.D{})
151+
if err != nil {
152+
return nil, fmt.Errorf("list collections failed: %w", err)
153+
}
154+
155+
rows := make([]string, len(names))
156+
copy(rows, names)
157+
158+
return &Result{
159+
Rows: rows,
160+
RowCount: len(rows),
161+
}, nil
162+
}
163+
164+
// executeGetCollectionNames executes a db.getCollectionNames() command.
165+
func executeGetCollectionNames(ctx context.Context, client *mongo.Client, database string) (*Result, error) {
166+
return executeShowCollections(ctx, client, database)
167+
}

executor_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,3 +379,99 @@ func TestCollectionAccessPatterns(t *testing.T) {
379379
})
380380
}
381381
}
382+
383+
func TestShowDatabases(t *testing.T) {
384+
client, cleanup := setupTestContainer(t)
385+
defer cleanup()
386+
387+
ctx := context.Background()
388+
389+
// Create a database by inserting a document
390+
_, err := client.Database("mydb").Collection("test").InsertOne(ctx, bson.M{"x": 1})
391+
require.NoError(t, err)
392+
393+
gc := gomongo.NewClient(client)
394+
395+
tests := []struct {
396+
name string
397+
statement string
398+
}{
399+
{"show dbs", "show dbs"},
400+
{"show databases", "show databases"},
401+
}
402+
403+
for _, tc := range tests {
404+
t.Run(tc.name, func(t *testing.T) {
405+
result, err := gc.Execute(ctx, "mydb", tc.statement)
406+
require.NoError(t, err)
407+
require.NotNil(t, result)
408+
require.GreaterOrEqual(t, result.RowCount, 1)
409+
410+
// Check that mydb is in the result
411+
found := false
412+
for _, row := range result.Rows {
413+
if row == "mydb" {
414+
found = true
415+
break
416+
}
417+
}
418+
require.True(t, found, "expected 'mydb' in database list, got: %v", result.Rows)
419+
})
420+
}
421+
}
422+
423+
func TestShowCollections(t *testing.T) {
424+
client, cleanup := setupTestContainer(t)
425+
defer cleanup()
426+
427+
ctx := context.Background()
428+
429+
// Create collections by inserting documents
430+
_, err := client.Database("testdb").Collection("users").InsertOne(ctx, bson.M{"name": "alice"})
431+
require.NoError(t, err)
432+
_, err = client.Database("testdb").Collection("orders").InsertOne(ctx, bson.M{"item": "book"})
433+
require.NoError(t, err)
434+
435+
gc := gomongo.NewClient(client)
436+
437+
result, err := gc.Execute(ctx, "testdb", "show collections")
438+
require.NoError(t, err)
439+
require.NotNil(t, result)
440+
require.Equal(t, 2, result.RowCount)
441+
442+
// Check that both collections are in the result
443+
collectionSet := make(map[string]bool)
444+
for _, row := range result.Rows {
445+
collectionSet[row] = true
446+
}
447+
require.True(t, collectionSet["users"], "expected 'users' collection")
448+
require.True(t, collectionSet["orders"], "expected 'orders' collection")
449+
}
450+
451+
func TestGetCollectionNames(t *testing.T) {
452+
client, cleanup := setupTestContainer(t)
453+
defer cleanup()
454+
455+
ctx := context.Background()
456+
457+
// Create collections by inserting documents
458+
_, err := client.Database("testdb").Collection("products").InsertOne(ctx, bson.M{"name": "widget"})
459+
require.NoError(t, err)
460+
_, err = client.Database("testdb").Collection("categories").InsertOne(ctx, bson.M{"name": "electronics"})
461+
require.NoError(t, err)
462+
463+
gc := gomongo.NewClient(client)
464+
465+
result, err := gc.Execute(ctx, "testdb", "db.getCollectionNames()")
466+
require.NoError(t, err)
467+
require.NotNil(t, result)
468+
require.Equal(t, 2, result.RowCount)
469+
470+
// Check that both collections are in the result
471+
collectionSet := make(map[string]bool)
472+
for _, row := range result.Rows {
473+
collectionSet[row] = true
474+
}
475+
require.True(t, collectionSet["products"], "expected 'products' collection")
476+
require.True(t, collectionSet["categories"], "expected 'categories' collection")
477+
}

translator.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ type operationType int
1515
const (
1616
opUnknown operationType = iota
1717
opFind
18+
opShowDatabases
19+
opShowCollections
20+
opGetCollectionNames
1821
)
1922

2023
// mongoOperation represents a parsed MongoDB operation.
@@ -69,10 +72,7 @@ func (v *mongoShellVisitor) visitStatement(ctx mongodb.IStatementContext) {
6972
if ctx.DbStatement() != nil {
7073
v.visitDbStatement(ctx.DbStatement())
7174
} else if ctx.ShellCommand() != nil {
72-
v.err = &UnsupportedOperationError{
73-
Operation: ctx.ShellCommand().GetText(),
74-
Hint: "shell commands not yet supported",
75-
}
75+
v.visitShellCommand(ctx.ShellCommand())
7676
}
7777
}
7878

@@ -81,9 +81,20 @@ func (v *mongoShellVisitor) visitDbStatement(ctx mongodb.IDbStatementContext) {
8181
case *mongodb.CollectionOperationContext:
8282
v.visitCollectionOperation(c)
8383
case *mongodb.GetCollectionNamesContext:
84+
v.operation.opType = opGetCollectionNames
85+
}
86+
}
87+
88+
func (v *mongoShellVisitor) visitShellCommand(ctx mongodb.IShellCommandContext) {
89+
switch ctx.(type) {
90+
case *mongodb.ShowDatabasesContext:
91+
v.operation.opType = opShowDatabases
92+
case *mongodb.ShowCollectionsContext:
93+
v.operation.opType = opShowCollections
94+
default:
8495
v.err = &UnsupportedOperationError{
85-
Operation: "getCollectionNames",
86-
Hint: "not yet supported",
96+
Operation: ctx.GetText(),
97+
Hint: "unknown shell command",
8798
}
8899
}
89100
}
@@ -102,10 +113,7 @@ func (v *mongoShellVisitor) visitCollectionOperation(ctx *mongodb.CollectionOper
102113
}
103114

104115
func (v *mongoShellVisitor) VisitGetCollectionNames(_ *mongodb.GetCollectionNamesContext) any {
105-
v.err = &UnsupportedOperationError{
106-
Operation: "getCollectionNames",
107-
Hint: "not yet supported",
108-
}
116+
v.operation.opType = opGetCollectionNames
109117
return nil
110118
}
111119

0 commit comments

Comments
 (0)