Skip to content

Commit

Permalink
Merge branch 'main' into sqlite/wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
BobdenOs committed May 28, 2024
2 parents 1f5d2c7 + 2caf1f7 commit c4e2562
Show file tree
Hide file tree
Showing 100 changed files with 1,262 additions and 636 deletions.
8 changes: 4 additions & 4 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"db-service": "1.8.0",
"sqlite": "1.6.0",
"postgres": "1.7.0",
"hana": "0.2.0"
"db-service": "1.9.1",
"sqlite": "1.7.1",
"postgres": "1.8.0",
"hana": "0.4.0"
}
32 changes: 32 additions & 0 deletions db-service/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
- This project adheres to [Semantic Versioning](http://semver.org/).

## [1.9.1](https://github.com/cap-js/cds-dbs/compare/db-service-v1.9.0...db-service-v1.9.1) (2024-05-16)


### Fixed

* dont mistake non-key access with foreign key ([#642](https://github.com/cap-js/cds-dbs/issues/642)) ([2cd2349](https://github.com/cap-js/cds-dbs/commit/2cd234994d6a9e99765e56f7548a42a35279a790))

## [1.9.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.8.0...db-service-v1.9.0) (2024-05-08)


### Added

* Add missing `func` cqn structures ([#629](https://github.com/cap-js/cds-dbs/issues/629)) ([9d7539a](https://github.com/cap-js/cds-dbs/commit/9d7539ab0fc7e70a6a00c0bd9cb4b3e362976e16))


### Fixed

* **`order by`:** reject non-fk traversals of own columns in order by ([#599](https://github.com/cap-js/cds-dbs/issues/599)) ([3288d63](https://github.com/cap-js/cds-dbs/commit/3288d63f0ee6a96580a3b2138ecb24a944371cf1))
* Align all quote functions with @sap/cds-compiler ([#619](https://github.com/cap-js/cds-dbs/issues/619)) ([42e9828](https://github.com/cap-js/cds-dbs/commit/42e9828baf11ec55281ea634ce56ce93e6741b91))
* assign artificial alias if selecting from anonymous subquery ([#608](https://github.com/cap-js/cds-dbs/issues/608)) ([e1a7711](https://github.com/cap-js/cds-dbs/commit/e1a77119f0a5241cfe4f50a37a473f2325ba5bde))
* avoid spread operator ([#630](https://github.com/cap-js/cds-dbs/issues/630)) ([a39fb65](https://github.com/cap-js/cds-dbs/commit/a39fb65f9419fe60e0324741039d004b40082903))
* flat update with arbitrary where clauses ([#598](https://github.com/cap-js/cds-dbs/issues/598)) ([f108798](https://github.com/cap-js/cds-dbs/commit/f108798c6c8035f9cdd0b9c6b8f334f1454c2faa))
* improved `=` and `!=` with val `null` ([#626](https://github.com/cap-js/cds-dbs/issues/626)) ([cbcfe3b](https://github.com/cap-js/cds-dbs/commit/cbcfe3b15e8ebcf7e844dc5406e4bc228d4c94c9))
* Improved placeholders and limit clause ([#567](https://github.com/cap-js/cds-dbs/issues/567)) ([d5d5dbb](https://github.com/cap-js/cds-dbs/commit/d5d5dbb7219bcef6134440715cf756fdd439f076))
* multiple result responses ([#602](https://github.com/cap-js/cds-dbs/issues/602)) ([bf0bed4](https://github.com/cap-js/cds-dbs/commit/bf0bed4549fe816e35481b0c9a7547a522a5a593))
* only consider persisted columns for simple operations ([#592](https://github.com/cap-js/cds-dbs/issues/592)) ([6e31bda](https://github.com/cap-js/cds-dbs/commit/6e31bda1bb15b1770b75c8971773806a26f7d452))


### Changed

* require `>= sap/[email protected]` ([#627](https://github.com/cap-js/cds-dbs/issues/627)) ([f4d09e2](https://github.com/cap-js/cds-dbs/commit/f4d09e27c3b07dd88925e196aefc1087d8357f7a))

## [1.8.0](https://github.com/cap-js/cds-dbs/compare/db-service-v1.7.0...db-service-v1.8.0) (2024-04-12)


Expand Down
4 changes: 2 additions & 2 deletions db-service/lib/SQLService.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib'),
const cds = require('@sap/cds'),
DEBUG = cds.debug('sql|db')
const { Readable } = require('stream')
const { resolveView } = require('@sap/cds/libx/_runtime/common/utils/resolveView')
Expand Down Expand Up @@ -135,7 +135,7 @@ class SQLService extends DatabaseService {
if (query._streaming) {
this._changeToStreams(cqn.SELECT.columns, rows, true, true)
if (!rows.length) return

const result = rows[0]
// stream is always on position 0. Further properties like etag are inserted later.
let [key, val] = Object.entries(result)[0]
Expand Down
2 changes: 1 addition & 1 deletion db-service/lib/common/DatabaseService.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const SessionContext = require('./session-context')
const ConnectionPool = require('./generic-pool')
const infer = require('../infer')
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')

/** @typedef {unknown} DatabaseDriver */

Expand Down
2 changes: 1 addition & 1 deletion db-service/lib/common/session-context.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')

class SessionContext {
constructor(ctx) {
Expand Down
108 changes: 76 additions & 32 deletions db-service/lib/cqn2sql.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const cds = require('@sap/cds/lib')
const cds = require('@sap/cds')
const cds_infer = require('./infer')
const cqn4sql = require('./cqn4sql')

Expand Down Expand Up @@ -165,25 +165,26 @@ class CQN2SQLRenderer {
/** @type {Object<string,import('@sap/cds/apis/csn').Definition>} */
static TypeMap = {
// Utilizing cds.linked inheritance
UUID: () => `NVARCHAR(36)`,
String: e => `NVARCHAR(${e.length || 5000})`,
Binary: e => `VARBINARY(${e.length || 5000})`,
Int64: () => 'BIGINT',
Int32: () => 'INTEGER',
UInt8: () => 'TINYINT',
Int16: () => 'SMALLINT',
UInt8: () => 'SMALLINT',
Int32: () => 'INT',
Int64: () => 'BIGINT',
Integer: () => 'INT',
Integer64: () => 'BIGINT',
LargeString: () => 'NCLOB',
LargeBinary: () => 'BLOB',
Association: () => false,
Composition: () => false,
array: () => 'NCLOB',
// HANA types
/* Disabled as these types are linked to normal cds types
'cds.hana.TINYINT': () => 'REAL',
'cds.hana.TINYINT': () => 'TINYINT',
'cds.hana.REAL': () => 'REAL',
'cds.hana.CHAR': e => `CHAR(${e.length || 1})`,
'cds.hana.ST_POINT': () => 'ST_POINT',
'cds.hana.ST_GEOMETRY': () => 'ST_GEO',*/
'cds.hana.ST_GEOMETRY': () => 'ST_GEOMETRY',
}

// DROP Statements ------------------------------------------------
Expand Down Expand Up @@ -211,7 +212,7 @@ class CQN2SQLRenderer {
if (from?.join && !q.SELECT.columns) {
throw new Error('CQN query using joins must specify the selected columns.')
}

// REVISIT: When selecting from an entity that is not in the model the from.where are not normalized (as cqn4sql is skipped)
if (!where && from?.ref?.length === 1 && from.ref[0]?.where) where = from.ref[0]?.where
let columns = this.SELECT_columns(q)
Expand Down Expand Up @@ -290,7 +291,7 @@ class CQN2SQLRenderer {
return `extensions__->${this.string('$."' + x.element.name + '"')} as ${x.as || x.element.name}`
}
///////////////////////////////////////////////////////////////////////////////////////
let sql = this.expr(x)
let sql = this.expr({ param: false, __proto__: x })
let alias = this.column_alias4(x, q)
if (alias) sql += ' as ' + this.quote(alias)
return sql
Expand All @@ -302,7 +303,7 @@ class CQN2SQLRenderer {
* @returns {string}
*/
column_alias4(x) {
return typeof x.as === 'string' ? x.as : x.func
return typeof x.as === 'string' ? x.as : x.func || x.val
}

/**
Expand Down Expand Up @@ -387,7 +388,7 @@ class CQN2SQLRenderer {
*/
limit({ rows, offset }) {
if (!rows) throw new Error('Rows parameter is missing in SELECT.limit(rows, offset)')
return !offset ? rows.val : `${rows.val} OFFSET ${offset.val}`
return !offset ? this.val(rows) : `${this.val(rows)} OFFSET ${this.val(offset)}`
}

/**
Expand Down Expand Up @@ -799,8 +800,8 @@ class CQN2SQLRenderer {
if (x.param) return wrap(this.param(x))
if ('ref' in x) return wrap(this.ref(x))
if ('val' in x) return wrap(this.val(x))
if ('xpr' in x) return wrap(this.xpr(x))
if ('func' in x) return wrap(this.func(x))
if ('xpr' in x) return wrap(this.xpr(x))
if ('list' in x) return wrap(this.list(x))
if ('SELECT' in x) return wrap(`(${this.SELECT(x)})`)
else throw cds.error`Unsupported expr: ${x}`
Expand Down Expand Up @@ -832,18 +833,32 @@ class CQN2SQLRenderer {
operator(x, i, xpr) {

// Translate = to IS NULL for rhs operand being NULL literal
if (x === '=') return xpr[i + 1]?.val === null ? 'is' : '='
if (x === '=') return xpr[i + 1]?.val === null
? _inline_null(xpr[i + 1]) || 'is'
: '='

// Translate == to IS NOT NULL for rhs operand being NULL literal, otherwise ...
// Translate == to IS NOT DISTINCT FROM, unless both operands cannot be NULL
if (x === '==') return xpr[i + 1]?.val === null ? 'is' : _not_null(i - 1) && _not_null(i + 1) ? '=' : this.is_not_distinct_from_
if (x === '==') return xpr[i + 1]?.val === null
? _inline_null(xpr[i + 1]) || 'is'
: _not_null(i - 1) && _not_null(i + 1)
? '='
: this.is_not_distinct_from_

// Translate != to IS NULL for rhs operand being NULL literal, otherwise...
// Translate != to IS DISTINCT FROM, unless both operands cannot be NULL
if (x === '!=') return xpr[i + 1]?.val === null ? 'is not' : _not_null(i - 1) && _not_null(i + 1) ? '<>' : this.is_distinct_from_
if (x === '!=') return xpr[i + 1]?.val === null
? _inline_null(xpr[i + 1]) || 'is not'
: _not_null(i - 1) && _not_null(i + 1)
? '<>'
: this.is_distinct_from_

else return x

function _inline_null(n) {
n.param = false
}

/** Checks if the operand at xpr[i+-1] can be NULL. @returns true if not */
function _not_null(i) {
const operand = xpr[i]
Expand Down Expand Up @@ -884,27 +899,34 @@ class CQN2SQLRenderer {
}

/**
* Renders a value into the correct SQL syntax of a placeholder for a prepared statement
* Renders a value into the correct SQL syntax or a placeholder for a prepared statement
* @param {import('./infer/cqn').val} param0
* @returns {string} SQL
*/
val({ val, param }) {
switch (typeof val) {
case 'function': throw new Error('Function values not supported.')
case 'undefined': return 'NULL'
case 'undefined': val = null
break
case 'boolean': return `${val}`
case 'number': return `${val}` // REVISIT for HANA
case 'object':
if (val === null) return 'NULL'
if (val instanceof Date) val = val.toJSON() // returns null if invalid
else if (val instanceof Readable); // go on with default below
else if (Buffer.isBuffer(val)); // go on with default below
else if (is_regexp(val)) val = val.source
else val = JSON.stringify(val)
case 'string': // eslint-disable-line no-fallthrough
if (val !== null) {
if (val instanceof Date) val = val.toJSON() // returns null if invalid
else if (val instanceof Readable); // go on with default below
else if (Buffer.isBuffer(val)); // go on with default below
else if (is_regexp(val)) val = val.source
else val = JSON.stringify(val)
}
}
if (!this.values || param === false) return this.string(val)
else this.values.push(val)
if (!this.values || param === false) {
switch (typeof val) {
case 'string': return this.string(val)
case 'object': return 'NULL'
default:
return `${val}`
}
}
this.values.push(val)
return '?'
}

Expand All @@ -914,9 +936,32 @@ class CQN2SQLRenderer {
* @param {import('./infer/cqn').func} param0
* @returns {string} SQL
*/
func({ func, args }) {
args = (args || []).map(e => (e === '*' ? e : { __proto__: e, toString: (x = e) => this.expr(x) }))
return this.class.Functions[func]?.apply(this.class.Functions, args) || `${func}(${args})`
func({ func, args, xpr }) {
const wrap = e => (e === '*' ? e : { __proto__: e, toString: (x = e) => this.expr(x) })
args = args || []
if (Array.isArray(args)) {
args = args.map(wrap)
} else if (typeof args === 'object') {
const org = args
const wrapped = {
toString: () => {
const ret = []
for (const prop in org) {
ret.push(`${this.quote(prop)} => ${wrapped[prop]}`)
}
return ret.join(',')
}
}
for (const prop in args) {
wrapped[prop] = wrap(args[prop])
}
args = wrapped
} else {
cds.error`Invalid arguments provided for function '${func}' (${args})`
}
const fn = this.class.Functions[func]?.apply(this.class.Functions, args) || `${func}(${args})`
if (xpr) return `${fn} ${this.xpr({ xpr })}`
return fn
}

/**
Expand Down Expand Up @@ -968,8 +1013,7 @@ class CQN2SQLRenderer {
quote(s) {
if (typeof s !== 'string') return '"' + s + '"'
if (s.includes('"')) return '"' + s.replace(/"/g, '""') + '"'
// Column names like "Order" clash with "ORDER" keyword so toUpperCase is required
if (s in this.class.ReservedWords || /^\d|[$' ?@./\\]/.test(s)) return '"' + s + '"'
if (s in this.class.ReservedWords || !/^[A-Za-z_][A-Za-z_$0-9]*$/.test(s)) return '"' + s + '"'
return s
}

Expand Down
Loading

0 comments on commit c4e2562

Please sign in to comment.