UNPKG

@cheetah.js/orm

Version:
233 lines (232 loc) 7.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BunDriverBase = void 0; const bun_1 = require("bun"); class BunDriverBase { constructor(options) { this.connectionString = this.buildConnectionString(options); } buildConnectionString(options) { if (options.connectionString) { return options.connectionString; } const { host, port, username, password, database } = options; const protocol = this.getProtocol(); return `${protocol}://${username}:${password}@${host}:${port}/${database}`; } async connect() { if (this.sql) { return; } this.sql = new bun_1.SQL(this.connectionString); await this.validateConnection(); } async validateConnection() { await this.sql.unsafe('SELECT 1'); } async disconnect() { if (!this.sql) { return; } await this.sql.close(); } async executeSql(sqlString) { if (!this.sql) { throw new Error('Database not connected'); } return await this.sql.unsafe(sqlString); } async transaction(callback) { if (!this.sql) { throw new Error('Database not connected'); } return await this.sql.begin(callback); } toDatabaseValue(value) { if (value instanceof Date) { return `'${value.toISOString()}'`; } switch (typeof value) { case 'string': return `'${this.escapeString(value)}'`; case 'number': return value; case 'boolean': return value; case 'object': return `'${this.escapeString(JSON.stringify(value))}'`; default: return `'${this.escapeString(String(value))}'`; } } escapeString(value) { return value.replace(/'/g, "''"); } escapeIdentifier(identifier) { return `"${identifier}"`; } buildWhereClause(where) { if (!where) { return ''; } return ` WHERE ${where}`; } buildOrderByClause(orderBy) { if (!orderBy || orderBy.length === 0) { return ''; } return ` ORDER BY ${orderBy.join(', ')}`; } buildLimitClause(limit) { if (!limit) { return ''; } return ` LIMIT ${limit}`; } buildOffsetClause(offset) { if (!offset) { return ''; } return ` OFFSET ${offset}`; } quote(identifier) { const q = this.getIdentifierQuote(); return `${q}${identifier}${q}`; } buildTableIdentifier(schema, tableName) { return schema ? `${this.quote(schema)}.${this.quote(tableName)}` : this.quote(tableName); } buildColumnConstraints(colDiff) { const parts = []; if (!colDiff.colChanges?.nullable) { parts.push('NOT NULL'); } if (colDiff.colChanges?.primary) { parts.push('PRIMARY KEY'); } if (colDiff.colChanges?.unique) { parts.push('UNIQUE'); } if (colDiff.colChanges?.default) { parts.push(`DEFAULT ${colDiff.colChanges.default}`); } return parts.length > 0 ? ' ' + parts.join(' ') : ''; } async executeStatement(statement) { const startTime = Date.now(); if (statement.statement === 'insert') { const sql = this.buildInsertSqlWithReturn(statement); const result = await this.sql.unsafe(sql); return this.handleInsertReturn(statement, result, sql, startTime); } const sql = this.buildStatementSql(statement); const result = await this.sql.unsafe(sql); return { query: { rows: Array.isArray(result) ? result : [] }, startTime, sql, }; } buildInsertSqlWithReturn(statement) { const baseSql = this.buildInsertSql(statement.table, statement.values, statement.columns, statement.alias); return this.appendReturningClause(baseSql, statement); } buildStatementSql(statement) { let sql = this.buildBaseSql(statement); sql += this.buildJoinClause(statement); sql += this.buildWhereAndOrderClauses(statement); return sql; } buildBaseSql(statement) { const { statement: type, table, columns, values, alias } = statement; switch (type) { case 'select': return `SELECT ${columns ? columns.join(', ') : '*'} FROM ${table} ${alias}`; case 'insert': return this.buildInsertSql(table, values, columns, alias); case 'update': return this.buildUpdateSql(table, values, alias); case 'delete': return this.buildDeleteSql(table, alias); case 'count': return `SELECT COUNT(*) as count FROM ${table} ${alias}`; default: return ''; } } buildInsertSql(table, values, columns, alias) { const q = this.getIdentifierQuote(); const fields = Object.keys(values) .map((v) => `${q}${v}${q}`) .join(', '); const vals = Object.values(values) .map((value) => this.toDatabaseValue(value)) .join(', '); return `INSERT INTO ${table} (${fields}) VALUES (${vals})`; } buildUpdateSql(table, values, alias) { const sets = Object.entries(values) .map(([key, value]) => `${key} = ${this.toDatabaseValue(value)}`) .join(', '); return `UPDATE ${table} as ${alias} SET ${sets}`; } buildDeleteSql(table, alias) { return `DELETE FROM ${table} AS ${alias}`; } buildJoinClause(statement) { if (!statement.join) return ''; return statement.join .map((join) => { const table = `${join.joinSchema}.${join.joinTable}`; return ` ${join.type} JOIN ${table} ${join.joinAlias} ON ${join.on}`; }) .join(''); } buildWhereAndOrderClauses(statement) { if (statement.statement === 'insert') return ''; let sql = this.buildWhereClause(statement.where); sql += this.buildOrderByClause(statement.orderBy); sql += this.buildLimitAndOffsetClause(statement); return sql; } buildLimitAndOffsetClause(statement) { const { offset, limit } = statement; if (offset && limit) { return this.buildOffsetClause(offset) + this.buildLimitClause(limit); } if (limit) { return this.buildLimitClause(limit); } return ''; } getForeignKeysFromConstraints(constraints, row, columnNameField) { const columnName = row[columnNameField]; return constraints .filter((c) => this.isForeignKeyConstraint(c, columnName)) .map((c) => this.parseForeignKeyDefinition(c.consDef)); } isForeignKeyConstraint(constraint, columnName) { return (constraint.type === 'FOREIGN KEY' && constraint.consDef.includes(columnName)); } parseForeignKeyDefinition(consDef) { const q = this.getIdentifierQuote(); const pattern = new RegExp(`REFERENCES\\s+${q}([^${q}]+)${q}\\s*\\(([^)]+)\\)`); const filter = consDef.match(pattern); if (!filter) { throw new Error('Invalid constraint definition'); } return { referencedColumnName: filter[2] .split(',')[0] .trim() .replace(new RegExp(q, 'g'), ''), referencedTableName: filter[1], }; } } exports.BunDriverBase = BunDriverBase;