From bebc610ef92038a52af12a13f43f4aca9f80e5df Mon Sep 17 00:00:00 2001 From: Greg Richardson Date: Wed, 15 May 2024 18:31:47 -0600 Subject: [PATCH] feat(sql-to-rest): json column in order by clause --- packages/sql-to-rest/src/processor.ts | 77 ++++++++++--------- .../sql-to-rest/src/renderers/http.test.ts | 17 ++++ packages/sql-to-rest/src/types/libpg-query.ts | 2 +- 3 files changed, 58 insertions(+), 38 deletions(-) diff --git a/packages/sql-to-rest/src/processor.ts b/packages/sql-to-rest/src/processor.ts index 6495cd29733..ca0c0f87f9e 100644 --- a/packages/sql-to-rest/src/processor.ts +++ b/packages/sql-to-rest/src/processor.ts @@ -651,10 +651,6 @@ function processQueryTarget( function processWhereClause(expression: WhereExpression, from: string): Filter { if ('A_Expr' in expression) { - if ('TypeCast' in expression.A_Expr.lexpr) { - throw new UnsupportedError('Casting is not supported in the WHERE clause') - } - let column: string if ('A_Expr' in expression.A_Expr.lexpr) { @@ -695,6 +691,8 @@ function processWhereClause(expression: WhereExpression, from: string): Filter { else { column = [relationName, columnName].join('.') } + } else if ('TypeCast' in expression.A_Expr.lexpr) { + throw new UnsupportedError('Casting is not supported in the WHERE clause') } else { throw new UnsupportedError(`Left side of WHERE clause must be a column`) } @@ -835,41 +833,46 @@ function processWhereClause(expression: WhereExpression, from: string): Filter { function processSortClause(sorts: SortBy[], from: string): Sort[] { return sorts.map((sortBy) => { - if ('TypeCast' in sortBy.SortBy.node) { - throw new UnsupportedError('Casting is not supported in the ORDER BY clause') - } - - if (!('ColumnRef' in sortBy.SortBy.node)) { - throw new UnsupportedError('ORDER BY clause only accepts columns') - } - - const { fields } = sortBy.SortBy.node.ColumnRef - - const [field] = fields - - if (!('String' in field)) { - const [fieldType] = Object.keys(field) - throw new UnsupportedError( - `ORDER BY column fields must be String type, received '${fieldType}'` - ) - } - - const stringFields = fields.filter((field): field is PgString => 'String' in field) - const qualifiedName = stringFields.map((field) => field.String.sval) - - // Relation and column names are last two parts of the qualified name - const [relationName] = qualifiedName.slice(-2, -1) - const [columnName] = qualifiedName.slice(-1) - let column: string - // If the column is prefixed with the primary table name, strip the prefix - if (!relationName || relationName === from) { - column = columnName - } - // Otherwise this is a foreign column, so keep the relation prefix - else { - column = [relationName, columnName].join('.') + if ('A_Expr' in sortBy.SortBy.node) { + try { + const target = processJsonTarget(sortBy.SortBy.node) + column = target.column + } catch (err) { + throw new UnsupportedError(`ORDER BY clause must reference a column`) + } + } else if ('ColumnRef' in sortBy.SortBy.node) { + const { fields } = sortBy.SortBy.node.ColumnRef + + const [field] = fields + + if (!('String' in field)) { + const [fieldType] = Object.keys(field) + throw new UnsupportedError( + `ORDER BY column fields must be String type, received '${fieldType}'` + ) + } + + const stringFields = fields.filter((field): field is PgString => 'String' in field) + const qualifiedName = stringFields.map((field) => field.String.sval) + + // Relation and column names are last two parts of the qualified name + const [relationName] = qualifiedName.slice(-2, -1) + const [columnName] = qualifiedName.slice(-1) + + // If the column is prefixed with the primary table name, strip the prefix + if (!relationName || relationName === from) { + column = columnName + } + // Otherwise this is a foreign column, so keep the relation prefix + else { + column = [relationName, columnName].join('.') + } + } else if ('TypeCast' in sortBy.SortBy.node) { + throw new UnsupportedError('Casting is not supported in the ORDER BY clause') + } else { + throw new UnsupportedError(`ORDER BY clause must reference a column`) } const direction = mapSortByDirection(sortBy.SortBy.sortby_dir) diff --git a/packages/sql-to-rest/src/renderers/http.test.ts b/packages/sql-to-rest/src/renderers/http.test.ts index c7ee2bfb7a1..61e4171402f 100644 --- a/packages/sql-to-rest/src/renderers/http.test.ts +++ b/packages/sql-to-rest/src/renderers/http.test.ts @@ -1169,6 +1169,23 @@ describe('select', () => { expect(fullPath).toBe('/books?select=address->city->>name&address->city->>code=eq.SFO') }) + test('order by json column', async () => { + const sql = stripIndents` + select + address->'city'->>'name' + from + books + order by + address->'city'->>'code' + ` + + const statement = await processSql(sql) + const { method, fullPath } = await renderHttp(statement) + + expect(method).toBe('GET') + expect(fullPath).toBe('/books?select=address->city->>name&order=address->city->>code') + }) + test('aggregate', async () => { const sql = stripIndents` select diff --git a/packages/sql-to-rest/src/types/libpg-query.ts b/packages/sql-to-rest/src/types/libpg-query.ts index b9729e76de7..87c5782218b 100644 --- a/packages/sql-to-rest/src/types/libpg-query.ts +++ b/packages/sql-to-rest/src/types/libpg-query.ts @@ -184,7 +184,7 @@ export type WhereExpression = A_Expr | BoolExpr | NullTest export type SortBy = { SortBy: { - node: ColumnRef + node: ColumnRef | A_Expr sortby_dir: string sortby_nulls: string }