@@ -100,6 +100,11 @@ struct MongoShellParser {
100100 return . runCommand( command: arg)
101101 }
102102
103+ // db["collection"].method(args) bracket notation
104+ if trimmed. hasPrefix ( " db[ " ) {
105+ return try parseBracketExpression ( trimmed)
106+ }
107+
103108 // db.collection.method(args) pattern
104109 guard trimmed. hasPrefix ( " db. " ) else {
105110 throw MongoShellParseError . invalidSyntax ( " Query must start with 'db.' or be a JSON command " )
@@ -110,23 +115,133 @@ struct MongoShellParser {
110115
111116 // MARK: - Private Parsing
112117
118+ /// Parse db["collection"].method(args) bracket notation.
119+ /// Supports both double and single quotes around the collection name.
120+ private static func parseBracketExpression( _ input: String ) throws -> MongoOperation {
121+ // input starts with db[
122+ let afterBracket = String ( input. dropFirst ( 3 ) ) // drop "db["
123+
124+ // Determine quote character (" or ')
125+ guard let quoteChar = afterBracket. first, quoteChar == " \" " || quoteChar == " ' " else {
126+ throw MongoShellParseError . invalidSyntax ( " Expected quoted collection name in db[...] " )
127+ }
128+
129+ // Find closing quote (handle escaped quotes)
130+ var collectionName = " "
131+ var i = afterBracket. index ( after: afterBracket. startIndex)
132+ var escapeNext = false
133+ while i < afterBracket. endIndex {
134+ let ch = afterBracket [ i]
135+ if escapeNext {
136+ collectionName. append ( ch)
137+ escapeNext = false
138+ i = afterBracket. index ( after: i)
139+ continue
140+ }
141+ if ch == " \\ " {
142+ escapeNext = true
143+ i = afterBracket. index ( after: i)
144+ continue
145+ }
146+ if ch == quoteChar {
147+ break
148+ }
149+ collectionName. append ( ch)
150+ i = afterBracket. index ( after: i)
151+ }
152+
153+ guard i < afterBracket. endIndex else {
154+ throw MongoShellParseError . invalidSyntax ( " Unterminated string in db[...] " )
155+ }
156+
157+ // Move past closing quote and expect "]"
158+ i = afterBracket. index ( after: i)
159+ guard i < afterBracket. endIndex, afterBracket [ i] == " ] " else {
160+ throw MongoShellParseError . invalidSyntax ( " Expected ']' after collection name in db[...] " )
161+ }
162+ i = afterBracket. index ( after: i)
163+
164+ let remaining = String ( afterBracket [ i... ] ) . trimmingCharacters ( in: . whitespacesAndNewlines)
165+
166+ // No method chain — treat as find all
167+ if remaining. isEmpty {
168+ return . find( collection: collectionName, filter: " {} " , options: MongoFindOptions ( ) )
169+ }
170+
171+ // Expect ".method(args)" after db["collection"]
172+ guard remaining. hasPrefix ( " . " ) else {
173+ throw MongoShellParseError . invalidSyntax ( " Expected '.method()' after db[ \" ... \" ] " )
174+ }
175+
176+ let methodChain = String ( remaining. dropFirst ( ) )
177+ return try parseMethodChain ( collection: collectionName, chain: methodChain)
178+ }
179+
113180 private static func parseDbExpression( _ input: String ) throws -> MongoOperation {
114181 // Remove "db." prefix
115182 let afterDb = String ( input. dropFirst ( 3 ) )
116183
117- // Find the collection name (everything before the first ".")
118- guard let dotIndex = afterDb. firstIndex ( of: " . " ) else {
119- // Just "db.collectionName" -- treat as find all
184+ guard let firstParen = afterDb. firstIndex ( of: " ( " ) else {
185+ // No parentheses at all — "db.collectionName" or "db.system.version" — treat as find all
120186 let collection = afterDb. trimmingCharacters ( in: . whitespacesAndNewlines)
187+ guard !collection. isEmpty else {
188+ throw MongoShellParseError . invalidSyntax ( " Missing collection name after 'db.' " )
189+ }
121190 return . find( collection: collection, filter: " {} " , options: MongoFindOptions ( ) )
122191 }
123192
124- let collection = String ( afterDb [ afterDb. startIndex..< dotIndex] )
125- let remainder = String ( afterDb [ afterDb. index ( after: dotIndex) ... ] )
193+ // Find the last "." before the first "(". Everything before it is the collection name,
194+ // and everything from it onward is the method chain.
195+ // This correctly handles dotted collection names like "system.version".
196+ let beforeParen = afterDb [ afterDb. startIndex..< firstParen]
197+ guard let lastDot = beforeParen. lastIndex ( of: " . " ) else {
198+ // No dot before paren — db-level method call like db.getCollectionNames()
199+ return try parseDbLevelMethod ( afterDb)
200+ }
201+
202+ let collection = String ( afterDb [ afterDb. startIndex..< lastDot] )
203+ let remainder = String ( afterDb [ afterDb. index ( after: lastDot) ... ] )
126204
127205 return try parseMethodChain ( collection: collection, chain: remainder)
128206 }
129207
208+ /// Parse a db-level method call like db.getCollectionNames(), db.stats(), etc.
209+ /// Input is the string after "db." — e.g. "getCollectionNames()" or "createCollection(\"test\")"
210+ private static func parseDbLevelMethod( _ input: String ) throws -> MongoOperation {
211+ guard let parenIndex = input. firstIndex ( of: " ( " ) else {
212+ throw MongoShellParseError . invalidSyntax ( " Expected method call with parentheses " )
213+ }
214+
215+ let methodName = String ( input [ input. startIndex..< parenIndex] )
216+ let argAndRest = try extractParenthesizedArgAndRemainder ( from: input, startingAt: parenIndex)
217+ let arg = argAndRest. arg
218+
219+ switch methodName {
220+ case " getCollectionNames " , " listCollections " :
221+ return . listCollections
222+
223+ case " createCollection " :
224+ let name = arg. trimmingCharacters ( in: . whitespacesAndNewlines)
225+ . trimmingCharacters ( in: CharacterSet ( charactersIn: " \" ' " ) )
226+ guard !name. isEmpty else {
227+ throw MongoShellParseError . missingArgument ( " createCollection requires a collection name " )
228+ }
229+ return . runCommand( command: " { \" create \" : \" \( name) \" } " )
230+
231+ case " dropDatabase " :
232+ return . runCommand( command: " { \" dropDatabase \" : 1 } " )
233+
234+ case " version " :
235+ return . runCommand( command: " { \" buildInfo \" : 1 } " )
236+
237+ case " stats " :
238+ return . runCommand( command: " { \" dbStats \" : 1 } " )
239+
240+ default :
241+ throw MongoShellParseError . unsupportedMethod ( methodName)
242+ }
243+ }
244+
130245 private static func parseMethodChain( collection: String , chain: String ) throws -> MongoOperation {
131246 guard let parenIndex = chain. firstIndex ( of: " ( " ) else {
132247 throw MongoShellParseError . invalidSyntax ( " Expected method call with parentheses " )
0 commit comments