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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# v1.2.1

- Feat: New 'Count' method that allows the counting of how many rows are in a table, how many not null columns, distinct values of a column and set a custom name for the count key in the result object.

- Fix(types): Fix typing mistake that caused an regression forcing an object to be passed to 'Select' and 'Find', calling those methods without any argument will behave the same way as passing an empty object, this is a typing only mistake, that doesn't change how the methods behave or parse its parameters.

# v1.2.0

- Feat: 'Select' and 'Find' methods can now receive an orderBy parameter to order return the results from the database
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "promiseorm",
"version": "1.2.0",
"version": "1.2.1",
"description": "A Typescript ORM for automatic creation and management of models and entries from simple objects",
"main": "build/index.js",
"files": [
Expand Down
12 changes: 11 additions & 1 deletion src/main/connection/DatabaseConnection.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IDatabaseConnectionRead, IDatabaseField, IDatabaseQueryFilterExpression } from '../interfaces';
import { IDatabaseConnectionRead, IDatabaseCount, IDatabaseField, IDatabaseQueryFilterExpression } from '../interfaces';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { DatabaseException } from '../errors';
import { BaseModel } from '../models';
Expand Down Expand Up @@ -112,4 +112,14 @@ export abstract class DatabaseConnection {
* @abstract
*/
public abstract createOrUpdateTable(tableName: string, fields: Record<string, IDatabaseField>): Promise<void>

/**
* Returns the count of the provided fields
* @param fields Array of objects that specify the columns to count, if the count should be distinct, and optionally to what name rename the column of the count
* @param filter The filter to apply to the records
* @returns A promise containing an object with the count of all the requested fields
* @throws [{@link DatabaseException}]
* @abstract
*/
public abstract count(database: string, { fields, filter }: { fields: IDatabaseCount[], filter?: IDatabaseQueryFilterExpression }): Promise<Record<string, number>>
}
13 changes: 13 additions & 0 deletions src/main/connection/MariaDBConnection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EDatabaseQueryFilterOperator,
EDatabaseTypes, EMariaDBFieldTypes,
IDatabaseConnectionRead,
IDatabaseCount,
IDatabaseField, IDatabaseQueryFilter,
IDatabaseQueryFilterExpression,
IMariaDBDescribeField,
Expand Down Expand Up @@ -197,6 +198,18 @@ export class MariaDBConnection extends DatabaseConnection {
return result.affectedRows;
}

/**
* @private
*/
override async count(database: string, { fields, filter }: { fields: IDatabaseCount[]; filter?: IDatabaseQueryFilterExpression; }): Promise<Record<string, number>> {
const conn = await this.getConnection();
const counts: string[] = [];
fields.forEach((field) => counts.push(`COUNT(${field.distinct ? 'DISTINCT ' : ''}${field.key})${field.alias ? ` AS ${conn.escape(field.alias)}` : ''}`));
const result = await conn.query(`SELECT ${counts.join(', ')} FROM ${conn.escapeId(database)}${filter ? ` WHERE ${this.filterBuilder(conn, filter)}` : ''}`);
await conn.release();
return result[0];
}

/**
* @private
*/
Expand Down
2 changes: 1 addition & 1 deletion src/main/interfaces/connection/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './mariadb';
export * from './IDatabaseConnectionRead';
export * from './mariadb';
5 changes: 5 additions & 0 deletions src/main/interfaces/database/IDatabaseCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface IDatabaseCount {
key: string;
distinct: boolean;
alias?: string;
};
1 change: 1 addition & 0 deletions src/main/interfaces/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './EDatabaseQueryFilterOperator';
export * from './IDatabaseQueryFilter';
export * from './IDatabaseOrderBy';
export * from './IDatabaseField';
export * from './IDatabaseCount';
export * from './EDatabaseTypes';
41 changes: 32 additions & 9 deletions src/main/models/BaseModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EDatabaseQueryFilterOperator, EDatabaseTypes, IDatabaseField, IDatabaseOrderBy, IDatabaseQueryFilter, IDatabaseQueryFilterExpression } from '../interfaces';
import { EDatabaseQueryFilterOperator, EDatabaseTypes, IDatabaseCount, IDatabaseField, IDatabaseOrderBy, IDatabaseQueryFilter, IDatabaseQueryFilterExpression } from '../interfaces';
import { DatabaseConnection } from '../connection';
import { DatabaseException } from '../errors';

Expand Down Expand Up @@ -157,7 +157,7 @@ export class BaseModel {
* @returns The data found
* @throws [{@link DatabaseException}]
*/
public async find(params: { query?: Record<string, any>, limit?: number, orderBy?: IDatabaseOrderBy }): Promise<Record<string, unknown>[]> {
public async find(params?: { query?: Record<string, any>, limit?: number, orderBy?: IDatabaseOrderBy }): Promise<Record<string, unknown>[]> {
const { query, limit, orderBy } = params || {};
this.checkIsReady();
return this.connection!.read({ keys: '*', database: this.name!,
Expand Down Expand Up @@ -205,7 +205,7 @@ export class BaseModel {
* @returns The data found
* @throws [{@link DatabaseException}]
*/
public async select(params: { fields: string[], query?: Record<string, any>, filter?: IDatabaseQueryFilterExpression, limit?: number, orderBy?: IDatabaseOrderBy },
public async select(params: { fields: string[], filter?: IDatabaseQueryFilterExpression, limit?: number, orderBy?: IDatabaseOrderBy },
): Promise<Record<string, unknown>[]> {
this.checkIsReady();
if (!params) throw new DatabaseException('Missing params for \'select\' method call, if you want to retrieve all data on this table call \'find\' instead.');
Expand Down Expand Up @@ -246,9 +246,6 @@ export class BaseModel {
* @throws [{@link DatabaseException}]
*/
public async update(find: Record<string, any>, update: Record<string, any>): Promise<Record<string, any>> {
this.checkIsReady();
if (!update) throw new DatabaseException('Empty update!');
this.validFieldValueCheck(update);
const filter: IDatabaseQueryFilterExpression = {
type: 'AND',
filters: Object.keys(find).map((fieldKey) => ({
Expand All @@ -257,9 +254,7 @@ export class BaseModel {
value: find[fieldKey],
})),
};
const keys = Object.keys(update).filter((field) => !this.fields[field].autoIncrement);
const values = keys.map((fieldKey) => update[fieldKey] ?? null);
return this.connection!.update(this.name!, keys, values, filter);
return this.updateWhere(filter, update);
}

/**
Expand Down Expand Up @@ -322,4 +317,32 @@ export class BaseModel {
this.filterCheck(filter);
return this.connection!.delete(this.name!, filter);
}

/**
* Counts rows in the table, call without parameters to get the total amount of rows
*
* Use the fields parameter to count the amount of rows within a specific column (without counting NULL values)
*
* Pass an object with { key: string, distinct: true } on the fields array to only count unique values for that column (Example: [{ key: 'category', distinct: true}] will return how many different categories there is on the table)
*
* Use the optional alias parameter to rename the column. Example, [{ key: 'category', distinct: true, alias: 'categoryCount'}] will return the object [{ categoryCount: 7 }].
*
* Pass a filter to only count rows that match an specific criteria. Example, only count the rows where the column 'price' is greater than 50 will return how many items cost more than 50.
*
* Note: if fields is not provided, will return [{ count: number }]
* @param fields
*/
public async count(params?: { fields?: (IDatabaseCount | string)[], filter?: IDatabaseQueryFilterExpression }): Promise<Record<string, number>> {
const fields = params?.fields ?? undefined;
const filter = params?.filter ?? undefined;

this.checkIsReady();
this.filterCheck(filter);
if (fields) this.fieldsCheck(fields.map((field) => typeof field === 'string' ? field : field.key));

const finalFields: IDatabaseCount[] = fields?.map((field) => typeof field === 'string' ?
{ key: field, distinct: false, alias: field } : { alias: field.alias ?? field.key, ...field }) ?? [{ key: '*', distinct: false, alias: 'count' }];

return this.connection!.count(this.name!, { fields: finalFields, filter });
}
}