Skip to content

Commit a45ab63

Browse files
committed
Add missing PostgreSQL operators and fix >^ geometric operator
Adds support for several PostgreSQL operators from core and popular extensions that were previously being split into shorter tokens: - <@> (earthdistance distance) - &&& (PostGIS 3D bounding box overlap) - |=| (PostGIS closest point of approach distance) - ~> (cube coordinate extraction) - #= (hstore replace fields) Also fixes the geometric "is above" operator which was incorrectly defined as ^> instead of >^ per the PostgreSQL docs.
1 parent 954e5a4 commit a45ab63

File tree

2 files changed

+142
-2
lines changed

2 files changed

+142
-2
lines changed

src/languages/postgresql/postgresql.formatter.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,22 +319,26 @@ export const postgresql: DialectOptions = {
319319
'##',
320320
'<->',
321321
'&&',
322+
'&&&',
322323
'&<',
323324
'&>',
324325
'<<|',
325326
'&<|',
326327
'|>>',
327328
'|&>',
328329
'<^',
329-
'^>',
330+
'>^',
330331
'?#',
331332
'?-',
332333
'?|',
333334
'?-|',
334335
'?||',
335336
'@>',
336337
'<@',
338+
'<@>',
337339
'~=',
340+
// PostGIS
341+
'|=|',
338342
// JSON
339343
'?',
340344
'@?',
@@ -376,6 +380,10 @@ export const postgresql: DialectOptions = {
376380
'<->>',
377381
'<<<->',
378382
'<->>>',
383+
// Cube
384+
'~>',
385+
// Hstore
386+
'#=',
379387
// Type cast
380388
'::',
381389
':',

test/postgresql.test.ts

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,22 +66,26 @@ describe('PostgreSqlFormatter', () => {
6666
'##',
6767
'<->',
6868
'&&',
69+
'&&&',
6970
'&<',
7071
'&>',
7172
'<<|',
7273
'&<|',
7374
'|>>',
7475
'|&>',
7576
'<^',
76-
'^>',
77+
'>^',
7778
'?#',
7879
'?-',
7980
'?|',
8081
'?-|',
8182
'?||',
8283
'@>',
8384
'<@',
85+
'<@>',
8486
'~=',
87+
// PostGIS
88+
'|=|',
8589
// JSON
8690
'?',
8791
'@?',
@@ -123,6 +127,10 @@ describe('PostgreSqlFormatter', () => {
123127
'<->>',
124128
'<<<->',
125129
'<->>>',
130+
// Cube
131+
'~>',
132+
// Hstore
133+
'#=',
126134
// Custom operators: from pgvector extension
127135
'<#>',
128136
'<=>',
@@ -250,4 +258,128 @@ describe('PostgreSqlFormatter', () => {
250258
dedent`COMMENT ON TABLE foo IS 'Hello my table';`
251259
);
252260
});
261+
262+
// Tests for PostgreSQL containment and full-text search operators
263+
describe('containment and search operators', () => {
264+
it('formats @> (contains) operator in WHERE clause', () => {
265+
expect(format(`SELECT * FROM foo WHERE bar @> '{1,2}';`)).toBe(dedent`
266+
SELECT
267+
*
268+
FROM
269+
foo
270+
WHERE
271+
bar @> '{1,2}';
272+
`);
273+
});
274+
275+
it('formats <@ (contained by) operator in WHERE clause', () => {
276+
expect(format(`SELECT * FROM foo WHERE bar <@ '{1,2,3}';`)).toBe(dedent`
277+
SELECT
278+
*
279+
FROM
280+
foo
281+
WHERE
282+
bar <@ '{1,2,3}';
283+
`);
284+
});
285+
286+
// https://www.postgresql.org/docs/current/earthdistance.html
287+
it('formats <@> (distance) operator in ORDER BY clause', () => {
288+
expect(format(`SELECT * FROM foo ORDER BY bar <@> point(1,2);`)).toBe(dedent`
289+
SELECT
290+
*
291+
FROM
292+
foo
293+
ORDER BY
294+
bar <@> point(1, 2);
295+
`);
296+
});
297+
298+
it('formats @> operator with JSONB data', () => {
299+
expect(format(`SELECT * FROM foo WHERE data @> '{"key": "value"}';`)).toBe(dedent`
300+
SELECT
301+
*
302+
FROM
303+
foo
304+
WHERE
305+
data @> '{"key": "value"}';
306+
`);
307+
});
308+
309+
it('formats <@ operator with JSONB data', () => {
310+
expect(format(`SELECT * FROM foo WHERE data <@ '{"key": "value", "other": 1}';`)).toBe(dedent`
311+
SELECT
312+
*
313+
FROM
314+
foo
315+
WHERE
316+
data <@ '{"key": "value", "other": 1}';
317+
`);
318+
});
319+
});
320+
321+
// Tests for PostGIS operators
322+
describe('PostGIS operators', () => {
323+
// https://postgis.net/docs/geometry_overlaps_nd.html
324+
it('formats &&& (3D bounding box overlap) operator', () => {
325+
expect(format(`SELECT * FROM foo WHERE geom_a &&& geom_b;`)).toBe(dedent`
326+
SELECT
327+
*
328+
FROM
329+
foo
330+
WHERE
331+
geom_a &&& geom_b;
332+
`);
333+
});
334+
335+
// https://postgis.net/docs/geometry_distance_cpa.html
336+
it('formats |=| (closest point of approach distance) operator', () => {
337+
expect(format(`SELECT * FROM foo ORDER BY traj_a |=| traj_b;`)).toBe(dedent`
338+
SELECT
339+
*
340+
FROM
341+
foo
342+
ORDER BY
343+
traj_a |=| traj_b;
344+
`);
345+
});
346+
});
347+
348+
// https://www.postgresql.org/docs/current/functions-geometry.html
349+
// Note: the formatter defines ^> but PostgreSQL docs say the operator is >^
350+
describe('geometric operator correctness', () => {
351+
it('formats >^ (is above) operator', () => {
352+
expect(format(`SELECT * FROM foo WHERE point(1,2) >^ point(3,4);`)).toBe(dedent`
353+
SELECT
354+
*
355+
FROM
356+
foo
357+
WHERE
358+
point(1, 2) >^ point(3, 4);
359+
`);
360+
});
361+
});
362+
363+
// Tests for extension operators (hstore, cube, ltree)
364+
describe('extension operators', () => {
365+
// https://www.postgresql.org/docs/current/cube.html
366+
it('formats ~> (cube coordinate extraction) operator', () => {
367+
expect(format(`SELECT c ~> 1 FROM foo;`)).toBe(dedent`
368+
SELECT
369+
c ~> 1
370+
FROM
371+
foo;
372+
`);
373+
});
374+
375+
// https://www.postgresql.org/docs/current/hstore.html
376+
it('formats #= (hstore replace fields) operator', () => {
377+
expect(format(`SELECT row #= hstore_data FROM foo;`)).toBe(dedent`
378+
SELECT
379+
row #= hstore_data
380+
FROM
381+
foo;
382+
`);
383+
});
384+
});
253385
});

0 commit comments

Comments
 (0)