Skip to content

Commit 3a679e5

Browse files
committed
Fixed $push operator for appending items to nested arrays
Also refactored the execute to separate better execution from query generation
1 parent a5f8406 commit 3a679e5

File tree

5 files changed

+163
-43
lines changed

5 files changed

+163
-43
lines changed

src/packages/pongo/src/e2e/compatibilityTest.e2e.spec.ts

+114
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,120 @@ void describe('MongoDB Compatibility Tests', () => {
167167
assert.strictEqual(pongoDoc, null);
168168
assert.strictEqual(mongoDoc, null);
169169
});
170+
171+
void it('should update a document in both PostgreSQL and MongoDB using $unset', async () => {
172+
const pongoCollection = pongoDb.collection<User>('testCollection');
173+
const mongoCollection = mongoDb.collection<User>('testCollection');
174+
const doc = { name: 'Roger', age: 30, address: { city: 'Wonderland' } };
175+
176+
const pongoInsertResult = await pongoCollection.insertOne(doc);
177+
const mongoInsertResult = await mongoCollection.insertOne(doc);
178+
179+
await pongoCollection.updateOne(
180+
{ _id: pongoInsertResult.insertedId },
181+
{ $unset: { address: '' } },
182+
);
183+
await mongoCollection.updateOne(
184+
{ _id: mongoInsertResult.insertedId },
185+
{ $unset: { address: '' } },
186+
);
187+
188+
const pongoDoc = await pongoCollection.findOne({
189+
_id: pongoInsertResult.insertedId,
190+
});
191+
const mongoDoc = await mongoCollection.findOne({
192+
_id: mongoInsertResult.insertedId,
193+
});
194+
195+
assert.deepStrictEqual(
196+
{
197+
name: pongoDoc!.name,
198+
age: pongoDoc!.age,
199+
address: undefined,
200+
},
201+
{
202+
name: mongoDoc!.name,
203+
age: mongoDoc!.age,
204+
address: undefined,
205+
},
206+
);
207+
});
208+
209+
void it('should update a document in both PostgreSQL and MongoDB using $inc', async () => {
210+
const pongoCollection = pongoDb.collection<User>('testCollection');
211+
const mongoCollection = mongoDb.collection<User>('testCollection');
212+
const doc = { name: 'Roger', age: 30 };
213+
214+
const pongoInsertResult = await pongoCollection.insertOne(doc);
215+
const mongoInsertResult = await mongoCollection.insertOne(doc);
216+
217+
const update = { $inc: { age: 1 } };
218+
219+
await pongoCollection.updateOne(
220+
{ _id: pongoInsertResult.insertedId },
221+
update,
222+
);
223+
await mongoCollection.updateOne(
224+
{ _id: mongoInsertResult.insertedId },
225+
update,
226+
);
227+
228+
const pongoDoc = await pongoCollection.findOne({
229+
_id: pongoInsertResult.insertedId,
230+
});
231+
const mongoDoc = await mongoCollection.findOne({
232+
_id: mongoInsertResult.insertedId,
233+
});
234+
235+
assert.deepStrictEqual(
236+
{
237+
name: pongoDoc!.name,
238+
age: 31,
239+
},
240+
{
241+
name: mongoDoc!.name,
242+
age: 31,
243+
},
244+
);
245+
});
246+
247+
void it('should update a document in both PostgreSQL and MongoDB using $push', async () => {
248+
const pongoCollection = pongoDb.collection<User>('testCollection');
249+
const mongoCollection = mongoDb.collection<User>('testCollection');
250+
const doc = { name: 'Roger', age: 30, tags: ['tag1'] };
251+
252+
const pongoInsertResult = await pongoCollection.insertOne(doc);
253+
const mongoInsertResult = await mongoCollection.insertOne(doc);
254+
255+
await pongoCollection.updateOne(
256+
{ _id: pongoInsertResult.insertedId },
257+
{ $push: { tags: 'tag2' } },
258+
);
259+
await mongoCollection.updateOne(
260+
{ _id: mongoInsertResult.insertedId },
261+
{ $push: { tags: 'tag2' } },
262+
);
263+
264+
const pongoDoc = await pongoCollection.findOne({
265+
_id: pongoInsertResult.insertedId,
266+
});
267+
const mongoDoc = await mongoCollection.findOne({
268+
_id: mongoInsertResult.insertedId,
269+
});
270+
271+
assert.deepStrictEqual(
272+
{
273+
name: pongoDoc!.name,
274+
age: pongoDoc!.age,
275+
tags: ['tag1', 'tag2'],
276+
},
277+
{
278+
name: mongoDoc!.name,
279+
age: mongoDoc!.age,
280+
tags: ['tag1', 'tag2'],
281+
},
282+
);
283+
});
170284
});
171285

172286
void describe('Find Operations', () => {

src/packages/pongo/src/postgres/client.ts

+31-26
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import {
99
type PongoUpdate,
1010
type PongoUpdateResult,
1111
} from '../main';
12-
import { sql } from './execute';
12+
import { executeSQL } from './execute';
1313
import { constructFilterQuery } from './filter';
1414
import { endPool, getPool } from './pool';
15+
import { sql } from './sql';
1516
import { constructUpdateQuery } from './update';
1617

1718
export const postgresClient = (
@@ -31,10 +32,12 @@ export const postgresCollection = <T>(
3132
collectionName: string,
3233
pool: pg.Pool,
3334
): PongoCollection<T> => {
34-
const createCollection = sql(
35+
const createCollection = executeSQL(
3536
pool,
36-
'CREATE TABLE IF NOT EXISTS %I (_id UUID PRIMARY KEY, data JSONB)',
37-
collectionName,
37+
sql(
38+
'CREATE TABLE IF NOT EXISTS %I (_id UUID PRIMARY KEY, data JSONB)',
39+
collectionName,
40+
),
3841
);
3942
return {
4043
createCollection: async () => {
@@ -45,12 +48,14 @@ export const postgresCollection = <T>(
4548

4649
const id = uuid();
4750

48-
const result = await sql(
51+
const result = await executeSQL(
4952
pool,
50-
'INSERT INTO %I (_id, data) VALUES (%L, %L)',
51-
collectionName,
52-
id,
53-
JSON.stringify({ ...document, _id: id }),
53+
sql(
54+
'INSERT INTO %I (_id, data) VALUES (%L, %L)',
55+
collectionName,
56+
id,
57+
JSON.stringify({ ...document, _id: id }),
58+
),
5459
);
5560

5661
return result.rowCount
@@ -66,12 +71,14 @@ export const postgresCollection = <T>(
6671
const filterQuery = constructFilterQuery(filter);
6772
const updateQuery = constructUpdateQuery(update);
6873

69-
const result = await sql(
74+
const result = await executeSQL(
7075
pool,
71-
'UPDATE %I SET data = %s WHERE %s',
72-
collectionName,
73-
updateQuery,
74-
filterQuery,
76+
sql(
77+
'UPDATE %I SET data = %s WHERE %s',
78+
collectionName,
79+
updateQuery,
80+
filterQuery,
81+
),
7582
);
7683
return result.rowCount
7784
? { acknowledged: true, modifiedCount: result.rowCount }
@@ -81,11 +88,9 @@ export const postgresCollection = <T>(
8188
await createCollection;
8289

8390
const filterQuery = constructFilterQuery(filter);
84-
const result = await sql(
91+
const result = await executeSQL(
8592
pool,
86-
'DELETE FROM %I WHERE %s',
87-
collectionName,
88-
filterQuery,
93+
sql('DELETE FROM %I WHERE %s', collectionName, filterQuery),
8994
);
9095
return result.rowCount
9196
? { acknowledged: true, deletedCount: result.rowCount }
@@ -95,23 +100,23 @@ export const postgresCollection = <T>(
95100
await createCollection;
96101

97102
const filterQuery = constructFilterQuery(filter);
98-
const result = await sql(
103+
const result = await executeSQL(
99104
pool,
100-
'SELECT data FROM %I WHERE %s LIMIT 1',
101-
collectionName,
102-
filterQuery,
105+
sql(
106+
'SELECT data FROM %I WHERE %s LIMIT 1',
107+
collectionName,
108+
filterQuery,
109+
),
103110
);
104111
return (result.rows[0]?.data ?? null) as T | null;
105112
},
106113
find: async (filter: PongoFilter<T>): Promise<T[]> => {
107114
await createCollection;
108115

109116
const filterQuery = constructFilterQuery(filter);
110-
const result = await sql(
117+
const result = await executeSQL(
111118
pool,
112-
'SELECT data FROM %I WHERE %s',
113-
collectionName,
114-
filterQuery,
119+
sql('SELECT data FROM %I WHERE %s', collectionName, filterQuery),
115120
);
116121

117122
return result.rows.map((row) => row.data as T);
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,5 @@
11
import type pg from 'pg';
2-
import format from 'pg-format';
3-
4-
export const sql = async <Result extends pg.QueryResultRow = pg.QueryResultRow>(
5-
pool: pg.Pool,
6-
sqlText: string,
7-
...params: unknown[]
8-
): Promise<pg.QueryResult<Result>> => {
9-
const client = await pool.connect();
10-
try {
11-
const query = format(sqlText, ...params);
12-
return await client.query<Result>(query);
13-
} finally {
14-
client.release();
15-
}
16-
};
2+
import type { SQL } from '../sql';
173

184
export const execute = async <Result = void>(
195
pool: pg.Pool,
@@ -26,3 +12,11 @@ export const execute = async <Result = void>(
2612
client.release();
2713
}
2814
};
15+
16+
export const executeSQL = async <
17+
Result extends pg.QueryResultRow = pg.QueryResultRow,
18+
>(
19+
pool: pg.Pool,
20+
sql: SQL,
21+
): Promise<pg.QueryResult<Result>> =>
22+
execute(pool, (client) => client.query<Result>(sql));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import format from 'pg-format';
2+
3+
export type SQL = string & { __brand: 'sql' };
4+
5+
export const sql = (sqlQuery: string, ...params: unknown[]): SQL => {
6+
return format(sqlQuery, ...params) as SQL;
7+
};

src/packages/pongo/src/postgres/update/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ export const constructUpdateQuery = <T>(update: PongoUpdate<T>): string => {
3636
const pushUpdate = update.$push!;
3737
for (const [key, value] of Object.entries(pushUpdate)) {
3838
updateQuery = format(
39-
"jsonb_set(%s, '{%s}', (COALESCE(data->'%s', '[]'::jsonb) || to_jsonb(%L)))",
39+
"jsonb_set(%s, '{%s}', (COALESCE(data->'%s', '[]'::jsonb) || '[%s]'::jsonb))",
4040
updateQuery,
4141
key,
4242
key,
43-
value,
43+
JSON.stringify(value),
4444
);
4545
}
4646
}

0 commit comments

Comments
 (0)