Add order/limit support to find queries

This commit is contained in:
Endeavorance 2025-04-18 10:27:48 -04:00
parent b4eeb2c296
commit 153725f785
4 changed files with 91 additions and 18 deletions

View file

@ -1,6 +1,6 @@
{
"name": "@endeavorance/prequel",
"version": "2.1.0",
"version": "2.2.0",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {

View file

@ -27,7 +27,10 @@ export class PrequelKVStore {
primary: true,
autoincrement: true,
},
key: "TEXT",
key: {
type: "TEXT",
unique: true,
},
value: "TEXT",
});
}

View file

@ -136,27 +136,36 @@ export class Table<RowShape> {
.map((colName) => `$${colName}`)
.join(", ");
this._createTableSQL = `CREATE TABLE IF NOT EXISTS "${name}" (${columnSQL});`;
const insertSql = `INSERT INTO "${name}" (${allCols}) VALUES (${allColVars}) RETURNING *;`;
const updateSql = `UPDATE "${name}" SET ${updateColumnSQL} WHERE "${this._primaryColumnName}" = $${this._primaryColumnName} RETURNING *;`;
const getByIdSql = `SELECT * FROM "${name}" WHERE "${this._primaryColumnName}" = ? LIMIT 1;`;
const delByIdSql = `DELETE FROM "${name}" WHERE "${this._primaryColumnName}" = ?;`;
const deleteRowSql = `DELETE FROM "${name}" WHERE "${this._primaryColumnName}" = $${this._primaryColumnName};`;
const truncateQuery = `DELETE FROM "${name}";`;
// Ensure the table exists in the database
this._createTableSQL = `CREATE TABLE IF NOT EXISTS "${name}" (${columnSQL});`;
this.db.exec(this._createTableSQL);
// Prepare common queries
this._insertQuery = this.db.query<RowShape, ArbitraryRow>(insertSql);
this._updateQuery = this.db.query<RowShape, ArbitraryRow>(updateSql);
this._findQuery = this.db.query<RowShape, string>(getByIdSql);
this._deleteQuery = this.db.query<void, string>(delByIdSql);
this._insertQuery = this.db.query<RowShape, ArbitraryRow>(
`INSERT INTO "${name}" (${allCols}) VALUES (${allColVars}) RETURNING *;`,
);
this._updateQuery = this.db.query<RowShape, ArbitraryRow>(
`UPDATE "${name}" SET ${updateColumnSQL} WHERE "${this._primaryColumnName}" = $${this._primaryColumnName} RETURNING *;`,
);
this._findQuery = this.db.query<RowShape, string>(
`SELECT * FROM "${name}" WHERE "${this._primaryColumnName}" = ? LIMIT 1;`,
);
this._deleteQuery = this.db.query<void, string>(
`DELETE FROM "${name}" WHERE "${this._primaryColumnName}" = ?;`,
);
this._sizeQuery = this.db.query<{ count: number }, []>(
`SELECT count(*) as count FROM ${name}`,
);
this._deleteRowQuery = this.db.query<void, ArbitraryRow>(deleteRowSql);
this._truncateQuery = this.db.query<void, []>(truncateQuery);
this._deleteRowQuery = this.db.query<void, ArbitraryRow>(
`DELETE FROM "${name}" WHERE "${this._primaryColumnName}" = $${this._primaryColumnName};`,
);
this._truncateQuery = this.db.query<void, []>(`DELETE FROM "${name}"`);
// If using with a Prequel instance, ensure the table is attached
// This is only for when creating tables directly and passing in a Prequel instance
@ -424,12 +433,34 @@ export class Table<RowShape> {
* The keys are column names and the values are the values to match.
* @returns An array of rows that match the specified conditions.
*/
public findAllWhere(conditions: Partial<RowShape>): RowShape[] {
public findAllWhere(
conditions: Partial<RowShape>,
options?: {
order?: string;
limit?: number;
skip?: number;
},
): RowShape[] {
const keys = Object.keys(conditions);
const whereParts = keys.map((key) => `"${key}" = $${key}`);
const whereClause = whereParts.join(" AND ");
const optionParts: string[] = [];
if (options?.order) {
optionParts.push(`ORDER BY ${options.order}`);
}
if (options?.limit) {
optionParts.push(`LIMIT ${options.limit}`);
if (options?.skip) {
optionParts.push(`OFFSET ${options.skip}`);
}
}
const query = this._db.prepare<RowShape, ArbitraryRow>(
`SELECT * FROM ${this} WHERE ${whereClause};`,
`SELECT * FROM ${this} WHERE ${whereClause} ${optionParts.join(" ")};`,
);
return query.all(shapeForQuery(conditions));

View file

@ -222,6 +222,45 @@ test(".findAllWhere() returns a row based on basic criteria", () => {
expect(manyJacks[2]).toEqual({ user_id: 3, name: "Jack" });
});
test(".findAllWhere() can set a limit on returned rows", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere({ name: "Jack" }, { limit: 1 });
expect(manyJacks).toHaveLength(1);
expect(manyJacks[0]).toEqual({ user_id: 1, name: "Jack" });
});
test(".findAllWhere() can set an offset on a limit", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere({ name: "Jack" }, { limit: 1, skip: 1 });
expect(manyJacks).toHaveLength(1);
expect(manyJacks[0]).toEqual({ user_id: 2, name: "Jack" });
});
test(".findAllWhere() can set an order on the results", () => {
const table = makeTestTable();
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
table.insertPartial({ name: "Jack" });
const manyJacks = table.findAllWhere(
{ name: "Jack" },
{ order: "user_id DESC" },
);
expect(manyJacks).toHaveLength(3);
expect(manyJacks[0]).toEqual({ user_id: 3, name: "Jack" });
});
test(".findOneById() returns a single element by its primary column", () => {
const table = makeTestTable();