126 lines
5.5 KiB
TypeScript
126 lines
5.5 KiB
TypeScript
/**
|
|
* Generische SQL-CRUD-Bausteine über die Tabellen-Registry (tableRegistry.ts) — von allen
|
|
* server/db/queries/*.ts-Modulen genutzt, damit Spalten-Codecs (JSON/Bool) nur an einer
|
|
* Stelle behandelt werden.
|
|
*/
|
|
import { db } from '../database'
|
|
import { TABLE_DEFINITIONS, type TableDef, type ColumnCodec } from './tableRegistry'
|
|
|
|
const tablesByName = new Map(TABLE_DEFINITIONS.map(t => [t.name, t]))
|
|
|
|
function requireTable(name: string): TableDef {
|
|
const t = tablesByName.get(name)
|
|
if (!t) throw new Error(`Unbekannte Tabelle: ${name}`)
|
|
return t
|
|
}
|
|
|
|
type SqlPrimitive = number | string | bigint | null
|
|
|
|
function toBindValue(raw: unknown, codec: ColumnCodec): SqlPrimitive {
|
|
if (raw === undefined || raw === null) return null
|
|
if (codec === 'json') return JSON.stringify(raw)
|
|
if (codec === 'bool') return raw ? 1 : 0
|
|
return raw as SqlPrimitive
|
|
}
|
|
|
|
function fromSqlValue(raw: unknown, codec: ColumnCodec): unknown {
|
|
if (raw === null || raw === undefined) return null
|
|
if (codec === 'json') return JSON.parse(raw as string)
|
|
if (codec === 'bool') return raw === 1
|
|
return raw
|
|
}
|
|
|
|
export function decodeRow<T>(table: TableDef, row: Record<string, unknown>): T {
|
|
const result: Record<string, unknown> = { id: row.id }
|
|
for (const c of table.columns) result[c.name] = fromSqlValue(row[c.name], c.codec)
|
|
return result as T
|
|
}
|
|
|
|
export function insertRow(tableName: string, obj: Record<string, unknown>): number {
|
|
const table = requireTable(tableName)
|
|
const cols = table.columns.map(c => `"${c.name}"`).join(', ')
|
|
const placeholders = table.columns.map(() => '?').join(', ')
|
|
const values = table.columns.map(c => toBindValue(obj[c.name], c.codec))
|
|
const info = db.prepare(`INSERT INTO "${tableName}" (${cols}) VALUES (${placeholders})`).run(...values)
|
|
return Number(info.lastInsertRowid)
|
|
}
|
|
|
|
/** Für Backup-Restore: fügt eine Zeile mit expliziter id ein, damit Fremdschlüssel-Referenzen im importierten Datensatz stabil bleiben. */
|
|
export function insertRowWithId(tableName: string, id: number, obj: Record<string, unknown>): void {
|
|
const table = requireTable(tableName)
|
|
const cols = ['"id"', ...table.columns.map(c => `"${c.name}"`)].join(', ')
|
|
const placeholders = ['?', ...table.columns.map(() => '?')].join(', ')
|
|
const values: SqlPrimitive[] = [id, ...table.columns.map(c => toBindValue(obj[c.name], c.codec))]
|
|
db.prepare(`INSERT INTO "${tableName}" (${cols}) VALUES (${placeholders})`).run(...values)
|
|
}
|
|
|
|
export function updateRow(tableName: string, id: number, patch: Record<string, unknown>): number {
|
|
const table = requireTable(tableName)
|
|
const entries = Object.entries(patch).filter(([k]) => k !== 'id')
|
|
if (entries.length === 0) return 0
|
|
const setSql = entries.map(([k]) => `"${k}" = ?`).join(', ')
|
|
const values = entries.map(([k, v]) => {
|
|
const colDef = table.columns.find(c => c.name === k)
|
|
return toBindValue(v, colDef?.codec ?? 'plain')
|
|
})
|
|
const info = db.prepare(`UPDATE "${tableName}" SET ${setSql} WHERE id = ?`).run(...values, id)
|
|
return Number(info.changes)
|
|
}
|
|
|
|
export function getRowById<T>(tableName: string, id: number): T | undefined {
|
|
const table = requireTable(tableName)
|
|
const row = db.prepare(`SELECT * FROM "${tableName}" WHERE id = ?`).get(id) as Record<string, unknown> | undefined
|
|
return row ? decodeRow<T>(table, row) : undefined
|
|
}
|
|
|
|
export function getAllRows<T>(tableName: string): T[] {
|
|
const table = requireTable(tableName)
|
|
const rows = db.prepare(`SELECT * FROM "${tableName}"`).all() as Record<string, unknown>[]
|
|
return rows.map(r => decodeRow<T>(table, r))
|
|
}
|
|
|
|
export function getAllRowsOrderedBy<T>(tableName: string, column: string): T[] {
|
|
const table = requireTable(tableName)
|
|
const rows = db.prepare(`SELECT * FROM "${tableName}" ORDER BY "${column}" ASC`).all() as Record<string, unknown>[]
|
|
return rows.map(r => decodeRow<T>(table, r))
|
|
}
|
|
|
|
export function getRowsWhereEquals<T>(tableName: string, column: string, value: SqlPrimitive): T[] {
|
|
const table = requireTable(tableName)
|
|
const rows = db.prepare(`SELECT * FROM "${tableName}" WHERE "${column}" = ?`).all(value) as Record<string, unknown>[]
|
|
return rows.map(r => decodeRow<T>(table, r))
|
|
}
|
|
|
|
export function getRowsWhereIn<T>(tableName: string, column: string, values: number[]): T[] {
|
|
if (values.length === 0) return []
|
|
const table = requireTable(tableName)
|
|
const placeholders = values.map(() => '?').join(', ')
|
|
const rows = db.prepare(`SELECT * FROM "${tableName}" WHERE "${column}" IN (${placeholders})`).all(...values) as Record<string, unknown>[]
|
|
return rows.map(r => decodeRow<T>(table, r))
|
|
}
|
|
|
|
export function deleteRowById(tableName: string, id: number): void {
|
|
requireTable(tableName)
|
|
db.prepare(`DELETE FROM "${tableName}" WHERE id = ?`).run(id)
|
|
}
|
|
|
|
export function deleteRowsWhereEquals(tableName: string, column: string, value: SqlPrimitive): number {
|
|
requireTable(tableName)
|
|
const info = db.prepare(`DELETE FROM "${tableName}" WHERE "${column}" = ?`).run(value)
|
|
return Number(info.changes)
|
|
}
|
|
|
|
export function deleteRowsWhereIn(tableName: string, column: string, values: number[]): number {
|
|
if (values.length === 0) return 0
|
|
requireTable(tableName)
|
|
const placeholders = values.map(() => '?').join(', ')
|
|
const info = db.prepare(`DELETE FROM "${tableName}" WHERE "${column}" IN (${placeholders})`).run(...values)
|
|
return Number(info.changes)
|
|
}
|
|
|
|
export function countWhereEquals(tableName: string, column: string, value: SqlPrimitive): number {
|
|
requireTable(tableName)
|
|
const row = db.prepare(`SELECT COUNT(*) as c FROM "${tableName}" WHERE "${column}" = ?`).get(value) as { c: number }
|
|
return row.c
|
|
}
|