diff --git a/CLAUDE.md b/CLAUDE.md index 1023978..02750ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,8 +11,9 @@ gomongo/ ├── client.go # Public API: Client, NewClient, Execute ├── executor.go # Parse → Translate → Execute pipeline ├── translator.go # Walk ANTLR parse tree, build driver operations +├── method_registry.go # Registry of MongoDB methods with status and hints ├── helper_functions.go # Convert ObjectId(), ISODate(), etc. to BSON -├── errors.go # Error types (ParseError, UnsupportedOperationError) +├── errors.go # Error types (ParseError, UnsupportedOperationError, DeprecatedOperationError) ├── executor_test.go # Integration tests with testcontainers └── go.mod ``` @@ -129,3 +130,27 @@ All query results are returned as Extended JSON (Relaxed) format using `bson.Mar - **Description** — Clearly describe what the PR changes and why - **Testing** — Include information about how the changes were tested - **Breaking Changes** — Clearly mark any breaking API changes + +## Adding New Method Support + +When adding support for a new MongoDB method: + +1. **Update `method_registry.go`** — Remove the method entry or change status to `statusSupported` +2. **Update `translator.go`** — Add handler in `visitMethodCall()` for the new method +3. **Add tests** — Create integration tests in `executor_test.go` +4. **Update README** — Add the method to the supported methods list + +### Method Registry + +The `method_registry.go` file maintains metadata about MongoDB methods: + +- **statusSupported** — Method is implemented and working +- **statusDeprecated** — Method is deprecated; error includes alternative suggestion +- **statusUnsupported** — Method is recognized but not yet implemented + +When implementing a previously unsupported method, remove its entry from the registry (supported methods don't need registry entries). + +### Error Types + +- **UnsupportedOperationError** — For methods not yet implemented (includes hint) +- **DeprecatedOperationError** — For deprecated methods (includes alternative) diff --git a/README.md b/README.md index a990ffa..482a85c 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,6 @@ Go library for parsing and executing MongoDB shell syntax using the native Mongo gomongo parses MongoDB shell commands (e.g., `db.users.find()`) and executes them using the Go MongoDB driver, eliminating the need for external mongosh CLI. -## Status - -**MVP v0.1.0** - Basic functionality implemented: - -| Feature | Status | -|---------|--------| -| `find()` with filter | Supported | -| `findOne()` | Not yet supported | -| Cursor modifiers (sort, limit, skip, projection) | Parsed but ignored | -| Helper functions (ObjectId, ISODate, UUID, etc.) | Supported | -| Shell commands (show dbs, show collections) | Not yet supported | -| Collection access (dot, bracket, getCollection) | Supported | - ## Installation ```bash @@ -33,6 +20,7 @@ package main import ( "context" "fmt" + "log" "github.com/bytebase/gomongo" "go.mongodb.org/mongo-driver/v2/mongo" @@ -40,21 +28,22 @@ import ( ) func main() { + ctx := context.Background() + // Connect to MongoDB client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017")) if err != nil { - panic(err) + log.Fatal(err) } - defer client.Disconnect(context.Background()) + defer client.Disconnect(ctx) // Create gomongo client gc := gomongo.NewClient(client) - // Execute MongoDB shell command - ctx := context.Background() - result, err := gc.Execute(ctx, "mydb", `db.users.find()`) + // Execute MongoDB shell commands + result, err := gc.Execute(ctx, "mydb", `db.users.find({ age: { $gt: 25 } })`) if err != nil { - panic(err) + log.Fatal(err) } // Print results (Extended JSON format) @@ -64,49 +53,6 @@ func main() { } ``` -## Supported Operations (MVP) - -| Category | Operation | Status | -|----------|-----------|--------| -| **Read** | `find()` | Supported (with filter) | -| | `findOne()` | Not yet supported | -| **Collection Access** | dot notation | Supported (`db.users`) | -| | bracket notation | Supported (`db["user-logs"]`) | -| | getCollection | Supported (`db.getCollection("users")`) | - -## Supported Filter Syntax - -```javascript -// Simple equality -db.users.find({ name: "alice" }) - -// Comparison operators -db.users.find({ age: { $gt: 25 } }) -db.users.find({ age: { $lte: 30 } }) - -// Multiple conditions -db.users.find({ active: true, age: { $gte: 18 } }) - -// Array operators -db.users.find({ tags: { $in: ["admin", "user"] } }) -``` - -## Supported Helper Functions - -| Helper | Example | BSON Type | -|--------|---------|-----------| -| `ObjectId()` | `ObjectId("507f1f77bcf86cd799439011")` | ObjectID | -| `ISODate()` | `ISODate("2024-01-01T00:00:00Z")` | DateTime | -| `new Date()` | `new Date("2024-01-01")` | DateTime | -| `UUID()` | `UUID("550e8400-e29b-41d4-a716-446655440000")` | Binary (subtype 4) | -| `Long()` / `NumberLong()` | `Long(123)` | int64 | -| `Int32()` / `NumberInt()` | `Int32(123)` | int32 | -| `Double()` | `Double(1.5)` | float64 | -| `Decimal128()` | `Decimal128("123.45")` | Decimal128 | -| `Timestamp()` | `Timestamp(1627811580, 1)` | Timestamp | -| `/pattern/flags` | `/^test/i` | Regex | -| `RegExp()` | `RegExp("pattern", "i")` | Regex | - ## Output Format Results are returned in Extended JSON (Relaxed) format: @@ -120,13 +66,130 @@ Results are returned in Extended JSON (Relaxed) format: } ``` -## Roadmap - -Future versions will add: -- `findOne()` support -- Cursor modifiers (sort, limit, skip, projection) -- Shell commands (show dbs, show collections) - -## License - -Apache License 2.0 +## Command Reference + +### Milestone 1: Read Operations + Utility + Aggregation (Current) + +#### Utility Commands + +| Command | Syntax | Status | +|---------|--------|--------| +| show dbs | `show dbs` | Supported | +| show databases | `show databases` | Supported | +| show collections | `show collections` | Supported | +| db.getCollectionNames() | `db.getCollectionNames()` | Supported | +| db.getCollectionInfos() | `db.getCollectionInfos()` | Supported | + +#### Read Commands + +| Command | Syntax | Status | Notes | +|---------|--------|--------|-------| +| db.collection.find() | `find(query, projection)` | Supported | options deferred | +| db.collection.findOne() | `findOne(query, projection)` | Supported | | +| db.collection.countDocuments() | `countDocuments(filter)` | Supported | options deferred | +| db.collection.estimatedDocumentCount() | `estimatedDocumentCount()` | Supported | options deferred | +| db.collection.distinct() | `distinct(field, query)` | Supported | options deferred | +| db.collection.getIndexes() | `getIndexes()` | Supported | | + +#### Cursor Modifiers + +| Method | Syntax | Status | +|--------|--------|--------| +| cursor.limit() | `limit(number)` | Supported | +| cursor.skip() | `skip(number)` | Supported | +| cursor.sort() | `sort(document)` | Supported | +| cursor.count() | `count()` | Deprecated - use countDocuments() | + +#### Aggregation + +| Command | Syntax | Status | Notes | +|---------|--------|--------|-------| +| db.collection.aggregate() | `aggregate(pipeline)` | Supported | options deferred | + +#### Object Constructors + +| Constructor | Supported Syntax | Unsupported Syntax | +|-------------|------------------|-------------------| +| ObjectId() | `ObjectId()`, `ObjectId("hex")` | `new ObjectId()` | +| ISODate() | `ISODate()`, `ISODate("string")` | `new ISODate()` | +| Date() | `Date()`, `Date("string")`, `Date(timestamp)` | `new Date()` | +| UUID() | `UUID("hex")` | `new UUID()` | +| NumberInt() | `NumberInt(value)` | `new NumberInt()` | +| NumberLong() | `NumberLong(value)` | `new NumberLong()` | +| NumberDecimal() | `NumberDecimal("value")` | `new NumberDecimal()` | +| Timestamp() | `Timestamp(t, i)` | `new Timestamp()` | +| BinData() | `BinData(subtype, base64)` | | +| RegExp() | `RegExp("pattern", "flags")`, `/pattern/flags` | | + +### Milestone 2: Write Operations (Planned) + +| Command | Syntax | Status | +|---------|--------|--------| +| db.collection.insertOne() | `insertOne(document)` | Not yet supported | +| db.collection.insertMany() | `insertMany(documents)` | Not yet supported | +| db.collection.updateOne() | `updateOne(filter, update)` | Not yet supported | +| db.collection.updateMany() | `updateMany(filter, update)` | Not yet supported | +| db.collection.deleteOne() | `deleteOne(filter)` | Not yet supported | +| db.collection.deleteMany() | `deleteMany(filter)` | Not yet supported | +| db.collection.replaceOne() | `replaceOne(filter, replacement)` | Not yet supported | +| db.collection.findOneAndUpdate() | `findOneAndUpdate(filter, update)` | Not yet supported | +| db.collection.findOneAndReplace() | `findOneAndReplace(filter, replacement)` | Not yet supported | +| db.collection.findOneAndDelete() | `findOneAndDelete(filter)` | Not yet supported | + +### Milestone 3: Administrative Operations (Planned) + +#### Index Management + +| Command | Syntax | Status | +|---------|--------|--------| +| db.collection.createIndex() | `createIndex(keys)` | Not yet supported | +| db.collection.createIndexes() | `createIndexes(indexSpecs)` | Not yet supported | +| db.collection.dropIndex() | `dropIndex(index)` | Not yet supported | +| db.collection.dropIndexes() | `dropIndexes()` | Not yet supported | + +#### Collection Management + +| Command | Syntax | Status | +|---------|--------|--------| +| db.createCollection() | `db.createCollection(name)` | Not yet supported | +| db.collection.drop() | `drop()` | Not yet supported | +| db.collection.renameCollection() | `renameCollection(newName)` | Not yet supported | +| db.dropDatabase() | `db.dropDatabase()` | Not yet supported | + +#### Database Information + +| Command | Syntax | Status | +|---------|--------|--------| +| db.stats() | `db.stats()` | Not yet supported | +| db.collection.stats() | `stats()` | Not yet supported | +| db.serverStatus() | `db.serverStatus()` | Not yet supported | +| db.serverBuildInfo() | `db.serverBuildInfo()` | Not yet supported | +| db.version() | `db.version()` | Not yet supported | +| db.hostInfo() | `db.hostInfo()` | Not yet supported | +| db.listCommands() | `db.listCommands()` | Not yet supported | + +### Not Planned + +The following categories are recognized but not planned for support: + +| Category | Reason | +|----------|--------| +| Database switching (`use `, `db.getSiblingDB()`) | Database is set at connection time | +| Interactive cursor methods (`hasNext()`, `next()`, `toArray()`) | Not an interactive shell | +| JavaScript execution (`forEach()`, `map()`) | No JavaScript engine | +| Replication (`rs.*`) | Cluster administration | +| Sharding (`sh.*`) | Cluster administration | +| User/Role management | Security administration | +| Client-side encryption | Security feature | +| Atlas Stream Processing (`sp.*`) | Atlas-specific | +| Native shell functions (`cat()`, `load()`, `quit()`) | Shell-specific | + +For deprecated methods (e.g., `db.collection.insert()`, `db.collection.update()`), gomongo returns actionable error messages directing users to modern alternatives. + +## Design Principles + +1. **No database switching** - Database is set at connection time only +2. **Not an interactive shell** - No cursor iteration, REPL-style commands, or stateful operations +3. **Syntax translator, not validator** - Arguments pass directly to the Go driver; the server validates +4. **Single syntax for constructors** - Use `ObjectId()`, not `new ObjectId()` +5. **Clear error messages** - Actionable guidance for unsupported or deprecated syntax diff --git a/errors.go b/errors.go index 15f7c99..47579b5 100644 --- a/errors.go +++ b/errors.go @@ -30,3 +30,13 @@ func (e *UnsupportedOperationError) Error() string { } return fmt.Sprintf("unsupported operation %q", e.Operation) } + +// DeprecatedOperationError represents a deprecated operation with alternatives. +type DeprecatedOperationError struct { + Operation string + Alternative string +} + +func (e *DeprecatedOperationError) Error() string { + return fmt.Sprintf("%s is deprecated. Use %s instead", e.Operation, e.Alternative) +} diff --git a/executor_test.go b/executor_test.go index f27e93d..8487844 100644 --- a/executor_test.go +++ b/executor_test.go @@ -2,6 +2,7 @@ package gomongo_test import ( "context" + "slices" "strings" "testing" "time" @@ -295,7 +296,36 @@ func TestUnsupportedOperation(t *testing.T) { var unsupportedErr *gomongo.UnsupportedOperationError require.ErrorAs(t, err, &unsupportedErr) - require.Equal(t, "insertOne", unsupportedErr.Operation) + require.Equal(t, "insertOne()", unsupportedErr.Operation) +} + +func TestUnsupportedAtlasSearchIndex(t *testing.T) { + client, cleanup := setupTestContainer(t) + defer cleanup() + + gc := gomongo.NewClient(client) + ctx := context.Background() + + // Test createSearchIndex + _, err := gc.Execute(ctx, "testdb", `db.movies.createSearchIndex({ name: "default", definition: { mappings: { dynamic: true } } })`) + require.Error(t, err) + + var unsupportedErr *gomongo.UnsupportedOperationError + require.ErrorAs(t, err, &unsupportedErr) + require.Equal(t, "createSearchIndex()", unsupportedErr.Operation) + require.Contains(t, unsupportedErr.Hint, "Atlas Search Index") +} + +func TestMethodRegistryStats(t *testing.T) { + total, deprecated, unsupported := gomongo.MethodRegistryStats() + + // Verify we have a reasonable number of methods registered + require.GreaterOrEqual(t, total, 100, "expected at least 100 methods in registry") + require.GreaterOrEqual(t, deprecated, 20, "expected at least 20 deprecated methods") + require.GreaterOrEqual(t, unsupported, 80, "expected at least 80 unsupported methods") + + // Log stats for visibility + t.Logf("Method Registry Stats: total=%d, deprecated=%d, unsupported=%d", total, deprecated, unsupported) } func TestFindWithFilter(t *testing.T) { @@ -582,14 +612,7 @@ func TestShowDatabases(t *testing.T) { require.GreaterOrEqual(t, result.RowCount, 1) // Check that mydb is in the result - found := false - for _, row := range result.Rows { - if row == "mydb" { - found = true - break - } - } - require.True(t, found, "expected 'mydb' in database list, got: %v", result.Rows) + require.True(t, slices.Contains(result.Rows, "mydb"), "expected 'mydb' in database list, got: %v", result.Rows) }) } } diff --git a/go.mod b/go.mod index 6ebd7d6..0dcc4e9 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.5 require ( github.com/antlr4-go/antlr/v4 v4.13.1 - github.com/bytebase/parser v0.0.0-20260119035746-76308b5d11fd + github.com/bytebase/parser v0.0.0-20260120080341-a57d4b68030c github.com/google/uuid v1.6.0 github.com/stretchr/testify v1.11.1 github.com/testcontainers/testcontainers-go/modules/mongodb v0.40.0 diff --git a/go.sum b/go.sum index d8ad4fc..6623903 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/bytebase/antlr/v4 v4.0.0-20240827034948-8c385f108920 h1:IfmPt5o5R70NKtOrs+QHOoCgViYZelZysGxVBvV4ybA= github.com/bytebase/antlr/v4 v4.0.0-20240827034948-8c385f108920/go.mod h1:ykhjIPiv0IWpu3OGXCHdz2eUSe8UNGGD6baqjs8jSuU= -github.com/bytebase/parser v0.0.0-20260119035746-76308b5d11fd h1:JCEEza5T4CTNWZuwHe6/7mqG7Qg+q2ZiHP00UtW+NtQ= -github.com/bytebase/parser v0.0.0-20260119035746-76308b5d11fd/go.mod h1:jeak/EfutSOAuWKvrFIT2IZunhWprM7oTFBRgZ9RCxo= +github.com/bytebase/parser v0.0.0-20260120080341-a57d4b68030c h1:owIVaPTU4DrzzajK0BsuyRtScB/8JNQco7bzg4ZduLA= +github.com/bytebase/parser v0.0.0-20260120080341-a57d4b68030c/go.mod h1:jeak/EfutSOAuWKvrFIT2IZunhWprM7oTFBRgZ9RCxo= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= diff --git a/helper_functions.go b/helper_functions.go index c7aab64..5ba7350 100644 --- a/helper_functions.go +++ b/helper_functions.go @@ -52,27 +52,31 @@ func convertIsoDateHelper(ctx mongodb.IIsoDateHelperContext) (bson.DateTime, err return parseDateTime(dateStr) } -// convertDateHelper converts new Date() or Date() to primitive.DateTime or string. +// convertDateHelper converts Date() to primitive.DateTime. +// Note: 'new' keyword is no longer supported in the parser. func convertDateHelper(ctx mongodb.IDateHelperContext) (any, error) { helper, ok := ctx.(*mongodb.DateHelperContext) if !ok { return nil, fmt.Errorf("invalid Date helper context") } - hasNew := helper.NEW() != nil + if helper.StringLiteral() != nil { + dateStr := unquoteString(helper.StringLiteral().GetText()) + return parseDateTime(dateStr) + } - if helper.StringLiteral() == nil { - if hasNew { - return bson.DateTime(time.Now().UnixMilli()), nil + if helper.NUMBER() != nil { + // Date(timestamp) - timestamp in milliseconds + numStr := helper.NUMBER().GetText() + ts, err := strconv.ParseInt(numStr, 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid timestamp: %w", err) } - return time.Now().Format(time.RFC3339), nil + return bson.DateTime(ts), nil } - dateStr := unquoteString(helper.StringLiteral().GetText()) - if hasNew { - return parseDateTime(dateStr) - } - return dateStr, nil + // Date() with no arguments returns current date + return bson.DateTime(time.Now().UnixMilli()), nil } // parseDateTime parses various date formats to primitive.DateTime. diff --git a/method_registry.go b/method_registry.go new file mode 100644 index 0000000..988aeaa --- /dev/null +++ b/method_registry.go @@ -0,0 +1,1665 @@ +package gomongo + +// methodStatus represents the support status of a MongoDB method. +type methodStatus int + +const ( + statusSupported methodStatus = iota + statusDeprecated + statusUnsupported +) + +// methodContext distinguishes where a method can be called. +type methodContext int + +const ( + contextCollection methodContext = iota + contextCursor + contextDatabase + contextConnection // Mongo(), connect(), connection methods + contextReplication // rs.* methods + contextSharding // sh.* methods + contextEncryption // KeyVault, ClientEncryption methods + contextBulk // Bulk operation methods + contextPlanCache // PlanCache methods + contextStream // sp.* stream processing methods + contextNative // Native shell functions like cat(), load(), quit() +) + +// methodInfo contains metadata about a MongoDB method. +type methodInfo struct { + status methodStatus + context methodContext + alternative string // for deprecated methods: what to use instead + hint string // additional guidance for unsupported methods +} + +// methodRegistry maps method names to their metadata. +// Key format: "methodName" for unique methods, or "context:methodName" for context-specific methods. +// When looking up, first try context-specific key, then fall back to generic key. +var methodRegistry = map[string]methodInfo{ + // ============================================================ + // DEPRECATED METHODS - These have alternatives users should use + // ============================================================ + + // Collection methods (deprecated) + "collection:count": { + status: statusDeprecated, + context: contextCollection, + alternative: "countDocuments() or estimatedDocumentCount()", + }, + "collection:insert": { + status: statusDeprecated, + context: contextCollection, + alternative: "insertOne() or insertMany()", + }, + "collection:update": { + status: statusDeprecated, + context: contextCollection, + alternative: "updateOne(), updateMany(), or replaceOne()", + }, + "collection:remove": { + status: statusDeprecated, + context: contextCollection, + alternative: "deleteOne() or deleteMany()", + }, + "collection:save": { + status: statusDeprecated, + context: contextCollection, + alternative: "insertOne() or replaceOne() with upsert option", + }, + "collection:findAndModify": { + status: statusDeprecated, + context: contextCollection, + alternative: "findOneAndUpdate(), findOneAndReplace(), or findOneAndDelete()", + }, + "collection:ensureIndex": { + status: statusDeprecated, + context: contextCollection, + alternative: "createIndex()", + }, + "collection:reIndex": { + status: statusDeprecated, + context: contextCollection, + alternative: "drop and recreate indexes, or use the reIndex database command", + }, + "collection:dropIndex": { + status: statusDeprecated, + context: contextCollection, + alternative: "dropIndexes()", + }, + "collection:copyTo": { + status: statusDeprecated, + context: contextCollection, + alternative: "aggregation with $out or $merge stage", + }, + "collection:group": { + status: statusDeprecated, + context: contextCollection, + alternative: "aggregate() with $group stage", + }, + + // Cursor methods (deprecated) + "cursor:count": { + status: statusDeprecated, + context: contextCursor, + alternative: "countDocuments() or estimatedDocumentCount()", + }, + "cursor:forEach": { + status: statusDeprecated, + context: contextCursor, + alternative: "for await...of syntax or toArray()", + }, + "cursor:snapshot": { + status: statusDeprecated, + context: contextCursor, + alternative: "hint() with _id index", + }, + "cursor:maxScan": { + status: statusDeprecated, + context: contextCursor, + alternative: "maxTimeMS()", + }, + "cursor:addOption": { + status: statusDeprecated, + context: contextCursor, + alternative: "specific cursor methods like noCursorTimeout(), tailable(), etc.", + }, + + // Database methods (deprecated) + "database:addUser": { + status: statusDeprecated, + context: contextDatabase, + alternative: "createUser()", + }, + "database:removeUser": { + status: statusDeprecated, + context: contextDatabase, + alternative: "dropUser()", + }, + "database:eval": { + status: statusDeprecated, + context: contextDatabase, + alternative: "aggregation framework or application-side logic", + }, + "database:copyDatabase": { + status: statusDeprecated, + context: contextDatabase, + alternative: "mongodump and mongorestore", + }, + "database:cloneDatabase": { + status: statusDeprecated, + context: contextDatabase, + alternative: "mongodump and mongorestore", + }, + "database:cloneCollection": { + status: statusDeprecated, + context: contextDatabase, + alternative: "aggregation with $out or $merge stage", + }, + + // ============================================================ + // UNSUPPORTED METHODS - Not yet implemented but recognized + // ============================================================ + + // Write operations (unsupported - read-only for now) + "collection:insertOne": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:insertMany": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:updateOne": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:updateMany": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:deleteOne": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:deleteMany": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:replaceOne": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:findOneAndUpdate": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:findOneAndReplace": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:findOneAndDelete": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + "collection:bulkWrite": { + status: statusUnsupported, + context: contextCollection, + hint: "write operations are not supported yet", + }, + + // Index management (unsupported) + "collection:createIndex": { + status: statusUnsupported, + context: contextCollection, + hint: "index management operations are not supported yet", + }, + "collection:createIndexes": { + status: statusUnsupported, + context: contextCollection, + hint: "index management operations are not supported yet", + }, + "collection:dropIndexes": { + status: statusUnsupported, + context: contextCollection, + hint: "index management operations are not supported yet", + }, + + // Collection management (unsupported) + "collection:drop": { + status: statusUnsupported, + context: contextCollection, + hint: "collection management operations are not supported yet", + }, + "collection:renameCollection": { + status: statusUnsupported, + context: contextCollection, + hint: "collection management operations are not supported yet", + }, + + // Collection stats (unsupported) + "collection:stats": { + status: statusUnsupported, + context: contextCollection, + hint: "stats operations are not supported yet", + }, + "collection:storageSize": { + status: statusUnsupported, + context: contextCollection, + hint: "stats operations are not supported yet", + }, + "collection:totalIndexSize": { + status: statusUnsupported, + context: contextCollection, + hint: "stats operations are not supported yet", + }, + "collection:totalSize": { + status: statusUnsupported, + context: contextCollection, + hint: "stats operations are not supported yet", + }, + "collection:dataSize": { + status: statusUnsupported, + context: contextCollection, + hint: "stats operations are not supported yet", + }, + "collection:isCapped": { + status: statusUnsupported, + context: contextCollection, + hint: "stats operations are not supported yet", + }, + "collection:validate": { + status: statusUnsupported, + context: contextCollection, + hint: "validation operations are not supported yet", + }, + "collection:latencyStats": { + status: statusUnsupported, + context: contextCollection, + hint: "stats operations are not supported yet", + }, + + // Cursor terminal methods (unsupported) + "cursor:toArray": { + status: statusUnsupported, + context: contextCursor, + hint: "use find() or findOne() directly to get results", + }, + "cursor:next": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor iteration is not supported; use find() to get all results", + }, + "cursor:tryNext": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor iteration is not supported; use find() to get all results", + }, + "cursor:hasNext": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor iteration is not supported; use find() to get all results", + }, + "cursor:close": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor management is handled automatically", + }, + "cursor:isClosed": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor management is handled automatically", + }, + "cursor:isExhausted": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor management is handled automatically", + }, + "cursor:objsLeftInBatch": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor batch management is not supported", + }, + "cursor:itcount": { + status: statusUnsupported, + context: contextCursor, + hint: "use countDocuments() instead", + }, + "cursor:size": { + status: statusUnsupported, + context: contextCursor, + hint: "use countDocuments() instead", + }, + "cursor:explain": { + status: statusUnsupported, + context: contextCursor, + hint: "explain is not supported yet", + }, + "cursor:pretty": { + status: statusUnsupported, + context: contextCursor, + hint: "output is already formatted as JSON", + }, + "cursor:map": { + status: statusUnsupported, + context: contextCursor, + hint: "use aggregation $project stage for transformations", + }, + + // Cursor modifier methods (unsupported) + "cursor:batchSize": { + status: statusUnsupported, + context: contextCursor, + hint: "batch size configuration is not supported yet", + }, + "cursor:collation": { + status: statusUnsupported, + context: contextCursor, + hint: "collation is not supported yet", + }, + "cursor:comment": { + status: statusUnsupported, + context: contextCursor, + hint: "query comments are not supported yet", + }, + "cursor:hint": { + status: statusUnsupported, + context: contextCursor, + hint: "index hints are not supported yet for find operations", + }, + "cursor:max": { + status: statusUnsupported, + context: contextCursor, + hint: "index bounds are not supported yet", + }, + "cursor:min": { + status: statusUnsupported, + context: contextCursor, + hint: "index bounds are not supported yet", + }, + "cursor:maxTimeMS": { + status: statusUnsupported, + context: contextCursor, + hint: "query timeout is not supported yet", + }, + "cursor:maxAwaitTimeMS": { + status: statusUnsupported, + context: contextCursor, + hint: "await timeout is not supported yet", + }, + "cursor:noCursorTimeout": { + status: statusUnsupported, + context: contextCursor, + hint: "cursor timeout configuration is not supported yet", + }, + "cursor:readConcern": { + status: statusUnsupported, + context: contextCursor, + hint: "read concern is not supported yet", + }, + "cursor:readPref": { + status: statusUnsupported, + context: contextCursor, + hint: "read preference is not supported yet", + }, + "cursor:returnKey": { + status: statusUnsupported, + context: contextCursor, + hint: "returnKey is not supported yet", + }, + "cursor:showRecordId": { + status: statusUnsupported, + context: contextCursor, + hint: "showRecordId is not supported yet", + }, + "cursor:tailable": { + status: statusUnsupported, + context: contextCursor, + hint: "tailable cursors are not supported yet", + }, + "cursor:allowDiskUse": { + status: statusUnsupported, + context: contextCursor, + hint: "allowDiskUse is not supported yet", + }, + + // Database methods (unsupported) + "database:createCollection": { + status: statusUnsupported, + context: contextDatabase, + hint: "database management operations are not supported yet", + }, + "database:dropDatabase": { + status: statusUnsupported, + context: contextDatabase, + hint: "database management operations are not supported yet", + }, + "database:stats": { + status: statusUnsupported, + context: contextDatabase, + hint: "stats operations are not supported yet", + }, + "database:serverStatus": { + status: statusUnsupported, + context: contextDatabase, + hint: "server status is not supported yet", + }, + "database:serverBuildInfo": { + status: statusUnsupported, + context: contextDatabase, + hint: "server info is not supported yet", + }, + "database:version": { + status: statusUnsupported, + context: contextDatabase, + hint: "version info is not supported yet", + }, + "database:hostInfo": { + status: statusUnsupported, + context: contextDatabase, + hint: "host info is not supported yet", + }, + "database:listCommands": { + status: statusUnsupported, + context: contextDatabase, + hint: "command listing is not supported yet", + }, + "database:runCommand": { + status: statusUnsupported, + context: contextDatabase, + hint: "raw command execution is not supported yet", + }, + "database:adminCommand": { + status: statusUnsupported, + context: contextDatabase, + hint: "admin commands are not supported yet", + }, + "database:getName": { + status: statusUnsupported, + context: contextDatabase, + hint: "database info is not supported yet", + }, + "database:getMongo": { + status: statusUnsupported, + context: contextDatabase, + hint: "connection management is not supported yet", + }, + "database:getSiblingDB": { + status: statusUnsupported, + context: contextDatabase, + hint: "database switching is not supported yet", + }, + + // User management (unsupported) + "database:createUser": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:dropUser": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:auth": { + status: statusUnsupported, + context: contextDatabase, + hint: "authentication is handled at connection level", + }, + + // Atlas Search Index methods (unsupported) + "collection:createSearchIndex": { + status: statusUnsupported, + context: contextCollection, + hint: "Atlas Search Index operations are not supported yet", + }, + "collection:createSearchIndexes": { + status: statusUnsupported, + context: contextCollection, + hint: "Atlas Search Index operations are not supported yet", + }, + "collection:dropSearchIndex": { + status: statusUnsupported, + context: contextCollection, + hint: "Atlas Search Index operations are not supported yet", + }, + "collection:updateSearchIndex": { + status: statusUnsupported, + context: contextCollection, + hint: "Atlas Search Index operations are not supported yet", + }, + "collection:getSearchIndexes": { + status: statusUnsupported, + context: contextCollection, + hint: "Atlas Search Index operations are not supported yet", + }, + + // Watch/Change Stream methods (unsupported) + "collection:watch": { + status: statusUnsupported, + context: contextCollection, + hint: "change stream operations are not supported yet", + }, + "database:watch": { + status: statusUnsupported, + context: contextDatabase, + hint: "change stream operations are not supported yet", + }, + + // Bulk operations (unsupported) + "collection:initializeOrderedBulkOp": { + status: statusUnsupported, + context: contextCollection, + hint: "bulk operations are not supported yet", + }, + "collection:initializeUnorderedBulkOp": { + status: statusUnsupported, + context: contextCollection, + hint: "bulk operations are not supported yet", + }, + + // Plan cache methods (unsupported) + "collection:getPlanCache": { + status: statusUnsupported, + context: contextCollection, + hint: "plan cache operations are not supported yet", + }, + + // Additional collection methods (unsupported) + "collection:hideIndex": { + status: statusUnsupported, + context: contextCollection, + hint: "index management operations are not supported yet", + }, + "collection:unhideIndex": { + status: statusUnsupported, + context: contextCollection, + hint: "index management operations are not supported yet", + }, + "collection:compactStructuredEncryptionData": { + status: statusUnsupported, + context: contextCollection, + hint: "encryption operations are not supported yet", + }, + + // Additional database methods (unsupported) + "database:currentOp": { + status: statusUnsupported, + context: contextDatabase, + hint: "operation monitoring is not supported yet", + }, + "database:killOp": { + status: statusUnsupported, + context: contextDatabase, + hint: "operation management is not supported yet", + }, + "database:fsyncLock": { + status: statusUnsupported, + context: contextDatabase, + hint: "fsync operations are not supported yet", + }, + "database:fsyncUnlock": { + status: statusUnsupported, + context: contextDatabase, + hint: "fsync operations are not supported yet", + }, + "database:setProfilingLevel": { + status: statusUnsupported, + context: contextDatabase, + hint: "profiling operations are not supported yet", + }, + "database:getProfilingStatus": { + status: statusUnsupported, + context: contextDatabase, + hint: "profiling operations are not supported yet", + }, + "database:setLogLevel": { + status: statusUnsupported, + context: contextDatabase, + hint: "log management is not supported yet", + }, + "database:getLogComponents": { + status: statusUnsupported, + context: contextDatabase, + hint: "log management is not supported yet", + }, + "database:rotateCertificates": { + status: statusUnsupported, + context: contextDatabase, + hint: "certificate operations are not supported yet", + }, + "database:shutdownServer": { + status: statusUnsupported, + context: contextDatabase, + hint: "server management is not supported yet", + }, + + // User/Role management (unsupported) + "database:getUser": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:getUsers": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:updateUser": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:changeUserPassword": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:dropAllUsers": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:grantRolesToUser": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:revokeRolesFromUser": { + status: statusUnsupported, + context: contextDatabase, + hint: "user management operations are not supported yet", + }, + "database:createRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:dropRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:getRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:getRoles": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:updateRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:dropAllRoles": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:grantPrivilegesToRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:revokePrivilegesFromRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:grantRolesToRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + "database:revokeRolesFromRole": { + status: statusUnsupported, + context: contextDatabase, + hint: "role management operations are not supported yet", + }, + + // ============================================================ + // CONNECTION METHODS (17) - Mongo(), connect(), connection chain + // ============================================================ + "connection:Mongo": { + status: statusUnsupported, + context: contextConnection, + hint: "connection is handled at client creation time", + }, + "connection:connect": { + status: statusUnsupported, + context: contextConnection, + hint: "connection is handled at client creation time", + }, + "connection:getDB": { + status: statusUnsupported, + context: contextConnection, + hint: "database is set at connection time", + }, + "connection:getDBNames": { + status: statusUnsupported, + context: contextConnection, + hint: "use 'show dbs' instead", + }, + "connection:getDBs": { + status: statusUnsupported, + context: contextConnection, + hint: "use 'show dbs' instead", + }, + "connection:getReadPrefMode": { + status: statusUnsupported, + context: contextConnection, + hint: "read preference is set at connection time", + }, + "connection:getReadPrefTagSet": { + status: statusUnsupported, + context: contextConnection, + hint: "read preference is set at connection time", + }, + "connection:getURI": { + status: statusUnsupported, + context: contextConnection, + hint: "connection URI is set at client creation time", + }, + "connection:getWriteConcern": { + status: statusUnsupported, + context: contextConnection, + hint: "write concern is set at connection time", + }, + "connection:setCausalConsistency": { + status: statusUnsupported, + context: contextConnection, + hint: "session management is not supported", + }, + "connection:setReadPref": { + status: statusUnsupported, + context: contextConnection, + hint: "read preference is set at connection time", + }, + "connection:setWriteConcern": { + status: statusUnsupported, + context: contextConnection, + hint: "write concern is set at connection time", + }, + "connection:startSession": { + status: statusUnsupported, + context: contextConnection, + hint: "session management is not supported", + }, + "connection:watch": { + status: statusUnsupported, + context: contextConnection, + hint: "change streams are not supported", + }, + "connection:Session": { + status: statusUnsupported, + context: contextConnection, + hint: "session management is not supported", + }, + "connection:SessionOptions": { + status: statusUnsupported, + context: contextConnection, + hint: "session management is not supported", + }, + + // ============================================================ + // REPLICATION METHODS (15) - rs.* methods + // ============================================================ + "replication:add": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:addArb": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:conf": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:freeze": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:help": { + status: statusUnsupported, + context: contextReplication, + hint: "help system is not supported", + }, + "replication:initiate": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:printReplicationInfo": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:printSecondaryReplicationInfo": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:reconfig": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:reconfigForPSASet": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:remove": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:status": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:stepDown": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + "replication:syncFrom": { + status: statusUnsupported, + context: contextReplication, + hint: "replica set administration is not supported", + }, + + // ============================================================ + // SHARDING METHODS (49) - sh.* methods + // ============================================================ + "sharding:abortMoveCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:abortReshardCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:abortUnshardCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:addShard": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:addShardTag": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:addShardToZone": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:addTagRange": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:balancerCollectionStatus": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:checkMetadataConsistency": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:commitReshardCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:disableAutoMerger": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:disableAutoSplit": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:disableBalancing": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:disableMigrations": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:enableAutoMerger": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:enableAutoSplit": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:enableBalancing": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:enableMigrations": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:enableSharding": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:getBalancerState": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:getShardedDataDistribution": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:help": { + status: statusUnsupported, + context: contextSharding, + hint: "help system is not supported", + }, + "sharding:isBalancerRunning": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:isConfigShardEnabled": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:listShards": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:moveChunk": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:moveCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:moveRange": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:removeRangeFromZone": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:removeShardFromZone": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:removeShardTag": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:removeTagRange": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:reshardCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:setBalancerState": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:shardAndDistributeCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:shardCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:splitAt": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:splitFind": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:startAutoMerger": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:startBalancer": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:status": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:stopAutoMerger": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:stopBalancer": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:unshardCollection": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:updateZoneKeyRange": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:waitForBalancer": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:waitForBalancerOff": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + "sharding:waitForPingChange": { + status: statusUnsupported, + context: contextSharding, + hint: "sharding administration is not supported", + }, + + // ============================================================ + // ENCRYPTION METHODS (18) - KeyVault, ClientEncryption + // ============================================================ + "encryption:ClientEncryption.createEncryptedCollection": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:ClientEncryption.decrypt": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:ClientEncryption.encrypt": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:ClientEncryption.encryptExpression": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:getClientEncryption": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:getKeyVault": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.addKeyAlternateName": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.addKeyAltName": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.createKey": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.createDataKey": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.deleteKey": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.getKey": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.getKeyByAltName": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.getKeys": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.removeKeyAlternateName": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.removeKeyAltName": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + "encryption:KeyVault.rewrapManyDataKey": { + status: statusUnsupported, + context: contextEncryption, + hint: "client-side encryption is not supported", + }, + + // ============================================================ + // BULK OPERATION METHODS (22) + // ============================================================ + "bulk:Bulk": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.execute": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.arrayFilters": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.collation": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.delete": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.deleteOne": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.hint": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.remove": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.removeOne": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.replaceOne": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.update": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.updateOne": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.find.upsert": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.getOperations": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.insert": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.toJSON": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Bulk.toString": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:initializeOrderedBulkOp": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:initializeUnorderedBulkOp": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + "bulk:Mongo.bulkWrite": { + status: statusUnsupported, + context: contextBulk, + hint: "use individual write commands instead", + }, + + // ============================================================ + // PLAN CACHE METHODS (6) + // ============================================================ + "plancache:getPlanCache": { + status: statusUnsupported, + context: contextPlanCache, + hint: "plan cache operations are not supported", + }, + "plancache:PlanCache.clear": { + status: statusUnsupported, + context: contextPlanCache, + hint: "plan cache operations are not supported", + }, + "plancache:PlanCache.clearPlansByQuery": { + status: statusUnsupported, + context: contextPlanCache, + hint: "plan cache operations are not supported", + }, + "plancache:PlanCache.help": { + status: statusUnsupported, + context: contextPlanCache, + hint: "help system is not supported", + }, + "plancache:PlanCache.list": { + status: statusUnsupported, + context: contextPlanCache, + hint: "plan cache operations are not supported", + }, + + // ============================================================ + // STREAM PROCESSING METHODS (10) - sp.* Atlas Stream Processing + // ============================================================ + "stream:createStreamProcessor": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:listConnections": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:listStreamProcessors": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:process": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:processor.drop": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:processor.sample": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:processor.start": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:processor.stats": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + "stream:processor.stop": { + status: statusUnsupported, + context: contextStream, + hint: "Atlas Stream Processing is not supported", + }, + + // ============================================================ + // NATIVE SHELL METHODS (18) - top-level functions + // ============================================================ + "native:_isWindows": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:_rand": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:cat": { + status: statusUnsupported, + context: contextNative, + hint: "file system operations are not supported", + }, + "native:getHostName": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:getMemInfo": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:hostname": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:isInteractive": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:listFiles": { + status: statusUnsupported, + context: contextNative, + hint: "file system operations are not supported", + }, + "native:load": { + status: statusUnsupported, + context: contextNative, + hint: "file system operations are not supported", + }, + "native:ls": { + status: statusUnsupported, + context: contextNative, + hint: "file system operations are not supported", + }, + "native:md5sumFile": { + status: statusUnsupported, + context: contextNative, + hint: "file system operations are not supported", + }, + "native:mkdir": { + status: statusUnsupported, + context: contextNative, + hint: "file system operations are not supported", + }, + "native:quit": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:removeFile": { + status: statusUnsupported, + context: contextNative, + hint: "file system operations are not supported", + }, + "native:sleep": { + status: statusUnsupported, + context: contextNative, + hint: "shell-specific function is not supported", + }, + "native:version": { + status: statusUnsupported, + context: contextNative, + hint: "use db.version() instead", + }, + "native:passwordPrompt": { + status: statusUnsupported, + context: contextNative, + hint: "interactive shell feature is not supported", + }, + + // ============================================================ + // ADDITIONAL DATABASE METHODS from milestone doc + // ============================================================ + "database:aggregate": { + status: statusUnsupported, + context: contextDatabase, + hint: "database-level aggregation is not supported yet", + }, + "database:commandHelp": { + status: statusUnsupported, + context: contextDatabase, + hint: "help system is not supported", + }, + "database:createView": { + status: statusUnsupported, + context: contextDatabase, + hint: "view creation is not supported yet", + }, + "database:getCollection": { + status: statusUnsupported, + context: contextDatabase, + hint: "use db.collectionName syntax directly", + }, + "database:getReplicationInfo": { + status: statusUnsupported, + context: contextDatabase, + hint: "replication information is not supported", + }, + "database:hello": { + status: statusUnsupported, + context: contextDatabase, + hint: "cluster state is not supported", + }, + "database:help": { + status: statusUnsupported, + context: contextDatabase, + hint: "help system is not supported", + }, + "database:logout": { + status: statusDeprecated, + context: contextDatabase, + alternative: "close connection and reconnect", + }, + "database:printCollectionStats": { + status: statusUnsupported, + context: contextDatabase, + hint: "use db.collection.stats() instead", + }, + "database:printReplicationInfo": { + status: statusUnsupported, + context: contextDatabase, + hint: "replication information is not supported", + }, + "database:printSecondaryReplicationInfo": { + status: statusUnsupported, + context: contextDatabase, + hint: "replication information is not supported", + }, + "database:printShardingStatus": { + status: statusUnsupported, + context: contextDatabase, + hint: "sharding information is not supported", + }, + "database:serverCmdLineOpts": { + status: statusUnsupported, + context: contextDatabase, + hint: "server configuration is not supported", + }, + "database:checkMetadataConsistency": { + status: statusUnsupported, + context: contextDatabase, + hint: "cluster administration is not supported", + }, + + // ============================================================ + // ADDITIONAL COLLECTION METHODS from milestone doc + // ============================================================ + "collection:analyzeShardKey": { + status: statusUnsupported, + context: contextCollection, + hint: "sharding features are not supported", + }, + "collection:configureQueryAnalyzer": { + status: statusUnsupported, + context: contextCollection, + hint: "sharding features are not supported", + }, + "collection:explain": { + status: statusUnsupported, + context: contextCollection, + hint: "explain is not supported yet", + }, + "collection:getShardDistribution": { + status: statusUnsupported, + context: contextCollection, + hint: "sharding features are not supported", + }, + "collection:getShardVersion": { + status: statusUnsupported, + context: contextCollection, + hint: "sharding features are not supported", + }, + "collection:mapReduce": { + status: statusDeprecated, + context: contextCollection, + alternative: "aggregate() with $group and other stages", + }, +} + +// MethodRegistryStats returns statistics about the method registry. +func MethodRegistryStats() (total, deprecated, unsupported int) { + for _, info := range methodRegistry { + total++ + switch info.status { + case statusDeprecated: + deprecated++ + case statusUnsupported: + unsupported++ + } + } + return total, deprecated, unsupported +} + +// lookupMethod looks up a method in the registry. +// It first tries the context-specific key, then falls back to a generic lookup. +func lookupMethod(ctx methodContext, methodName string) (methodInfo, bool) { + var contextPrefix string + switch ctx { + case contextCollection: + contextPrefix = "collection:" + case contextCursor: + contextPrefix = "cursor:" + case contextDatabase: + contextPrefix = "database:" + case contextConnection: + contextPrefix = "connection:" + case contextReplication: + contextPrefix = "replication:" + case contextSharding: + contextPrefix = "sharding:" + case contextEncryption: + contextPrefix = "encryption:" + case contextBulk: + contextPrefix = "bulk:" + case contextPlanCache: + contextPrefix = "plancache:" + case contextStream: + contextPrefix = "stream:" + case contextNative: + contextPrefix = "native:" + } + + // Try context-specific lookup first + if info, ok := methodRegistry[contextPrefix+methodName]; ok { + return info, true + } + + // Fall back to generic lookup (for methods that are the same across contexts) + if info, ok := methodRegistry[methodName]; ok { + return info, true + } + + return methodInfo{}, false +} diff --git a/translator.go b/translator.go index 3a9a5a9..ce853a7 100644 --- a/translator.go +++ b/translator.go @@ -180,53 +180,56 @@ func (v *mongoShellVisitor) extractGetCollectionInfosArgs(ctx *mongodb.GetCollec v.operation.filter = filter } -func (v *mongoShellVisitor) extractDistinctArgs(ctx *mongodb.GenericMethodContext) { - args := ctx.Arguments() +// extractCountDocumentsArgsFromMethod extracts arguments from CountDocumentsMethodContext. +func (v *mongoShellVisitor) extractCountDocumentsArgsFromMethod(ctx mongodb.ICountDocumentsMethodContext) { + method, ok := ctx.(*mongodb.CountDocumentsMethodContext) + if !ok { + return + } + v.extractArgumentsForCountDocuments(method.Arguments()) +} + +// extractArgumentsForCountDocuments extracts countDocuments arguments from IArgumentsContext. +func (v *mongoShellVisitor) extractArgumentsForCountDocuments(args mongodb.IArgumentsContext) { if args == nil { - v.err = fmt.Errorf("distinct() requires a field name argument") return } argsCtx, ok := args.(*mongodb.ArgumentsContext) if !ok { - v.err = fmt.Errorf("distinct() requires a field name argument") return } allArgs := argsCtx.AllArgument() if len(allArgs) == 0 { - v.err = fmt.Errorf("distinct() requires a field name argument") return } - // First argument is the field name (required) + // First argument is the filter (optional) firstArg, ok := allArgs[0].(*mongodb.ArgumentContext) if !ok { - v.err = fmt.Errorf("distinct() requires a field name argument") return } valueCtx := firstArg.Value() if valueCtx == nil { - v.err = fmt.Errorf("distinct() requires a field name argument") return } - literalValue, ok := valueCtx.(*mongodb.LiteralValueContext) + docValue, ok := valueCtx.(*mongodb.DocumentValueContext) if !ok { - v.err = fmt.Errorf("distinct() field name must be a string") + v.err = fmt.Errorf("countDocuments() filter must be a document") return } - stringLiteral, ok := literalValue.Literal().(*mongodb.StringLiteralValueContext) - if !ok { - v.err = fmt.Errorf("distinct() field name must be a string") + filter, err := convertDocument(docValue.Document()) + if err != nil { + v.err = fmt.Errorf("invalid filter: %w", err) return } + v.operation.filter = filter - v.operation.distinctField = unquoteString(stringLiteral.StringLiteral().GetText()) - - // Second argument is the filter (optional) + // Second argument is the options (optional) if len(allArgs) < 2 { return } @@ -236,55 +239,119 @@ func (v *mongoShellVisitor) extractDistinctArgs(ctx *mongodb.GenericMethodContex return } - filterValueCtx := secondArg.Value() - if filterValueCtx == nil { + optionsValueCtx := secondArg.Value() + if optionsValueCtx == nil { return } - docValue, ok := filterValueCtx.(*mongodb.DocumentValueContext) + optionsDocValue, ok := optionsValueCtx.(*mongodb.DocumentValueContext) if !ok { - v.err = fmt.Errorf("distinct() filter must be a document") + v.err = fmt.Errorf("countDocuments() options must be a document") return } - filter, err := convertDocument(docValue.Document()) + optionsDoc, err := convertDocument(optionsDocValue.Document()) if err != nil { - v.err = fmt.Errorf("invalid filter: %w", err) + v.err = fmt.Errorf("invalid options: %w", err) return } - v.operation.filter = filter + + // Extract supported options: hint, limit, skip + for _, elem := range optionsDoc { + switch elem.Key { + case "hint": + v.operation.hint = elem.Value + case "limit": + if val, ok := elem.Value.(int32); ok { + limit := int64(val) + v.operation.limit = &limit + } else if val, ok := elem.Value.(int64); ok { + v.operation.limit = &val + } + case "skip": + if val, ok := elem.Value.(int32); ok { + skip := int64(val) + v.operation.skip = &skip + } else if val, ok := elem.Value.(int64); ok { + v.operation.skip = &val + } + } + } } -func (v *mongoShellVisitor) extractCountDocumentsArgs(ctx *mongodb.GenericMethodContext) { - args := ctx.Arguments() +// extractDistinctArgsFromMethod extracts arguments from DistinctMethodContext. +func (v *mongoShellVisitor) extractDistinctArgsFromMethod(ctx mongodb.IDistinctMethodContext) { + method, ok := ctx.(*mongodb.DistinctMethodContext) + if !ok { + return + } + v.extractArgumentsForDistinct(method.Arguments()) +} + +// extractArgumentsForDistinct extracts distinct arguments from IArgumentsContext. +func (v *mongoShellVisitor) extractArgumentsForDistinct(args mongodb.IArgumentsContext) { if args == nil { + v.err = fmt.Errorf("distinct() requires a field name argument") return } argsCtx, ok := args.(*mongodb.ArgumentsContext) if !ok { + v.err = fmt.Errorf("distinct() requires a field name argument") return } allArgs := argsCtx.AllArgument() if len(allArgs) == 0 { + v.err = fmt.Errorf("distinct() requires a field name argument") return } - // First argument is the filter (optional) + // First argument is the field name (required) firstArg, ok := allArgs[0].(*mongodb.ArgumentContext) if !ok { + v.err = fmt.Errorf("distinct() requires a field name argument") return } valueCtx := firstArg.Value() if valueCtx == nil { + v.err = fmt.Errorf("distinct() requires a field name argument") return } - docValue, ok := valueCtx.(*mongodb.DocumentValueContext) + literalValue, ok := valueCtx.(*mongodb.LiteralValueContext) if !ok { - v.err = fmt.Errorf("countDocuments() filter must be a document") + v.err = fmt.Errorf("distinct() field name must be a string") + return + } + + stringLiteral, ok := literalValue.Literal().(*mongodb.StringLiteralValueContext) + if !ok { + v.err = fmt.Errorf("distinct() field name must be a string") + return + } + + v.operation.distinctField = unquoteString(stringLiteral.StringLiteral().GetText()) + + // Second argument is the filter (optional) + if len(allArgs) < 2 { + return + } + + secondArg, ok := allArgs[1].(*mongodb.ArgumentContext) + if !ok { + return + } + + filterValueCtx := secondArg.Value() + if filterValueCtx == nil { + return + } + + docValue, ok := filterValueCtx.(*mongodb.DocumentValueContext) + if !ok { + v.err = fmt.Errorf("distinct() filter must be a document") return } @@ -294,55 +361,63 @@ func (v *mongoShellVisitor) extractCountDocumentsArgs(ctx *mongodb.GenericMethod return } v.operation.filter = filter +} - // Second argument is the options (optional) - if len(allArgs) < 2 { +// extractAggregationPipelineFromMethod extracts pipeline from AggregateMethodContext. +func (v *mongoShellVisitor) extractAggregationPipelineFromMethod(ctx mongodb.IAggregateMethodContext) { + method, ok := ctx.(*mongodb.AggregateMethodContext) + if !ok { return } + v.extractArgumentsForAggregate(method.Arguments()) +} - secondArg, ok := allArgs[1].(*mongodb.ArgumentContext) +// extractArgumentsForAggregate extracts aggregate pipeline from IArgumentsContext. +func (v *mongoShellVisitor) extractArgumentsForAggregate(args mongodb.IArgumentsContext) { + if args == nil { + // Empty pipeline: aggregate() + v.operation.pipeline = bson.A{} + return + } + + argsCtx, ok := args.(*mongodb.ArgumentsContext) if !ok { + v.err = fmt.Errorf("aggregate() requires an array argument") return } - optionsValueCtx := secondArg.Value() - if optionsValueCtx == nil { + allArgs := argsCtx.AllArgument() + if len(allArgs) == 0 { + v.operation.pipeline = bson.A{} return } - optionsDocValue, ok := optionsValueCtx.(*mongodb.DocumentValueContext) + // First argument should be the pipeline array + firstArg, ok := allArgs[0].(*mongodb.ArgumentContext) if !ok { - v.err = fmt.Errorf("countDocuments() options must be a document") + v.err = fmt.Errorf("aggregate() requires an array argument") return } - optionsDoc, err := convertDocument(optionsDocValue.Document()) - if err != nil { - v.err = fmt.Errorf("invalid options: %w", err) + valueCtx := firstArg.Value() + if valueCtx == nil { + v.err = fmt.Errorf("aggregate() requires an array argument") return } - // Extract supported options: hint, limit, skip - for _, elem := range optionsDoc { - switch elem.Key { - case "hint": - v.operation.hint = elem.Value - case "limit": - if val, ok := elem.Value.(int32); ok { - limit := int64(val) - v.operation.limit = &limit - } else if val, ok := elem.Value.(int64); ok { - v.operation.limit = &val - } - case "skip": - if val, ok := elem.Value.(int32); ok { - skip := int64(val) - v.operation.skip = &skip - } else if val, ok := elem.Value.(int64); ok { - v.operation.skip = &val - } - } + arrayValue, ok := valueCtx.(*mongodb.ArrayValueContext) + if !ok { + v.err = fmt.Errorf("aggregate() requires an array argument, got %T", valueCtx) + return + } + + pipeline, err := convertArray(arrayValue.Array()) + if err != nil { + v.err = fmt.Errorf("invalid aggregation pipeline: %w", err) + return } + + v.operation.pipeline = pipeline } func (v *mongoShellVisitor) extractCollectionName(ctx mongodb.ICollectionAccessContext) string { @@ -520,67 +595,42 @@ func (v *mongoShellVisitor) extractProjection(ctx mongodb.IProjectionMethodConte v.operation.projection = projection } -func (v *mongoShellVisitor) extractAggregationPipeline(ctx *mongodb.GenericMethodContext) { - args := ctx.Arguments() - if args == nil { - // Empty pipeline: aggregate() - v.operation.pipeline = bson.A{} - return - } - - argsCtx, ok := args.(*mongodb.ArgumentsContext) - if !ok { - v.err = fmt.Errorf("aggregate() requires an array argument") - return - } - - allArgs := argsCtx.AllArgument() - if len(allArgs) == 0 { - v.operation.pipeline = bson.A{} - return - } - - // First argument should be the pipeline array - firstArg, ok := allArgs[0].(*mongodb.ArgumentContext) - if !ok { - v.err = fmt.Errorf("aggregate() requires an array argument") - return - } - - valueCtx := firstArg.Value() - if valueCtx == nil { - v.err = fmt.Errorf("aggregate() requires an array argument") - return - } - - arrayValue, ok := valueCtx.(*mongodb.ArrayValueContext) - if !ok { - v.err = fmt.Errorf("aggregate() requires an array argument, got %T", valueCtx) - return - } - - pipeline, err := convertArray(arrayValue.Array()) - if err != nil { - v.err = fmt.Errorf("invalid aggregation pipeline: %w", err) - return - } - - v.operation.pipeline = pipeline -} - func (v *mongoShellVisitor) visitMethodCall(ctx mongodb.IMethodCallContext) { mc, ok := ctx.(*mongodb.MethodCallContext) if !ok { return } + // Determine method context for error messages + getMethodContext := func() methodContext { + if v.operation.opType == opFind || v.operation.opType == opFindOne { + return contextCursor + } + return contextCollection + } + + // Supported read operations if mc.FindMethod() != nil { v.operation.opType = opFind v.extractFindFilter(mc.FindMethod()) } else if mc.FindOneMethod() != nil { v.operation.opType = opFindOne v.extractFindOneFilter(mc.FindOneMethod()) + } else if mc.CountDocumentsMethod() != nil { + v.operation.opType = opCountDocuments + v.extractCountDocumentsArgsFromMethod(mc.CountDocumentsMethod()) + } else if mc.EstimatedDocumentCountMethod() != nil { + v.operation.opType = opEstimatedDocumentCount + } else if mc.DistinctMethod() != nil { + v.operation.opType = opDistinct + v.extractDistinctArgsFromMethod(mc.DistinctMethod()) + } else if mc.AggregateMethod() != nil { + v.operation.opType = opAggregate + v.extractAggregationPipelineFromMethod(mc.AggregateMethod()) + } else if mc.GetIndexesMethod() != nil { + v.operation.opType = opGetIndexes } else if mc.SortMethod() != nil { + // Supported cursor modifiers v.extractSort(mc.SortMethod()) } else if mc.LimitMethod() != nil { v.extractLimit(mc.LimitMethod()) @@ -588,37 +638,180 @@ func (v *mongoShellVisitor) visitMethodCall(ctx mongodb.IMethodCallContext) { v.extractSkip(mc.SkipMethod()) } else if mc.ProjectionMethod() != nil { v.extractProjection(mc.ProjectionMethod()) + } else if mc.CountMethod() != nil { + // Deprecated cursor method + v.handleUnsupportedMethod(contextCursor, "count") + } else if mc.InsertOneMethod() != nil { + // Unsupported write operations + v.handleUnsupportedMethod(contextCollection, "insertOne") + } else if mc.InsertManyMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "insertMany") + } else if mc.UpdateOneMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "updateOne") + } else if mc.UpdateManyMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "updateMany") + } else if mc.DeleteOneMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "deleteOne") + } else if mc.DeleteManyMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "deleteMany") + } else if mc.ReplaceOneMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "replaceOne") + } else if mc.FindOneAndUpdateMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "findOneAndUpdate") + } else if mc.FindOneAndReplaceMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "findOneAndReplace") + } else if mc.FindOneAndDeleteMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "findOneAndDelete") + } else if mc.CreateIndexMethod() != nil { + // Unsupported index operations + v.handleUnsupportedMethod(contextCollection, "createIndex") + } else if mc.CreateIndexesMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "createIndexes") + } else if mc.DropIndexMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "dropIndex") + } else if mc.DropIndexesMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "dropIndexes") + } else if mc.DropMethod() != nil { + // Unsupported collection management + v.handleUnsupportedMethod(contextCollection, "drop") + } else if mc.RenameCollectionMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "renameCollection") + } else if mc.StatsMethod() != nil { + // Unsupported stats operations + v.handleUnsupportedMethod(contextCollection, "stats") + } else if mc.StorageSizeMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "storageSize") + } else if mc.TotalIndexSizeMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "totalIndexSize") + } else if mc.TotalSizeMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "totalSize") + } else if mc.DataSizeMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "dataSize") + } else if mc.IsCappedMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "isCapped") + } else if mc.ValidateMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "validate") + } else if mc.LatencyStatsMethod() != nil { + v.handleUnsupportedMethod(contextCollection, "latencyStats") + } else if mc.BatchSizeMethod() != nil { + // Unsupported cursor methods + v.handleUnsupportedMethod(contextCursor, "batchSize") + } else if mc.CloseMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "close") + } else if mc.CollationMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "collation") + } else if mc.CommentMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "comment") + } else if mc.ExplainMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "explain") + } else if mc.ForEachMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "forEach") + } else if mc.HasNextMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "hasNext") + } else if mc.HintMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "hint") + } else if mc.IsClosedMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "isClosed") + } else if mc.IsExhaustedMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "isExhausted") + } else if mc.ItcountMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "itcount") + } else if mc.MapMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "map") + } else if mc.MaxMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "max") + } else if mc.MaxAwaitTimeMSMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "maxAwaitTimeMS") + } else if mc.MaxTimeMSMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "maxTimeMS") + } else if mc.MinMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "min") + } else if mc.NextMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "next") + } else if mc.NoCursorTimeoutMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "noCursorTimeout") + } else if mc.ObjsLeftInBatchMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "objsLeftInBatch") + } else if mc.PrettyMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "pretty") + } else if mc.ReadConcernMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "readConcern") + } else if mc.ReadPrefMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "readPref") + } else if mc.ReturnKeyMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "returnKey") + } else if mc.ShowRecordIdMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "showRecordId") + } else if mc.SizeMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "size") + } else if mc.TailableMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "tailable") + } else if mc.ToArrayMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "toArray") + } else if mc.TryNextMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "tryNext") + } else if mc.AllowDiskUseMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "allowDiskUse") + } else if mc.AddOptionMethod() != nil { + v.handleUnsupportedMethod(contextCursor, "addOption") } else if gm := mc.GenericMethod(); gm != nil { + // Fallback for any methods not explicitly handled above gmCtx, ok := gm.(*mongodb.GenericMethodContext) if !ok { return } methodName := gmCtx.Identifier().GetText() + + // Handle supported methods that may come through genericMethod + // (e.g., aggregate() with no arguments goes to genericMethod, not aggregateMethod) switch methodName { case "aggregate": v.operation.opType = opAggregate - v.extractAggregationPipeline(gmCtx) - case "getIndexes": - v.operation.opType = opGetIndexes + v.extractArgumentsForAggregate(gmCtx.Arguments()) case "countDocuments": v.operation.opType = opCountDocuments - v.extractCountDocumentsArgs(gmCtx) + v.extractArgumentsForCountDocuments(gmCtx.Arguments()) case "estimatedDocumentCount": v.operation.opType = opEstimatedDocumentCount case "distinct": v.operation.opType = opDistinct - v.extractDistinctArgs(gmCtx) - case "count": - // cursor.count() is deprecated - v.err = &UnsupportedOperationError{ - Operation: "count()", - Hint: "MongoDB drivers deprecate their respective cursor and collection count() APIs in favor of countDocuments() and estimatedDocumentCount()", - } + v.extractArgumentsForDistinct(gmCtx.Arguments()) + case "getIndexes": + v.operation.opType = opGetIndexes default: - v.err = &UnsupportedOperationError{ - Operation: methodName, - Hint: "unknown method", - } + v.handleUnsupportedMethod(getMethodContext(), methodName) + } + } +} + +// handleUnsupportedMethod checks the method registry and returns appropriate errors. +func (v *mongoShellVisitor) handleUnsupportedMethod(ctx methodContext, methodName string) { + info, found := lookupMethod(ctx, methodName) + if !found { + // Method not in registry - unknown method + v.err = &UnsupportedOperationError{ + Operation: methodName + "()", + Hint: "unknown method", + } + return + } + + switch info.status { + case statusDeprecated: + v.err = &DeprecatedOperationError{ + Operation: methodName + "()", + Alternative: info.alternative, + } + case statusUnsupported: + v.err = &UnsupportedOperationError{ + Operation: methodName + "()", + Hint: info.hint, + } + case statusSupported: + // This shouldn't happen - supported methods should be handled explicitly + v.err = &UnsupportedOperationError{ + Operation: methodName + "()", + Hint: "method is supported but not handled", } } }