Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
15 changes: 15 additions & 0 deletions packages/lib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- Array element access (e.g., `items[0].name`)
- GROUP BY with aggregation functions (COUNT, SUM, AVG, MIN, MAX)
- JOINs between collections
- Option to return MongoDB cursor for fine-grained result processing

## Installation

Expand All @@ -47,6 +48,13 @@ const queryLeaf = new QueryLeaf(mongoClient, 'mydatabase');
const results = await queryLeaf.execute('SELECT * FROM users WHERE age > 21');
console.log(results);

// Get a MongoDB cursor for more control over result processing
const cursor = await queryLeaf.execute('SELECT * FROM users WHERE age > 30', { returnCursor: true });
await cursor.forEach((doc) => {
console.log(`User: ${doc.name}`);
});
await cursor.close();

// When you're done, close your MongoDB client
await mongoClient.close();
```
Expand All @@ -64,6 +72,13 @@ const queryLeaf = new DummyQueryLeaf('mydatabase');
// Operations will be logged to console but not executed
await queryLeaf.execute('SELECT * FROM users WHERE age > 21');
// [DUMMY MongoDB] FIND in mydatabase.users with filter: { "age": { "$gt": 21 } }

// You can also use the cursor option with DummyQueryLeaf
const cursor = await queryLeaf.execute('SELECT * FROM users LIMIT 10', { returnCursor: true });
await cursor.forEach((doc) => {
// Process each document
});
await cursor.close();
```

## SQL Query Examples
Expand Down
19 changes: 19 additions & 0 deletions packages/lib/src/examples/basic-usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ async function main() {
console.error('Error:', error instanceof Error ? error.message : String(error));
}
}
// Example showing how to use the cursor option
console.log('\nUsing the returnCursor option:');
try {
// Using the returnCursor option to get a MongoDB cursor
const cursor = await queryLeaf.execute('SELECT * FROM users WHERE active = true', {
returnCursor: true,
});

// Now we can use the cursor methods directly
console.log('Iterating through cursor results with forEach:');
await cursor.forEach((doc: any) => {
console.log(`- User: ${doc.name}, Email: ${doc.email}`);
});

// Always close the cursor when done
await cursor.close();
} catch (error) {
console.error('Cursor error:', error instanceof Error ? error.message : String(error));
}
} finally {
// Close the MongoDB client that we created
// QueryLeaf does not manage MongoDB connections
Expand Down
45 changes: 45 additions & 0 deletions packages/lib/src/executor/dummy-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,51 @@ class DummyCursor {
},
];
}

/**
* Dummy method to simulate cursor forEach
* @param callback Function to execute for each document
*/
async forEach(callback: (doc: any) => void): Promise<void> {
const results = await this.toArray();
for (const doc of results) {
callback(doc);
}
}

/**
* Dummy method to simulate cursor next
* @returns The next document or null if none
*/
async next(): Promise<any | null> {
const results = await this.toArray();
return results.length > 0 ? results[0] : null;
}

/**
* Dummy method to simulate cursor hasNext
* @returns Whether there are more documents
*/
async hasNext(): Promise<boolean> {
const results = await this.toArray();
return results.length > 0;
}

/**
* Dummy method to simulate cursor count
* @returns The count of documents in the cursor
*/
async count(): Promise<number> {
const results = await this.toArray();
return results.length;
}

/**
* Dummy method to simulate cursor close
*/
async close(): Promise<void> {
console.log(`[DUMMY MongoDB] Closing cursor for ${this.operation} on ${this.collectionName}`);
}
}

/**
Expand Down
14 changes: 10 additions & 4 deletions packages/lib/src/executor/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CommandExecutor, Command } from '../interfaces';
import { CommandExecutor, Command, ExecutionOptions } from '../interfaces';
import { MongoClient, ObjectId } from 'mongodb';

/**
Expand Down Expand Up @@ -35,12 +35,14 @@ export class MongoExecutor implements CommandExecutor {
/**
* Execute a series of MongoDB commands
* @param commands Array of commands to execute
* @param options Execution options
* @returns Result of the last command
*/
async execute(commands: Command[]): Promise<any> {
async execute(commands: Command[], options?: ExecutionOptions): Promise<any> {
// We assume the client is already connected

const database = this.client.db(this.dbName);
const returnCursor = options?.returnCursor || false;

// Execute each command in sequence
let result = null;
Expand Down Expand Up @@ -69,7 +71,8 @@ export class MongoExecutor implements CommandExecutor {
findCursor.limit(command.limit);
}

result = await findCursor.toArray();
// Return cursor or array based on options
result = returnCursor ? findCursor : await findCursor.toArray();
break;

case 'INSERT':
Expand All @@ -96,7 +99,10 @@ export class MongoExecutor implements CommandExecutor {
case 'AGGREGATE':
// Handle aggregation commands
const pipeline = command.pipeline.map((stage) => this.convertObjectIds(stage));
result = await database.collection(command.collection).aggregate(pipeline).toArray();
const aggregateCursor = database.collection(command.collection).aggregate(pipeline);

// Return cursor or array based on options
result = returnCursor ? aggregateCursor : await aggregateCursor.toArray();
break;

default:
Expand Down
16 changes: 12 additions & 4 deletions packages/lib/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { SqlStatement, Command, SqlParser, SqlCompiler, CommandExecutor } from './interfaces';
import {
SqlStatement,
Command,
SqlParser,
SqlCompiler,
CommandExecutor,
ExecutionOptions,
} from './interfaces';
import { MongoClient } from 'mongodb';
import { SqlParserImpl } from './parser';
import { SqlCompilerImpl } from './compiler';
Expand Down Expand Up @@ -27,12 +34,13 @@ export class QueryLeaf {
/**
* Execute a SQL query on MongoDB
* @param sql SQL query string
* @returns Query results
* @param options Execution options
* @returns Query results or cursor if returnCursor is true
*/
async execute(sql: string): Promise<any> {
async execute(sql: string, options?: ExecutionOptions): Promise<any> {
const statement = this.parse(sql);
const commands = this.compile(statement);
return await this.executor.execute(commands);
return await this.executor.execute(commands, options);
}

/**
Expand Down
17 changes: 14 additions & 3 deletions packages/lib/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,28 +105,39 @@ export interface SqlCompiler {
compile(statement: SqlStatement): Command[];
}

/**
* Options for query execution
*/
export interface ExecutionOptions {
/**
* If true, return the MongoDB cursor instead of an array of results
* This only applies to FIND and AGGREGATE commands, other commands always return their result
*/
returnCursor?: boolean;
}

/**
* MongoDB command executor interface
*/
export interface CommandExecutor {
connect(): Promise<void>;
close(): Promise<void>;
execute(commands: Command[]): Promise<any>;
execute(commands: Command[], options?: ExecutionOptions): Promise<any>;
}

/**
* Main QueryLeaf interface
*/
export interface QueryLeaf {
execute(sql: string): Promise<any>;
execute(sql: string, options?: ExecutionOptions): Promise<any>;
parse(sql: string): SqlStatement;
compile(statement: SqlStatement): Command[];
getExecutor(): CommandExecutor;
close(): Promise<void>;
}

export interface Squongo extends QueryLeaf {
execute(sql: string): Promise<any>;
execute(sql: string, options?: ExecutionOptions): Promise<any>;
parse(sql: string): SqlStatement;
compile(statement: SqlStatement): Command[];
getExecutor(): CommandExecutor;
Expand Down
Loading
Loading