Skip to content

Instantly share code, notes, and snippets.

@fl0wo
Last active April 17, 2024 15:31
Show Gist options
  • Save fl0wo/8f01e0f96309c84912f3a8f23c31a9b4 to your computer and use it in GitHub Desktop.
Save fl0wo/8f01e0f96309c84912f3a8f23c31a9b4 to your computer and use it in GitHub Desktop.
UpdateRowBeforeAndAfterPlugin but with diffs
describe('UpdateRowBeforeAndAfterPlugin', () => {
const plugin = new UpdateRowBeforeAndAfterPlugin(
{} as any,
{} as any,
{} as any
);
it.each([
['UPDATE "users" SET name = "Alice" WHERE id = ?', 0],
['UPDATE "users" SET name = "Alice" WHERE id = ? AND age = ?', 0],
['UPDATE "users" SET name = "Alice" WHERE age = ? AND id = ?', 1],
['UPDATE "users" SET name = "Alice" WHERE age = ? AND id = ? AND name = ?', 1],
['UPDATE "users" SET name = "Alice" WHERE age = ? AND name = ? AND id232 = ? AND botid = ? AND notYourId = ? AND id = ?', 5],
])('getIndexOfIdQuestionMark(%s) should return %i', (sql, expected) => {
const query = {sql};
const result = plugin.getIndexOfIdQuestionMark(query as any);
expect(result).toBe(expected);
});
});
import {LibSQLPlugin} from "libsql-client-hooks";
import {Client, InStatement, ResultSet} from '@libsql/core/api';
import {
deepDiffPartials,
isUpdateForTable,
removeDoubleQuotes,
replaceAllSequencesFromQuery
} from "./utils";
import {TableNamesTurso} from "../index";
export class UpdateRowBeforeAndAfterPlugin<T> implements LibSQLPlugin {
private rowBefore: T | undefined;
constructor(
private readonly tableName: TableNamesTurso,
private readonly queryGetRowBeforeById: (id: any) => Promise<T>,
private readonly execWithBeforeAndAfter: (
rowBefore: T,
rowAfter: T,
rowDiff?: {
before: Partial<T>,
after: Partial<T>
}
) => Promise<void>
) {
}
async beforeExecute(query: InStatement, __: Client){
if(!isUpdateForTable(query, this.tableName)) {
return query;
}
//{
// sql: 'update "trades" set "amount" = ?, "id" = ?, "id_bot" = ?, "placed_by" = ?, "price" = ?, "side" = ?, "symbol" = ?, "timestamp" = ? where "trades"."id" = ? returning "id", "id_bot", "symbol", "side", "amount", "price", "placed_by", "fee_rate", "fee_coin", "fee_type", "fee_cost", "profit", "profit_percentage", "timestamp", "order", "type", "taker_or_maker", "cost", "created_at", "updated_at"',
// args: [
// 0,
// 'fake-trade-test',
// 'test_bot_id',
// 'bot',
// 0,
// 'sell',
// '',
// 1713352086126,
// 'fake-trade-test'
// ]
// }
// 1. Find the index of the ? for the "id" field
const nQMarksBefore = this.getIndexOfIdQuestionMark(query as any);
// 2. Get the value of the ? for the "id" field
const args = (query as any)?.args;
const rowId = args?.[nQMarksBefore];
// 3. Get the row before the update
this.rowBefore = await this.queryGetRowBeforeById(rowId);
return query;
}
getIndexOfIdQuestionMark(query?:{sql?:string}) {
if (!query?.sql) {
throw new Error('query.sql is not defined');
}
const txtQuery = replaceAllSequencesFromQuery(
removeDoubleQuotes(query.sql),
' = ',
'='
);
const idIndex = txtQuery.indexOf(' id=?');
const txtQueryBefore = txtQuery.substring(0, idIndex);
return txtQueryBefore.split('?').length - 1;
}
async afterExecute(result: ResultSet, query: InStatement, ___: Client){
if(!isUpdateForTable(query)) {
return result;
}
if(!this.rowBefore) {
throw new Error('Row before is not set');
}
if(result.rows.length !== 1) {
throw new Error('Expected one row to be returned');
}
const diff = deepDiffPartials<T>(
this.rowBefore,
result.rows[0] as T,
{
ignoreUndefinedFields: true
});
await this.execWithBeforeAndAfter(
this.rowBefore,
result.rows[0] as T,
diff
);
return result;
}
}
export function deepDiffPartials<T>(rowBefore: T, rowAfter: T, options?:{
ignoreUndefinedFields: boolean
}):{
before: Partial<T>,
after: Partial<T>
} {
const before: Partial<T> = {};
const after: Partial<T> = {};
for(const key in rowBefore) {
if(options?.ignoreUndefinedFields && (!rowBefore[key] || !rowAfter[key])) {
continue;
}
if(rowBefore[key] !== rowAfter[key]) {
before[key] = rowBefore[key];
after[key] = rowAfter[key];
}
}
return {before, after};
}
export function replaceAllSequencesFromQuery(txt: string,sequence:string,replace:string) {
if(!txt) return txt;
return txt.replace(new RegExp(sequence, 'g'), replace);
}
export function removeDoubleQuotes(txt: string) {
if(!txt) return txt;
return txt.replace(/"/g, '');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment