diff --git a/packages/transform/__test__/deparse.test.ts b/packages/transform/__test__/deparse.test.ts new file mode 100644 index 00000000..49917efe --- /dev/null +++ b/packages/transform/__test__/deparse.test.ts @@ -0,0 +1,114 @@ +// this the deparser for PG17 +import { deparse } from '../../deparser/src'; + +// this is the type of the AST for PG13 +import { SelectStmt } from 'libpg-query'; + +// import the transformer +import { transformPG13ToPG17 } from '../src/index'; + +xdescribe('PG13 to PG17 transformer', () => { + + it('should deparse a select statement', async () => { + const stmt: { SelectStmt: SelectStmt } = { + SelectStmt: { + targetList: [{ ResTarget: { val: { ColumnRef: { fields: [{ A_Star: {} }] } } } }], + fromClause: [{ RangeVar: { relname: 'users' } }], + limitOption: 'LIMIT_OPTION_DEFAULT', + op: 'SETOP_NONE' + } + } + // transform the PG13 AST to a PG17 AST + const pg17Stmt = transformPG13ToPG17(stmt as any); + const deparsed = await deparse(pg17Stmt); + expect(deparsed).toBe('SELECT * FROM ONLY users'); + }); + + it('should deparse a select statement with WHERE clause', async () => { + const stmt: { SelectStmt: SelectStmt } = { + SelectStmt: { + targetList: [ + { + ResTarget: { + val: { + ColumnRef: { + fields: [ + { String: { str: 'name' } } + ] + } + } + } + } + ], + fromClause: [{ RangeVar: { relname: 'users' } }], + whereClause: { + A_Expr: { + kind: 'AEXPR_OP', + name: [{ String: { str: '=' } }], + lexpr: { + ColumnRef: { + fields: [{ String: { str: 'id' } }] + } + }, + rexpr: { + A_Const: { + val: { Integer: { ival: 1 } } + } + } + } + }, + limitOption: 'LIMIT_OPTION_DEFAULT', + op: 'SETOP_NONE' + } + }; + + const pg17Stmt = transformPG13ToPG17(stmt as any); + const deparsed = await deparse(pg17Stmt); + expect(deparsed).toBe('SELECT name FROM ONLY users WHERE id = 1'); + }); + + it('should deparse a select statement with JOIN', async () => { + const stmt: { SelectStmt: SelectStmt } = { + SelectStmt: { + targetList: [{ ResTarget: { val: { ColumnRef: { fields: [{ A_Star: {} }] } } } }], + fromClause: [ + { + JoinExpr: { + jointype: 'JOIN_INNER', + larg: { RangeVar: { relname: 'users' } }, + rarg: { RangeVar: { relname: 'posts' } }, + quals: { + A_Expr: { + kind: 'AEXPR_OP', + name: [{ String: { str: '=' } }], + lexpr: { + ColumnRef: { + fields: [ + { String: { str: 'users' } }, + { String: { str: 'id' } } + ] + } + }, + rexpr: { + ColumnRef: { + fields: [ + { String: { str: 'posts' } }, + { String: { str: 'user_id' } } + ] + } + } + } + } + } + } + ], + limitOption: 'LIMIT_OPTION_DEFAULT', + op: 'SETOP_NONE' + } + }; + + const pg17Stmt = transformPG13ToPG17(stmt as any); + const deparsed = await deparse(pg17Stmt); + expect(deparsed).toBe('SELECT * FROM ONLY users JOIN ONLY posts ON users.id = posts.user_id'); + }); +}); \ No newline at end of file diff --git a/packages/transform/package.json b/packages/transform/package.json index 28abdc4a..14107d84 100644 --- a/packages/transform/package.json +++ b/packages/transform/package.json @@ -31,7 +31,10 @@ "test:watch": "jest --watch" }, "devDependencies": { - "pg-proto-parser": "^1.28.2" + "pg-proto-parser": "^1.28.2", + "pgsql-deparser": "^17.6.2", + "@pgsql/parser": "^1.0.0", + "libpg-query": "^13.5.2" }, "keywords": [] } diff --git a/packages/transform/src/index.ts b/packages/transform/src/index.ts index 6dc9687f..4463aef0 100644 --- a/packages/transform/src/index.ts +++ b/packages/transform/src/index.ts @@ -329,6 +329,9 @@ export function transformPG13ToPG17(node: PG13Node): PG17Node { if ('MergeAction' in node) { return { MergeAction: transformMergeAction(node.MergeAction) }; } + if ('SelectStmt' in node) { + return { SelectStmt: transformSelectStmt(node.SelectStmt) }; + } throw new Error(`Unknown node type: ${JSON.stringify(node)}`); } @@ -1411,10 +1414,43 @@ function transformMergeAction(mergeAction: PG13Types.MergeAction): PG17Types.Mer }; } +function transformSelectStmt(selectStmt: PG13Types.SelectStmt): PG17Types.SelectStmt { + return { + distinctClause: transformNodeArray(selectStmt.distinctClause), + intoClause: selectStmt.intoClause ? transformIntoClause(selectStmt.intoClause) : undefined, + targetList: transformNodeArray(selectStmt.targetList), + fromClause: transformNodeArray(selectStmt.fromClause), + whereClause: transformOptionalNode(selectStmt.whereClause), + groupClause: transformNodeArray(selectStmt.groupClause), + groupDistinct: selectStmt.groupDistinct, + havingClause: transformOptionalNode(selectStmt.havingClause), + windowClause: transformNodeArray(selectStmt.windowClause), + valuesLists: transformNodeArray(selectStmt.valuesLists), + sortClause: transformNodeArray(selectStmt.sortClause), + limitOffset: transformOptionalNode(selectStmt.limitOffset), + limitCount: transformOptionalNode(selectStmt.limitCount), + limitOption: selectStmt.limitOption, + lockingClause: transformNodeArray(selectStmt.lockingClause), + withClause: selectStmt.withClause ? transformWithClause(selectStmt.withClause) : undefined, + op: selectStmt.op, + all: selectStmt.all, + larg: selectStmt.larg ? transformSelectStmt(selectStmt.larg) : undefined, + rarg: selectStmt.rarg ? transformSelectStmt(selectStmt.rarg) : undefined + }; +} + function transformTableSampleClause(tableSampleClause: PG13Types.TableSampleClause): PG17Types.TableSampleClause { return tableSampleClause as PG17Types.TableSampleClause; } +function transformWithClause(withClause: PG13Types.WithClause): PG17Types.WithClause { + return { + ctes: transformNodeArray(withClause.ctes), + recursive: withClause.recursive, + location: withClause.location + }; +} + export { Node as PG13Node } from './13/types'; export { Node as PG17Node } from './17/types'; export * as PG13Types from './13/types'; diff --git a/yarn.lock b/yarn.lock index 7ce20fb6..74e90d81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1227,6 +1227,16 @@ node-addon-api "^3.2.1" node-gyp-build "^4.3.0" +"@pgsql/parser@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@pgsql/parser/-/parser-1.0.0.tgz#5873444ad033b079c7bfe45e6c4d4c0adf020f7e" + integrity sha512-clOjRiq/s4kbt7LmqGJzqMuiq8RVox5bLkfFRKAZUtg9gYTl7Y9tUNaom6n5fiy12PcI1eOpc0wVzNZ5xOa1FA== + +"@pgsql/types@^13.11.1": + version "13.11.1" + resolved "https://registry.yarnpkg.com/@pgsql/types/-/types-13.11.1.tgz#f822e09bbb553d9a444ced7ad67f4526c8ba389f" + integrity sha512-5JeN7DZYCVbxPPFI47Z370Jzr3eHZpqgwCdutYWV8e8XtuhxF7UuvWvOsx2oPJe9KnQen0vzJWnVq/61bRvxmw== + "@pgsql/types@^17.6.1": version "17.6.1" resolved "https://registry.yarnpkg.com/@pgsql/types/-/types-17.6.1.tgz#fcbe4910321bd0dfa38aa0c26d87eff7b098d0e9" @@ -4568,6 +4578,13 @@ libpg-query@17.5.2: dependencies: "@pgsql/types" "^17.6.1" +libpg-query@^13.5.2: + version "13.5.2" + resolved "https://registry.yarnpkg.com/libpg-query/-/libpg-query-13.5.2.tgz#138ec937891722097bae7785f5696d0f12781711" + integrity sha512-fy5fB2+zt4ITNf1NhujlUZ/MuV12R5KvVKZDcCdCY1dtSdb3Im4ePPVqR3p4+MS9dG96Dzg2cUSkmvrePKjqPQ== + dependencies: + "@pgsql/types" "^13.11.1" + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"