Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion mongodb/MongoShellLexer.g4
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,22 @@ NUMBER_DECIMAL: 'NumberDecimal';
TIMESTAMP: 'Timestamp';
REG_EXP: 'RegExp';

// Cursor modifiers (methods)
// Collection methods
FIND: 'find';
FIND_ONE: 'findOne';
COUNT_DOCUMENTS: 'countDocuments';
ESTIMATED_DOCUMENT_COUNT: 'estimatedDocumentCount';
DISTINCT: 'distinct';
AGGREGATE: 'aggregate';
GET_INDEXES: 'getIndexes';

// Cursor modifiers (methods)
SORT: 'sort';
LIMIT: 'limit';
SKIP_: 'skip';
PROJECTION: 'projection';
PROJECT: 'project';
COUNT: 'count';

// Punctuation
LPAREN: '(';
Expand Down
66 changes: 57 additions & 9 deletions mongodb/MongoShellParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
* MongoDB Shell (mongosh) Parser Grammar
* For use with ANTLR 4
*
* Supports MVP read operations:
* Milestone 1: Read Operations + Utility + Aggregation
* - Shell commands: show dbs, show databases, show collections
* - Database statements: db.collection.method(...)
* - Read methods: find(), findOne()
* - Cursor modifiers: sort(), limit(), skip(), projection(), project()
* - Helper functions: ObjectId(), ISODate(), UUID(), Long(), etc.
* - Utility: db.getCollectionNames(), db.getCollectionInfos()
* - Collection info: db.collection.getIndexes()
* - Read methods: find(), findOne(), countDocuments(), estimatedDocumentCount(), distinct()
* - Aggregation: db.collection.aggregate()
* - Cursor modifiers: sort(), limit(), skip(), count(), projection(), project()
* - Object constructors: ObjectId(), ISODate(), UUID(), NumberInt(), NumberLong(), NumberDecimal()
* - Document syntax with unquoted keys and trailing commas
*/

Expand Down Expand Up @@ -55,9 +57,15 @@ methodChain
methodCall
: findMethod
| findOneMethod
| countDocumentsMethod
| estimatedDocumentCountMethod
| distinctMethod
| aggregateMethod
| getIndexesMethod
| sortMethod
| limitMethod
| skipMethod
| countMethod
| projectionMethod
| genericMethod
;
Expand All @@ -71,6 +79,31 @@ findOneMethod
: FIND_ONE LPAREN argument? RPAREN
;

// countDocuments(filter?, options?)
countDocumentsMethod
: COUNT_DOCUMENTS LPAREN arguments? RPAREN
;

// estimatedDocumentCount(options?)
estimatedDocumentCountMethod
: ESTIMATED_DOCUMENT_COUNT LPAREN argument? RPAREN
;

// distinct(field, query?, options?)
distinctMethod
: DISTINCT LPAREN arguments RPAREN
;

// aggregate(pipeline, options?)
aggregateMethod
: AGGREGATE LPAREN arguments RPAREN
;

// getIndexes()
getIndexesMethod
: GET_INDEXES LPAREN RPAREN
;

sortMethod
: SORT LPAREN document RPAREN
;
Expand All @@ -83,6 +116,11 @@ skipMethod
: SKIP_ LPAREN NUMBER RPAREN
;

// cursor.count() - returns count of documents matching the query
countMethod
: COUNT LPAREN RPAREN
;

projectionMethod
: (PROJECTION | PROJECT) LPAREN document RPAREN
;
Expand Down Expand Up @@ -125,6 +163,14 @@ value
| REGEX_LITERAL # regexLiteralValue
| regExpConstructor # regexpConstructorValue
| literal # literalValue
| newKeywordError # newKeywordValue
;

// Catch 'new' keyword usage and provide helpful error message
newKeywordError
: NEW (OBJECT_ID | ISO_DATE | DATE | UUID | LONG | NUMBER_LONG | INT32 | NUMBER_INT | DOUBLE | DECIMAL128 | NUMBER_DECIMAL | TIMESTAMP | REG_EXP)
{ p.NotifyErrorListeners("'new' keyword is not supported. Use ObjectId(), ISODate(), UUID(), etc. directly without 'new'", nil, nil) }
LPAREN arguments? RPAREN
;

// Array: [ value, ... ] with optional trailing comma
Expand All @@ -149,19 +195,16 @@ helperFunction
// ObjectId("hex") or ObjectId()
objectIdHelper
: OBJECT_ID LPAREN stringLiteral? RPAREN
| NEW OBJECT_ID { p.NotifyErrorListeners("'new' keyword is not supported. Use ObjectId() directly", nil, nil) }
;

// ISODate("iso-string") or ISODate()
isoDateHelper
: ISO_DATE LPAREN stringLiteral? RPAREN
| NEW ISO_DATE { p.NotifyErrorListeners("'new' keyword is not supported. Use ISODate() directly", nil, nil) }
;

// Date() or Date("string") or Date(timestamp)
dateHelper
: DATE LPAREN (stringLiteral | NUMBER)? RPAREN
| NEW DATE { p.NotifyErrorListeners("'new' keyword is not supported. Use Date() directly", nil, nil) }
;

// UUID("uuid-string")
Expand Down Expand Up @@ -198,7 +241,6 @@ timestampHelper
// RegExp("pattern", "flags") constructor
regExpConstructor
: REG_EXP LPAREN stringLiteral (COMMA stringLiteral)? RPAREN
| NEW REG_EXP { p.NotifyErrorListeners("'new' keyword is not supported. Use RegExp() directly", nil, nil) }
;

// Literals
Expand Down Expand Up @@ -233,9 +275,15 @@ identifier
| NULL
| FIND
| FIND_ONE
| COUNT_DOCUMENTS
| ESTIMATED_DOCUMENT_COUNT
| DISTINCT
| AGGREGATE
| GET_INDEXES
| SORT
| LIMIT
| SKIP_
| COUNT
| PROJECTION
| PROJECT
| GET_COLLECTION
Expand Down
107 changes: 107 additions & 0 deletions mongodb/examples/collection-aggregate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// db.collection.aggregate() - Aggregation pipeline

// Empty pipeline
db.orders.aggregate([])

// Single stage pipelines
db.orders.aggregate([{ $match: { status: "completed" } }])
db.orders.aggregate([{ $group: { _id: "$category", count: { $sum: 1 } } }])
db.orders.aggregate([{ $sort: { createdAt: -1 } }])
db.orders.aggregate([{ $limit: 10 }])
db.orders.aggregate([{ $skip: 20 }])
db.orders.aggregate([{ $project: { name: 1, total: 1, _id: 0 } }])
db.orders.aggregate([{ $unwind: "$items" }])
db.orders.aggregate([{ $count: "totalOrders" }])

// $match stage variations
db.users.aggregate([{ $match: { age: { $gt: 18 } } }])
db.users.aggregate([{ $match: { status: { $in: ["active", "pending"] } } }])
db.users.aggregate([{ $match: { $or: [{ role: "admin" }, { role: "moderator" }] } }])
db.users.aggregate([{ $match: { "address.country": "USA" } }])

// $group stage variations
db.orders.aggregate([{ $group: { _id: "$customerId", total: { $sum: "$amount" } } }])
db.orders.aggregate([{ $group: { _id: "$category", avgPrice: { $avg: "$price" } } }])
db.orders.aggregate([{ $group: { _id: "$status", count: { $sum: 1 }, items: { $push: "$name" } } }])
db.orders.aggregate([{ $group: { _id: null, totalRevenue: { $sum: "$amount" } } }])
db.sales.aggregate([{ $group: { _id: { year: { $year: "$date" }, month: { $month: "$date" } }, total: { $sum: "$amount" } } }])

// $project stage variations
db.users.aggregate([{ $project: { name: 1, email: 1 } }])
db.users.aggregate([{ $project: { password: 0, ssn: 0 } }])
db.users.aggregate([{ $project: { fullName: { $concat: ["$firstName", " ", "$lastName"] } } }])
db.orders.aggregate([{ $project: { total: { $multiply: ["$price", "$quantity"] } } }])

// $sort stage variations
db.users.aggregate([{ $sort: { name: 1 } }])
db.users.aggregate([{ $sort: { createdAt: -1 } }])
db.users.aggregate([{ $sort: { lastName: 1, firstName: 1 } }])

// $lookup stage (join)
db.orders.aggregate([{ $lookup: { from: "users", localField: "customerId", foreignField: "_id", as: "customer" } }])
db.orders.aggregate([{ $lookup: { from: "products", localField: "productIds", foreignField: "_id", as: "products" } }])

// Multi-stage pipelines
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } },
{ $limit: 10 }
])

db.sales.aggregate([
{ $match: { date: { $gte: ISODate("2024-01-01"), $lt: ISODate("2025-01-01") } } },
{ $group: {
_id: { year: { $year: "$date" }, month: { $month: "$date" } },
totalRevenue: { $sum: "$amount" },
avgOrderValue: { $avg: "$amount" },
orderCount: { $sum: 1 }
} },
{ $sort: { "_id.year": 1, "_id.month": 1 } }
])

db.users.aggregate([
{ $match: { status: "active" } },
{ $lookup: { from: "orders", localField: "_id", foreignField: "customerId", as: "orders" } },
{ $project: { name: 1, email: 1, orderCount: { $size: "$orders" } } },
{ $sort: { orderCount: -1 } },
{ $limit: 100 }
])

// Pipeline with $addFields
db.orders.aggregate([
{ $addFields: { totalWithTax: { $multiply: ["$total", 1.1] } } }
])

// Pipeline with $set (alias for $addFields)
db.orders.aggregate([
{ $set: { processed: true, processedAt: ISODate() } }
])

// Pipeline with $unset
db.users.aggregate([
{ $unset: ["password", "ssn", "internalNotes"] }
])

// Pipeline with $replaceRoot
db.orders.aggregate([
{ $replaceRoot: { newRoot: "$shipping" } }
])

// Pipeline with $facet (multiple pipelines)
db.products.aggregate([
{ $facet: {
categoryCounts: [{ $group: { _id: "$category", count: { $sum: 1 } } }],
priceStats: [{ $group: { _id: null, avgPrice: { $avg: "$price" }, maxPrice: { $max: "$price" } } }]
} }
])

// Aggregate with options (options passed to driver)
db.orders.aggregate([{ $match: { status: "completed" } }], { allowDiskUse: true })
db.orders.aggregate([{ $group: { _id: "$category" } }], { maxTimeMS: 60000 })
db.orders.aggregate([{ $sort: { total: -1 } }], { collation: { locale: "en" } })

// Aggregate with collection access patterns
db["orders"].aggregate([{ $match: { status: "pending" } }])
db['audit-logs'].aggregate([{ $group: { _id: "$action", count: { $sum: 1 } } }])
db.getCollection("sales").aggregate([{ $match: { year: 2024 } }])
45 changes: 45 additions & 0 deletions mongodb/examples/collection-countDocuments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// db.collection.countDocuments() - Count documents matching a filter

// Count all documents
db.users.countDocuments()
db.users.countDocuments({})

// Count with simple filter
db.users.countDocuments({ status: "active" })
db.users.countDocuments({ verified: true })
db.users.countDocuments({ role: "admin" })

// Count with comparison operators
db.users.countDocuments({ age: { $gt: 18 } })
db.users.countDocuments({ age: { $gte: 21, $lt: 65 } })
db.users.countDocuments({ loginCount: { $gte: 10 } })

// Count with logical operators
db.users.countDocuments({ $or: [{ status: "active" }, { status: "pending" }] })
db.users.countDocuments({ $and: [{ verified: true }, { active: true }] })

// Count with array operators
db.users.countDocuments({ tags: { $in: ["premium", "enterprise"] } })
db.users.countDocuments({ roles: { $all: ["read", "write"] } })

// Count with existence check
db.users.countDocuments({ email: { $exists: true } })
db.users.countDocuments({ deletedAt: { $exists: false } })

// Count with nested documents
db.users.countDocuments({ "address.country": "USA" })
db.users.countDocuments({ "profile.verified": true })

// Count with helper functions
db.users.countDocuments({ createdAt: { $gt: ISODate("2024-01-01") } })
db.users.countDocuments({ lastLogin: { $lt: ISODate("2024-06-01") } })

// Count with options (options passed to driver)
db.users.countDocuments({ status: "active" }, { skip: 10, limit: 100 })
db.users.countDocuments({ verified: true }, { maxTimeMS: 5000 })
db.users.countDocuments({}, { hint: { status: 1 } })

// Count with collection access patterns
db["users"].countDocuments({ active: true })
db['audit-logs'].countDocuments({ level: "error" })
db.getCollection("orders").countDocuments({ status: "completed" })
43 changes: 43 additions & 0 deletions mongodb/examples/collection-distinct.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// db.collection.distinct() - Find distinct values for a field

// Distinct with field only (required)
db.users.distinct("status")
db.users.distinct("country")
db.users.distinct("role")
db.orders.distinct("category")
db.products.distinct("brand")

// Distinct with nested field
db.users.distinct("address.city")
db.users.distinct("address.country")
db.users.distinct("profile.department")

// Distinct with field and empty query
db.users.distinct("status", {})
db.users.distinct("city", {})

// Distinct with field and filter query
db.users.distinct("city", { country: "USA" })
db.users.distinct("status", { active: true })
db.users.distinct("role", { department: "engineering" })
db.orders.distinct("productId", { status: "completed" })

// Distinct with comparison operators in query
db.users.distinct("city", { age: { $gt: 18 } })
db.users.distinct("status", { createdAt: { $gt: ISODate("2024-01-01") } })

// Distinct with logical operators in query
db.users.distinct("role", { $or: [{ active: true }, { verified: true }] })
db.users.distinct("department", { $and: [{ status: "active" }, { role: "employee" }] })

// Distinct with array operators in query
db.users.distinct("city", { tags: { $in: ["premium", "enterprise"] } })

// Distinct with field, query, and options (options passed to driver)
db.users.distinct("email", { status: "active" }, { collation: { locale: "en" } })
db.users.distinct("name", {}, { maxTimeMS: 5000 })

// Distinct with collection access patterns
db["users"].distinct("status")
db['audit-logs'].distinct("action")
db.getCollection("orders").distinct("status", { year: 2024 })
17 changes: 17 additions & 0 deletions mongodb/examples/collection-estimatedDocumentCount.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// db.collection.estimatedDocumentCount() - Fast estimated count using collection metadata

// Basic estimated count (no filter - uses metadata)
db.users.estimatedDocumentCount()
db.orders.estimatedDocumentCount()
db.products.estimatedDocumentCount()

// Estimated count with options (options passed to driver)
db.users.estimatedDocumentCount({})
db.users.estimatedDocumentCount({ maxTimeMS: 1000 })
db.users.estimatedDocumentCount({ maxTimeMS: 5000 })

// Estimated count with collection access patterns
db["users"].estimatedDocumentCount()
db['audit-logs'].estimatedDocumentCount()
db.getCollection("orders").estimatedDocumentCount()
db.getCollection("large-collection").estimatedDocumentCount({ maxTimeMS: 10000 })
Loading
Loading