From c2bb851bf7e5f0aed28d4755606035847ed8e262 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Sun, 7 Jul 2024 15:07:39 +0200 Subject: [PATCH] Extracted SQL builder from execution on collection to enable unit testing SQL generation in the future --- src/packages/pongo/src/postgres/client.ts | 113 +-------------- .../pongo/src/postgres/postgresCollection.ts | 132 ++++++++++++++++++ 2 files changed, 134 insertions(+), 111 deletions(-) create mode 100644 src/packages/pongo/src/postgres/postgresCollection.ts diff --git a/src/packages/pongo/src/postgres/client.ts b/src/packages/pongo/src/postgres/client.ts index 8bec85d..c64c5da 100644 --- a/src/packages/pongo/src/postgres/client.ts +++ b/src/packages/pongo/src/postgres/client.ts @@ -1,19 +1,6 @@ -import pg from 'pg'; -import { v4 as uuid } from 'uuid'; -import { - type DbClient, - type PongoCollection, - type PongoDeleteResult, - type PongoFilter, - type PongoInsertOneResult, - type PongoUpdate, - type PongoUpdateResult, -} from '../main'; -import { executeSQL } from './execute'; -import { constructFilterQuery } from './filter'; +import { type DbClient } from '../main'; import { endPool, getPool } from './pool'; -import { sql } from './sql'; -import { buildUpdateQuery } from './update'; +import { postgresCollection } from './postgresCollection'; export const postgresClient = ( connectionString: string, @@ -27,99 +14,3 @@ export const postgresClient = ( collection: (name: string) => postgresCollection(name, pool), }; }; - -export const postgresCollection = ( - collectionName: string, - pool: pg.Pool, -): PongoCollection => { - const createCollection = executeSQL( - pool, - sql( - 'CREATE TABLE IF NOT EXISTS %I (_id UUID PRIMARY KEY, data JSONB)', - collectionName, - ), - ); - return { - createCollection: async () => { - await createCollection; - }, - insertOne: async (document: T): Promise => { - await createCollection; - - const id = uuid(); - - const result = await executeSQL( - pool, - sql( - 'INSERT INTO %I (_id, data) VALUES (%L, %L)', - collectionName, - id, - JSON.stringify({ ...document, _id: id }), - ), - ); - - return result.rowCount - ? { insertedId: id, acknowledged: true } - : { insertedId: null, acknowledged: false }; - }, - updateOne: async ( - filter: PongoFilter, - update: PongoUpdate, - ): Promise => { - await createCollection; - - const filterQuery = constructFilterQuery(filter); - const updateQuery = buildUpdateQuery(update); - - const result = await executeSQL( - pool, - sql( - 'UPDATE %I SET data = %s WHERE %s', - collectionName, - updateQuery, - filterQuery, - ), - ); - return result.rowCount - ? { acknowledged: true, modifiedCount: result.rowCount } - : { acknowledged: false, modifiedCount: 0 }; - }, - deleteOne: async (filter: PongoFilter): Promise => { - await createCollection; - - const filterQuery = constructFilterQuery(filter); - const result = await executeSQL( - pool, - sql('DELETE FROM %I WHERE %s', collectionName, filterQuery), - ); - return result.rowCount - ? { acknowledged: true, deletedCount: result.rowCount } - : { acknowledged: false, deletedCount: 0 }; - }, - findOne: async (filter: PongoFilter): Promise => { - await createCollection; - - const filterQuery = constructFilterQuery(filter); - const result = await executeSQL( - pool, - sql( - 'SELECT data FROM %I WHERE %s LIMIT 1', - collectionName, - filterQuery, - ), - ); - return (result.rows[0]?.data ?? null) as T | null; - }, - find: async (filter: PongoFilter): Promise => { - await createCollection; - - const filterQuery = constructFilterQuery(filter); - const result = await executeSQL( - pool, - sql('SELECT data FROM %I WHERE %s', collectionName, filterQuery), - ); - - return result.rows.map((row) => row.data as T); - }, - }; -}; diff --git a/src/packages/pongo/src/postgres/postgresCollection.ts b/src/packages/pongo/src/postgres/postgresCollection.ts new file mode 100644 index 0000000..f865e2b --- /dev/null +++ b/src/packages/pongo/src/postgres/postgresCollection.ts @@ -0,0 +1,132 @@ +import pg from 'pg'; +import { v4 as uuid } from 'uuid'; +import { + type PongoCollection, + type PongoDeleteResult, + type PongoFilter, + type PongoInsertOneResult, + type PongoUpdate, + type PongoUpdateResult, +} from '../main'; +import { executeSQL } from './execute'; +import { constructFilterQuery } from './filter'; +import { sql, type SQL } from './sql'; +import { buildUpdateQuery } from './update'; + +export const postgresCollection = ( + collectionName: string, + pool: pg.Pool, +): PongoCollection => { + const createCollection = executeSQL( + pool, + SQLBuilder.createCollection(collectionName), + ); + return { + createCollection: async () => { + await createCollection; + }, + insertOne: async (document: T): Promise => { + await createCollection; + + const id = uuid(); + + const result = await executeSQL( + pool, + SQLBuilder.insertOne(collectionName, id, document), + ); + + return result.rowCount + ? { insertedId: id, acknowledged: true } + : { insertedId: null, acknowledged: false }; + }, + updateOne: async ( + filter: PongoFilter, + update: PongoUpdate, + ): Promise => { + await createCollection; + + const result = await executeSQL( + pool, + SQLBuilder.updateOne(collectionName, filter, update), + ); + return result.rowCount + ? { acknowledged: true, modifiedCount: result.rowCount } + : { acknowledged: false, modifiedCount: 0 }; + }, + deleteOne: async (filter: PongoFilter): Promise => { + await createCollection; + + const result = await executeSQL( + pool, + SQLBuilder.deleteOne(collectionName, filter), + ); + return result.rowCount + ? { acknowledged: true, deletedCount: result.rowCount } + : { acknowledged: false, deletedCount: 0 }; + }, + findOne: async (filter: PongoFilter): Promise => { + await createCollection; + + const result = await executeSQL( + pool, + SQLBuilder.findOne(collectionName, filter), + ); + return (result.rows[0]?.data ?? null) as T | null; + }, + find: async (filter: PongoFilter): Promise => { + await createCollection; + + const result = await executeSQL( + pool, + SQLBuilder.find(collectionName, filter), + ); + return result.rows.map((row) => row.data as T); + }, + }; +}; + +export const SQLBuilder = { + createCollection: (collectionName: string): SQL => + sql( + 'CREATE TABLE IF NOT EXISTS %I (_id UUID PRIMARY KEY, data JSONB)', + collectionName, + ), + insertOne: (collectionName: string, id: string, document: T): SQL => + sql( + 'INSERT INTO %I (_id, data) VALUES (%L, %L)', + collectionName, + id, + JSON.stringify({ ...document, _id: id }), + ), + updateOne: ( + collectionName: string, + filter: PongoFilter, + update: PongoUpdate, + ): SQL => { + const filterQuery = constructFilterQuery(filter); + const updateQuery = buildUpdateQuery(update); + + return sql( + 'UPDATE %I SET data = %s WHERE %s', + collectionName, + updateQuery, + filterQuery, + ); + }, + deleteOne: (collectionName: string, filter: PongoFilter): SQL => { + const filterQuery = constructFilterQuery(filter); + return sql('DELETE FROM %I WHERE %s', collectionName, filterQuery); + }, + findOne: (collectionName: string, filter: PongoFilter): SQL => { + const filterQuery = constructFilterQuery(filter); + return sql( + 'SELECT data FROM %I WHERE %s LIMIT 1', + collectionName, + filterQuery, + ); + }, + find: (collectionName: string, filter: PongoFilter): SQL => { + const filterQuery = constructFilterQuery(filter); + return sql('SELECT data FROM %I WHERE %s', collectionName, filterQuery); + }, +};