export function integer(name) {
return { kind: 'integer', name };
}
export function text(name) {
return { kind: 'text', name };
}
export function table(name, columns) {
const result = { _: { name } };
for (const key of Object.keys(columns)) {
result[key] = {
kind: columns[key].kind,
name: columns[key].name,
tableName: name,
};
}
return result;
}
function isColumn(value) {
return value && typeof value === 'object' && 'tableName' in value;
}
function getColumnValue(context, column) {
const row = context[column.tableName];
if (row === null) {
return null;
}
return row[column.name];
}
export function eq(left, right) {
return function (context) {
const leftValue = getColumnValue(context, left);
const rightValue = isColumn(right)
? getColumnValue(context, right)
: right;
return leftValue === rightValue;
};
}
export function and(...conditions) {
return (context) => conditions.every((condition) => condition(context));
}
export function or(...conditions) {
return (context) => conditions.some((condition) => condition(context));
}
export function asc(column) {
return { column, direction: 'asc' };
}
export function desc(column) {
return { column, direction: 'desc' };
}
export default function drizzle(data) {
const clonedData = {};
for (const tableName of Object.keys(data)) {
clonedData[tableName] = data[tableName].map((row) => ({ ...row }));
}
return {
select(selection) {
return new QueryBuilder(clonedData, selection);
},
};
}
class QueryBuilder {
constructor(data, selection) {
this.data = data;
this.selection = selection;
this.baseTable = null;
this.joins = [];
this.condition = null;
this.orderings = [];
this.limitCount = null;
this.offsetCount = 0;
}
from(table) {
this.baseTable = table;
return this;
}
innerJoin(table, on) {
this.joins.push({ type: 'inner', table, on });
return this;
}
leftJoin(table, on) {
this.joins.push({ type: 'left', table, on });
return this;
}
where(condition) {
this.condition = condition;
return this;
}
orderBy(...orderings) {
this.orderings = orderings;
return this;
}
limit(count) {
this.limitCount = count;
return this;
}
offset(count) {
this.offsetCount = count;
return this;
}
all() {
let contexts = this._createBaseContexts();
for (const join of this.joins) {
contexts = this._applyJoin(contexts, join);
}
if (this.condition) {
contexts = contexts.filter((context) => this.condition(context));
}
if (this.orderings.length > 0) {
contexts.sort((a, b) => this._compareContexts(a, b));
}
if (this.offsetCount > 0) {
contexts = contexts.slice(this.offsetCount);
}
if (this.limitCount !== null) {
contexts = contexts.slice(0, this.limitCount);
}
return contexts.map((context) => this._project(context));
}
_createBaseContexts() {
const tableName = this.baseTable._.name;
return this.data[tableName].map((row) => ({
[tableName]: { ...row },
}));
}
_applyJoin(contexts, join) {
const tableName = join.table._.name;
const joinedRows = this.data[tableName];
const result = [];
for (const context of contexts) {
let matched = false;
for (const row of joinedRows) {
const nextContext = {
...context,
[tableName]: { ...row },
};
if (join.on(nextContext)) {
result.push(nextContext);
matched = true;
}
}
if (join.type === 'left' && !matched) {
result.push({
...context,
[tableName]: null,
});
}
}
return result;
}
_compareContexts(a, b) {
for (const ordering of this.orderings) {
const aValue = getColumnValue(a, ordering.column);
const bValue = getColumnValue(b, ordering.column);
if (aValue < bValue) {
return ordering.direction === 'asc' ? -1 : 1;
}
if (aValue > bValue) {
return ordering.direction === 'asc' ? 1 : -1;
}
}
return 0;
}
_project(context) {
if (this.selection) {
const row = {};
for (const key of Object.keys(this.selection)) {
const column = this.selection[key];
row[key] = getColumnValue(context, column);
}
return row;
}
if (this.joins.length === 0) {
const tableName = this.baseTable._.name;
return { ...context[tableName] };
}
const result = {};
for (const tableName of Object.keys(context)) {
result[tableName] =
context[tableName] === null
? null
: { ...context[tableName] };
}
return result;
}
}