Skip to content

Commit

Permalink
FEATURE : distinct property of the builder().find() now is implemented.
Browse files Browse the repository at this point in the history
  • Loading branch information
dragolea committed Jan 30, 2024
1 parent 4006d4f commit cc24da3
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 33 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
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 cc24da3

Please sign in to comment.