Skip to content

Commit 83aab04

Browse files
committed
update docs
1 parent c28d1c9 commit 83aab04

File tree

4 files changed

+187
-15
lines changed

4 files changed

+187
-15
lines changed

docs/usage/core-concepts.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,16 @@ The main class that ties everything together. It provides a simple API to execut
5050

5151
```typescript
5252
const queryLeaf = new QueryLeaf(mongoClient, 'mydatabase');
53+
54+
// Execute a query and get all results as an array
5355
const results = await queryLeaf.execute('SELECT * FROM users');
56+
57+
// Or use a cursor for more control and memory efficiency
58+
const cursor = await queryLeaf.executeCursor('SELECT * FROM users');
59+
await cursor.forEach(user => {
60+
console.log(`Processing user: ${user.name}`);
61+
});
62+
await cursor.close();
5463
```
5564

5665
### DummyQueryLeaf
@@ -61,6 +70,13 @@ A special implementation of QueryLeaf that doesn't execute real MongoDB operatio
6170
const dummyLeaf = new DummyQueryLeaf('mydatabase');
6271
await dummyLeaf.execute('SELECT * FROM users');
6372
// [DUMMY MongoDB] FIND in mydatabase.users with filter: {}
73+
74+
// Cursor support works with DummyQueryLeaf too
75+
const cursor = await dummyLeaf.executeCursor('SELECT * FROM users');
76+
await cursor.forEach(user => {
77+
// Process mock data
78+
});
79+
await cursor.close();
6480
```
6581

6682
## Relationship Between SQL and MongoDB Concepts
@@ -101,14 +117,31 @@ QueryLeaf uses specific naming conventions for mapping SQL to MongoDB:
101117

102118
## Execution Flow
103119

120+
QueryLeaf supports two main execution methods:
121+
122+
### Standard Execution
123+
104124
When you call `queryLeaf.execute(sqlQuery)`, the following happens:
105125

106126
1. The SQL query is parsed into an AST
107127
2. The AST is compiled into MongoDB commands
108128
3. The commands are executed against the MongoDB database
109-
4. The results are returned
129+
4. All results are loaded into memory and returned as an array
130+
131+
This is simple to use but can be memory-intensive for large result sets.
132+
133+
### Cursor Execution
134+
135+
When you call `queryLeaf.executeCursor(sqlQuery)`, the following happens:
136+
137+
1. The SQL query is parsed into an AST
138+
2. The AST is compiled into MongoDB commands
139+
3. For SELECT queries, a MongoDB cursor is returned instead of loading all results
140+
4. You control how and when results are processed (streaming/batching)
141+
142+
This approach is more memory-efficient for large datasets and gives you more control.
110143

111-
If any step fails, an error is thrown with details about what went wrong.
144+
If any step fails in either approach, an error is thrown with details about what went wrong.
112145

113146
## Extending QueryLeaf
114147

docs/usage/examples.md

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,37 @@ const usersInNY = await queryLeaf.execute(`
7373
`);
7474
```
7575

76+
### Using Cursors for Large Result Sets
77+
78+
```typescript
79+
// Use cursor for memory-efficient processing of large result sets
80+
const cursor = await queryLeaf.executeCursor(`
81+
SELECT _id, customer, total, items
82+
FROM orders
83+
WHERE status = 'completed'
84+
`);
85+
86+
try {
87+
// Process one document at a time without loading everything in memory
88+
let totalRevenue = 0;
89+
await cursor.forEach(order => {
90+
// Process each order individually
91+
totalRevenue += order.total;
92+
93+
// Access and process nested data
94+
order.items.forEach(item => {
95+
// Process each item in the order
96+
console.log(`Order ${order._id}: ${item.quantity}x ${item.name}`);
97+
});
98+
});
99+
100+
console.log(`Total revenue: $${totalRevenue}`);
101+
} finally {
102+
// Always close the cursor when done
103+
await cursor.close();
104+
}
105+
```
106+
76107
### Array Element Access
77108

78109
```typescript
@@ -169,11 +200,11 @@ async function getUserDashboardData(userId) {
169200
}
170201
```
171202

172-
### Product Catalog with Filtering
203+
### Product Catalog with Filtering and Cursor-Based Pagination
173204

174205
```typescript
175-
// Product catalog with filtering
176-
async function getProductCatalog(filters = {}) {
206+
// Product catalog with filtering and cursor-based pagination
207+
async function getProductCatalog(filters = {}, useCursor = false) {
177208
const client = new MongoClient('mongodb://localhost:27017');
178209
await client.connect();
179210

@@ -217,9 +248,52 @@ async function getProductCatalog(filters = {}) {
217248
query += ` LIMIT ${filters.limit}`;
218249
}
219250

220-
// Execute query
221-
return await queryLeaf.execute(query);
251+
if (filters.offset) {
252+
query += ` OFFSET ${filters.offset}`;
253+
}
254+
255+
// Execute query with or without cursor based on preference
256+
if (useCursor) {
257+
// Return a cursor for client-side pagination or streaming
258+
return await queryLeaf.executeCursor(query);
259+
} else {
260+
// Return all results at once (traditional approach)
261+
return await queryLeaf.execute(query);
262+
}
263+
} catch (error) {
264+
console.error('Error fetching product catalog:', error);
265+
throw error;
266+
} finally {
267+
if (!useCursor) {
268+
// If we returned a cursor, the caller is responsible for closing the client
269+
// after they are done with the cursor
270+
await client.close();
271+
}
272+
}
273+
}
274+
275+
// Example of using the product catalog with cursor
276+
async function streamProductCatalog() {
277+
const client = new MongoClient('mongodb://localhost:27017');
278+
await client.connect();
279+
280+
let cursor = null;
281+
try {
282+
const queryLeaf = new QueryLeaf(client, 'store');
283+
// Get a cursor for large result set
284+
cursor = await getProductCatalog({ category: 'Electronics', inStock: true }, true);
285+
286+
// Stream products to client one by one
287+
console.log('Streaming products:');
288+
await cursor.forEach(product => {
289+
console.log(`- ${product.name}: $${product.price} (${product.stock} in stock)`);
290+
});
291+
} catch (error) {
292+
console.error('Error:', error);
222293
} finally {
294+
// Close cursor if we have one
295+
if (cursor) await cursor.close();
296+
// Close client connection
223297
await client.close();
224298
}
225299
}

docs/usage/mongodb-client.md

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,25 @@ await mongoClient.connect();
4646
Once you have a MongoDB client, you can create a QueryLeaf instance:
4747

4848
```typescript
49-
import { QueryLeaf } from 'queryleaf';
49+
import { QueryLeaf } from '@queryleaf/lib';
5050

5151
// Create a QueryLeaf instance with your MongoDB client
5252
const queryLeaf = new QueryLeaf(mongoClient, 'mydatabase');
5353

54-
// Now you can execute SQL queries
54+
// Execute SQL queries and get all results at once
5555
const results = await queryLeaf.execute('SELECT * FROM users');
56+
57+
// For large result sets, use cursor execution for better memory efficiency
58+
const cursor = await queryLeaf.executeCursor('SELECT * FROM users');
59+
try {
60+
// Process results one at a time
61+
await cursor.forEach(user => {
62+
console.log(`User: ${user.name}`);
63+
});
64+
} finally {
65+
// Always close the cursor when done
66+
await cursor.close();
67+
}
5668
```
5769

5870
## Connection Management
@@ -80,7 +92,7 @@ async function main() {
8092
// Create QueryLeaf instance
8193
const queryLeaf = new QueryLeaf(client, 'mydatabase');
8294

83-
// Execute queries
95+
// Execute queries - get all results at once
8496
const users = await queryLeaf.execute('SELECT * FROM users LIMIT 10');
8597
console.log(`Found ${users.length} users`);
8698

@@ -90,6 +102,23 @@ async function main() {
90102
);
91103
console.log(`Found ${products.length} electronic products`);
92104

105+
// For large result sets, use cursor execution
106+
const ordersCursor = await queryLeaf.executeCursor(
107+
'SELECT * FROM orders WHERE total > 1000'
108+
);
109+
try {
110+
// Process results in a memory-efficient way
111+
let count = 0;
112+
await ordersCursor.forEach(order => {
113+
console.log(`Processing order #${order.orderId}`);
114+
count++;
115+
});
116+
console.log(`Processed ${count} high-value orders`);
117+
} finally {
118+
// Always close the cursor when done
119+
await ordersCursor.close();
120+
}
121+
93122
} catch (error) {
94123
console.error('Error:', error);
95124
} finally {

packages/lib/README.md

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
- Array element access (e.g., `items[0].name`)
2222
- GROUP BY with aggregation functions (COUNT, SUM, AVG, MIN, MAX)
2323
- JOINs between collections
24-
- Option to return MongoDB cursor for fine-grained result processing
24+
- Direct MongoDB cursor access for fine-grained result processing and memory efficiency
2525

2626
## Installation
2727

@@ -48,8 +48,8 @@ const queryLeaf = new QueryLeaf(mongoClient, 'mydatabase');
4848
const results = await queryLeaf.execute('SELECT * FROM users WHERE age > 21');
4949
console.log(results);
5050

51-
// Get a MongoDB cursor for more control over result processing
52-
const cursor = await queryLeaf.execute('SELECT * FROM users WHERE age > 30', { returnCursor: true });
51+
// Get a MongoDB cursor for more control over result processing and memory efficiency
52+
const cursor = await queryLeaf.executeCursor('SELECT * FROM users WHERE age > 30');
5353
await cursor.forEach((doc) => {
5454
console.log(`User: ${doc.name}`);
5555
});
@@ -73,8 +73,8 @@ const queryLeaf = new DummyQueryLeaf('mydatabase');
7373
await queryLeaf.execute('SELECT * FROM users WHERE age > 21');
7474
// [DUMMY MongoDB] FIND in mydatabase.users with filter: { "age": { "$gt": 21 } }
7575

76-
// You can also use the cursor option with DummyQueryLeaf
77-
const cursor = await queryLeaf.execute('SELECT * FROM users LIMIT 10', { returnCursor: true });
76+
// You can also use cursor functionality with DummyQueryLeaf
77+
const cursor = await queryLeaf.executeCursor('SELECT * FROM users LIMIT 10');
7878
await cursor.forEach((doc) => {
7979
// Process each document
8080
});
@@ -100,6 +100,42 @@ SELECT status, COUNT(*) as count FROM orders GROUP BY status
100100
SELECT u.name, o.total FROM users u JOIN orders o ON u._id = o.userId
101101
```
102102

103+
## Working with Cursors
104+
105+
When working with large result sets, using MongoDB cursors directly can be more memory-efficient and gives you more control over result processing:
106+
107+
```typescript
108+
// Get a cursor for a SELECT query
109+
const cursor = await queryLeaf.executeCursor('SELECT * FROM products WHERE price > 100');
110+
111+
// Option 1: Convert to array (loads all results into memory)
112+
const results = await cursor.toArray();
113+
console.log(`Found ${results.length} products`);
114+
115+
// Option 2: Iterate with forEach (memory efficient)
116+
await cursor.forEach(product => {
117+
console.log(`Processing ${product.name}...`);
118+
});
119+
120+
// Option 3: Manual iteration with next/hasNext (most control)
121+
while (await cursor.hasNext()) {
122+
const product = await cursor.next();
123+
// Process each product individually
124+
console.log(`Product: ${product.name}, $${product.price}`);
125+
}
126+
127+
// Always close the cursor when done
128+
await cursor.close();
129+
```
130+
131+
Features:
132+
- Returns MongoDB `FindCursor` for normal queries and `AggregationCursor` for aggregations
133+
- Supports all cursor methods like `forEach()`, `toArray()`, `next()`, `hasNext()`
134+
- Efficiently handles large result sets with MongoDB's batching system
135+
- Works with all advanced QueryLeaf features (filtering, sorting, aggregations, etc.)
136+
- Only available for read operations (SELECT queries)
137+
```
138+
103139
## Links
104140
105141
- [Website](https://queryleaf.com)

0 commit comments

Comments
 (0)