Skip to content

Commit

Permalink
Merge pull request #16 from markwylde/limit-offset-sort-present
Browse files Browse the repository at this point in the history
feat: limit, offset, sort, present
  • Loading branch information
markwylde authored Dec 24, 2024
2 parents a55834d + c6585a1 commit 3b7a781
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 7 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ The final record will be:
### `.remove(key)`
Query the database using a complex query object. This method allows for advanced querying using a combination of fields and operators.

### `.query(queryObject)`
### `.query(queryObject, options)`
Query the database using a complex query object. This method allows for advanced querying using a combination of fields and operators.

**Example:**
Expand All @@ -153,6 +153,11 @@ const records = await doubledb.query({
{ firstName: { $eq: 'Joe' } },
{ firstName: { $eq: 'joe' } }
]
}, {
limit: 10,
offset: 5,
sort: { firstName: 1 },
project: { firstName: 1, lastName: 1 }
});
```

Expand Down Expand Up @@ -195,6 +200,12 @@ const records = await doubledb.query({
- **$exists**: Matches documents where the field exists (or does not exist if set to false).
- **$not**: Matches documents that do not match the specified condition.

### Query Options:
- **limit**: Limits the number of returned records.
- **offset**: Skips the first `number` of records.
- **sort**: Sorts the records based on the specified fields. Use `1` for ascending and `-1` for descending.
- **project**: Projects only the specified fields in the returned records.

This query method is powerful and allows combining multiple conditions and operators to fetch the desired records from the database.

### `.batchInsert(documents)`
Expand Down
68 changes: 62 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ type QueryOptions = {
lt?: string;
lte?: string;
gte?: string;
sort?: { [key: string]: 1 | -1 };
project?: { [key: string]: 1 };
}

export type DoubleDb = {
Expand All @@ -28,7 +30,7 @@ export type DoubleDb = {
patch: (id: string, newDocument: Partial<Document>) => Promise<Document>;
remove: (id: string) => Promise<void>;
read: (id: string) => Promise<Document | undefined>;
query: (queryObject: object) => Promise<Document[]>;
query: (queryObject?: object, options?: { limit?: number; offset?: number; sort?: { [key: string]: 1 | -1 }; project?: { [key: string]: 1 } }) => Promise<Document[]>;
close: () => Promise<void>;
batchInsert: (documents: Document[]) => Promise<Document[]>;
upsert: (id: string, document: Document) => Promise<Document>;
Expand Down Expand Up @@ -327,9 +329,37 @@ async function createDoubleDb(dataDirectory: string): Promise<DoubleDb> {
return db.del(id);
}

async function query(queryObject: object): Promise<Document[]> {
if (!isObject(queryObject)) {
throw new Error('doubledb.query: queryObject must be an object');
async function query(queryObject?: object, options?: { limit?: number; offset?: number; sort?: { [key: string]: 1 | -1 }; project?: { [key: string]: 1 } }): Promise<Document[]> {
if (!queryObject || Object.keys(queryObject).length === 0) {
const allIds = await getAllIds();
let results = await Promise.all([...allIds].map(id => read(id)));
const offset = options?.offset ?? 0;
const limit = options?.limit ?? results.length;

if (options?.sort) {
const sortFields = Object.entries(options.sort);
results.sort((a, b) => {
for (const [field, direction] of sortFields) {
if (a[field] < b[field]) return direction === 1 ? -1 : 1;
if (a[field] > b[field]) return direction === 1 ? 1 : -1;
}
return 0;
});
}

if (options?.project) {
results = results.map(doc => {
const projected: Document = {};
for (const field of Object.keys(options.project)) {
if (field in doc) {
projected[field] = doc[field];
}
}
return projected;
});
}

return results.filter((doc): doc is Document => doc !== undefined).slice(offset, offset + limit);
}

let resultIds = new Set<string>();
Expand All @@ -354,8 +384,34 @@ async function createDoubleDb(dataDirectory: string): Promise<DoubleDb> {
isFirstCondition = false;
}

const results = await Promise.all([...resultIds].map(id => read(id)));
return results.filter((doc): doc is Document => doc !== undefined);
let results = await Promise.all([...resultIds].map(id => read(id)));
const offset = options?.offset ?? 0;
const limit = options?.limit ?? results.length;

if (options?.sort) {
const sortFields = Object.entries(options.sort);
results.sort((a, b) => {
for (const [field, direction] of sortFields) {
if (a[field] < b[field]) return direction === 1 ? -1 : 1;
if (a[field] > b[field]) return direction === 1 ? 1 : -1;
}
return 0;
});
}

if (options?.project) {
results = results.map(doc => {
const projected: Document = {};
for (const field of Object.keys(options.project)) {
if (field in doc) {
projected[field] = doc[field];
}
}
return projected;
});
}

return results.filter((doc): doc is Document => doc !== undefined).slice(offset, offset + limit);
}

async function handleOperators(key: string, operators: object): Promise<Set<string>> {
Expand Down
78 changes: 78 additions & 0 deletions test/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ async function setupTestDb() {
return db;
}

test('empty query', async () => {
const db = await setupTestDb();
await db.insert({ value: 'alpha' });
await db.insert({ value: 'beta' });
const result = await db.query({});
assert.strictEqual(result.length, 2);
result.sort((a, b) => a.value < b.value ? -1 : 1);
assert.strictEqual(result[0].value, 'alpha');
assert.strictEqual(result[1].value, 'beta');
await db.close();
});

test('$sw operator on string', async () => {
const db = await setupTestDb();
await db.insert({ value: 'alpha' });
Expand Down Expand Up @@ -151,3 +163,69 @@ test('Complex query with $or', async () => {
assert.strictEqual(result[0].category, 'b');
await db.close();
});

test('query limit, offset, sort', async () => {
const db = await setupTestDb();
for (let i = 0; i < 10; i++) {
await db.insert({ value: i });
}
const result = await db.query({}, {
limit: 3,
offset: 2,
sort: {
value: 1
}
});
assert.strictEqual(result.length, 3);
assert.strictEqual(result[0].value, 2);
assert.strictEqual(result[1].value, 3);
assert.strictEqual(result[2].value, 4);

await db.close();
});

test('query with sort option', async () => {
const db = await setupTestDb();
await db.insert({ value: 'gamma' });
await db.insert({ value: 'alpha' });
await db.insert({ value: 'beta' });
const result = await db.query({}, { sort: { value: 1 } });
assert.strictEqual(result.length, 3);
assert.strictEqual(result[0].value, 'alpha');
assert.strictEqual(result[1].value, 'beta');
assert.strictEqual(result[2].value, 'gamma');
await db.close();
});

test('query with project option', async () => {
const db = await setupTestDb();
await db.insert({ value: 'gamma', extra: 'data' });
await db.insert({ value: 'alpha', extra: 'info' });
const result = await db.query({}, { project: { value: 1 } });

result.sort((a, b) => a.value < b.value ? -1 : 1);
assert.strictEqual(result.length, 2);
assert.strictEqual(Object.keys(result[0]).length, 1);
assert.strictEqual(result[0].value, 'alpha');
assert.strictEqual(Object.keys(result[1]).length, 1);
assert.strictEqual(result[1].value, 'gamma');

await db.close();
});

test('query with sort and project options', async () => {
const db = await setupTestDb();
await db.insert({ value: 'gamma', extra: 'data' });
await db.insert({ value: 'alpha', extra: 'info' });
await db.insert({ value: 'beta', extra: 'details' });
const result = await db.query({}, { sort: { value: 1 }, project: { value: 1 } });
assert.strictEqual(result.length, 3);
assert.strictEqual(Object.keys(result[0]).length, 1);
assert.strictEqual(result[0].value, 'alpha');
assert.strictEqual(Object.keys(result[1]).length, 1);
assert.strictEqual(result[1].value, 'beta');
assert.strictEqual(Object.keys(result[2]).length, 1);
assert.strictEqual(result[2].value, 'gamma');
await db.close();
});

0 comments on commit 3b7a781

Please sign in to comment.