Skip to content
This repository has been archived by the owner on Jul 14, 2023. It is now read-only.

Commit

Permalink
Merge branch 'sapmentors:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
rbhuva committed Mar 19, 2022
2 parents 3baf6c9 + bbdef88 commit ebc66b5
Show file tree
Hide file tree
Showing 6 changed files with 1,269 additions and 965 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

### 0.1.24 (2022-03-19)


### Bug Fixes

* return timestamps, not now() for createdat and modifiedat columns ([#278](https://github.com/sapmentors/cds-pg/issues/278)) ([8a2c05b](https://github.com/sapmentors/cds-pg/commit/8a2c05bef8106838ba3228bbf1040e7241ee12d5))

### 0.1.23 (2022-03-16)

### 0.1.22 (2022-01-25)


Expand Down
80 changes: 75 additions & 5 deletions __tests__/lib/pg/ql.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,57 @@ describe('QL to PostgreSQL', () => {
expect(beer).toBeNull()
})

test.todo('-> with distinct')
test.todo('-> with orderBy')
test.todo('-> with groupBy')
test.todo('-> with having')
test.todo('-> with joins')
test('-> with distinct', async () => {
const { Beers } = cds.entities('csw')
const results = await cds.run(SELECT.distinct.from(Beers).columns('abv'))
expect(results.length).toStrictEqual(6)
const otherResults = await cds.run(SELECT.distinct.from(Beers).columns('abv', 'ibu'))
expect(otherResults.length).toStrictEqual(9)
})

test('-> with orderBy', async () => {
const { Beers } = cds.entities('csw')
const beers = await cds.run(
SELECT.from(Beers)
.where({ abv: { '>': 1.0 } })
.orderBy({ abv: 'desc' })
)
expect(beers[0].abv).toStrictEqual('5.9')
const reverseBeers = await cds.run(
SELECT.from(Beers)
.where({ abv: { '>': 1.0 } })
.orderBy({ abv: 'asc' })
)
expect(reverseBeers[0].abv).toStrictEqual('4.9')
})

test('-> with groupBy', async () => {
const { Beers } = cds.entities('csw')
const results = await cds.run(SELECT.from(Beers).columns('count(*) as count', 'brewery_id').groupBy('brewery_id'))
expect(results.length).toStrictEqual(6)
})

test('-> with having', async () => {
const { Beers } = cds.entities('csw')
const results = await cds.run(
SELECT.from(Beers).columns('brewery_id').groupBy('brewery_id').having('count(*) >=', 2)
)
expect(results.length).toStrictEqual(3)
})

test('-> with joins', async () => {
const { Beers } = cds.entities('csw')
const results = await cds.run(
SELECT.from(Beers, (b) => {
b`.*`,
b.brewery((br) => {
br`.*`
})
}).where({ brewery_id: '4aeebbed-90c2-4bdd-aa70-d8eecb8eaebb' })
)
expect(results[0].brewery).toHaveProperty('name', 'Rittmayer Hallerndorf')
expect(results.length).toStrictEqual(4)
})
})

describe('INSERT', () => {
Expand Down Expand Up @@ -111,5 +157,29 @@ describe('QL to PostgreSQL', () => {
const beer = await cds.run(SELECT.one(Beers).where({ name: 'Test' }))
expect(beer).toHaveProperty('name', 'Test')
})

// see https://cap.cloud.sap/docs/node.js/databases#insertresult-beta and https://answers.sap.com/questions/13569793/api-of-insert-query-results-for-cap-nodejs.html
test('-> with InsertResult Beta API', async () => {
const { Beers } = cds.entities('csw')

const entries = [
{ name: 'Beer1', abv: 1.0, ibu: 1, brewery_ID: '0465e9ca-6255-4f5c-b8ba-7439531f8d28' },
{ name: 'Beer2', abv: 2.0, ibu: 2, brewery_ID: '0465e9ca-6255-4f5c-b8ba-7439531f8d28' },
{ name: 'Beer3', abv: 3.0, ibu: 3, brewery_ID: '0465e9ca-6255-4f5c-b8ba-7439531f8d28' }
]

const uuidRegex = /[\d|a-f]{8}-[\d|a-f]{4}-[\d|a-f]{4}-[\d|a-f]{4}-[\d|a-f]{12}/
const timestampRegex = /[\d]{4}-[\d]{2}-[\d]{2}T[\d]{2}:[\d]{2}:[\d]{2}.[\d]{3}Z/

const insertResult = await cds.run(INSERT.into(Beers).entries(entries))
expect(insertResult.affectedRows).toStrictEqual(3)
expect(insertResult == 3).toStrictEqual(true)
expect(insertResult.valueOf()).toStrictEqual(insertResult.affectedRows)
const beers = [...entries]
expect(beers.length).toStrictEqual(3)
expect(beers[0].ID).toMatch(uuidRegex)
expect(beers[0].createdAt).toMatch(timestampRegex)
expect(beers[0].modifiedAt).toMatch(timestampRegex)
})
})
})
32 changes: 29 additions & 3 deletions docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ because we know the data model, we can predict the result of the OData call, thu

The two most crucial files/code points are:

- API implementation of `cds.Service` in `index.js`
- `cqn` to PostgreSQL-`sql` "compiler" in `lib/cqn2pgsql.js`
- API implementation of `cds.DatabaseService` in `lib/pg/Service.js`
- `cqn` to PostgreSQL-`sql` translation in `lib/pg`

There most likely the majority of your work will take place.

Expand Down Expand Up @@ -87,7 +87,33 @@ Choose `PostgreSQL` as `System`,
Upon successful login, you'll see the `beershop` db, w00t 🍺
![postgresql beershop database](./images/postgres-beershop.png)

Finally, rename `__tests__/__assets__/cap-proj/default-env-template.json` to `__tests__/__assets__/cap-proj/default-env.json` for having the sample CAP project authenticate locally against the dockerized PostgreSQL.
### run CAP project against the dockerized PostgreSQL

In the previous step you've confirmed that PostgreSQL is working. Now you can now either start a new terminal to run:

```
npm run test:as-pg
```

or you stop the running `npm run test:pg:up-nobg` by CTRL+c and start the database using:

```
npm run test:pg:up
```

and then run:

```
npm run test:as-pg
```

this will startup the CAP project which you then can reach at 'http:https://localhost:4004'. You can experiment with the provided endpoints using the browser. The more efficient way are the REST Client scripts in the `__tests__/__assets__/cap-proj/rest-client-test` folder. This is also the most efficient way to report an issue. Fork this project, create a sample request in the REST Client tests and create a pull request. Check the chapter [comparison possibility with sqlite](#comparison-possibility-with-sqlite) on how to check if it's a missing functionality in cds-pg or maybe a missing feature in the @sap/cds standard.

To stop the running CAP servcie use CTRL+c. To stop the running PostgreSQL docker container use:

```
npm run test:pg:down
```

### runnable queries and runtime debug capabilites

Expand Down
29 changes: 15 additions & 14 deletions lib/pg/execute.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const DEBUG = cds.debug('cds-pg')
* @param {*} txTimestamp
* @return {import('pg').QueryArrayResult}
*/
const executeGenericCQN = (model, dbc, query, user /*, locale, txTimestamp */) => {
const { sql, values = [] } = _cqnToSQL(model, query, user)
const executeGenericCQN = (model, dbc, query, user, locale, txTimestamp) => {
const { sql, values = [] } = _cqnToSQL(model, query, user, locale, txTimestamp)
const isOne = query.SELECT && query.SELECT.one
const postPropertyMapper = getPostProcessMapper(PG_TYPE_CONVERSION_MAP, model, query)
return _executeSQLReturningRows(dbc, sql, values, isOne, postPropertyMapper)
Expand All @@ -41,11 +41,11 @@ const executeGenericCQN = (model, dbc, query, user /*, locale, txTimestamp */) =
* @param {*} txTimestamp
* @return {import('pg').QueryArrayResult}
*/
const executeSelectCQN = (model, dbc, query, user /*, locale, txTimestamp*/) => {
const executeSelectCQN = (model, dbc, query, user, locale, txTimestamp) => {
if (hasExpand(query)) {
return processExpand(dbc, query, model, user)
return processExpand(dbc, query, model, user, locale, txTimestamp)
} else {
const { sql, values = [] } = _cqnToSQL(model, query, user)
const { sql, values = [] } = _cqnToSQL(model, query, user, locale, txTimestamp)
const isOne = query.SELECT && query.SELECT.one
const postPropertyMapper = getPostProcessMapper(PG_TYPE_CONVERSION_MAP, model, query)
return _executeSQLReturningRows(dbc, sql, values, isOne, postPropertyMapper)
Expand All @@ -65,8 +65,8 @@ const executeSelectCQN = (model, dbc, query, user /*, locale, txTimestamp*/) =>
* @param {*} txTimestamp
* @return {Array}
*/
const executeInsertCQN = async (model, dbc, cqn, user) => {
const { sql, values = [] } = _cqnToSQL(model, cqn, user)
const executeInsertCQN = async (model, dbc, cqn, user, locale, txTimestamp) => {
const { sql, values = [] } = _cqnToSQL(model, cqn, user, locale, txTimestamp)
const postPropertyMapper = getPostProcessMapper(PG_TYPE_CONVERSION_MAP, model, cqn)
const resultPromises = []

Expand Down Expand Up @@ -116,13 +116,13 @@ async function executePlainSQL(dbc, rawSql, rawValues) {
* @param {*} user
* @return {import('pg').QueryArrayResult} the
*/
const processExpand = (dbc, cqn, model, user) => {
const processExpand = (dbc, cqn, model, user, locale, txTimestamp) => {
let queries = []
const expandQueries = createJoinCQNFromExpanded(cqn, model, true)
for (const cqn of expandQueries.queries) {
// REVISIT
// Why is the post processing in expand different?
const { sql, values } = _cqnToSQL(model, cqn, user, true)
const { sql, values } = _cqnToSQL(model, cqn, user, locale, txTimestamp, true)
const postPropertyMapper = getPostProcessMapper(PG_TYPE_CONVERSION_MAP, model, cqn)

queries.push(_executeSQLReturningRows(dbc, sql, values, false, postPropertyMapper))
Expand All @@ -140,7 +140,7 @@ const processExpand = (dbc, cqn, model, user) => {
* @param {Boolean} isExpand
* @return {Object} the query object containing sql and values
*/
function _cqnToSQL(model, cqn, user, isExpand = false) {
function _cqnToSQL(model, cqn, user, locale, txTimestamp, isExpand = false) {
return _replacePlaceholders(
sqlFactory(
cqn,
Expand All @@ -152,8 +152,9 @@ function _cqnToSQL(model, cqn, user, isExpand = false) {
FunctionBuilder: PGFunctionBuilder
},
isExpand, // Passed to inform the select builder that we are dealing with an expand call
now: 'NOW ()',
user
now: txTimestamp || { sql: 'NOW ()' },
user,
locale
},
model,
isExpand
Expand Down Expand Up @@ -221,8 +222,8 @@ async function _executeSQLReturningRows(dbc, sql, values, isOne, postMapper, pro
return postProcess(result, postMapper, propertyMapper, objStructMapper)
}

const executeUpdateCQN = async (model, dbc, cqn) => {
const result = await executeGenericCQN(model, dbc, cqn)
const executeUpdateCQN = async (model, dbc, cqn, user, locale, txTimestamp) => {
const result = await executeGenericCQN(model, dbc, cqn, user, locale, txTimestamp)
return Array.isArray(result) ? result.length : result
}

Expand Down
Loading

0 comments on commit ebc66b5

Please sign in to comment.