@cheetah.js/orm
Version:
A simple ORM for Cheetah.js
233 lines (232 loc) • 7.87 kB
JavaScript
;
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;