Skip to content

Commit c2bb851

Browse files
committed
Extracted SQL builder from execution on collection to enable unit testing SQL generation in the future
1 parent 4b33197 commit c2bb851

File tree

2 files changed

+134
-111
lines changed

2 files changed

+134
-111
lines changed
+2-111
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,6 @@
1-
import pg from 'pg';
2-
import { v4 as uuid } from 'uuid';
3-
import {
4-
type DbClient,
5-
type PongoCollection,
6-
type PongoDeleteResult,
7-
type PongoFilter,
8-
type PongoInsertOneResult,
9-
type PongoUpdate,
10-
type PongoUpdateResult,
11-
} from '../main';
12-
import { executeSQL } from './execute';
13-
import { constructFilterQuery } from './filter';
1+
import { type DbClient } from '../main';
142
import { endPool, getPool } from './pool';
15-
import { sql } from './sql';
16-
import { buildUpdateQuery } from './update';
3+
import { postgresCollection } from './postgresCollection';
174

185
export const postgresClient = (
196
connectionString: string,
@@ -27,99 +14,3 @@ export const postgresClient = (
2714
collection: <T>(name: string) => postgresCollection<T>(name, pool),
2815
};
2916
};
30-
31-
export const postgresCollection = <T>(
32-
collectionName: string,
33-
pool: pg.Pool,
34-
): PongoCollection<T> => {
35-
const createCollection = executeSQL(
36-
pool,
37-
sql(
38-
'CREATE TABLE IF NOT EXISTS %I (_id UUID PRIMARY KEY, data JSONB)',
39-
collectionName,
40-
),
41-
);
42-
return {
43-
createCollection: async () => {
44-
await createCollection;
45-
},
46-
insertOne: async (document: T): Promise<PongoInsertOneResult> => {
47-
await createCollection;
48-
49-
const id = uuid();
50-
51-
const result = await executeSQL(
52-
pool,
53-
sql(
54-
'INSERT INTO %I (_id, data) VALUES (%L, %L)',
55-
collectionName,
56-
id,
57-
JSON.stringify({ ...document, _id: id }),
58-
),
59-
);
60-
61-
return result.rowCount
62-
? { insertedId: id, acknowledged: true }
63-
: { insertedId: null, acknowledged: false };
64-
},
65-
updateOne: async (
66-
filter: PongoFilter<T>,
67-
update: PongoUpdate<T>,
68-
): Promise<PongoUpdateResult> => {
69-
await createCollection;
70-
71-
const filterQuery = constructFilterQuery(filter);
72-
const updateQuery = buildUpdateQuery(update);
73-
74-
const result = await executeSQL(
75-
pool,
76-
sql(
77-
'UPDATE %I SET data = %s WHERE %s',
78-
collectionName,
79-
updateQuery,
80-
filterQuery,
81-
),
82-
);
83-
return result.rowCount
84-
? { acknowledged: true, modifiedCount: result.rowCount }
85-
: { acknowledged: false, modifiedCount: 0 };
86-
},
87-
deleteOne: async (filter: PongoFilter<T>): Promise<PongoDeleteResult> => {
88-
await createCollection;
89-
90-
const filterQuery = constructFilterQuery(filter);
91-
const result = await executeSQL(
92-
pool,
93-
sql('DELETE FROM %I WHERE %s', collectionName, filterQuery),
94-
);
95-
return result.rowCount
96-
? { acknowledged: true, deletedCount: result.rowCount }
97-
: { acknowledged: false, deletedCount: 0 };
98-
},
99-
findOne: async (filter: PongoFilter<T>): Promise<T | null> => {
100-
await createCollection;
101-
102-
const filterQuery = constructFilterQuery(filter);
103-
const result = await executeSQL(
104-
pool,
105-
sql(
106-
'SELECT data FROM %I WHERE %s LIMIT 1',
107-
collectionName,
108-
filterQuery,
109-
),
110-
);
111-
return (result.rows[0]?.data ?? null) as T | null;
112-
},
113-
find: async (filter: PongoFilter<T>): Promise<T[]> => {
114-
await createCollection;
115-
116-
const filterQuery = constructFilterQuery(filter);
117-
const result = await executeSQL(
118-
pool,
119-
sql('SELECT data FROM %I WHERE %s', collectionName, filterQuery),
120-
);
121-
122-
return result.rows.map((row) => row.data as T);
123-
},
124-
};
125-
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import pg from 'pg';
2+
import { v4 as uuid } from 'uuid';
3+
import {
4+
type PongoCollection,
5+
type PongoDeleteResult,
6+
type PongoFilter,
7+
type PongoInsertOneResult,
8+
type PongoUpdate,
9+
type PongoUpdateResult,
10+
} from '../main';
11+
import { executeSQL } from './execute';
12+
import { constructFilterQuery } from './filter';
13+
import { sql, type SQL } from './sql';
14+
import { buildUpdateQuery } from './update';
15+
16+
export const postgresCollection = <T>(
17+
collectionName: string,
18+
pool: pg.Pool,
19+
): PongoCollection<T> => {
20+
const createCollection = executeSQL(
21+
pool,
22+
SQLBuilder.createCollection(collectionName),
23+
);
24+
return {
25+
createCollection: async () => {
26+
await createCollection;
27+
},
28+
insertOne: async (document: T): Promise<PongoInsertOneResult> => {
29+
await createCollection;
30+
31+
const id = uuid();
32+
33+
const result = await executeSQL(
34+
pool,
35+
SQLBuilder.insertOne(collectionName, id, document),
36+
);
37+
38+
return result.rowCount
39+
? { insertedId: id, acknowledged: true }
40+
: { insertedId: null, acknowledged: false };
41+
},
42+
updateOne: async (
43+
filter: PongoFilter<T>,
44+
update: PongoUpdate<T>,
45+
): Promise<PongoUpdateResult> => {
46+
await createCollection;
47+
48+
const result = await executeSQL(
49+
pool,
50+
SQLBuilder.updateOne(collectionName, filter, update),
51+
);
52+
return result.rowCount
53+
? { acknowledged: true, modifiedCount: result.rowCount }
54+
: { acknowledged: false, modifiedCount: 0 };
55+
},
56+
deleteOne: async (filter: PongoFilter<T>): Promise<PongoDeleteResult> => {
57+
await createCollection;
58+
59+
const result = await executeSQL(
60+
pool,
61+
SQLBuilder.deleteOne(collectionName, filter),
62+
);
63+
return result.rowCount
64+
? { acknowledged: true, deletedCount: result.rowCount }
65+
: { acknowledged: false, deletedCount: 0 };
66+
},
67+
findOne: async (filter: PongoFilter<T>): Promise<T | null> => {
68+
await createCollection;
69+
70+
const result = await executeSQL(
71+
pool,
72+
SQLBuilder.findOne(collectionName, filter),
73+
);
74+
return (result.rows[0]?.data ?? null) as T | null;
75+
},
76+
find: async (filter: PongoFilter<T>): Promise<T[]> => {
77+
await createCollection;
78+
79+
const result = await executeSQL(
80+
pool,
81+
SQLBuilder.find(collectionName, filter),
82+
);
83+
return result.rows.map((row) => row.data as T);
84+
},
85+
};
86+
};
87+
88+
export const SQLBuilder = {
89+
createCollection: (collectionName: string): SQL =>
90+
sql(
91+
'CREATE TABLE IF NOT EXISTS %I (_id UUID PRIMARY KEY, data JSONB)',
92+
collectionName,
93+
),
94+
insertOne: <T>(collectionName: string, id: string, document: T): SQL =>
95+
sql(
96+
'INSERT INTO %I (_id, data) VALUES (%L, %L)',
97+
collectionName,
98+
id,
99+
JSON.stringify({ ...document, _id: id }),
100+
),
101+
updateOne: <T>(
102+
collectionName: string,
103+
filter: PongoFilter<T>,
104+
update: PongoUpdate<T>,
105+
): SQL => {
106+
const filterQuery = constructFilterQuery(filter);
107+
const updateQuery = buildUpdateQuery(update);
108+
109+
return sql(
110+
'UPDATE %I SET data = %s WHERE %s',
111+
collectionName,
112+
updateQuery,
113+
filterQuery,
114+
);
115+
},
116+
deleteOne: <T>(collectionName: string, filter: PongoFilter<T>): SQL => {
117+
const filterQuery = constructFilterQuery(filter);
118+
return sql('DELETE FROM %I WHERE %s', collectionName, filterQuery);
119+
},
120+
findOne: <T>(collectionName: string, filter: PongoFilter<T>): SQL => {
121+
const filterQuery = constructFilterQuery(filter);
122+
return sql(
123+
'SELECT data FROM %I WHERE %s LIMIT 1',
124+
collectionName,
125+
filterQuery,
126+
);
127+
},
128+
find: <T>(collectionName: string, filter: PongoFilter<T>): SQL => {
129+
const filterQuery = constructFilterQuery(filter);
130+
return sql('SELECT data FROM %I WHERE %s', collectionName, filterQuery);
131+
},
132+
};

0 commit comments

Comments
 (0)