- Add database/queries/{sources,rules,mappings,records}.sql — one file per
route, all business logic in PostgreSQL functions
- Replace parameterized queries in all four route files with lit()/jsonLit()
literal interpolation for debuggability
- Add api/lib/sql.js with lit(), jsonLit(), arr() helpers
- Fix get_view_data to use json_agg (preserves column order) with subquery
(guarantees sort order is respected before aggregation)
- Fix jsonLit() for JSONB params so plain strings become valid JSON
- Update manage.py option 3 to deploy database/queries/ instead of functions.sql
- Add SPEC.md covering architecture, philosophy, and manage.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
30 lines
1.2 KiB
JavaScript
30 lines
1.2 KiB
JavaScript
/**
|
|
* SQL literal helpers
|
|
* Build literal SQL values for direct interpolation into query strings.
|
|
* Queries are fully formed strings — easy to copy and run in psql for debugging.
|
|
*/
|
|
|
|
// Escape a single value for safe interpolation into SQL
|
|
function lit(val) {
|
|
if (val === null || val === undefined) return 'NULL';
|
|
if (typeof val === 'boolean') return val ? 'TRUE' : 'FALSE';
|
|
if (typeof val === 'number') return String(Math.trunc(val));
|
|
if (typeof val === 'object') return `'${JSON.stringify(val).replace(/'/g, "''")}'`;
|
|
return `'${String(val).replace(/'/g, "''")}'`;
|
|
}
|
|
|
|
// Format a value for a JSONB parameter — always JSON.stringifies first so that
|
|
// plain strings become valid JSON ("ROBLOX" not ROBLOX), arrays and objects work too.
|
|
function jsonLit(val) {
|
|
if (val === null || val === undefined) return 'NULL';
|
|
return `'${JSON.stringify(val).replace(/'/g, "''")}'`;
|
|
}
|
|
|
|
// Format a JS array as a PostgreSQL text array literal: ARRAY['a','b']
|
|
function arr(val) {
|
|
if (!Array.isArray(val) || val.length === 0) return "ARRAY[]::text[]";
|
|
return `ARRAY[${val.map(s => `'${String(s).replace(/'/g, "''")}'`).join(',')}]`;
|
|
}
|
|
|
|
module.exports = { lit, jsonLit, arr };
|