Skip to content

Commit

Permalink
Merge pull request #30 from dxfrontier/feature/distinct
Browse files Browse the repository at this point in the history
  • Loading branch information
dragolea authored Jan 30, 2024
2 parents 4006d4f + 757e4e6 commit 39e4c4e
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 36 deletions.
127 changes: 95 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,16 @@ The goal of **BaseRepository** is to significantly reduce the boilerplate code r
- [findOne](#findone)
- [builder](#builder)
- [.find](#find-1)
- [distinct](#distinct)
- [orderAsc](#orderasc)
- [orderDesc](#orderdesc)
- [groupBy](#groupby)
- [columns](#columns)
- [columnsFormatter](#columnsformatter)
- [limit](#limit)
- [getExpand](#getexpand)
- [forUpdate](#forupdate)
- [forShareLock](#forsharelock)
- [execute](#execute)
- [update](#update)
- [updateLocaleTexts](#updatelocaletexts)
Expand Down Expand Up @@ -167,12 +170,9 @@ class MyRepository extends BaseRepository<MyEntity> {
const result13 = await this.count()
}

// .... enhance with custom QL methods
public customQLMethod(results: MyEntity[], req: TypedRequest<MyEntity>) {
const result123 = await this.exists(...)
const result143 = await this.count()

const customQL = SELECT.from(this.entity.name)
// Enhance with custom QL methods ...
customQLMethod() {
const customQL = SELECT(MyEntity).columns(...).where(...)
// ...
}
}
Expand Down Expand Up @@ -287,9 +287,10 @@ class MyRepository extends BaseRepository<MyEntity> {
const result13 = await this.count()
}

// .... enhance with custom QL methods
// Enhance with custom QL methods ...
customQLMethod() {
const customQL = = SELECT.from(this.entity.name)
const customQL = SELECT(MyEntity).columns(...).where(...)
// ...
}
}

Expand Down Expand Up @@ -398,7 +399,7 @@ The `create` method allows you to create a new entry in the table.
`Parameters`
- `entry (object)`: An object representing the entry to be created. The object should match the structure expected by `MyEntity`
- `entry (Object)`: An object representing the entry to be created. The object should match the structure expected by `MyEntity`
`Return`
Expand Down Expand Up @@ -579,7 +580,7 @@ The `getAllAndLimit` method allows you to find and retrieve a list of items with
`Parameters`
- `props` `(object)`: An object containing the following properties:
- `props` `(Object)`: An object containing the following properties:
- `limit` `(number)`: The maximum number of items to retrieve.
- `skip?` `(optional, number)`: This property, if applied, will 'skip' a certain number of items (default: 0).
Expand Down Expand Up @@ -698,7 +699,7 @@ The `find` method allows you to find and retrieve entries from the table that ma
| Method | Parameters | Description |
| :------------------------------------------------------------------------- | :---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `this.find(): Promise<T \| undefined>` | | Get all table items. |
| `this.find(keys: Entry<T>): Promise<T \| undefined>` | `keys (object)` | An object representing the keys to filter the entries. <br /> Each key should correspond to a property in `MyEntity`, and the values should match the filter criteria. |
| `this.find(keys: Entry<T>): Promise<T \| undefined>` | `keys (Object)` | An object representing the keys to filter the entries. <br /> Each key should correspond to a property in `MyEntity`, and the values should match the filter criteria. |
| `this.find(filter :`**[Filter\<T\>](#filter)**`): Promise<T \| undefined>` | `filter (Filter)` | An instance of **[Filter\<T\>](#filter)** |
`Return`
Expand Down Expand Up @@ -774,7 +775,7 @@ class MyRepository extends BaseRepository<MyEntity> {
The `findOne` method allows you to find and retrieve a single entry from the table that matches the specified keys.
`Parameters`
- `keys (object)`: An object representing the keys to filter the entries. Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.
- `keys (Object)`: An object representing the keys to filter the entries. Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.
`Return`
Expand Down Expand Up @@ -817,20 +818,39 @@ class MyRepository extends BaseRepository<MyEntity> {
| Method | Parameters | Description |
| :---------------------------------------------------------------------------- | :---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `this.builder().find(): SelectBuilder<T>` | | Get all table items. |
| `this.builder().find(keys: Entry<T>): SelectBuilder<T>` | `keys (object)` | An object representing the keys to filter the entries. <br /> Each key should correspond to a property in `MyEntity`, and the values should match the filter criteria. |
| `this.builder().find(keys: Entry<T>): SelectBuilder<T>` | `keys (Object)` | An object representing the keys to filter the entries. <br /> Each key should correspond to a property in `MyEntity`, and the values should match the filter criteria. |
| `this.builder().find(filter :`**[Filter\<T\>](#filter)**`): SelectBuilder<T>` | `filter (Filter)` | An instance of **[Filter\<T\>](#filter)** |
`Return`
- `SelectBuilder<T>`: A `SelectBuilder` instance that provides access to the following methods for constructing a `SELECT`:
- [orderAsc](#orderasc)
- [orderDesc](#orderdesc)
- [groupBy](#groupby)
- [columns](#columns)
- [columnsFormatter](#columnsformatter)
- [limit](#limit)
- [getExpand](#getexpand)
- [execute](#execute)
- [distinct](#distinct)
- [orderAsc()](#orderasc)
- [orderDesc()](#orderdesc)
- [groupBy()](#groupby)
- [columns()](#columns)
- [columnsFormatter()](#columnsformatter)
- [limit()](#limit)
- [getExpand()](#getexpand)
- [forUpdate()](#forupdate)
- [forShareLock()](#forsharelock)
- [execute()](#execute)
<p align="right">(<a href="#table-of-contents">back to top</a>)</p>
###### distinct
Skip duplicates similar to SQL distinct.
`Example`
```ts
const results = await this.builder()
.find() // get all items
.distinct.columns('country')
.execute();
```
<p align="right">(<a href="#table-of-contents">back to top</a>)</p>
Expand Down Expand Up @@ -931,24 +951,24 @@ const results = await this.builder()
###### columnsFormatter
The `columnsFormatter` method can be used :
The `columnsFormatter` allows you to specify which columns to be renamed or apply aggregate function
This method can be used :
- To `rename` columns in your query results.
- To apply `aggregate functions` to specific columns, such as calculating averages, sums etc.
`Parameters`
- `columns (object-1, object-n, ...)` An object specifying the columns to be modified.
- `columns (Array<object>)` An array of objects specifying the columns to be modified.

- `column` `(string)`: The name of the column to be processed.
- `column1` `(string)` : The name of the column to be processed. (Applied only for `CONCAT`)
- `column2` `(string)` : The name of the column to be processed. (Applied only for `CONCAT`)
- `aggregate?` `[optional] (string)`: This property, if applied, will `call aggregate function` for the specified `column` name, below you can find the available aggregate functions :

- String : `'LOWER' | 'UPPER' | 'LENGTH' | 'CONCAT' | 'TRIM'`
- Number : `'AVG' | 'MIN' | 'MAX' | 'SUM' | 'ABS' | 'CEILING' | 'TOTAL' | 'COUNT' | 'ROUND' | 'FLOOR'`
- Date : `'DAY' | 'MONTH' | 'YEAR' | 'HOUR' | 'MINUTE' | 'SECOND'`

- `renameAs` `(string)`: This property creates a new column with the given name

`Example 1`
Expand Down Expand Up @@ -1026,6 +1046,49 @@ const results = await this.builder()

<p align="right">(<a href="#table-of-contents">back to top</a>)</p>

###### forUpdate

Exclusively locks the selected rows for subsequent updates in the current transaction, thereby preventing concurrent updates by other parallel transactions.

`Example`

```ts
const results = await this.builder()
.find({
name: 'A company name',
})
.getExpand('orders', 'reviews')
.forUpdate()
.execute();
```

> [!TIP]
> More info can be found on the official SAP CAP [forUpdate](https://cap.cloud.sap/docs/node.js/cds-ql#forupdate) documentation.

###### forShareLock

Locks the selected rows in the current transaction, thereby preventing concurrent updates by other parallel transactions, until the transaction is committed or rolled back. Using a shared lock allows all transactions to read the locked record.

If a queried record is already exclusively locked by another transaction, the .forShareLock() method waits for the lock to be released.

`Example`

```ts
// Expand only 'orders' association
const results = await this.builder()
.find({
name: 'A company name',
})
.getExpand('orders', 'reviews')
.forShareLock()
.execute();
```

> [!TIP]
> More info can be found on the official SAP CAP [forShareLock](#https://cap.cloud.sap/docs/node.js/cds-ql#forsharelock) documentation.

<p align="right">(<a href="#table-of-contents">back to top</a>)</p>

###### execute

Finally, to execute the constructed query and retrieve the results as an array of objects, use the execute method. It returns a promise that resolves to the query result.
Expand Down Expand Up @@ -1064,8 +1127,8 @@ The `update` method allows you to update entries in the table that match the spe

`Parameters`

- `keys (object)`: An object representing the keys to filter the entries. Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.
- `fieldsToUpdate (object)`: An object representing the fields and their updated values for the matching entries.
- `keys (Object)`: An object representing the keys to filter the entries. Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.
- `fieldsToUpdate (Object)`: An object representing the fields and their updated values for the matching entries.

`Return`

Expand Down Expand Up @@ -1105,8 +1168,8 @@ The `updateLocaleTexts` method allows you to update entries in the table that ma

`Parameters`

- `localeCodeKeys (object)`: An object containing language codes `'en', 'de', 'fr', 'ro', ... ` and entity keys to filter entries.
- `fieldsToUpdate (object)`: An object representing the keys and values to update. Each key corresponds to a property in the entity.
- `localeCodeKeys (Object)`: An object containing language codes `'en', 'de', 'fr', 'ro', ... ` and entity keys to filter entries.
- `fieldsToUpdate (Object)`: An object representing the keys and values to update. Each key corresponds to a property in the entity.

`Return`

Expand Down Expand Up @@ -1143,7 +1206,7 @@ The `delete` method allows you to delete entries from the table that match the s

`Parameters`

- `keys (object)`: An object representing the keys to filter the entries. Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.
- `keys (Object)`: An object representing the keys to filter the entries. Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.

`Return`

Expand Down Expand Up @@ -1228,7 +1291,7 @@ The `exists` method allows you to check whether entries exist in the table that

`Parameters`

- `keys (object)`: Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.
- `keys (Object)`: Each key should correspond to a property in the `MyEntity`, and the values should match the filter criteria.

`Return`

Expand Down Expand Up @@ -1405,7 +1468,7 @@ Please make sure to update tests as appropriate.

![Licence](https://img.shields.io/github/license/Ileriayo/markdown-badges?style=for-the-badge)

Copyright (c) 2023 DXFrontier
Copyright (c) 2024 DXFrontier

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
32 changes: 32 additions & 0 deletions lib/util/SelectBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ class SelectBuilder<T, Keys> {
this.select = SELECT.from(this.entity).where(this.keys);
}

/**
* Skip duplicates similar to distinct from SQL
* @returns SelectBuilder instance
*
* @example
* const results = await this.builder().find()
* .distinct
* .columns('country')
* .execute();
*/
get distinct(): this {
this.select.SELECT.distinct = true;
return this;
}

/**
* Orders the selected columns in ascending order.
* @param columns An array of column names to order ascending.
Expand Down Expand Up @@ -259,6 +274,23 @@ class SelectBuilder<T, Keys> {
return this;
}

/**
* Exclusively locks the selected rows for subsequent updates in the current transaction, thereby preventing concurrent updates by other parallel transactions.
*/
public forUpdate(): this {
void this.select.forUpdate();
return this;
}

/**
* Locks the selected rows in the current transaction, thereby preventing concurrent updates by other parallel transactions, until the transaction is committed or rolled back. Using a shared lock allows all transactions to read the locked record.
* If a queried record is already exclusively locked by another transaction, the .forShareLock() method waits for the lock to be released.
*/
public forShareLock(): this {
void this.select.forShareLock();
return this;
}

/**
* Executes the query and returns the result as an array of objects.
* @returns A promise that resolves to the query result.
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": "@dxfrontier/cds-ts-repository",
"version": "0.3.8",
"version": "1.0.0",
"description": "The goal of SAP CAP CDS-QL BaseRepository is to significantly reduce the boilerplate code required to implement data access layers for persistance entities by providing out of the box actions on the database like .create(), .createMany(), .getAll(), .update(), .find(), .exists() ...",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
10 changes: 9 additions & 1 deletion test/unit/active-entity/SELECT.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,22 @@ describe('SELECT', () => {
});

describe('.builder()', () => {
describe('======> .filter() - Overload ', () => {
describe('======> .filter() - Overload - no arguments (get all items)', () => {
it('should return 6 record', async () => {
// Act
const results = await bookRepository.builder().find().execute();

// Assert
expect(results?.length).toEqual(6);
});

it('should return 3 distinct records', async () => {
// Act
const results = await bookRepository.builder().find().distinct.columns('currency_code').execute();

// Assert
expect(results?.length).toEqual(3);
});
});

describe('======> .filter() - Overload - filter / filters ', () => {
Expand Down

0 comments on commit 39e4c4e

Please sign in to comment.