quicklite
Version:
A lightweight ORM toolkit for SQLite in Node.js applications
293 lines • 9.65 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueryBuilder = void 0;
/**
* SQL query builder for constructing complex queries
*/
class QueryBuilder {
/**
* Creates a new QueryBuilder instance
* @param db Database instance
* @param tableName Table name
*/
constructor(db, tableName) {
this.selectFields = ['*'];
this.whereConditions = [];
this.orConditions = [];
this.joinConditions = [];
this.groupByFields = [];
this.havingConditions = [];
this.orderByConditions = [];
this.params = [];
this.db = db;
this.tableName = tableName;
}
/**
* Sets the fields to select
* @param fields Field names
* @returns this instance for chaining
*/
select(...fields) {
if (fields.length > 0) {
this.selectFields = fields;
}
return this;
}
/**
* Adds a WHERE condition
* @param field Field name
* @param operator Comparison operator
* @param value Comparison value
* @returns this instance for chaining
*/
where(field, operator, value) {
this.whereConditions.push({ field, operator, value });
if (value !== undefined && !['IS NULL', 'IS NOT NULL'].includes(operator)) {
if (operator === 'BETWEEN' && Array.isArray(value)) {
this.params.push(value[0], value[1]);
}
else if (operator === 'IN' || operator === 'NOT IN') {
if (Array.isArray(value)) {
this.params.push(...value);
}
}
else {
this.params.push(value);
}
}
return this;
}
/**
* Adds an AND condition
* @param field Field name
* @param operator Comparison operator
* @param value Comparison value
* @returns this instance for chaining
*/
and(field, operator, value) {
return this.where(field, operator, value);
}
/**
* Adds an OR condition
* @param conditions Callback for creating OR conditions
* @returns this instance for chaining
*/
or(conditions) {
const subQuery = new QueryBuilder(this.db, this.tableName);
conditions(subQuery);
if (subQuery.whereConditions.length > 0) {
this.orConditions.push(subQuery.whereConditions);
this.params.push(...subQuery.params);
}
return this;
}
/**
* Adds a grouped AND condition with parentheses
* @param conditions Callback for creating grouped conditions
* @returns this instance for chaining
*/
andWhere(conditions) {
const subQuery = new QueryBuilder(this.db, this.tableName);
conditions(subQuery);
if (subQuery.whereConditions.length > 0) {
this.whereConditions.push({
field: `(${this.buildWhereConditionString(subQuery.whereConditions)})`,
operator: '=',
value: undefined
});
this.params.push(...subQuery.params);
}
return this;
}
/**
* Adds a table join
* @param table Table to join
* @param type Join type
* @param on ON clause
* @returns this instance for chaining
*/
join(table, type, on) {
this.joinConditions.push({ table, type, on });
return this;
}
/**
* Adds an INNER JOIN
* @param table Table to join
* @param on ON clause
* @returns this instance for chaining
*/
innerJoin(table, on) {
return this.join(table, 'INNER', on);
}
/**
* Adds a LEFT JOIN
* @param table Table to join
* @param on ON clause
* @returns this instance for chaining
*/
leftJoin(table, on) {
return this.join(table, 'LEFT', on);
}
/**
* Sets GROUP BY fields
* @param fields Field names
* @returns this instance for chaining
*/
groupBy(...fields) {
this.groupByFields = fields;
return this;
}
/**
* Adds a HAVING condition
* @param field Field name
* @param operator Comparison operator
* @param value Comparison value
* @returns this instance for chaining
*/
having(field, operator, value) {
this.havingConditions.push({ field, operator, value });
if (value !== undefined && !['IS NULL', 'IS NOT NULL'].includes(operator)) {
this.params.push(value);
}
return this;
}
/**
* Sets ORDER BY clause
* @param field Field name
* @param direction Sort direction
* @returns this instance for chaining
*/
orderBy(field, direction = 'ASC') {
this.orderByConditions.push({ field, direction });
return this;
}
/**
* Sets LIMIT clause
* @param limit Maximum number of results
* @returns this instance for chaining
*/
limit(limit) {
this.limitValue = limit;
return this;
}
/**
* Sets OFFSET clause
* @param offset Number of results to skip
* @returns this instance for chaining
*/
offset(offset) {
this.offsetValue = offset;
return this;
}
/**
* Builds a WHERE condition string from conditions
* @param conditions WHERE conditions
* @returns WHERE condition string
*/
buildWhereConditionString(conditions) {
return conditions.map(condition => {
if (['IS NULL', 'IS NOT NULL'].includes(condition.operator)) {
return `${condition.field} ${condition.operator}`;
}
else if (condition.operator === 'BETWEEN' && Array.isArray(condition.value)) {
return `${condition.field} BETWEEN ? AND ?`;
}
else if (['IN', 'NOT IN'].includes(condition.operator) && Array.isArray(condition.value)) {
const placeholders = condition.value.map(() => '?').join(', ');
return `${condition.field} ${condition.operator} (${placeholders})`;
}
else if (condition.field.startsWith('(') && condition.field.endsWith(')')) {
// For subqueries, just use the field name (which contains the entire condition)
return condition.field;
}
else {
return `${condition.field} ${condition.operator} ?`;
}
}).join(' AND ');
}
/**
* Builds the complete SQL query
* @returns SQL query and parameters
*/
build() {
let sql = `SELECT ${this.selectFields.join(', ')} FROM ${this.tableName}`;
const params = [...this.params];
// Add JOINs
if (this.joinConditions.length > 0) {
const joinStr = this.joinConditions.map(join => `${join.type} JOIN ${join.table} ON ${join.on}`).join(' ');
sql += ` ${joinStr}`;
}
// Add WHERE conditions
const whereConditions = [];
if (this.whereConditions.length > 0) {
whereConditions.push(this.buildWhereConditionString(this.whereConditions));
}
// Add OR conditions
if (this.orConditions.length > 0) {
const orClauses = this.orConditions.map(conditions => `(${this.buildWhereConditionString(conditions)})`);
if (orClauses.length > 0) {
whereConditions.push(`(${orClauses.join(' OR ')})`);
}
}
if (whereConditions.length > 0) {
sql += ` WHERE ${whereConditions.join(' AND ')}`;
}
// Add GROUP BY
if (this.groupByFields.length > 0) {
sql += ` GROUP BY ${this.groupByFields.join(', ')}`;
}
// Add HAVING
if (this.havingConditions.length > 0) {
sql += ` HAVING ${this.buildWhereConditionString(this.havingConditions)}`;
}
// Add ORDER BY
if (this.orderByConditions.length > 0) {
const orderStr = this.orderByConditions.map(order => `${order.field} ${order.direction}`).join(', ');
sql += ` ORDER BY ${orderStr}`;
}
// Add LIMIT and OFFSET
if (this.limitValue !== undefined) {
sql += ` LIMIT ${this.limitValue}`;
if (this.offsetValue !== undefined) {
sql += ` OFFSET ${this.offsetValue}`;
}
}
return { sql, params };
}
/**
* Executes the query and returns all results
* @returns Query results
*/
all() {
const { sql, params } = this.build();
const stmt = this.db.prepare(sql);
return stmt.all(...params);
}
/**
* Executes the query and returns the first result
* @returns First result or null if none
*/
first() {
const { sql, params } = this.build();
const stmt = this.db.prepare(sql);
return stmt.get(...params) || null;
}
/**
* Counts the number of matching records
* @returns Record count
*/
count() {
// Save the current select fields
const originalSelectFields = [...this.selectFields];
// Change select to COUNT(*)
this.selectFields = ['COUNT(*) as count'];
const { sql, params } = this.build();
const stmt = this.db.prepare(sql);
const result = stmt.get(...params);
// Restore original select fields
this.selectFields = originalSelectFields;
return (result === null || result === void 0 ? void 0 : result.count) || 0;
}
}
exports.QueryBuilder = QueryBuilder;
//# sourceMappingURL=QueryBuilder.js.map