Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Version 0.8.3 - 2024-11-28

### Fixed

- Rewrite subselects to use path expressions on @cap-js databases
Copy link
Contributor Author

@sjvans sjvans Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Rewrite subselects to use path expressions on @cap-js databases
- Use path expressions instead of manually constructed semi joins on @cap-js databases


## Version 0.8.2 - 2024-11-27

### Fixed
Expand Down
87 changes: 85 additions & 2 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const _buildSubSelect = (model, { entity, relative, element, next }, row, previo
return childCqn
}

const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => {
const _old_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => {
const keys = Object.values(dataSubjectEntity.keys)
const as = _alias(dataSubjectEntity)

Expand All @@ -156,6 +156,84 @@ const _getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row, model) => {
return cqn
}

const _getRelation = (left, right, abort) => {
let a
for (const assoc in left.associations) {
if (left.associations[assoc].target === right.name) {
a = left.associations[assoc]
break
}
}
if (a) return { base: left, target: right, assoc: a }
return abort ? undefined : _getRelation(right, left, true)
}

const _new_getDataSubjectIdQuery = ({ dataSubjectEntity, subs }, row) => {
const qs = []

// multiple subs => entity reused in different branches => must check all
for (const sub of subs) {
const path = []
let s = sub
while (s) {
if (!path.length) {
// the known entity instance as starting point
const kp = Object.keys(s.entity.keys).reduce((acc, cur) => {
if (cur !== 'IsActiveEntity') acc.push(`${cur}='${row[cur]}'`)
return acc
}, [])
path.push({ id: s.entity.name, where: kp })
}

let relation = _getRelation(s.entity, s.next?.entity || dataSubjectEntity)
if (!relation) {
// TODO: no relation found
} else if (relation.base === s.entity) {
// assoc in base
if (relation.assoc === s.element) {
// forwards
path.push({ to: relation.assoc.name })
} else {
// backwards
path[0].id = s.element.name
path.unshift({ id: relation.target.name })
}
} else {
// assoc in target
path[0].id = s.element.name
path.unshift({ id: relation.base.name })
}

s = s.next
}

// construct path as string
const p = path.reduce((acc, cur) => {
if (!acc) {
acc += `${cur.id}${cur.where ? `[${cur.where.join(' and ')}]` : ''}`
} else {
if (cur.id) {
const close = acc.match(/([\]]+)$/)?.[1]
if (close)
acc =
acc.slice(0, close.length * -1) +
`[exists ${cur.id}${cur.where ? `[${cur.where.join(' and ')}]` : ''}]` +
close
else acc += `[exists ${cur.id}${cur.where ? `[${cur.where.join(' and ')}]` : ''}]`
} else if (cur.to) acc += `.${cur.to}`
}
return acc
}, '')

qs.push(SELECT.one.from(p).columns(...Object.keys(dataSubjectEntity.keys)))
}

// merge queries, if necessary
const q = qs[0]
for (let i = 1; i < qs.length; i++) q.SELECT.from.ref[0].where.push('or', ...qs[i].SELECT.from.ref[0].where)
return q
}

const _getUps = (entity, model) => {
if (entity.own($parents) == null) {
const ups = []
Expand Down Expand Up @@ -246,7 +324,12 @@ const addDataSubjectForDetailsEntity = (row, log, req, entity, model) => {
const map = _getDataSubjectsMap(req)
if (map.has(role)) log.data_subject.id = map.get(role)
// REVISIT by downward lookups row might already contain ID - some potential to optimize
else map.set(role, _getDataSubjectIdQuery(dataSubjectInfo, row, model))
else {
module.exports._getDataSubjectIdQuery ??= cds.env.requires.db?.impl?.startsWith('@cap-js/')
Copy link
Contributor Author

@sjvans sjvans Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

explicit feature flag!

? _new_getDataSubjectIdQuery
: _old_getDataSubjectIdQuery
map.set(role, module.exports._getDataSubjectIdQuery(dataSubjectInfo, row, model))
}
}

const resolveDataSubjects = (logs, req) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cap-js/audit-logging",
"version": "0.8.2",
"version": "0.8.3",
Copy link
Contributor Author

@sjvans sjvans Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.9.0

"description": "CDS plugin providing integration to the SAP Audit Log service as well as out-of-the-box personal data-related audit logging based on annotations.",
"repository": "cap-js/audit-logging",
"author": "SAP SE (https://www.sap.com)",
Expand Down
Loading