@sap/cds-dk 10.1.0 → 10.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/bin/bind.js +4 -0
- package/bin/build.js +1 -0
- package/bin/compile/index.js +12 -11
- package/bin/completion/index.js +21 -10
- package/bin/completion/scripts/cds.fish +179 -127
- package/bin/completion/scripts/cds.ps1 +88 -14
- package/bin/completion/scripts/cds.sh +71 -5
- package/bin/completion/scripts/cds.zsh +81 -15
- package/bin/completion/templates/cds-tmpl.ps1 +15 -6
- package/bin/deploy/index.js +7 -3
- package/bin/env.js +4 -1
- package/bin/export.js +9 -1
- package/bin/import.js +4 -1
- package/bin/init.js +1 -0
- package/bin/inspect.js +3 -1
- package/bin/lint.js +5 -3
- package/bin/minify.js +6 -1
- package/bin/repl.js +11 -2
- package/bin/run.js +12 -1
- package/bin/serve.js +13 -2
- package/bin/up.js +4 -1
- package/bin/util/containerize.js +2 -0
- package/bin/watch/index.js +8 -1
- package/lib/build/plugins/internal.js +0 -26
- package/lib/build/plugins/nodejs/index.js +8 -3
- package/lib/build/util.js +2 -4
- package/lib/cds.js +8 -2
- package/lib/init/constants.js +1 -1
- package/lib/init/merge.js +21 -17
- package/lib/init/template/cloud-logging/index.js +3 -3
- package/lib/init/template/data/as-json.js +3 -0
- package/lib/init/template/destination/index.js +3 -3
- package/lib/init/template/enterprise-messaging/index.js +4 -1
- package/lib/init/template/event-mesh/index.js +3 -0
- package/lib/init/template/multitenancy/index.js +2 -2
- package/lib/init/template/workzone-standard/index.js +1 -1
- package/node_modules/@cap-js/db-service/CHANGELOG.md +30 -0
- package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
- package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
- package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
- package/node_modules/@cap-js/db-service/lib/cqn4sql.js +125 -21
- package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
- package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
- package/node_modules/@cap-js/db-service/lib/search.js +1 -1
- package/node_modules/@cap-js/db-service/package.json +1 -1
- package/node_modules/@cap-js/sqlite/CHANGELOG.md +28 -0
- package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
- package/node_modules/@cap-js/sqlite/package.json +3 -3
- package/node_modules/@sap/cds/CHANGELOG.md +19 -0
- package/node_modules/@sap/cds/common.cds +3 -3
- package/node_modules/@sap/cds/lib/compile/minify.js +3 -1
- package/node_modules/@sap/cds/lib/env/cds-env.js +1 -1
- package/node_modules/@sap/cds/lib/env/cds-requires.js +1 -0
- package/node_modules/@sap/cds/libx/_runtime/common/generic/crud.js +7 -1
- package/node_modules/@sap/cds/libx/_runtime/common/utils/cqn.js +1 -1
- package/node_modules/@sap/cds/libx/_runtime/fiori/lean-draft.js +1 -1
- package/node_modules/@sap/cds/libx/_runtime/messaging/common-utils/normalizeIncomingMessage.js +1 -1
- package/node_modules/@sap/cds/libx/_runtime/messaging/kafka.js +1 -1
- package/node_modules/@sap/cds/libx/http/location.js +10 -11
- package/node_modules/@sap/cds/libx/odata/middleware/read.js +1 -1
- package/node_modules/@sap/cds/libx/queue/SchedulingService.js +4 -0
- package/node_modules/@sap/cds/libx/queue/index.js +44 -10
- package/node_modules/@sap/cds/libx/queue/processing.js +47 -16
- package/node_modules/@sap/cds/libx/queue/utils.js +3 -0
- package/node_modules/@sap/cds/package.json +1 -1
- package/node_modules/@sap/cds/tasks/enterprise-messaging-deploy.js +13 -5
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +15 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +7 -3
- package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +2 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +12 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +5 -3
- package/node_modules/@sap/cds-compiler/lib/transform/db/flattening.js +4 -3
- package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
- package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +19 -0
- package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
- package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +1 -1
- package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
- package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +29 -2
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +34 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +3 -0
- package/node_modules/@sap/cds-mtxs/lib/utils.js +5 -4
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +62 -7
- package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
- package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +8 -3
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +11 -2
- package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +3 -2
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
- package/node_modules/proxy-addr/HISTORY.md +60 -54
- package/node_modules/proxy-addr/README.md +1 -1
- package/node_modules/proxy-addr/index.js +32 -0
- package/node_modules/proxy-addr/package.json +8 -4
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
- package/node_modules/yaml/dist/nodes/Alias.js +25 -23
- package/node_modules/yaml/package.json +1 -1
- package/package.json +2 -2
- /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
- /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
|
@@ -33,6 +33,9 @@ const notSupportedOps = [['>'], ['<'], ['>='], ['<='], ['*'], ['+'], ['-'], ['/'
|
|
|
33
33
|
|
|
34
34
|
const allOps = eqOps.concat(eqOps).concat(notEqOps).concat(notSupportedOps)
|
|
35
35
|
|
|
36
|
+
|
|
37
|
+
const { depthGuarded, guardEntry } = require('./recursion-guard')
|
|
38
|
+
|
|
36
39
|
const { pseudos } = require('./infer/pseudos')
|
|
37
40
|
/**
|
|
38
41
|
* Transforms a CDL style query into SQL-Like CQN:
|
|
@@ -55,7 +58,17 @@ const { pseudos } = require('./infer/pseudos')
|
|
|
55
58
|
* @returns {object} transformedQuery the transformed query
|
|
56
59
|
*/
|
|
57
60
|
function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
61
|
+
// guardEntry bounds subquery nesting and resets the depth budget per top-level call
|
|
62
|
+
return guardEntry(_cqn4sql)(originalQuery, model, useTechnicalAlias)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function _cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
58
66
|
const getImplicitAlias = str => _getImplicitAlias(str, useTechnicalAlias)
|
|
67
|
+
// guard xpr and inline/expand-on-struct nesting
|
|
68
|
+
// eslint-disable-next-line no-func-assign
|
|
69
|
+
getTransformedTokenStream = depthGuarded(getTransformedTokenStream)
|
|
70
|
+
// eslint-disable-next-line no-func-assign
|
|
71
|
+
nestedProjectionOnStructure = depthGuarded(nestedProjectionOnStructure)
|
|
59
72
|
let inferred = typeof originalQuery === 'string' ? cds.parse.cql(originalQuery) : cds.ql.clone(originalQuery)
|
|
60
73
|
const hasCustomJoins =
|
|
61
74
|
originalQuery.SELECT?.from.args && (!originalQuery.joinTree || originalQuery.joinTree.isInitial)
|
|
@@ -69,6 +82,9 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
69
82
|
const { where, having } = transformSearch(searchTerm)
|
|
70
83
|
if (where) inferred.SELECT.where = where
|
|
71
84
|
else if (having) inferred.SELECT.having = having
|
|
85
|
+
// Defer the ranking ORDER BY to after infer(), where the outer table alias is known and the
|
|
86
|
+
// deep-search sub-select can be correlated to the outer row (see buildSearchRankOrderBy).
|
|
87
|
+
defineProperty(inferred, '$searchRank', searchTerm)
|
|
72
88
|
}
|
|
73
89
|
}
|
|
74
90
|
// query modifiers can also be defined in from ref leaf infix filter
|
|
@@ -182,7 +198,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
182
198
|
/**
|
|
183
199
|
* If the target entity is annotated with persistence skip and has an underlying db entity,
|
|
184
200
|
* we treat it as a runtime view and transform it into a CTE.
|
|
185
|
-
*
|
|
201
|
+
*
|
|
186
202
|
* @param {object} transformedQuery - The query object to be transformed.
|
|
187
203
|
* @param {string} model - The data model used for inference and transformation.
|
|
188
204
|
*/
|
|
@@ -198,10 +214,10 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
198
214
|
}
|
|
199
215
|
|
|
200
216
|
/**
|
|
201
|
-
* Recursively call cqn4sql for all nested runtime views to calculate cte and
|
|
217
|
+
* Recursively call cqn4sql for all nested runtime views to calculate cte and
|
|
202
218
|
* add it as a with clause to the transformed query.
|
|
203
219
|
* Alias the runtime view with a unique alias and update all references to the runtime view to point to the alias.
|
|
204
|
-
*
|
|
220
|
+
*
|
|
205
221
|
* @param {object} rootDefinition - The root definition of the query. This is used to recursively process nested runtime views.
|
|
206
222
|
* @param {object} transformedQuery - The query object to be transformed.
|
|
207
223
|
* @param {string} model - The data model used for infer and cqn4sql.
|
|
@@ -317,9 +333,31 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
317
333
|
|
|
318
334
|
// Since all the expressions in the SELECT part of the query have been computed,
|
|
319
335
|
// one can reference aliases of the queries columns in the orderBy clause.
|
|
320
|
-
|
|
321
|
-
|
|
336
|
+
let effectiveOrderBy = orderBy
|
|
337
|
+
// Rank by $search relevance — only when the score exists (HANA fuzzy, not opted out); else
|
|
338
|
+
// it would sort by a constant boolean.
|
|
339
|
+
const ranksSearch =
|
|
340
|
+
cds.db?.kind === 'hana' && cds.env.hana?.fuzzy !== false && cds.env.hana?.fuzzy?.ranked_search !== false
|
|
341
|
+
// count queries or static values do not need ranked search
|
|
342
|
+
const isCountQuery = columns?.length === 1 && typeof columns[0] === 'object' && (columns[0].func === 'count' || 'val' in columns[0])
|
|
343
|
+
// a user-provided ORDER BY takes precedence over the search rank — don't inject the ranking then
|
|
344
|
+
const hasExplicitOrderBy = (orderBy || []).some(o => !o.implicit)
|
|
345
|
+
const searchRank = ranksSearch && !isCountQuery && !hasExplicitOrderBy && inferred.$searchRank && buildSearchRankOrderBy(inferred.$searchRank)
|
|
346
|
+
if (searchRank) {
|
|
347
|
+
// precedence: user ordering, then rank, then the runtime's implicit key ordering
|
|
348
|
+
const implicitAt = (orderBy || []).findIndex(o => o.implicit)
|
|
349
|
+
const at = implicitAt === -1 ? (orderBy?.length ?? 0) : implicitAt
|
|
350
|
+
effectiveOrderBy = [...(orderBy || [])]
|
|
351
|
+
effectiveOrderBy.splice(at, 0, searchRank)
|
|
352
|
+
}
|
|
353
|
+
if (effectiveOrderBy) {
|
|
354
|
+
const transformedOrderBy = getTransformedOrderByGroupBy(effectiveOrderBy, true)
|
|
322
355
|
if (transformedOrderBy.length) {
|
|
356
|
+
// the rank is the only order-by entry that is a correlated sub-select
|
|
357
|
+
if (searchRank?.$searchRank) {
|
|
358
|
+
const rank = transformedOrderBy.find(o => o.SELECT)
|
|
359
|
+
if (rank) correlateSearchRank(rank, transformedFrom.as)
|
|
360
|
+
}
|
|
323
361
|
transformedQuery.SELECT.orderBy = transformedOrderBy
|
|
324
362
|
}
|
|
325
363
|
}
|
|
@@ -383,6 +421,8 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
383
421
|
*/
|
|
384
422
|
function translateAssocsToJoins() {
|
|
385
423
|
let from
|
|
424
|
+
// guard association-path length
|
|
425
|
+
const joinForBranch = depthGuarded(_joinForBranch)
|
|
386
426
|
/**
|
|
387
427
|
* remember already seen aliases, do not create a join for them again
|
|
388
428
|
*/
|
|
@@ -406,7 +446,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
406
446
|
})
|
|
407
447
|
return from.args.length > 1 ? from : from.args[0]
|
|
408
448
|
|
|
409
|
-
function
|
|
449
|
+
function _joinForBranch(lhs, node) {
|
|
410
450
|
const nextAssoc = inferred.joinTree.findNextAssoc(node)
|
|
411
451
|
if (!nextAssoc || alreadySeen.has(nextAssoc.$refLink.alias)) return lhs.args.length > 1 ? lhs : lhs.args[0]
|
|
412
452
|
|
|
@@ -643,7 +683,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
643
683
|
if (col.cast) ret.cast = resolveEnumCastType(col.cast)
|
|
644
684
|
return ret
|
|
645
685
|
}
|
|
646
|
-
return
|
|
686
|
+
return { ...col }
|
|
647
687
|
}
|
|
648
688
|
|
|
649
689
|
function handleEmptyColumns(columns) {
|
|
@@ -995,11 +1035,11 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
995
1035
|
const keyName = k.as || k.ref.join('_')
|
|
996
1036
|
const fkName = `${elemName}_${keyName}` // e.g., 'head_id'
|
|
997
1037
|
const fkFullName = `${columnAlias}_${fkName}` // e.g., 'department_head_id'
|
|
998
|
-
|
|
1038
|
+
|
|
999
1039
|
// Check if this FK is excluded
|
|
1000
1040
|
if (exclude.some(e => (e.ref?.at(-1) || e.as || e) === fkName)) continue
|
|
1001
1041
|
if (exclude.some(e => (e.ref?.at(-1) || e.as || e) === fkFullName)) continue
|
|
1002
|
-
|
|
1042
|
+
|
|
1003
1043
|
const flatColumn = {
|
|
1004
1044
|
ref: [joinAlias, fkName],
|
|
1005
1045
|
as: fkFullName,
|
|
@@ -1017,7 +1057,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1017
1057
|
calcElement.as = fullName
|
|
1018
1058
|
}
|
|
1019
1059
|
res.push(calcElement)
|
|
1020
|
-
}
|
|
1060
|
+
}
|
|
1021
1061
|
else {
|
|
1022
1062
|
// Scalar element
|
|
1023
1063
|
const flatColumn = {
|
|
@@ -1322,7 +1362,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1322
1362
|
else if (col.xpr) transformedColumn = { xpr: getTransformedTokenStream(col.xpr) }
|
|
1323
1363
|
else if (col.func) transformedColumn = { args: getTransformedFunctionArgs(col.args), func: col.func }
|
|
1324
1364
|
// val
|
|
1325
|
-
else transformedColumn =
|
|
1365
|
+
else transformedColumn = { ...col }
|
|
1326
1366
|
if (col.sort) transformedColumn.sort = col.sort
|
|
1327
1367
|
if (col.nulls) transformedColumn.nulls = col.nulls
|
|
1328
1368
|
res.push(transformedColumn)
|
|
@@ -1397,8 +1437,13 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1397
1437
|
if (!exclude.includes(k)) {
|
|
1398
1438
|
const { index, tableAlias } = inferred.$combinedElements[k][0]
|
|
1399
1439
|
const element = tableAlias.elements[k]
|
|
1400
|
-
// ignore FK for odata csn (but not for subquery sources where FK is not a separate element) / ignore blobs from wildcard expansion
|
|
1401
|
-
if (
|
|
1440
|
+
// ignore FK for odata csn (but not for subquery sources where FK is not a separate element) / ignore blobs and vectors from wildcard expansion
|
|
1441
|
+
if (
|
|
1442
|
+
(!tableAlias.SELECT && isManagedAssocInFlatMode(element)) ||
|
|
1443
|
+
element.type === 'cds.LargeBinary' ||
|
|
1444
|
+
element.type === 'cds.Vector'
|
|
1445
|
+
)
|
|
1446
|
+
continue
|
|
1402
1447
|
// for wildcard on subquery in from, just reference the elements
|
|
1403
1448
|
if (tableAlias.SELECT && !element.elements && !element.target) {
|
|
1404
1449
|
wildcardColumns.push(index ? { ref: [index, k] } : { ref: [k] })
|
|
@@ -1461,7 +1506,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1461
1506
|
* - `tableAlias` — table alias prepended to the column ref.
|
|
1462
1507
|
* @param {string[]} [csnPath=[]] - Accumulated CSN element path (used for `_csnPath` metadata on leaf columns).
|
|
1463
1508
|
* @param {{ exclude?: Array, replace?: Array }} [excludeAndReplace] - Columns to exclude or replace during wildcard expansion.
|
|
1464
|
-
* @param {boolean} [isWildcard=false] - Whether this expansion originates from a wildcard; filters out LargeBinary.
|
|
1509
|
+
* @param {boolean} [isWildcard=false] - Whether this expansion originates from a wildcard; filters out LargeBinary and Vector.
|
|
1465
1510
|
* @returns {object[]} Flat column(s) for the given element.
|
|
1466
1511
|
*/
|
|
1467
1512
|
function getFlatColumnsFor(column, names, csnPath = [], excludeAndReplace, isWildcard = false) {
|
|
@@ -1474,7 +1519,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1474
1519
|
const { $refLinks, flatName, isJoinRelevant } = column
|
|
1475
1520
|
let firstNonJoinRelevantAssoc, stepAfterAssoc
|
|
1476
1521
|
let element = $refLinks ? $refLinks[$refLinks.length - 1].definition : column
|
|
1477
|
-
if (isWildcard && element.type === 'cds.LargeBinary') return []
|
|
1522
|
+
if (isWildcard && (element.type === 'cds.LargeBinary' || element.type === 'cds.Vector')) return []
|
|
1478
1523
|
if (element.on && !element.keys) return [] // unmanaged doesn't make it into columns
|
|
1479
1524
|
if (element.virtual === true) return []
|
|
1480
1525
|
|
|
@@ -1733,8 +1778,10 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1733
1778
|
}
|
|
1734
1779
|
|
|
1735
1780
|
const whereExists = { SELECT: whereExistsSubqueries(whereExistsSubSelects) }
|
|
1736
|
-
|
|
1737
|
-
//
|
|
1781
|
+
// append, don't index by `i`: a preceding branch (e.g. flattening `<assoc> is null`)
|
|
1782
|
+
// may have changed the transformed stream's length, so `[i + 1]` would leave holes
|
|
1783
|
+
transformedTokenStream.push(whereExists)
|
|
1784
|
+
// skip the association ref which we just turned into the subquery
|
|
1738
1785
|
i += 1
|
|
1739
1786
|
} else if (token !== null && typeof token === 'object' && '#' in token) {
|
|
1740
1787
|
// Enum token: resolve to its value
|
|
@@ -1812,8 +1859,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1812
1859
|
// reject virtual elements in expressions as they will lead to a sql error down the line
|
|
1813
1860
|
if (lhsDef?.virtual) throw new Error(`Virtual elements are not allowed in expressions`)
|
|
1814
1861
|
|
|
1815
|
-
let result = is_regexp(token?.val) ? token :
|
|
1816
|
-
// REVISIT: required because we copy the token above and lose the not enumerable "param: false"
|
|
1862
|
+
let result = typeof token !== 'object' || is_regexp(token?.val) ? token : { ...token }
|
|
1817
1863
|
if (typeof token === 'object' && 'val' in token && 'param' in token) Object.defineProperty(result, 'param', { value: token.param })
|
|
1818
1864
|
if (token.ref) {
|
|
1819
1865
|
const { definition } = token.$refLinks.at(-1)
|
|
@@ -1830,7 +1876,6 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1830
1876
|
const stepToFind = token.ref[1]?.id || token.ref[1]
|
|
1831
1877
|
const outerAlias = outerQuery.$combinedElements?.[stepToFind]?.[0].index
|
|
1832
1878
|
if (outerAlias) {
|
|
1833
|
-
let result = copy(token)
|
|
1834
1879
|
result.ref = [outerAlias, token.flatName]
|
|
1835
1880
|
transformedTokenStream.push(result)
|
|
1836
1881
|
continue
|
|
@@ -2016,7 +2061,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
2016
2061
|
*/
|
|
2017
2062
|
function getTransformedFrom(from, existingWhere = []) {
|
|
2018
2063
|
const transformedWhere = []
|
|
2019
|
-
let transformedFrom =
|
|
2064
|
+
let transformedFrom = { ...from }
|
|
2020
2065
|
if (from.$refLinks) defineProperty(transformedFrom, '$refLinks', [...from.$refLinks])
|
|
2021
2066
|
if (from.args) {
|
|
2022
2067
|
transformedFrom.args = []
|
|
@@ -2579,6 +2624,65 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
2579
2624
|
return { xpr: [matchColumns.length === 1 ? matchColumns[0] : { list: matchColumns }, 'in', subquery] }
|
|
2580
2625
|
}
|
|
2581
2626
|
|
|
2627
|
+
/**
|
|
2628
|
+
* Builds the ORDER BY entry ranking rows by $search relevance, sorted desc.
|
|
2629
|
+
*
|
|
2630
|
+
* Flat search: the score is on the outer row.
|
|
2631
|
+
* Deep search: the score lives in a semi-join, so emit a correlated scalar sub-select
|
|
2632
|
+
* (SELECT search(…, true) FROM <same source> WHERE innerKey = <outerAlias>.key) DESC.
|
|
2633
|
+
* Key comparisons are seeded unqualified (infer() binds them to the sub-select's own source);
|
|
2634
|
+
* correlateSearchRank redirects the rhs to the outer alias afterwards.
|
|
2635
|
+
*
|
|
2636
|
+
* @param {object} searchTerm the search term as returned by getSearch (func or xpr shape)
|
|
2637
|
+
* @returns {object|null} an orderBy entry, or null if there is nothing to rank by
|
|
2638
|
+
*/
|
|
2639
|
+
function buildSearchRankOrderBy(searchTerm) {
|
|
2640
|
+
if (searchTerm.func) return { func: searchTerm.func, args: [...searchTerm.args, { val: true }], sort: 'desc' }
|
|
2641
|
+
if (!searchTerm.xpr) return null
|
|
2642
|
+
|
|
2643
|
+
const searchSelect = searchTerm.xpr[2]
|
|
2644
|
+
const searchFunc = searchSelect.SELECT.where[0]
|
|
2645
|
+
const innerKeys = searchSelect.SELECT.columns // unqualified pk refs, e.g. [{ ref: ['ID'] }]
|
|
2646
|
+
|
|
2647
|
+
const where = []
|
|
2648
|
+
for (let i = 0; i < innerKeys.length; i++) {
|
|
2649
|
+
if (i) where.push('and')
|
|
2650
|
+
// seeded unqualified on both sides; correlateSearchRank redirects the rhs to the outer row
|
|
2651
|
+
where.push({ ref: [...innerKeys[i].ref] }, '=', { ref: [...innerKeys[i].ref] })
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
const entry = {
|
|
2655
|
+
__proto__: SELECT.from(searchSelect.SELECT.from)
|
|
2656
|
+
// the correlated outer row fans out to many child rows -> MAX makes it a single score
|
|
2657
|
+
.columns({ func: 'max', args: [{ func: searchFunc.func, args: [...searchFunc.args, { val: true }] }] })
|
|
2658
|
+
.where(where),
|
|
2659
|
+
sort: 'desc',
|
|
2660
|
+
}
|
|
2661
|
+
defineProperty(entry, '$searchRank', true)
|
|
2662
|
+
return entry
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
/**
|
|
2666
|
+
* Correlates the (transformed) deep-search ranking sub-select to the outer row.
|
|
2667
|
+
*
|
|
2668
|
+
* buildSearchRankOrderBy seeds its WHERE as `ref = ref` comparisons on the sub-select's own
|
|
2669
|
+
* alias; this rewrites each rhs to `<outerAlias>.<key>`. Driven off the `=` operator (not a
|
|
2670
|
+
* fixed stride) so it holds for any key count.
|
|
2671
|
+
*
|
|
2672
|
+
* @param {object} entry the transformed orderBy entry produced from a `$searchRank` sub-select
|
|
2673
|
+
* @param {string} outerAlias the final table alias of the outer query source
|
|
2674
|
+
*/
|
|
2675
|
+
function correlateSearchRank(entry, outerAlias) {
|
|
2676
|
+
const where = entry.SELECT.where
|
|
2677
|
+
for (let i = 1; i < where.length; i++) {
|
|
2678
|
+
// seeded comparisons are exactly `<ref> = <ref>`; rewrite the rhs ref to the outer row
|
|
2679
|
+
if (where[i] === '=' && where[i - 1]?.ref && where[i + 1]?.ref) {
|
|
2680
|
+
const rhs = where[i + 1]
|
|
2681
|
+
rhs.ref = [outerAlias, ...rhs.ref.slice(1)]
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2582
2686
|
/**
|
|
2583
2687
|
* Calculates the name of the source which can be used to address the given node.
|
|
2584
2688
|
*
|
|
@@ -5,15 +5,19 @@ const cds = require('@sap/cds')
|
|
|
5
5
|
const JoinTree = require('./join-tree')
|
|
6
6
|
const { pseudos } = require('./pseudos')
|
|
7
7
|
const { isCalculatedOnRead, getImplicitAlias, getModelUtils, defineProperty, hasOwnSkip, isRuntimeView } = require('../utils')
|
|
8
|
+
const { depthGuarded, guardEntry } = require('../recursion-guard')
|
|
8
9
|
const cdsTypes = cds.builtin.types
|
|
9
10
|
/**
|
|
10
11
|
* @param {import('@sap/cds/apis/cqn').Query|string} originalQuery
|
|
11
12
|
* @param {import('@sap/cds/apis/csn').CSN} [model]
|
|
12
13
|
* @returns {import('./cqn').Query} = q with .target and .elements
|
|
13
14
|
*/
|
|
14
|
-
function
|
|
15
|
+
function _infer(originalQuery, model, useTechnicalAlias = true) {
|
|
15
16
|
if (!model) throw new Error('Please specify a model')
|
|
16
17
|
const inferred = originalQuery
|
|
18
|
+
// guard func args / lists / xpr / exists nesting
|
|
19
|
+
// eslint-disable-next-line no-func-assign
|
|
20
|
+
inferArg = depthGuarded(inferArg)
|
|
17
21
|
|
|
18
22
|
const { getDefinition } = getModelUtils(model, originalQuery)
|
|
19
23
|
|
|
@@ -291,19 +295,23 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
|
|
|
291
295
|
*/
|
|
292
296
|
function walkTokenStream(tokenStream, inXpr = false) {
|
|
293
297
|
let skipJoins
|
|
294
|
-
const processToken =
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
298
|
+
const processToken = depthGuarded(walker())
|
|
299
|
+
tokenStream.forEach(processToken)
|
|
300
|
+
|
|
301
|
+
function walker() {
|
|
302
|
+
return t => {
|
|
303
|
+
if (t === 'exists') {
|
|
304
|
+
// no joins for infix filters along `exists <path>`
|
|
305
|
+
skipJoins = true
|
|
306
|
+
} else if (t.xpr) {
|
|
307
|
+
// don't miss an exists within an expression
|
|
308
|
+
t.xpr.forEach(processToken)
|
|
309
|
+
} else {
|
|
310
|
+
inferArg(t, queryElements, null, { inExists: skipJoins, inXpr, inQueryModifier: true })
|
|
311
|
+
skipJoins = false
|
|
312
|
+
}
|
|
304
313
|
}
|
|
305
314
|
}
|
|
306
|
-
tokenStream.forEach(processToken)
|
|
307
315
|
}
|
|
308
316
|
/**
|
|
309
317
|
* Processes references starting with `$self`, which are intended to target other query elements.
|
|
@@ -631,7 +639,7 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
|
|
|
631
639
|
} else if (arg.inline && queryElements) {
|
|
632
640
|
const elements = resolveInline(arg)
|
|
633
641
|
for (const elName in elements) {
|
|
634
|
-
if (queryElements[elName] !== undefined) rejectDuplicatedElement(elName)
|
|
642
|
+
if (queryElements[elName] !== undefined) rejectDuplicatedElement(elName)
|
|
635
643
|
}
|
|
636
644
|
Object.assign(queryElements, elements)
|
|
637
645
|
} else {
|
|
@@ -667,7 +675,7 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
|
|
|
667
675
|
// ignore whole expand if target of assoc along path has ”@cds.persistence.skip”
|
|
668
676
|
if (arg.expand) {
|
|
669
677
|
const { $refLinks } = arg
|
|
670
|
-
|
|
678
|
+
|
|
671
679
|
const skip = $refLinks.some(link => {
|
|
672
680
|
const def = getDefinition(link.definition.target)
|
|
673
681
|
return hasOwnSkip(def) && !isRuntimeView(def)
|
|
@@ -1095,7 +1103,7 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1095
1103
|
for (const k in elements) {
|
|
1096
1104
|
if (!exclude(k)) {
|
|
1097
1105
|
const element = elements[k]
|
|
1098
|
-
if (element.type !== 'cds.LargeBinary') {
|
|
1106
|
+
if (element.type !== 'cds.LargeBinary' && element.type !== 'cds.Vector') {
|
|
1099
1107
|
queryElements[k] = element
|
|
1100
1108
|
}
|
|
1101
1109
|
// only relevant if we actually select the calculated element
|
|
@@ -1115,7 +1123,7 @@ function infer(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1115
1123
|
}
|
|
1116
1124
|
if (exclude(name) || name in queryElements) return true
|
|
1117
1125
|
const element = tableAliases[0].tableAlias.elements[name]
|
|
1118
|
-
if (element.type !== 'cds.LargeBinary') queryElements[name] = element
|
|
1126
|
+
if (element.type !== 'cds.LargeBinary' && element.type !== 'cds.Vector') queryElements[name] = element
|
|
1119
1127
|
if (isCalculatedOnRead(element)) {
|
|
1120
1128
|
linkCalculatedElement(element)
|
|
1121
1129
|
}
|
|
@@ -1287,4 +1295,7 @@ function getMainAlias (query) {
|
|
|
1287
1295
|
return mainAlias
|
|
1288
1296
|
}
|
|
1289
1297
|
|
|
1298
|
+
// guardEntry bounds nested-subquery re-entry (expand, from, set)
|
|
1299
|
+
const infer = guardEntry(_infer)
|
|
1300
|
+
|
|
1290
1301
|
module.exports = infer
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const cds = require('@sap/cds')
|
|
4
|
+
|
|
5
|
+
// shared across cqn4sql and infer to guard against stack overflow from user-controlled nesting depth
|
|
6
|
+
const MAX_RECURSION_DEPTH = 100
|
|
7
|
+
|
|
8
|
+
let recursionDepth = 0
|
|
9
|
+
|
|
10
|
+
function enterRecursion() {
|
|
11
|
+
if (++recursionDepth > MAX_RECURSION_DEPTH) throw cds.error(400, `Query exceeds the maximum nesting depth of ${MAX_RECURSION_DEPTH}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function depthGuarded(fn) {
|
|
15
|
+
return function (...args) {
|
|
16
|
+
enterRecursion()
|
|
17
|
+
try {
|
|
18
|
+
return fn.apply(this, args)
|
|
19
|
+
} finally {
|
|
20
|
+
recursionDepth--
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// entry points (infer / cqn4sql): outermost call resets the counter, so a thrown
|
|
26
|
+
// depth error can't leave it polluted for the next request
|
|
27
|
+
function guardEntry(fn) {
|
|
28
|
+
return function (...args) {
|
|
29
|
+
const outermost = recursionDepth === 0
|
|
30
|
+
enterRecursion()
|
|
31
|
+
try {
|
|
32
|
+
return fn.apply(this, args)
|
|
33
|
+
} finally {
|
|
34
|
+
if (outermost) recursionDepth = 0
|
|
35
|
+
else recursionDepth--
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { depthGuarded, guardEntry }
|
|
@@ -166,7 +166,7 @@ const computeColumnsToBeSearched = (cqn, entity = { __searchableColumns: [] }) =
|
|
|
166
166
|
if (
|
|
167
167
|
cqn.SELECT.columns.length === 1 &&
|
|
168
168
|
column.func === 'count' &&
|
|
169
|
-
|
|
169
|
+
column.as === '$count'
|
|
170
170
|
) {
|
|
171
171
|
return
|
|
172
172
|
}
|
|
@@ -4,6 +4,34 @@
|
|
|
4
4
|
- The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
5
5
|
- This project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [3.1.1](https://github.com/cap-js/cds-dbs/compare/sqlite-v3.1.0...sqlite-v3.1.1) (2026-09-15)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Dependencies
|
|
11
|
+
|
|
12
|
+
* The following workspace dependencies were updated
|
|
13
|
+
* dependencies
|
|
14
|
+
* @cap-js/db-service bumped from ^3.1.0 to ^3.1.1
|
|
15
|
+
|
|
16
|
+
## [3.1.0](https://github.com/cap-js/cds-dbs/compare/sqlite-v3.0.2...sqlite-v3.1.0) (2026-09-07)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
* Add support for `cds.Vector` on SQLite and Postgres ([#1676](https://github.com/cap-js/cds-dbs/issues/1676)) ([f0d2203](https://github.com/cap-js/cds-dbs/commit/f0d220324e8a1c7489966545000476bfd33f6b17))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
* INSERT.from(SELECT) used non-existent `value` ([#1565](https://github.com/cap-js/cds-dbs/issues/1565)) ([fd98844](https://github.com/cap-js/cds-dbs/commit/fd98844c83a78d6cf2b0b71344d3ee1d88528106))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
* The following workspace dependencies were updated
|
|
32
|
+
* dependencies
|
|
33
|
+
* @cap-js/db-service bumped from ^3.0.1 to ^3.1.0
|
|
34
|
+
|
|
7
35
|
## [3.0.2](https://github.com/cap-js/cds-dbs/compare/sqlite-v3.0.1...sqlite-v3.0.2) (2026-07-01)
|
|
8
36
|
|
|
9
37
|
|
|
@@ -14,16 +14,6 @@ const sqliteKeywords = keywords.reduce((prev, curr) => {
|
|
|
14
14
|
return prev
|
|
15
15
|
}, {})
|
|
16
16
|
|
|
17
|
-
// define date and time functions in js to allow for throwing errors
|
|
18
|
-
const isTime = /^\d{1,2}:\d{1,2}:\d{1,2}$/
|
|
19
|
-
const hasTimezone = /([+-]\d{1,2}:?\d{0,2}|Z)$/
|
|
20
|
-
const toDate = (d, allowTime = false) => {
|
|
21
|
-
const date = new Date(allowTime && isTime.test(d) ? `1970-01-01T${d}Z` : hasTimezone.test(d) ? d : d + 'Z')
|
|
22
|
-
if (Number.isNaN(date.getTime())) throw new Error(`Value does not contain a valid ${allowTime ? 'time' : 'date'} "${d}"`)
|
|
23
|
-
return date
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
|
|
27
17
|
class SQLiteService extends SQLService {
|
|
28
18
|
|
|
29
19
|
get factory() {
|
|
@@ -46,6 +36,10 @@ class SQLiteService extends SQLService {
|
|
|
46
36
|
dbc.function('hour', deterministic, d => d === null ? null : toDate(d, true).getUTCHours())
|
|
47
37
|
dbc.function('minute', deterministic, d => d === null ? null : toDate(d, true).getUTCMinutes())
|
|
48
38
|
dbc.function('second', deterministic, d => d === null ? null : toDate(d, true).getUTCSeconds())
|
|
39
|
+
dbc.function('COSINE_SIMILARITY', deterministic, (a, b) => cosineSimilarity(toFloatArray(a), toFloatArray(b)))
|
|
40
|
+
dbc.function('L2DISTANCE', deterministic, (a, b) => l2Distance(toFloatArray(a), toFloatArray(b)))
|
|
41
|
+
dbc.function('L2NORMALIZE', deterministic, v => v == null ? null : fromFloatArray(l2Normalize(toFloatArray(v)), v))
|
|
42
|
+
dbc.function('VECTOR_EMBEDDING', deterministic, (input, text_type, model_and_version) => input == null ? null : JSON.stringify(hashEmbedding(String(input))))
|
|
49
43
|
if (database !== ':memory:') dbc.pragma?.('journal_mode = WAL') || dbc.exec('PRAGMA journal_mode = WAL')
|
|
50
44
|
return dbc
|
|
51
45
|
} catch (err) {
|
|
@@ -184,7 +178,7 @@ class SQLiteService extends SQLService {
|
|
|
184
178
|
let ps = await this.prepare(sql)
|
|
185
179
|
const vals = await this._prepareStreams(values)
|
|
186
180
|
const { changes } = await ps.run(vals)
|
|
187
|
-
return this._return_affected
|
|
181
|
+
return this._return_affected(changes)
|
|
188
182
|
}
|
|
189
183
|
|
|
190
184
|
onPlainSQL({ query, data }, next) {
|
|
@@ -273,9 +267,9 @@ class SQLiteService extends SQLService {
|
|
|
273
267
|
// Reading decimal as string to not loose precision
|
|
274
268
|
Decimal: cds.env.features.ieee754compatible
|
|
275
269
|
? (expr, elem) =>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
270
|
+
elem?.scale
|
|
271
|
+
? `CASE WHEN ${expr} IS NULL THEN NULL ELSE format('%.${elem.scale}f', ${expr}) END`
|
|
272
|
+
: `CASE WHEN ${expr} IS NULL THEN NULL ELSE rtrim(rtrim(format('%.999f', ${expr}), '0'), '.') END`
|
|
279
273
|
: undefined,
|
|
280
274
|
// Binary is not allowed in json objects
|
|
281
275
|
Binary: expr => `${expr} || ''`,
|
|
@@ -321,4 +315,106 @@ function loadSQLite(driver) {
|
|
|
321
315
|
}
|
|
322
316
|
}
|
|
323
317
|
|
|
318
|
+
// define date and time functions in js to allow for throwing errors
|
|
319
|
+
const isTime = d => /^\d{1,2}:\d{1,2}:\d{1,2}$/.test(d)
|
|
320
|
+
const hasTimezone = d => /([+-]\d{1,2}:?\d{0,2}|Z)$/.test(d)
|
|
321
|
+
const toDate = (d, allowTime = false) => {
|
|
322
|
+
const date = new Date(allowTime && isTime(d) ? `1970-01-01T${d}Z` : hasTimezone(d) ? d : d + 'Z')
|
|
323
|
+
if (Number.isNaN(date.getTime())) throw new Error(`Value does not contain a valid ${allowTime ? 'time' : 'date'} "${d}"`)
|
|
324
|
+
return date
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Vector functions implemented in JavaScript for SQLite (registered as custom SQL functions)
|
|
328
|
+
function cosineSimilarity(a, b) {
|
|
329
|
+
if (a == null || b == null) return null
|
|
330
|
+
let dot = 0, normA = 0, normB = 0
|
|
331
|
+
for (let i = 0; i < a.length; i++) {
|
|
332
|
+
dot += a[i] * b[i]
|
|
333
|
+
normA += a[i] * a[i]
|
|
334
|
+
normB += b[i] * b[i]
|
|
335
|
+
}
|
|
336
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB)
|
|
337
|
+
return denom === 0 ? 0 : dot / denom
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function l2Distance(a, b) {
|
|
341
|
+
if (a == null || b == null) return null
|
|
342
|
+
let sum = 0
|
|
343
|
+
for (let i = 0; i < a.length; i++) {
|
|
344
|
+
const diff = a[i] - b[i]
|
|
345
|
+
sum += diff * diff
|
|
346
|
+
}
|
|
347
|
+
return Math.sqrt(sum)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function l2Normalize(v) {
|
|
351
|
+
if (v == null) return null
|
|
352
|
+
let norm = 0
|
|
353
|
+
for (let i = 0; i < v.length; i++) norm += v[i] * v[i]
|
|
354
|
+
if (norm === 0) return v
|
|
355
|
+
norm = Math.sqrt(norm)
|
|
356
|
+
for (let i = 0; i < v.length; i++) v[i] /= norm
|
|
357
|
+
return v
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Deterministic synchronous hash-based embedding function for SQLite.
|
|
362
|
+
* NOTE: If a synchronous embedding library becomes available for Node.js,
|
|
363
|
+
* it can be integrated here to replace the hash-based implementation.
|
|
364
|
+
*/
|
|
365
|
+
function hashEmbedding(text, dimensions = 384) {
|
|
366
|
+
if (text == null) return null
|
|
367
|
+
const vector = new Float32Array(dimensions)
|
|
368
|
+
const normalized = text.toLowerCase()
|
|
369
|
+
const ngramSize = 3
|
|
370
|
+
|
|
371
|
+
if (normalized.length >= ngramSize) {
|
|
372
|
+
for (let i = 0; i <= normalized.length - ngramSize; i++)
|
|
373
|
+
project(ngramHash(normalized, i, ngramSize), vector, dimensions)
|
|
374
|
+
} else {
|
|
375
|
+
for (let i = 0; i < normalized.length; i++)
|
|
376
|
+
project(normalized.charCodeAt(i), vector, dimensions)
|
|
377
|
+
}
|
|
378
|
+
return Array.from(l2Normalize(vector))
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function ngramHash(text, start, len) {
|
|
382
|
+
let hash = 0x811c9dc5
|
|
383
|
+
for (let i = start; i < start + len; i++) {
|
|
384
|
+
hash ^= text.charCodeAt(i)
|
|
385
|
+
hash = Math.imul(hash, 0x01000193)
|
|
386
|
+
}
|
|
387
|
+
return hash
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function project(hash, vector, dimensions) {
|
|
391
|
+
for (let band = 0; band < 4; band++) {
|
|
392
|
+
const h = rehash(hash, band)
|
|
393
|
+
vector[Math.abs(h % dimensions)] += ((h >>> 16) & 1) === 0 ? 1.0 : -1.0
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function rehash(hash, band) {
|
|
398
|
+
let h = hash ^ Math.imul(band, 0x9e3779b9)
|
|
399
|
+
h ^= h >>> 16
|
|
400
|
+
h = Math.imul(h, 0x45d9f3b)
|
|
401
|
+
h ^= h >>> 16
|
|
402
|
+
return h
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Vector type conversion helpers for SQLite - handle various input formats (Buffer, string, array, typed arrays)
|
|
406
|
+
function toFloatArray(vector) {
|
|
407
|
+
if (vector == null) return null
|
|
408
|
+
if (vector instanceof Float32Array) return Array.from(vector)
|
|
409
|
+
if (Buffer.isBuffer(vector)) return JSON.parse(vector.toString('utf8'))
|
|
410
|
+
if (vector instanceof Uint8Array) return JSON.parse(Buffer.from(vector).toString('utf8'))
|
|
411
|
+
if (typeof vector === 'string') return JSON.parse(vector)
|
|
412
|
+
if (Array.isArray(vector)) return vector
|
|
413
|
+
throw new Error(`Unsupported vector type: ${typeof vector}`)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function fromFloatArray(arr, original) {
|
|
417
|
+
return original instanceof Float32Array ? new Float32Array(arr) : JSON.stringify(arr)
|
|
418
|
+
}
|
|
419
|
+
|
|
324
420
|
module.exports = SQLiteService
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/sqlite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "CDS database service for SQLite",
|
|
5
5
|
"homepage": "https://github.com/cap-js/cds-dbs/tree/main/sqlite#cds-database-service-for-sqlite",
|
|
6
6
|
"repository": {
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
"test": "cds-test"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@cap-js/db-service": "^3.
|
|
29
|
+
"@cap-js/db-service": "^3.1.1"
|
|
30
30
|
},
|
|
31
31
|
"peerDependencies": {
|
|
32
32
|
"@sap/cds": "^10",
|
|
33
33
|
"sql.js": "^1.13.0",
|
|
34
|
-
"better-sqlite3": "^12.0.0"
|
|
34
|
+
"better-sqlite3": "^12.0.0 || ^13.0.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
37
37
|
"sql.js": {
|