Skip to content

Commit

Permalink
Benchmarks & Helpers test
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirlovon committed Mar 23, 2021
1 parent bd3ad68 commit 99316d7
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 50 deletions.
53 changes: 43 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,35 @@
<h3 align="center">AloeDB</h3>
<p align="center"><i>Light, Embeddable, NoSQL database for Deno</i></p>

<p align="center">
<b>Work in progress!</b>
</p>

<br>

## Features
* Simple to use API, similar to [MongoDB](https://www.mongodb.com/)!
## Features
* 🎉 Simple to use API, similar to [MongoDB](https://www.mongodb.com/)!
* 🚀 Optimized for a large number of operations.
* ⚖ No dependencies, even without [std](https://deno.land/std)!
* 📁 Stores data in readable JSON file.

<br>

## Importing
## 📦 Importing
```typescript
import { Database, Operators, Types } from 'https://deno.land/x/aloedb/mod.ts'
import { Database } from 'https://deno.land/x/aloedb/mod.ts'
```

<br>

## Example
## 📖 Example
```typescript
import { Database } from 'https://deno.land/x/aloedb/mod.ts'
import { Database } from 'https://deno.land/x/aloedb/mod.ts';

// Structure of stored documents
interface Film {
title: string;
year: number;
film: boolean;
genres: string[];
authors: { director: string };n
authors: { director: string };
}

// Initialization
Expand All @@ -60,3 +57,39 @@ await db.updateOne({ title: 'Drive' }, { year: 2011 });
// Delete operations
await db.deleteOne({ title: 'Drive' });
```

<br>

## 🏃‍♂️ Benchmarks
This database is not aimed at a heavily loaded backend, but its speed should be good enough for small APIs working with less than a million documents.

To give you an example, here is the speed of a database operations with *1000* documents:

| Insertion | Searching | Updating | Deleting |
| ------------- | ------------- | ------------- | ------------- |
| 15k _ops/sec_ | 65k _ops/sec_ | 8k _ops/sec_ | 10k _ops/sec_ |

<!--
<br>
## 📚 Guide
### Create Operations
### Read Operations
### Update Operations
### Delete Operations
<br>
## 💡 Tips & Tricks
<br>
## 💚 Node.js Version
-->

19 changes: 11 additions & 8 deletions lib/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class Database<Schema extends Acceptable<Schema> = Document> {

const internal: Schema = deepClone(document);
this.documents.push(internal);
if (!onlyInMemory) await this.save();
if (!onlyInMemory) this.save();

return immutable ? deepClone(internal) : internal;
} catch (error) {
Expand Down Expand Up @@ -104,7 +104,7 @@ export class Database<Schema extends Acceptable<Schema> = Document> {
}

this.documents = [...this.documents, ...inserted];
if (!onlyInMemory) await this.save();
if (!onlyInMemory) this.save();

return immutable ? deepClone(inserted) : inserted;
} catch (error) {
Expand Down Expand Up @@ -196,7 +196,7 @@ export class Database<Schema extends Acceptable<Schema> = Document> {
if (schemaValidator) schemaValidator(updated);

this.documents[position] = updated;
if (!onlyInMemory) await this.save();
if (!onlyInMemory) this.save();

return document;
} catch (error) {
Expand Down Expand Up @@ -234,7 +234,7 @@ export class Database<Schema extends Acceptable<Schema> = Document> {
}

this.documents = temporary;
if (!onlyInMemory) await this.save();
if (!onlyInMemory) this.save();

return originals;
} catch (error) {
Expand All @@ -260,7 +260,7 @@ export class Database<Schema extends Acceptable<Schema> = Document> {
const deleted: Schema = this.documents[position];

this.documents.splice(position, 1);
if (!onlyInMemory) await this.save();
if (!onlyInMemory) this.save();

return deleted;
} catch (error) {
Expand Down Expand Up @@ -296,7 +296,7 @@ export class Database<Schema extends Acceptable<Schema> = Document> {
temporary = cleanArray(temporary);

this.documents = temporary;
if (!onlyInMemory) await this.save();
if (!onlyInMemory) this.save();

return deleted;
} catch (error) {
Expand Down Expand Up @@ -328,8 +328,10 @@ export class Database<Schema extends Acceptable<Schema> = Document> {
*/
public async drop(): Promise<void> {
try {
const { onlyInMemory } = this.config;

this.documents = [];
await this.save();
if (!onlyInMemory) this.save();
} catch (error) {
throw new DatabaseError('Error dropping database', error);
}
Expand Down Expand Up @@ -385,14 +387,15 @@ export class Database<Schema extends Acceptable<Schema> = Document> {
* Write documents to the database storage file.
* Called automatically after each insert, update or delete operation. _(Only if `onlyInMemory` mode disabled)_
*/
public async save(): Promise<void> {
public save(): void {
try {
if (!this.writer) return;

const encoded: string = this.config.pretty
? JSON.stringify(this.documents, null, '\t')
: JSON.stringify(this.documents);

// No need for await
this.writer.write(encoded);

} catch (error) {
Expand Down
54 changes: 24 additions & 30 deletions lib/operators.ts → lib/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,33 @@ import { isArray, isUndefined, isString, isNumber, isBoolean, isNull, isObject }
/**
* Selects documents where the value of a field more than specified number.
* @param value
* @example
* ```typescript
* db.documents; // [{ value: 5 }]
* db.findOne({ value: moreThan(6) }); // null
* db.findOne({ value: moreThan(3) }); // { value: 5 }
* ```
*/
export function moreThan(value: number) {
return (target: DocumentValue) => isNumber(target) && target > value;
return (target: Readonly<DocumentValue>) => isNumber(target) && target > value;
}

/**
* Selects documents where the value of a field more than or equal to the specified number.
* @param value
* @param value
*/
export function moreThanOrEqual(value: number) {
return (target: DocumentValue) => isNumber(target) && target >= value;
return (target: Readonly<DocumentValue>) => isNumber(target) && target >= value;
}

/**
* Selects documents where the value of a field less than specified number.
* @param value
* @param value
*/
export function lessThan(value: number) {
return (target: DocumentValue) => isNumber(target) && target < value;
return (target: Readonly<DocumentValue>) => isNumber(target) && target < value;
}

/**
* Selects documents where the value of a field less than or equal to the specified number.
* @param value
* @param value
*/
export function lessThanOrEqual(value: number) {
return (target: DocumentValue) => isNumber(target) && target <= value;
return (target: Readonly<DocumentValue>) => isNumber(target) && target <= value;
}

/**
Expand All @@ -46,7 +40,7 @@ export function lessThanOrEqual(value: number) {
* @param max Range end.
*/
export function between(min: number, max: number) {
return (target: DocumentValue) => isNumber(target) && target > min && target < max;
return (target: Readonly<DocumentValue>) => isNumber(target) && target > min && target < max;
}

/**
Expand All @@ -55,22 +49,22 @@ export function between(min: number, max: number) {
* @param max Range end.
*/
export function betweenOrEqual(min: number, max: number) {
return (target: DocumentValue) => isNumber(target) && target >= min && target <= max;
return (target: Readonly<DocumentValue>) => isNumber(target) && target >= min && target <= max;
}

/**
* Matches if field exists.
*/
export function exists() {
return (target: DocumentValue) => !isUndefined(target);
return (target?: Readonly<DocumentValue>) => !isUndefined(target);
}

/**
* Matches if value type equal to specified type.
* @param type Type of the value.
*/
export function type(type: 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object') {
return (target: DocumentValue) => {
return (target: Readonly<DocumentValue>) => {
switch (type) {
case 'string':
return isString(target);
Expand All @@ -92,56 +86,56 @@ export function type(type: 'string' | 'number' | 'boolean' | 'null' | 'array' |

/**
* Matches if array includes specified value.
* @param value
* @param value
*/
export function includes(value: DocumentPrimitive) {
return (target: DocumentValue) => isArray(target) && target.includes(value);
return (target: Readonly<DocumentValue>) => isArray(target) && target.includes(value);
}

/**
* Matches if array length equal to specified length.
* @param length Length of the array.
* @param length Length of the array.
*/
export function length(length: number) {
return (target: DocumentValue) => isArray(target) && target.length === length;
return (target: Readonly<DocumentValue>) => isArray(target) && target.length === length;
}

/**
*
* @param values
*
* @param values
*/
export function someElementMatch(...values: QueryValue[]) {
return (target: DocumentValue) => isArray(target) && target.some(targetValue => values.every(value => matchValues(value, targetValue)));
return (target: Readonly<DocumentValue>) => isArray(target) && target.some(targetValue => values.every(value => matchValues(value, targetValue)));
}

/**
*
* @param values
*
* @param values
*/
export function everyElementMatch(...values: QueryValue[]) {
return (target: DocumentValue) => isArray(target) && target.every(targetValue => values.every(value => matchValues(value, targetValue)));
return (target: Readonly<DocumentValue>) => isArray(target) && target.every(targetValue => values.every(value => matchValues(value, targetValue)));
}

/**
* Logical AND operator. Selects documents where the value of a field equals to all specified values.
* @param values Query values.
*/
export function and(...values: QueryValue[]) {
return (target: DocumentValue) => values.every(value => matchValues(value, target));
return (target: Readonly<DocumentValue>) => values.every(value => matchValues(value, target as DocumentValue));
}

/**
* Logical OR operator. Selects documents where the value of a field equals at least one specified value.
* @param values Query values.
*/
export function or(...values: QueryValue[]) {
return (target: DocumentValue) => values.some(value => matchValues(value, target));
return (target: Readonly<DocumentValue>) => values.some(value => matchValues(value, target as DocumentValue));
}

/**
* Logical NOT operator. Selects documents where the value of a field not equal to specified value.
* @param value Query value.
*/
export function not(value: QueryValue) {
return (target: DocumentValue) => matchValues(value, target) === false;
return (target: Readonly<DocumentValue>) => matchValues(value, target as DocumentValue) === false;
}
4 changes: 2 additions & 2 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { Database } from './lib/database.ts';
export * as Operators from './lib/operators.ts';
export * as Types from './lib/types.ts';
export * from './lib/helpers.ts';
export * from './lib/types.ts';
Binary file modified other/logo.afdesign
Binary file not shown.
Binary file removed other/logo2.afdesign
Binary file not shown.
34 changes: 34 additions & 0 deletions tests/benchmark/benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Database } from '../../mod.ts';
import { RunBenchmark } from './utils.ts';

// Path to the temp file
const TEMP_FILE: string = './temp_benchmark_db.json';

// Initialization
const db = new Database({ onlyInMemory: false, immutable: true, path: TEMP_FILE, pretty: true });

// Running insertion operations
await RunBenchmark('Insertion', 1000, async (iteration) => {
await db.insertOne({ foo: 'bar' + iteration });
});

// Running searching operations
await RunBenchmark('Searching', 1000, async (iteration) => {
await db.findOne({ foo: 'bar' + iteration });
});

// Running updating operations
await RunBenchmark('Updating', 1000, async (iteration) => {
await db.updateOne({ foo: 'bar' + iteration }, { foo: 'bar' + iteration });
});

// Running deleting operations
await RunBenchmark('Deleting', 1000, async (iteration) => {
await db.deleteMany({ foo: 'bar' + iteration });
});

// Remove temp file
setTimeout(() => {
Deno.removeSync(TEMP_FILE);
}, 1000);

28 changes: 28 additions & 0 deletions tests/benchmark/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Run benchmark test
* @param name Name of the benchmark
* @param iterations Amount of iterations
* @param test Test to run
*/
export async function RunBenchmark(name: string, iterations: number, test: (iteration: number) => Promise<void>): Promise<void> {
const testStart = Date.now();
for(let i = 0; i < iterations; i++) await test(i);
const testEnd = Date.now();

const timeResult = testEnd - testStart;
const operationsCount = 1000 / (timeResult / iterations);
const formated = formatNumber(operationsCount);

console.log(`${name}: ${formated} ops/sec (${timeResult} ms)`);
}

/**
* Format big numbers to more readable format (16000000 -> 16M)
* @param number Number to format
* @returns Formated number
*/
export function formatNumber(number: number): string {
if (number >= 1000000) return (number/1000000).toFixed(1) + 'M';
if (number >= 1000) return (number/1000).toFixed(1) + 'K';
return number.toFixed(1);
}
Loading

0 comments on commit 99316d7

Please sign in to comment.