Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 17 additions & 0 deletions srv/lib/scheduler-ready.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,20 @@ export function computeReadiness({ schedulerArmed, dbOk }) {
db: dbOk ? 'connected' : 'error',
};
}

/**
* Dialect-safe liveness ping SQL for a cheap `cds.db.run(...)`.
*
* #2975 regression: HANA rejects a bare `SELECT 1` ("sql syntax error") — it
* requires `SELECT 1 FROM DUMMY`. SQLite rejects `FROM DUMMY`. The original
* /health/ready probe hardcoded `SELECT 1`, so dbOk was ALWAYS false on HANA,
* /health/ready stayed 503, and CF's startup health gate crash-looped the app
* on every deploy. (The integration test ran on in-memory SQLite, where bare
* `SELECT 1` works, so it never caught this.)
*
* @param {string} [kind] db kind, defaults to cds.db.kind at call sites
* @returns {string}
*/
export function dbPingSql(kind) {
return kind === 'hana' ? 'SELECT 1 FROM DUMMY' : 'SELECT 1';
}
6 changes: 3 additions & 3 deletions srv/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ import { uploadPetSubmission, decodePhotoUpload } from './lib/petoberfest-upload
import { fetchPetPhoto } from './lib/petoberfest-photo-store.js';
import { installDbWrap } from './lib/metrics-db-wrap.js';
import { wireDraftIntegrityPreflight } from './lib/draft-integrity-preflight.js';
import { isSchedulerReady, computeReadiness } from './lib/scheduler-ready.js';
import { isSchedulerReady, computeReadiness, dbPingSql } from './lib/scheduler-ready.js';
import './graphql-config.js';
import { makeA2aRouter } from './lib/a2a/rpc-router.js';
import { a2aIasAuthMiddleware } from './lib/a2a/ias-auth-mw.js';
Expand Down Expand Up @@ -297,7 +297,7 @@ cds.on('bootstrap', (app) => {
app.get('/health/ready', async (req, res) => {
let dbOk = false;
if (cds.db) {
try { await cds.db.run('SELECT 1'); dbOk = true; }
try { await cds.db.run(dbPingSql(cds.db.kind)); dbOk = true; }
catch (_err) { dbOk = false; }
}
// cds.db absent → dbOk stays false
Expand All @@ -307,7 +307,7 @@ cds.on('bootstrap', (app) => {

app.get('/health/db', async (req, res) => {
try {
await cds.db?.run('SELECT 1 FROM DUMMY');
await cds.db?.run(dbPingSql(cds.db?.kind));
res.json({ status: 'ok', db: 'connected' });
} catch (err) {
console.error('[health/db]', err.message);
Expand Down
14 changes: 13 additions & 1 deletion test/unit/srv/lib/scheduler-ready.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { markSchedulerReady, isSchedulerReady, _resetForTests, computeReadiness } from '../../../../srv/lib/scheduler-ready.js';
import { markSchedulerReady, isSchedulerReady, _resetForTests, computeReadiness, dbPingSql } from '../../../../srv/lib/scheduler-ready.js';

describe('scheduler-ready latch', () => {
beforeEach(() => _resetForTests());
Expand Down Expand Up @@ -39,3 +39,15 @@ describe('computeReadiness', () => {
expect(computeReadiness({ schedulerArmed: false, dbOk: false }).ready).toBe(false);
});
});

describe('dbPingSql (#2975 HANA regression)', () => {
it('uses FROM DUMMY on HANA (bare SELECT 1 is a HANA syntax error)', () => {
expect(dbPingSql('hana')).toBe('SELECT 1 FROM DUMMY');
});
it('uses bare SELECT 1 on SQLite (FROM DUMMY is a SQLite error)', () => {
expect(dbPingSql('sqlite')).toBe('SELECT 1');
});
it('defaults to bare SELECT 1 when kind is undefined', () => {
expect(dbPingSql()).toBe('SELECT 1');
});
});
Loading