diff --git a/src/languages/postgresql/postgresql.formatter.ts b/src/languages/postgresql/postgresql.formatter.ts index 4b7d5248f..aeb8ff241 100644 --- a/src/languages/postgresql/postgresql.formatter.ts +++ b/src/languages/postgresql/postgresql.formatter.ts @@ -319,6 +319,7 @@ export const postgresql: DialectOptions = { '##', '<->', '&&', + '&&&', '&<', '&>', '<<|', @@ -326,7 +327,7 @@ export const postgresql: DialectOptions = { '|>>', '|&>', '<^', - '^>', + '>^', '?#', '?-', '?|', @@ -334,7 +335,10 @@ export const postgresql: DialectOptions = { '?||', '@>', '<@', + '<@>', '~=', + // PostGIS + '|=|', // JSON '?', '@?', @@ -376,6 +380,10 @@ export const postgresql: DialectOptions = { '<->>', '<<<->', '<->>>', + // Cube + '~>', + // Hstore + '#=', // Type cast '::', ':', diff --git a/test/postgresql.test.ts b/test/postgresql.test.ts index c16fba7e3..d62e03608 100644 --- a/test/postgresql.test.ts +++ b/test/postgresql.test.ts @@ -66,6 +66,7 @@ describe('PostgreSqlFormatter', () => { '##', '<->', '&&', + '&&&', '&<', '&>', '<<|', @@ -73,7 +74,7 @@ describe('PostgreSqlFormatter', () => { '|>>', '|&>', '<^', - '^>', + '>^', '?#', '?-', '?|', @@ -81,7 +82,10 @@ describe('PostgreSqlFormatter', () => { '?||', '@>', '<@', + '<@>', '~=', + // PostGIS + '|=|', // JSON '?', '@?', @@ -123,6 +127,10 @@ describe('PostgreSqlFormatter', () => { '<->>', '<<<->', '<->>>', + // Cube + '~>', + // Hstore + '#=', // Custom operators: from pgvector extension '<#>', '<=>', @@ -250,4 +258,128 @@ describe('PostgreSqlFormatter', () => { dedent`COMMENT ON TABLE foo IS 'Hello my table';` ); }); + + // Tests for PostgreSQL containment and full-text search operators + describe('containment and search operators', () => { + it('formats @> (contains) operator in WHERE clause', () => { + expect(format(`SELECT * FROM foo WHERE bar @> '{1,2}';`)).toBe(dedent` + SELECT + * + FROM + foo + WHERE + bar @> '{1,2}'; + `); + }); + + it('formats <@ (contained by) operator in WHERE clause', () => { + expect(format(`SELECT * FROM foo WHERE bar <@ '{1,2,3}';`)).toBe(dedent` + SELECT + * + FROM + foo + WHERE + bar <@ '{1,2,3}'; + `); + }); + + // https://www.postgresql.org/docs/current/earthdistance.html + it('formats <@> (distance) operator in ORDER BY clause', () => { + expect(format(`SELECT * FROM foo ORDER BY bar <@> point(1,2);`)).toBe(dedent` + SELECT + * + FROM + foo + ORDER BY + bar <@> point(1, 2); + `); + }); + + it('formats @> operator with JSONB data', () => { + expect(format(`SELECT * FROM foo WHERE data @> '{"key": "value"}';`)).toBe(dedent` + SELECT + * + FROM + foo + WHERE + data @> '{"key": "value"}'; + `); + }); + + it('formats <@ operator with JSONB data', () => { + expect(format(`SELECT * FROM foo WHERE data <@ '{"key": "value", "other": 1}';`)).toBe(dedent` + SELECT + * + FROM + foo + WHERE + data <@ '{"key": "value", "other": 1}'; + `); + }); + }); + + // Tests for PostGIS operators + describe('PostGIS operators', () => { + // https://postgis.net/docs/geometry_overlaps_nd.html + it('formats &&& (3D bounding box overlap) operator', () => { + expect(format(`SELECT * FROM foo WHERE geom_a &&& geom_b;`)).toBe(dedent` + SELECT + * + FROM + foo + WHERE + geom_a &&& geom_b; + `); + }); + + // https://postgis.net/docs/geometry_distance_cpa.html + it('formats |=| (closest point of approach distance) operator', () => { + expect(format(`SELECT * FROM foo ORDER BY traj_a |=| traj_b;`)).toBe(dedent` + SELECT + * + FROM + foo + ORDER BY + traj_a |=| traj_b; + `); + }); + }); + + // https://www.postgresql.org/docs/current/functions-geometry.html + // Note: the formatter defines ^> but PostgreSQL docs say the operator is >^ + describe('geometric operator correctness', () => { + it('formats >^ (is above) operator', () => { + expect(format(`SELECT * FROM foo WHERE point(1,2) >^ point(3,4);`)).toBe(dedent` + SELECT + * + FROM + foo + WHERE + point(1, 2) >^ point(3, 4); + `); + }); + }); + + // Tests for extension operators (hstore, cube, ltree) + describe('extension operators', () => { + // https://www.postgresql.org/docs/current/cube.html + it('formats ~> (cube coordinate extraction) operator', () => { + expect(format(`SELECT c ~> 1 FROM foo;`)).toBe(dedent` + SELECT + c ~> 1 + FROM + foo; + `); + }); + + // https://www.postgresql.org/docs/current/hstore.html + it('formats #= (hstore replace fields) operator', () => { + expect(format(`SELECT row #= hstore_data FROM foo;`)).toBe(dedent` + SELECT + row #= hstore_data + FROM + foo; + `); + }); + }); });