Skip to content

Commit

Permalink
feat: strict mode to validate input for INSERT, UPDATE and `UPSER…
Browse files Browse the repository at this point in the history
…T` (#384)

Add a strict mode to validate input for INSERT, UPDATE and UPSERT.
This "strict" mode is a configuration that can be turned on, e. g.
cds.env.features.db_strict.

---------

Co-authored-by: I543501 <lars.lutz@sap.com>
Co-authored-by: Bob den Os <108393871+BobdenOs@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 30, 2024
1 parent 7527764 commit 4644483
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
20 changes: 20 additions & 0 deletions db-service/lib/SQLService.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,26 @@ class SQLService extends DatabaseService {
init() {
this.on(['INSERT', 'UPSERT', 'UPDATE'], require('./fill-in-keys')) // REVISIT should be replaced by correct input processing eventually
this.on(['INSERT', 'UPSERT', 'UPDATE'], require('./deep-queries').onDeep)
if (cds.env.features.db_strict) {
this.before(['INSERT', 'UPSERT', 'UPDATE'], ({ query }) => {
const elements = query.target?.elements; if (!elements) return
const kind = query.kind || Object.keys(query)[0]
const operation = query[kind]
if (!operation.columns && !operation.entries && !operation.data) return
const columns =
operation.columns ||
Object.keys(
operation.data || operation.entries?.reduce((acc, obj) => {
return Object.assign(acc, obj)
}, {}),
)
const invalidColumns = columns.filter(c => !(c in elements))

if (invalidColumns.length > 0) {
cds.error(`STRICT MODE: Trying to ${kind} non existent columns (${invalidColumns})`)
}
})
}
this.on(['SELECT'], this.onSELECT)
this.on(['INSERT'], this.onINSERT)
this.on(['UPSERT'], this.onUPSERT)
Expand Down
104 changes: 104 additions & 0 deletions test/compliance/strictMode.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
const cds = require('../cds.js')
const Books = 'complex.Books'

describe('strict mode', () => {
beforeAll(() => {
process.env.cds_features_db__strict = true
})

cds.test(__dirname + '/resources')

afterAll(() => {
process.env.cds_features_db__strict = undefined
})

async function runAndExpectError(cqn, expectedMessage) {
let error
try {
await cds.run(cqn)
} catch (e) {
error = e
}
if (expectedMessage === 'notExisting') expect(error.message.toLowerCase()).toContain('notexisting')
else expect(error.message).toEqual(expectedMessage)
}
describe('UPDATE Scenarios', () => {
test('Update with multiple errors', async () => {
await runAndExpectError(
UPDATE.entity(Books).where({ ID: 2 }).set({ abc: 'bar', abc2: 'baz' }),
'STRICT MODE: Trying to UPDATE non existent columns (abc,abc2)',
)
})

test('Update with single error', async () => {
await runAndExpectError(
UPDATE.entity(Books).where({ ID: 2 }).set({ abc: 'bar' }),
'STRICT MODE: Trying to UPDATE non existent columns (abc)',
)
})

test('Update on non existing entity', async () => {
await runAndExpectError(UPDATE.entity('notExisting').where({ ID: 2 }).set({ abc: 'bar' }), 'notExisting')
})
})

describe('INSERT Scenarios', () => {
test('Insert with single error using entries', async () => {
await runAndExpectError(
INSERT.into(Books).entries({ abc: 'bar' }),
'STRICT MODE: Trying to INSERT non existent columns (abc)',
)
})

test('Insert with multiple errors using entries', async () => {
await runAndExpectError(
INSERT.into(Books).entries([{ abc: 'bar' }, { abc2: 'bar2' }]),
'STRICT MODE: Trying to INSERT non existent columns (abc,abc2)',
)
})

test('Insert with single error using columns and values', async () => {
await runAndExpectError(
INSERT.into(Books).columns(['abc']).values(['foo', 'bar']),
'STRICT MODE: Trying to INSERT non existent columns (abc)',
)
})

test('Insert with multiple errors with columns and rows', async () => {
await runAndExpectError(
INSERT.into(Books).columns(['abc', 'abc2']).rows(['foo', 'bar'], ['foo2', 'bar2'], ['foo3', 'bar3']),
'STRICT MODE: Trying to INSERT non existent columns (abc,abc2)',
)
})

test('Insert with single error using columns and rows', async () => {
await runAndExpectError(
INSERT.into(Books).columns(['abc']).rows(['foo', 'bar'], ['foo2', 'bar2'], ['foo3', 'bar3']),
'STRICT MODE: Trying to INSERT non existent columns (abc)',
)
})

test('Insert on non existing entity using entries', async () => {
await runAndExpectError(INSERT.into('notExisting').entries({ abc: 'bar' }), 'notExisting')
})
})

describe('UPSERT Scenarios', () => {
test('UPSERT with single error', async () => {
await runAndExpectError(
UPSERT.into(Books).entries({ abc: 'bar' }),
'STRICT MODE: Trying to UPSERT non existent columns (abc)',
)
})
test('UPSERT with multiple errors', async () => {
await runAndExpectError(
UPSERT.into(Books).entries({ abc: 'bar', abc2: 'baz' }),
'STRICT MODE: Trying to UPSERT non existent columns (abc,abc2)',
)
})

test('UPSERT on non existing entity', async () => {
await runAndExpectError(UPSERT.into('notExisting').entries({ abc: 'bar' }), 'notExisting')
})
})
})

0 comments on commit 4644483

Please sign in to comment.