├── scripts ├── tsconfig.json ├── build_libpg_query.sh ├── data │ ├── skippedEntities.ts │ ├── expressionTypes.ts │ ├── bitMasks.ts │ ├── nullableFields.ts │ ├── fieldMetadata.ts │ ├── expressionFields.ts │ └── typeMappings.ts ├── build_libpg_query.bat ├── generateTests.ts └── inferFieldMetadata.ts ├── .gitmodules ├── .vscode ├── extensions.json └── settings.json ├── .gitignore ├── src ├── lib │ ├── index.ts │ ├── walk.ts │ ├── binding.ts │ ├── select.ts │ └── tokens.ts ├── async.h ├── sync.h ├── helpers.h ├── addon.cc ├── helpers.cc ├── sync.cc └── async.cc ├── tsconfig.json ├── biome.json ├── test ├── select.test-d.ts ├── postgres_regress │ └── __snapshots__ │ │ ├── comments.test.ts.snap │ │ ├── jsonpath.test.ts.snap │ │ ├── jsonpath_encoding.test.ts.snap │ │ ├── md5.test.ts.snap │ │ ├── create_function_c.test.ts.snap │ │ ├── async.test.ts.snap │ │ ├── mvcc.test.ts.snap │ │ ├── oidjoins.test.ts.snap │ │ ├── init_privs.test.ts.snap │ │ ├── lseg.test.ts.snap │ │ ├── portals_p2.test.ts.snap │ │ ├── reindex_catalog.test.ts.snap │ │ ├── regproc.test.ts.snap │ │ ├── copydml.test.ts.snap │ │ ├── path.test.ts.snap │ │ ├── security_label.test.ts.snap │ │ ├── roleattributes.test.ts.snap │ │ ├── prepared_xacts.test.ts.snap │ │ ├── delete.test.ts.snap │ │ ├── regex.test.ts.snap │ │ └── json_encoding.test.ts.snap └── parseTestFile.ts ├── package.json ├── .github └── workflows │ └── ci.yml ├── binding.gyp └── readme.md /scripts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": ["**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libpg_query"] 2 | path = libpg_query 3 | url = https://github.com/pganalyze/libpg_query.git 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["llvm-vs-code-extensions.vscode-clangd", "biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib/ 2 | node_modules 3 | prebuilds 4 | test/postgres_regress/**/*.test.ts 5 | build 6 | pnpm-workspace.yaml 7 | -------------------------------------------------------------------------------- /src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ast.js' 2 | export * from './binding.js' 3 | export * from './node.js' 4 | export * from './select.js' 5 | export * from './tokens.js' 6 | export * from './walk.js' 7 | -------------------------------------------------------------------------------- /src/async.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Napi::Value ParseQueryAsync(const Napi::CallbackInfo& info); 4 | Napi::Value ParsePlPgSQLAsync(const Napi::CallbackInfo& info); 5 | Napi::Value FingerprintAsync(const Napi::CallbackInfo& info); 6 | -------------------------------------------------------------------------------- /scripts/build_libpg_query.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -x 4 | 5 | if [ -z "$CI" ]; then 6 | git submodule update --init --recursive 7 | fi 8 | 9 | unset MAKEFLAGS 10 | unset MFLAGS 11 | 12 | cd libpg_query 13 | make build 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/lib"], 3 | "compilerOptions": { 4 | "strict": true, 5 | "outDir": "lib", 6 | "declaration": true, 7 | "module": "esnext", 8 | "lib": ["esnext"], 9 | "target": "es2020", 10 | "moduleResolution": "node", 11 | "allowSyntheticDefaultImports": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/sync.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | Napi::String ParseQuerySync(const Napi::CallbackInfo& info); 4 | Napi::String ParsePlPgSQLSync(const Napi::CallbackInfo& info); 5 | Napi::String FingerprintSync(const Napi::CallbackInfo& info); 6 | Napi::Value ScanSync(const Napi::CallbackInfo& info); 7 | Napi::Value SplitWithScannerSync(const Napi::CallbackInfo& info); 8 | -------------------------------------------------------------------------------- /src/helpers.h: -------------------------------------------------------------------------------- 1 | #include "pg_query.h" 2 | #include 3 | 4 | Napi::Error CreateError(Napi::Env env, const PgQueryError& err); 5 | Napi::String QueryParseResult(Napi::Env env, const PgQueryParseResult& result); 6 | Napi::String PlPgSQLParseResult(Napi::Env env, const PgQueryPlpgsqlParseResult& result); 7 | Napi::String FingerprintResult(Napi::Env env, const PgQueryFingerprintResult & result); 8 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json", 3 | "extends": ["@radashi-org/biome-config"], 4 | "linter": { 5 | "rules": { 6 | "complexity": { 7 | "noBannedTypes": "off" 8 | }, 9 | "performance": { 10 | "noDelete": "off" 11 | }, 12 | "style": { 13 | "noUnusedTemplateLiteral": "off" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/data/skippedEntities.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * These are not used anywhere. They're for libpg_query's internal use. 3 | */ 4 | export const skippedEntities = new Set([ 5 | 'BlockId', 6 | 'BlockIdData', 7 | 'BlockNumber', 8 | 'NodeTag', 9 | 'ParallelVacuumState', 10 | 'Query', 11 | 'QuerySource', 12 | 'RangeTblEntry', 13 | 'RangeTblFunction', 14 | 'TableFunc', 15 | 'VacAttrStatsP', 16 | 'pg_wchar', 17 | ]) 18 | -------------------------------------------------------------------------------- /test/select.test-d.ts: -------------------------------------------------------------------------------- 1 | import { describe, expectTypeOf, test } from 'vitest' 2 | import type { ColumnRef, Expr } from '../src/lib/ast' 3 | import { select } from '../src/lib/select' 4 | 5 | describe('select', () => { 6 | test('union of multiple node types', () => { 7 | const value = {} as Expr 8 | const fields = select(value, 'fields') 9 | expectTypeOf(fields).toEqualTypeOf() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /scripts/data/expressionTypes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * If a node type should exist in the `Expr` union type, but currently doesn't, 3 | * add it here. This is only necessary if the type doesn't already meet one of 4 | * these criteria: 5 | * - Its type name ends in "Expr" 6 | * - It has a field named "xpr" 7 | */ 8 | export const expressionTypes = new Set([ 9 | 'A_Const', 10 | 'List', 11 | 'TypeCast', 12 | 'FuncCall', 13 | 'ColumnRef', 14 | 'ParamRef', 15 | ]) 16 | -------------------------------------------------------------------------------- /scripts/data/bitMasks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * If a "typedef" is a bitmask, add it here. 3 | */ 4 | export const bitMasks: Record = { 5 | AclMode: [ 6 | 'ACL_NO_RIGHTS', 7 | 'ACL_INSERT', 8 | 'ACL_SELECT', 9 | 'ACL_UPDATE', 10 | 'ACL_DELETE', 11 | 'ACL_TRUNCATE', 12 | 'ACL_REFERENCES', 13 | 'ACL_TRIGGER', 14 | 'ACL_EXECUTE', 15 | 'ACL_USAGE', 16 | 'ACL_CREATE', 17 | 'ACL_CREATE_TEMP', 18 | 'ACL_CONNECT', 19 | 'ACL_SET', 20 | 'ACL_ALTER_SYSTEM', 21 | ], 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "biomejs.biome", 4 | "editor.codeActionsOnSave": { 5 | "source.organizeImports": "explicit", 6 | "quickfix.biome": "explicit" 7 | }, 8 | "files.associations": { 9 | "*.gyp": "python" 10 | }, 11 | "[python]": { 12 | "editor.defaultFormatter": "ms-python.autopep8" 13 | }, 14 | "rewrap.wrappingColumn": 80, 15 | "clangd.arguments": ["--compile-commands-dir=${workspaceFolder}/build"], 16 | "autoHide.autoHidePanel": false 17 | } 18 | -------------------------------------------------------------------------------- /scripts/data/nullableFields.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * If a field's nullability is incorrectly inferred, add it here. 3 | */ 4 | export const nullableFields = new Set([ 5 | 'A_Expr.rexpr', 6 | 'A_Indices.uidx', 7 | 'AccessPriv.priv_name', 8 | 'Aggref.aggdistinct', 9 | 'Alias.colnames', 10 | 'Boolean.boolval', 11 | 'ClosePortalStmt.portalname', 12 | 'ClusterStmt.relation', 13 | 'CurrentOfExpr.cursor_name', 14 | 'DeallocateStmt.name', 15 | 'IntoClause.viewQuery', 16 | 'OnConflictClause.infer', 17 | 'SetOperationStmt.groupClauses', 18 | 'SubPlan.testexpr', 19 | 'VacuumRelation.relation', 20 | ]) 21 | -------------------------------------------------------------------------------- /scripts/data/fieldMetadata.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { mapValues } from 'radashi' 4 | import type { NodeFieldMetadataByTag } from '../inferFieldMetadata' 5 | 6 | const dataDir = new URL('.', import.meta.url).pathname 7 | const data = mapValues( 8 | JSON.parse( 9 | fs.readFileSync(path.join(dataDir, 'fieldMetadata.json'), 'utf8'), 10 | ) as NodeFieldMetadataByTag, 11 | fieldMap => 12 | mapValues(fieldMap!, ([nullable, tags, listTags]) => ({ 13 | nullable, 14 | tags, 15 | listTags, 16 | })), 17 | ) 18 | 19 | /** 20 | * Field metadata is inferred from test cases sourced from the libpg_query 21 | * repository. Check out the {@link ../inferFieldMetadata.ts inferFieldMetadata} 22 | * module for more details. 23 | */ 24 | export const fieldMetadataMap = data as { 25 | [typeName: string]: { 26 | [fieldName: string]: typeof data[string][string] | undefined 27 | } | undefined 28 | } 29 | -------------------------------------------------------------------------------- /scripts/build_libpg_query.bat: -------------------------------------------------------------------------------- 1 | @echo on 2 | setlocal enabledelayedexpansion 3 | 4 | if not defined CI ( 5 | git submodule update --init --recursive 6 | ) 7 | 8 | set buildDir=%cd% 9 | set projectDir=%cd%\.. 10 | 11 | set MAKEFLAGS= 12 | set MFLAGS= 13 | 14 | cd ../libpg_query 15 | nmake /F Makefile.msvc build 16 | 17 | rem Search for pg_query.lib, error if not found 18 | for /f "delims=" %%f in ('dir /b /s pg_query.lib') do set file=%%f 19 | if not defined file ( 20 | echo "ERROR: pg_query.lib not found" 21 | 22 | ) 23 | 24 | rem Error if pg_query.h is missing 25 | for /f "delims=" %%f in ('dir /b /s pg_query.h') do set file=%%f 26 | if not defined file ( 27 | echo "ERROR: pg_query.h not found" 28 | ) 29 | 30 | rem Copy pg_query.lib to windows dir 31 | copy /Y pg_query.lib "%projectDir%\libpg_query\" 32 | 33 | rem Copy header 34 | copy /Y pg_query.h "%projectDir%\libpg_query\" 35 | 36 | rem Cleanup: revert to original directory 37 | cd /D %buildDir% 38 | 39 | exit /B 0 40 | -------------------------------------------------------------------------------- /scripts/data/expressionFields.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * If a field accepts an arbitrary expression, add it here. 3 | */ 4 | export const expressionFields = new Set([ 5 | 'A_ArrayExpr.elements', 6 | 'A_Indirection.arg', 7 | 'AlterTableCmd.def', 8 | 'BoolExpr.args', 9 | 'CollateClause.arg', 10 | 'CreatePolicyStmt.qual', 11 | 'DeleteStmt.whereClause', 12 | 'FuncCall.args', 13 | 'FuncCall.agg_filter', 14 | 'GroupingSet.content', 15 | 'IndexStmt.whereClause', 16 | 'JoinExpr.quals', 17 | 'MergeWhenClause.condition', 18 | 'MergeWhenClause.values', 19 | 'MergeStmt.joinCondition', 20 | 'MinMaxExpr.args', 21 | 'RangeFunction.functions', 22 | 'RangeTableSample.args', 23 | 'ResTarget.val', 24 | 'ReturnStmt.returnval', 25 | 'RowExpr.args', 26 | 'RuleStmt.whereClause', 27 | 'SelectStmt.groupClause', 28 | 'SelectStmt.havingClause', 29 | 'SelectStmt.whereClause', 30 | 'SelectStmt.limitCount', 31 | 'SelectStmt.limitOffset', 32 | 'SortBy.node', 33 | 'TypeCast.arg', 34 | 'UpdateStmt.whereClause', 35 | 'XmlExpr.args', 36 | ]) 37 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/comments.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`comments.sql > line 5 1`] = ` 4 | [ 5 | " -- trailing single line 6 | SELECT /* embedded single line */ 'embedded' AS second", 7 | { 8 | "stmts": [ 9 | { 10 | "stmt": { 11 | "SelectStmt": { 12 | "limitOption": "LIMIT_OPTION_DEFAULT", 13 | "op": "SETOP_NONE", 14 | "targetList": [ 15 | { 16 | "ResTarget": { 17 | "location": 59, 18 | "name": "second", 19 | "val": { 20 | "A_Const": { 21 | "location": 59, 22 | "sval": { 23 | "sval": "embedded", 24 | }, 25 | }, 26 | }, 27 | }, 28 | }, 29 | ], 30 | }, 31 | }, 32 | }, 33 | ], 34 | "version": 160001, 35 | }, 36 | ] 37 | `; 38 | -------------------------------------------------------------------------------- /scripts/data/typeMappings.ts: -------------------------------------------------------------------------------- 1 | // C types -> TypeScript types 2 | const primitiveTypes = { 3 | void: 'void', 4 | bool: 'boolean', 5 | bits32: 'number', 6 | char: 'string', 7 | 'char*': 'string', 8 | double: 'number', 9 | float4: 'number', 10 | long: 'number', 11 | int: 'number', 12 | int16: 'number', 13 | int32: 'number', 14 | uint16: 'number', 15 | uint32: 'number', 16 | uint64: 'number', 17 | 'unsigned int': 'number', 18 | 'signed int': 'number', 19 | } 20 | 21 | export const typeMappings: Record = { 22 | ...primitiveTypes, 23 | 24 | // Overrides 25 | List: 'any[]', 26 | NameData: 'string', 27 | Node: 'Node', 28 | RelFileNumber: 'number', 29 | 'Alias.colnames': NodeArray('String'), 30 | 'Constraint.generated_when': '"a" | "d"', 31 | 'SelectStmt.valuesLists': 'List[]', 32 | 'WithClause.ctes': NodeArray('CommonTableExpr'), 33 | } 34 | 35 | function NodeArray(types: string) { 36 | return `(${Node(types)})[]` 37 | } 38 | 39 | function Node(types: string) { 40 | return types 41 | .split(' | ') 42 | .map(type => `{ ${type}: ${type} }`) 43 | .join(' | ') 44 | } 45 | -------------------------------------------------------------------------------- /scripts/generateTests.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | 4 | const testDir = 'libpg_query/test/sql/postgres_regress' 5 | const testFiles = fs.readdirSync(testDir) 6 | 7 | const cwd = process.cwd() 8 | 9 | const renderTest = (testFile: string) => ` 10 | import { describe, test, expect } from "vitest" 11 | import { parseTestFile } from "../parseTestFile" 12 | import { parseQuery } from "../../index" 13 | 14 | describe("${testFile}", () => { 15 | const tests = parseTestFile("${path.relative(cwd, path.join(testDir, testFile))}") 16 | for (const { line, stmt } of tests) { 17 | test("line " + line, async () => { 18 | try { 19 | const ast = await parseQuery(stmt) 20 | expect([stmt, ast]).toMatchSnapshot() 21 | } catch (error) { 22 | expect([stmt, error]).toMatchSnapshot() 23 | } 24 | }) 25 | } 26 | }) 27 | ` 28 | 29 | const outDir = 'test/postgres_regress' 30 | fs.mkdirSync(outDir, { recursive: true }) 31 | 32 | for (const testFile of testFiles) { 33 | fs.writeFileSync( 34 | path.join(outDir, testFile.replace(/\.sql$/, '.test.ts')), 35 | renderTest(testFile), 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/addon.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sync.h" // NOLINT(build/include) 3 | #include "async.h" // NOLINT(build/include) 4 | 5 | // Expose synchronous and asynchronous access to our parsing functions 6 | Napi::Object Init(Napi::Env env, Napi::Object exports) { 7 | exports.Set( 8 | Napi::String::New(env, "parseQuerySync"), 9 | Napi::Function::New(env, ParseQuerySync) 10 | ); 11 | 12 | exports.Set( 13 | Napi::String::New(env, "parseQueryAsync"), 14 | Napi::Function::New(env, ParseQueryAsync) 15 | ); 16 | 17 | exports.Set( 18 | Napi::String::New(env, "parsePlPgSQLSync"), 19 | Napi::Function::New(env, ParsePlPgSQLSync) 20 | ); 21 | 22 | exports.Set( 23 | Napi::String::New(env, "parsePlPgSQLAsync"), 24 | Napi::Function::New(env, ParsePlPgSQLAsync) 25 | ); 26 | 27 | exports.Set( 28 | Napi::String::New(env, "fingerprintSync"), 29 | Napi::Function::New(env, FingerprintSync) 30 | ); 31 | 32 | exports.Set( 33 | Napi::String::New(env, "fingerprintAsync"), 34 | Napi::Function::New(env, FingerprintAsync) 35 | ); 36 | 37 | exports.Set( 38 | Napi::String::New(env, "scanSync"), 39 | Napi::Function::New(env, ScanSync) 40 | ); 41 | 42 | exports.Set( 43 | Napi::String::New(env, "splitWithScannerSync"), 44 | Napi::Function::New(env, SplitWithScannerSync) 45 | ); 46 | 47 | return exports; 48 | } 49 | 50 | NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init) 51 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/jsonpath.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`jsonpath.sql > line 32 1`] = ` 4 | [ 5 | "select '"\\b\\f\\r\\n\\t\\v\\"\\''\\\\"'::jsonpath", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "SelectStmt": { 11 | "limitOption": "LIMIT_OPTION_DEFAULT", 12 | "op": "SETOP_NONE", 13 | "targetList": [ 14 | { 15 | "ResTarget": { 16 | "location": 7, 17 | "val": { 18 | "TypeCast": { 19 | "arg": { 20 | "A_Const": { 21 | "location": 7, 22 | "sval": { 23 | "sval": ""\\b\\f\\r\\n\\t\\v\\"\\'\\\\"", 24 | }, 25 | }, 26 | }, 27 | "location": 30, 28 | "typeName": { 29 | "location": 32, 30 | "names": [ 31 | { 32 | "String": { 33 | "sval": "jsonpath", 34 | }, 35 | }, 36 | ], 37 | "typemod": -1, 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | ], 44 | }, 45 | }, 46 | }, 47 | ], 48 | "version": 160001, 49 | }, 50 | ] 51 | `; 52 | -------------------------------------------------------------------------------- /test/parseTestFile.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import { fingerprintSync, splitWithScannerSync } from '../src/lib/binding' 3 | 4 | export function parseTestFile(testPath: string) { 5 | let sql = fs.readFileSync(testPath, 'utf8') 6 | sql = sql.replace(/FROM STDIN;[\S\s]+\n\\./i, 'FROM STDIN;') 7 | 8 | const stmts = splitWithScannerSync(sql) 9 | const lineBreaks = getLineBreakLocations(sql) 10 | const fingerprints = new Set() 11 | 12 | const tests: { line: number; stmt: string }[] = [] 13 | 14 | for (let { location, length } of stmts) { 15 | // Skip comments and empty lines. 16 | const originalLocation = location 17 | while (sql[location] === '\n' || sql.substr(location, 2) === '--') { 18 | location = sql.indexOf('\n', location + 1) + 1 19 | } 20 | length -= location - originalLocation 21 | 22 | const stmt = sql.slice(location, location + length) 23 | if (stmt) { 24 | try { 25 | const fingerprint = fingerprintSync(stmt) 26 | if (fingerprints.has(fingerprint)) { 27 | continue 28 | } 29 | 30 | // Get the line number. 31 | const line = 32 | lineBreaks.findIndex(lineBreak => location < lineBreak) + 1 || 33 | lineBreaks.length 34 | 35 | tests.push({ line, stmt }) 36 | fingerprints.add(fingerprint) 37 | } catch {} 38 | } 39 | } 40 | 41 | return tests 42 | } 43 | 44 | function getLineBreakLocations(sql: string) { 45 | const locations: number[] = [] 46 | for (let i = 0; i < sql.length; i++) { 47 | if (sql[i] === '\n') { 48 | locations.push(i) 49 | } 50 | } 51 | return locations 52 | } 53 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/jsonpath_encoding.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`jsonpath_encoding.sql > line 13 1`] = ` 4 | [ 5 | " -- just to label the results files 6 | 7 | -- checks for double-quoted values 8 | 9 | -- basic unicode input 10 | SELECT '"\\u"'::jsonpath", 11 | { 12 | "stmts": [ 13 | { 14 | "stmt": { 15 | "SelectStmt": { 16 | "limitOption": "LIMIT_OPTION_DEFAULT", 17 | "op": "SETOP_NONE", 18 | "targetList": [ 19 | { 20 | "ResTarget": { 21 | "location": 113, 22 | "val": { 23 | "TypeCast": { 24 | "arg": { 25 | "A_Const": { 26 | "location": 113, 27 | "sval": { 28 | "sval": ""\\u"", 29 | }, 30 | }, 31 | }, 32 | "location": 119, 33 | "typeName": { 34 | "location": 121, 35 | "names": [ 36 | { 37 | "String": { 38 | "sval": "jsonpath", 39 | }, 40 | }, 41 | ], 42 | "typemod": -1, 43 | }, 44 | }, 45 | }, 46 | }, 47 | }, 48 | ], 49 | }, 50 | }, 51 | }, 52 | ], 53 | "version": 160001, 54 | }, 55 | ] 56 | `; 57 | -------------------------------------------------------------------------------- /src/lib/walk.ts: -------------------------------------------------------------------------------- 1 | import { NodePath, type NodeTag } from './node.js' 2 | 3 | export type Walker = ( 4 | node: TNodePath, 5 | ) => boolean | void 6 | 7 | export type Visitor = { 8 | [TNodeTag in NodeTag]?: Walker> 9 | } 10 | 11 | /** 12 | * Walks the tree of nodes, calling the callback for each node. You may pass a 13 | * simple callback (which receives every encountered node) or a visitor object 14 | * (which may specify callbacks for particular node types). 15 | * 16 | * If a callback returns `false`, the walk will continue to the next sibling 17 | * node, rather than recurse into the children of the current node. 18 | */ 19 | export function walk( 20 | root: any, 21 | callback: Walker | Visitor, 22 | parent: NodePath | null = null, 23 | keyPath: readonly (string | number)[] = [], 24 | ) { 25 | if (typeof callback !== 'function') { 26 | const visitor = callback 27 | callback = path => visitor[path.tag]?.(path as any) 28 | } 29 | if (Array.isArray(root)) { 30 | root.forEach((node, index) => { 31 | walk(node, callback, parent, [...keyPath, index]) 32 | }) 33 | } else if (typeof root === 'object' && root !== null) { 34 | const keys = Object.keys(root) 35 | if (keys.length === 1 && /^[A-Z]/.test(keys[0])) { 36 | const tag = keys[0] as NodeTag 37 | const node = root[tag] 38 | const path = new NodePath(tag, node, parent, keyPath) 39 | if (callback(path) === false) { 40 | return 41 | } 42 | for (const key in node) { 43 | walk(node[key], callback, path, [...keyPath, key]) 44 | } 45 | } else { 46 | for (const key of keys) { 47 | walk(root[key], callback, parent, [...keyPath, key]) 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/helpers.cc: -------------------------------------------------------------------------------- 1 | #include "helpers.h" // NOLINT(build/include) 2 | #include "pg_query.h" 3 | #include "helpers.h" 4 | #include 5 | 6 | Napi::Error CreateError(Napi::Env env, const PgQueryError& err) 7 | { 8 | auto error = Napi::Error::New(env, err.message); 9 | error.Set("fileName", err.filename); 10 | error.Set("functionName", err.funcname); 11 | error.Set("lineNumber", Napi::Value::From(env, err.lineno)); 12 | error.Set("cursorPosition", Napi::Value::From(env, err.cursorpos)); 13 | error.Set("context", err.context ? Napi::Value::From(env, err.context) : env.Null()); 14 | return error; 15 | } 16 | 17 | Napi::String QueryParseResult(Napi::Env env, const PgQueryParseResult& result) 18 | { 19 | if (result.error) { 20 | auto throwVal = CreateError(env, *result.error); 21 | pg_query_free_parse_result(result); 22 | throw throwVal; 23 | } 24 | 25 | auto returnVal = Napi::String::New(env, result.parse_tree); 26 | pg_query_free_parse_result(result); 27 | return returnVal; 28 | } 29 | 30 | Napi::String PlPgSQLParseResult(Napi::Env env, const PgQueryPlpgsqlParseResult& result) 31 | { 32 | if (result.error) { 33 | auto throwVal = CreateError(env, *result.error); 34 | pg_query_free_plpgsql_parse_result(result); 35 | throw throwVal; 36 | } 37 | 38 | auto returnVal = Napi::String::New(env, result.plpgsql_funcs); 39 | pg_query_free_plpgsql_parse_result(result); 40 | return returnVal; 41 | } 42 | 43 | 44 | Napi::String FingerprintResult(Napi::Env env, const PgQueryFingerprintResult & result) 45 | { 46 | if (result.error) { 47 | auto throwVal = CreateError(env, *result.error); 48 | pg_query_free_fingerprint_result(result); 49 | throw throwVal; 50 | } 51 | 52 | auto returnVal = Napi::String::New(env, result.fingerprint_str); 53 | pg_query_free_fingerprint_result(result); 54 | return returnVal; 55 | } 56 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/md5.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`md5.sql > line 12 1`] = ` 4 | [ 5 | "select md5('a') = '0cc175b9c0f1b6a831c399e269772661' AS "TRUE"", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "SelectStmt": { 11 | "limitOption": "LIMIT_OPTION_DEFAULT", 12 | "op": "SETOP_NONE", 13 | "targetList": [ 14 | { 15 | "ResTarget": { 16 | "location": 7, 17 | "name": "TRUE", 18 | "val": { 19 | "A_Expr": { 20 | "kind": "AEXPR_OP", 21 | "lexpr": { 22 | "FuncCall": { 23 | "args": [ 24 | { 25 | "A_Const": { 26 | "location": 11, 27 | "sval": { 28 | "sval": "a", 29 | }, 30 | }, 31 | }, 32 | ], 33 | "funcformat": "COERCE_EXPLICIT_CALL", 34 | "funcname": [ 35 | { 36 | "String": { 37 | "sval": "md5", 38 | }, 39 | }, 40 | ], 41 | "location": 7, 42 | }, 43 | }, 44 | "location": 16, 45 | "name": [ 46 | { 47 | "String": { 48 | "sval": "=", 49 | }, 50 | }, 51 | ], 52 | "rexpr": { 53 | "A_Const": { 54 | "location": 18, 55 | "sval": { 56 | "sval": "0cc175b9c0f1b6a831c399e269772661", 57 | }, 58 | }, 59 | }, 60 | }, 61 | }, 62 | }, 63 | }, 64 | ], 65 | }, 66 | }, 67 | }, 68 | ], 69 | "version": 160001, 70 | }, 71 | ] 72 | `; 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pg-nano/pg-parser", 3 | "type": "module", 4 | "version": "16.1.5", 5 | "sideEffects": false, 6 | "description": "Node.js addon for PostgreSQL query parsing", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "exports": { 10 | ".": { 11 | "types": "./lib/index.d.ts", 12 | "default": "./lib/index.js" 13 | }, 14 | "./ast": { 15 | "types": "./lib/ast.d.ts", 16 | "default": "./lib/ast.js" 17 | } 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/pg-nano/pg-parser.git" 22 | }, 23 | "scripts": { 24 | "dev": "tinyrun --names js,gyp 'pnpm -s dev:js' 'pnpm -s dev:gyp'", 25 | "dev:js": "rimraf lib && tsc -p . --watch --preserveWatchOutput", 26 | "dev:gyp": "watchlist src --eager --no-clear -- pnpm -s build:gyp", 27 | "build": "tinyrun --names js,gyp 'pnpm -s build:js' 'pnpm -s build:gyp'", 28 | "build:js": "rimraf lib && tsc -p .", 29 | "build:gyp": "node-gyp rebuild && pnpm --no-bail prepare:clangd", 30 | "prepare:clangd": "cd build && compiledb make -f binding.Makefile -n || true", 31 | "prepare:types": "tsx scripts/inferFieldMetadata.ts && tsx scripts/generateTypes.ts && pnpm -s format", 32 | "prepare:tests": "tsx scripts/generateTests.ts", 33 | "prepublishOnly": "pnpm -s build", 34 | "install": "[ ! -d .git ] && prebuild-install --runtime napi || true", 35 | "ci:prebuild": "prebuild --runtime napi --target 8 --strip --verbose", 36 | "format": "biome format --write package.json src/lib/*.ts test/*.ts scripts/*.ts", 37 | "version": "biome format --write package.json", 38 | "test": "vitest" 39 | }, 40 | "keywords": ["postgres", "postgresql", "pg", "query", "parser", "plpgsql"], 41 | "dependencies": { 42 | "bindings": "^1.5.0", 43 | "node-addon-api": "^8.1.0", 44 | "prebuild-install": "^7.1.2" 45 | }, 46 | "devDependencies": { 47 | "@biomejs/biome": "1.8.3", 48 | "@radashi-org/biome-config": "^1.0.2", 49 | "@types/bindings": "^1.5.5", 50 | "node-gyp": "^10.0.1", 51 | "prebuild": "^13.0.1", 52 | "prettier": "^3.3.3", 53 | "radashi": "^12.2.2", 54 | "rimraf": "^6.0.1", 55 | "tinyrun": "^1.0.1", 56 | "tsx": "^4.19.2", 57 | "typescript": "^5.5.4", 58 | "vitest": "^2.1.4", 59 | "watchlist": "npm:@aleclarson/watchlist@^0.3.3" 60 | }, 61 | "files": ["lib"], 62 | "engines": { 63 | "node": ">=16" 64 | }, 65 | "binary": { 66 | "napi_versions": [8] 67 | }, 68 | "config": { 69 | "runtime": "napi", 70 | "target": 8 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/lib/binding.ts: -------------------------------------------------------------------------------- 1 | import bindings from 'bindings' 2 | import type { Node } from './ast' 3 | import type { TokenKind, KeywordKind } from './tokens' 4 | 5 | const loadAddon = () => bindings('queryparser') 6 | 7 | let PgQuery: { 8 | parseQueryAsync: ( 9 | query: string, 10 | callback: (err: Error | null, result: string) => void, 11 | ) => void 12 | parseQuerySync: (query: string) => string 13 | parsePlPgSQLAsync: ( 14 | query: string, 15 | callback: (err: Error | null, result: string) => void, 16 | ) => void 17 | parsePlPgSQLSync: (query: string) => string 18 | fingerprintAsync: ( 19 | query: string, 20 | callback: (err: Error | null, result: string) => void, 21 | ) => void 22 | fingerprintSync: (query: string) => string 23 | scanSync: (query: string) => ScanResult 24 | splitWithScannerSync: (query: string) => { 25 | location: number 26 | length: number 27 | }[] 28 | } 29 | 30 | export type ParseResult = { 31 | stmts: { stmt: Node; stmt_location?: number; stmt_len?: number }[] 32 | version: number 33 | } 34 | 35 | export interface Token { 36 | kind: TokenKind 37 | start: number 38 | end: number 39 | keyword: KeywordKind 40 | } 41 | 42 | export type ScanResult = Token[] 43 | 44 | export function parseQuery(query: string) { 45 | return new Promise((resolve, reject) => { 46 | PgQuery ??= loadAddon() 47 | PgQuery.parseQueryAsync(query, (err, result) => { 48 | err ? reject(err) : resolve(JSON.parse(result)) 49 | }) 50 | }) 51 | } 52 | 53 | export function parseQuerySync(query: string): ParseResult { 54 | return JSON.parse((PgQuery ??= loadAddon()).parseQuerySync(query)) 55 | } 56 | 57 | export function parsePlPgSQL(query: string) { 58 | return new Promise((resolve, reject) => { 59 | PgQuery ??= loadAddon() 60 | PgQuery.parsePlPgSQLAsync(query, (err, result) => { 61 | err ? reject(err) : resolve(JSON.parse(result)) 62 | }) 63 | }) 64 | } 65 | 66 | export function parsePlPgSQLSync(query: string): Node[] { 67 | return JSON.parse((PgQuery ??= loadAddon()).parsePlPgSQLSync(query)) 68 | } 69 | 70 | export function fingerprint(query: string) { 71 | return new Promise((resolve, reject) => { 72 | PgQuery ??= loadAddon() 73 | PgQuery.fingerprintAsync(query, (err, result) => { 74 | err ? reject(err) : resolve(result) 75 | }) 76 | }) 77 | } 78 | 79 | export function fingerprintSync(query: string): string { 80 | return (PgQuery ??= loadAddon()).fingerprintSync(query) 81 | } 82 | 83 | export function scanSync(query: string) { 84 | return (PgQuery ??= loadAddon()).scanSync(query) 85 | } 86 | 87 | export function splitWithScannerSync(query: string) { 88 | return (PgQuery ??= loadAddon()).splitWithScannerSync(query) 89 | } 90 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI (GitHub) 2 | on: 3 | - push 4 | - pull_request 5 | permissions: {} 6 | jobs: 7 | CI: 8 | if: startsWith(github.ref, 'refs/tags/') 9 | permissions: 10 | contents: write 11 | name: ${{ matrix.os }} - Node.js ${{ matrix.nodejs_version }} ${{ matrix.nodejs_arch }} 12 | runs-on: ${{ matrix.os }} 13 | container: ${{ matrix.container }} 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - os: ubuntu-latest 19 | container: rockylinux:8 20 | nodejs_version: 20 21 | - os: ubuntu-latest 22 | container: node:20-alpine3.20 23 | - os: macos-latest 24 | nodejs_version: 20 25 | nodejs_arch: x64 26 | - os: macos-latest 27 | nodejs_version: 20 28 | nodejs_arch: arm64 29 | # - os: windows-latest 30 | # nodejs_version: 20 31 | # nodejs_arch: x86 32 | # - os: windows-latest 33 | # nodejs_version: 20 34 | # nodejs_arch: x64 35 | steps: 36 | - uses: pnpm/action-setup@v4 37 | with: 38 | version: 9.7.1 39 | 40 | - name: Dependencies (Rocky Linux glibc) 41 | if: contains(matrix.container, 'rockylinux') 42 | run: | 43 | curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash - 44 | dnf install -y gcc-toolset-11-gcc-c++ make git python3.12 nodejs 45 | echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH 46 | echo "PYTHON=/usr/bin/python3.12" >> $GITHUB_ENV 47 | 48 | - name: Dependencies (Linux musl) 49 | if: contains(matrix.container, 'alpine') 50 | run: apk add build-base git python3 --update-cache 51 | 52 | - name: Dependencies (Python - macOS, Windows) 53 | if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows') 54 | uses: actions/setup-python@v5 55 | with: 56 | python-version: "3.12" 57 | 58 | - name: Checkout 59 | uses: actions/checkout@v4 60 | with: 61 | submodules: true 62 | 63 | - name: Dependencies (Node.js - macOS, Windows) 64 | if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows') 65 | uses: actions/setup-node@v4 66 | with: 67 | node-version: ${{ matrix.nodejs_version }} 68 | architecture: ${{ matrix.nodejs_arch }} 69 | cache: pnpm 70 | 71 | - name: Install 72 | run: pnpm install --ignore-scripts 73 | 74 | # - name: Test 75 | # run: pnpm test 76 | 77 | - name: Prebuild 78 | env: 79 | prebuild_upload: ${{ secrets.GITHUB_TOKEN }} 80 | run: pnpm run ci:prebuild 81 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/create_function_c.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`create_function_c.sql > line 34 1`] = ` 4 | [ 5 | "CREATE FUNCTION test1 (int) RETURNS int LANGUAGE internal 6 | AS 'nosuch'", 7 | { 8 | "stmts": [ 9 | { 10 | "stmt": { 11 | "CreateFunctionStmt": { 12 | "funcname": [ 13 | { 14 | "String": { 15 | "sval": "test1", 16 | }, 17 | }, 18 | ], 19 | "options": [ 20 | { 21 | "DefElem": { 22 | "arg": { 23 | "String": { 24 | "sval": "internal", 25 | }, 26 | }, 27 | "defaction": "DEFELEM_UNSPEC", 28 | "defname": "language", 29 | "location": 40, 30 | }, 31 | }, 32 | { 33 | "DefElem": { 34 | "arg": { 35 | "List": { 36 | "items": [ 37 | { 38 | "String": { 39 | "sval": "nosuch", 40 | }, 41 | }, 42 | ], 43 | }, 44 | }, 45 | "defaction": "DEFELEM_UNSPEC", 46 | "defname": "as", 47 | "location": 62, 48 | }, 49 | }, 50 | ], 51 | "parameters": [ 52 | { 53 | "FunctionParameter": { 54 | "argType": { 55 | "location": 23, 56 | "names": [ 57 | { 58 | "String": { 59 | "sval": "pg_catalog", 60 | }, 61 | }, 62 | { 63 | "String": { 64 | "sval": "int4", 65 | }, 66 | }, 67 | ], 68 | "typemod": -1, 69 | }, 70 | "mode": "FUNC_PARAM_DEFAULT", 71 | }, 72 | }, 73 | ], 74 | "returnType": { 75 | "location": 36, 76 | "names": [ 77 | { 78 | "String": { 79 | "sval": "pg_catalog", 80 | }, 81 | }, 82 | { 83 | "String": { 84 | "sval": "int4", 85 | }, 86 | }, 87 | ], 88 | "typemod": -1, 89 | }, 90 | }, 91 | }, 92 | }, 93 | ], 94 | "version": 160001, 95 | }, 96 | ] 97 | `; 98 | -------------------------------------------------------------------------------- /scripts/inferFieldMetadata.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs' 2 | import path from 'node:path' 3 | import { castArrayIfExists, select, unique } from 'radashi' 4 | import type { Node } from '../src/lib/ast' 5 | import { parseQuerySync } from '../src/lib/binding' 6 | import { $ } from '../src/lib/select' 7 | import { walk } from '../src/lib/walk' 8 | import { parseTestFile } from '../test/parseTestFile' 9 | 10 | const testDir = 'libpg_query/test/sql/postgres_regress' 11 | const testFiles = fs.readdirSync(testDir) 12 | 13 | export type NodeFieldMetadata = [ 14 | nullable: boolean, 15 | tags: string[] | null, 16 | listTags: string[] | null, 17 | ] 18 | 19 | export type NodeFieldMetadataByTag = { 20 | [typeName: string]: { 21 | [fieldName: string]: NodeFieldMetadata 22 | } 23 | } 24 | 25 | const fieldsByNodeTag: NodeFieldMetadataByTag = {} 26 | 27 | const toNodeTag = (value: unknown) => { 28 | if (value != null && typeof value === 'object') { 29 | const keys = Object.keys(value) 30 | if (keys.length === 1 && /^[A-Z]/.test(keys[0])) { 31 | return keys[0] 32 | } 33 | if (keys.length === 0) { 34 | return '{}' 35 | } 36 | } 37 | } 38 | 39 | const inferNodeTags = (value: unknown) => 40 | Array.isArray(value) 41 | ? unique(select(value, toNodeTag)) 42 | : castArrayIfExists(toNodeTag(value)) ?? null 43 | 44 | const inferListTags = (value: Node[]) => { 45 | const lists = value.filter($.isList) 46 | const itemTags = lists.flatMap(list => inferNodeTags(list.List.items) ?? []) 47 | return itemTags.length > 0 ? unique(itemTags) : null 48 | } 49 | 50 | for (const testFile of testFiles) { 51 | try { 52 | const stmts = parseTestFile(path.join(testDir, testFile)) 53 | for (const { stmt } of stmts) { 54 | try { 55 | const ast = parseQuerySync(stmt) 56 | walk(ast, path => { 57 | const defaultNullability = path.tag in fieldsByNodeTag 58 | const seen = (fieldsByNodeTag[path.tag] ??= {}) 59 | 60 | for (const key in path.node) { 61 | const value = (path.node as any)[key] 62 | const tags = inferNodeTags(value) 63 | const listTags = 64 | tags && Array.isArray(value) && tags.includes('List') 65 | ? inferListTags(value) 66 | : null 67 | 68 | if (seen[key] == null) { 69 | seen[key] = [defaultNullability, tags, listTags] 70 | } else { 71 | if (tags) { 72 | seen[key][1] = seen[key][1] 73 | ? unique([...tags, ...seen[key][1]]) 74 | : tags 75 | } 76 | if (listTags) { 77 | seen[key][2] = seen[key][2] 78 | ? unique([...listTags, ...seen[key][2]]) 79 | : listTags 80 | } 81 | } 82 | } 83 | 84 | for (const key in seen) { 85 | if (!(key in path.node)) { 86 | seen[key] ??= [true, null, null] 87 | seen[key][0] = true 88 | } 89 | } 90 | }) 91 | } catch {} 92 | } 93 | } catch {} 94 | } 95 | 96 | const scriptDir = new URL('.', import.meta.url).pathname 97 | fs.writeFileSync( 98 | path.join(scriptDir, 'data/fieldMetadata.json'), 99 | JSON.stringify(fieldsByNodeTag, null, 2), 100 | ) 101 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/async.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`async.sql > line 6 1`] = ` 4 | [ 5 | "SELECT pg_notify('notify_async1','sample message1')", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "SelectStmt": { 11 | "limitOption": "LIMIT_OPTION_DEFAULT", 12 | "op": "SETOP_NONE", 13 | "targetList": [ 14 | { 15 | "ResTarget": { 16 | "location": 7, 17 | "val": { 18 | "FuncCall": { 19 | "args": [ 20 | { 21 | "A_Const": { 22 | "location": 17, 23 | "sval": { 24 | "sval": "notify_async1", 25 | }, 26 | }, 27 | }, 28 | { 29 | "A_Const": { 30 | "location": 33, 31 | "sval": { 32 | "sval": "sample message1", 33 | }, 34 | }, 35 | }, 36 | ], 37 | "funcformat": "COERCE_EXPLICIT_CALL", 38 | "funcname": [ 39 | { 40 | "String": { 41 | "sval": "pg_notify", 42 | }, 43 | }, 44 | ], 45 | "location": 7, 46 | }, 47 | }, 48 | }, 49 | }, 50 | ], 51 | }, 52 | }, 53 | }, 54 | ], 55 | "version": 160001, 56 | }, 57 | ] 58 | `; 59 | 60 | exports[`async.sql > line 16 1`] = ` 61 | [ 62 | "NOTIFY notify_async2", 63 | { 64 | "stmts": [ 65 | { 66 | "stmt": { 67 | "NotifyStmt": { 68 | "conditionname": "notify_async2", 69 | }, 70 | }, 71 | }, 72 | ], 73 | "version": 160001, 74 | }, 75 | ] 76 | `; 77 | 78 | exports[`async.sql > line 23 1`] = ` 79 | [ 80 | "SELECT pg_notification_queue_usage()", 81 | { 82 | "stmts": [ 83 | { 84 | "stmt": { 85 | "SelectStmt": { 86 | "limitOption": "LIMIT_OPTION_DEFAULT", 87 | "op": "SETOP_NONE", 88 | "targetList": [ 89 | { 90 | "ResTarget": { 91 | "location": 7, 92 | "val": { 93 | "FuncCall": { 94 | "funcformat": "COERCE_EXPLICIT_CALL", 95 | "funcname": [ 96 | { 97 | "String": { 98 | "sval": "pg_notification_queue_usage", 99 | }, 100 | }, 101 | ], 102 | "location": 7, 103 | }, 104 | }, 105 | }, 106 | }, 107 | ], 108 | }, 109 | }, 110 | }, 111 | ], 112 | "version": 160001, 113 | }, 114 | ] 115 | `; 116 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/mvcc.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`mvcc.sql > line 9 1`] = ` 4 | [ 5 | "BEGIN", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "TransactionStmt": { 11 | "kind": "TRANS_STMT_BEGIN", 12 | }, 13 | }, 14 | }, 15 | ], 16 | "version": 160001, 17 | }, 18 | ] 19 | `; 20 | 21 | exports[`mvcc.sql > line 11 1`] = ` 22 | [ 23 | "SET LOCAL enable_seqscan = false", 24 | { 25 | "stmts": [ 26 | { 27 | "stmt": { 28 | "VariableSetStmt": { 29 | "args": [ 30 | { 31 | "A_Const": { 32 | "location": 27, 33 | "sval": { 34 | "sval": "false", 35 | }, 36 | }, 37 | }, 38 | ], 39 | "is_local": true, 40 | "kind": "VAR_SET_VALUE", 41 | "name": "enable_seqscan", 42 | }, 43 | }, 44 | }, 45 | ], 46 | "version": 160001, 47 | }, 48 | ] 49 | `; 50 | 51 | exports[`mvcc.sql > line 17 1`] = ` 52 | [ 53 | "CREATE TABLE clean_aborted_self(key int, data text)", 54 | { 55 | "stmts": [ 56 | { 57 | "stmt": { 58 | "CreateStmt": { 59 | "oncommit": "ONCOMMIT_NOOP", 60 | "relation": { 61 | "inh": true, 62 | "location": 13, 63 | "relname": "clean_aborted_self", 64 | "relpersistence": "p", 65 | }, 66 | "tableElts": [ 67 | { 68 | "ColumnDef": { 69 | "colname": "key", 70 | "is_local": true, 71 | "location": 32, 72 | "typeName": { 73 | "location": 36, 74 | "names": [ 75 | { 76 | "String": { 77 | "sval": "pg_catalog", 78 | }, 79 | }, 80 | { 81 | "String": { 82 | "sval": "int4", 83 | }, 84 | }, 85 | ], 86 | "typemod": -1, 87 | }, 88 | }, 89 | }, 90 | { 91 | "ColumnDef": { 92 | "colname": "data", 93 | "is_local": true, 94 | "location": 41, 95 | "typeName": { 96 | "location": 46, 97 | "names": [ 98 | { 99 | "String": { 100 | "sval": "text", 101 | }, 102 | }, 103 | ], 104 | "typemod": -1, 105 | }, 106 | }, 107 | }, 108 | ], 109 | }, 110 | }, 111 | }, 112 | ], 113 | "version": 160001, 114 | }, 115 | ] 116 | `; 117 | 118 | exports[`mvcc.sql > line 44 1`] = ` 119 | [ 120 | "ROLLBACK", 121 | { 122 | "stmts": [ 123 | { 124 | "stmt": { 125 | "TransactionStmt": { 126 | "kind": "TRANS_STMT_ROLLBACK", 127 | }, 128 | }, 129 | }, 130 | ], 131 | "version": 160001, 132 | }, 133 | ] 134 | `; 135 | -------------------------------------------------------------------------------- /src/lib/select.ts: -------------------------------------------------------------------------------- 1 | import type { NodeTag } from './node.js' 2 | import { typeGuards } from './typeGuards.js' 3 | 4 | /** 5 | * If a node type is given, unwrap it to its fields. If any other object type is 6 | * given, return it as is. 7 | */ 8 | type NodeFields = T extends any 9 | ? keyof T extends infer TKey 10 | ? TKey extends NodeTag & keyof T 11 | ? T[TKey] 12 | : T 13 | : never 14 | : never 15 | 16 | // Utility types for handling cases where multiple object types are possible 17 | type Keys = T extends object ? keyof T : never 18 | type Access = T extends object 19 | ? K extends keyof T 20 | ? T[K] 21 | : undefined 22 | : undefined 23 | 24 | /** 25 | * The return type of the `select` function. It takes an object and a 26 | * dot-separated field path. The field path should *not* include node types 27 | * (i.e. SelectStmt). 28 | */ 29 | export type FieldSelection< 30 | T extends object, 31 | TFieldPath extends string, 32 | > = T extends any 33 | ? NodeFields extends infer TFields 34 | ? TFieldPath extends `${infer TField}.${infer TRest}` 35 | ? TField extends Keys 36 | ? Access extends infer TValue 37 | ? TValue extends object 38 | ? FieldSelection 39 | : undefined 40 | : never 41 | : undefined 42 | : TFieldPath extends Keys 43 | ? Access 44 | : undefined 45 | : never 46 | : never 47 | 48 | /** 49 | * Select a field using a dot-separated field path (which must not contain node 50 | * types like "SelectStmt"). If a field in the field path is not found, 51 | * `undefined` is returned, so this can be used to safely check for a field deep 52 | * within a node tree. Especially useful when dealing with a node that can be 53 | * multiple types, but you only care about using one of them. 54 | * 55 | * **Caveat:** Array fields are not supported. 56 | */ 57 | export function select( 58 | root: T, 59 | path: TFieldPath, 60 | ): FieldSelection { 61 | const keys = path.split('.') 62 | let current: any = root 63 | 64 | for (const key of keys) { 65 | if (current === null || typeof current !== 'object') { 66 | return undefined as any 67 | } 68 | 69 | // Check if the current object is a node (has a single capitalized key) 70 | const tag = onlyKey(current) 71 | if (tag && /^[A-Z]/.test(tag)) { 72 | current = current[tag] 73 | } 74 | 75 | if (!(key in current)) { 76 | return undefined as any 77 | } 78 | 79 | current = current[key] 80 | } 81 | 82 | return current 83 | } 84 | 85 | function onlyKey(obj: object): string | undefined { 86 | let i = 0 87 | let key: string | undefined 88 | for (key in obj) { 89 | if (++i > 1) { 90 | return undefined 91 | } 92 | } 93 | return key 94 | } 95 | 96 | /** 97 | * Proxy a given node so you can deeply and safely access its fields without the 98 | * burden of type-checking first. It also dissolves node types, so you can do 99 | * `$(node).larg.sortClause` instead of `node.larg.SelectStmt.sortClause`. 100 | */ 101 | export const $ = (root => { 102 | return new Proxy(root, { 103 | get(target, prop: string) { 104 | return select(target, prop) 105 | }, 106 | }) 107 | }) as ((root: T) => NodeFields) & typeof typeGuards 108 | 109 | Object.assign($, typeGuards) 110 | -------------------------------------------------------------------------------- /src/sync.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "sync.h" // NOLINT(build/include) 4 | #include "helpers.h" // NOLINT(build/include) 5 | #include "protobuf/pg_query.pb-c.h" // NOLINT(build/include) 6 | 7 | Napi::String ParseQuerySync(const Napi::CallbackInfo& info) { 8 | std::string query = info[0].As(); 9 | PgQueryParseResult result = pg_query_parse(query.c_str()); 10 | 11 | return QueryParseResult(info.Env(), result); 12 | } 13 | 14 | Napi::String ParsePlPgSQLSync(const Napi::CallbackInfo& info) { 15 | std::string query = info[0].As(); 16 | PgQueryPlpgsqlParseResult result = pg_query_parse_plpgsql(query.c_str()); 17 | 18 | return PlPgSQLParseResult(info.Env(), result); 19 | } 20 | 21 | Napi::String FingerprintSync(const Napi::CallbackInfo& info) { 22 | std::string query = info[0].As(); 23 | PgQueryFingerprintResult result = pg_query_fingerprint(query.c_str()); 24 | 25 | return FingerprintResult(info.Env(), result); 26 | } 27 | 28 | Napi::Value ScanSync(const Napi::CallbackInfo& info) { 29 | Napi::Env env = info.Env(); 30 | 31 | if (info.Length() < 1 || !info[0].IsString()) { 32 | Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException(); 33 | return env.Null(); 34 | } 35 | 36 | std::string input = info[0].As().Utf8Value(); 37 | PgQueryScanResult result = pg_query_scan(input.c_str()); 38 | 39 | if (result.error) { 40 | Napi::Error::New(env, result.error->message).ThrowAsJavaScriptException(); 41 | pg_query_free_scan_result(result); 42 | return env.Null(); 43 | } 44 | 45 | PgQuery__ScanResult* scan_result = pg_query__scan_result__unpack(NULL, result.pbuf.len, (const uint8_t *) result.pbuf.data); 46 | 47 | Napi::Array tokens = Napi::Array::New(env, scan_result->n_tokens); 48 | 49 | for (size_t i = 0; i < scan_result->n_tokens; i++) { 50 | PgQuery__ScanToken* scan_token = scan_result->tokens[i]; 51 | const ProtobufCEnumValue* token_kind = protobuf_c_enum_descriptor_get_value(&pg_query__token__descriptor, scan_token->token); 52 | const ProtobufCEnumValue* keyword_kind = protobuf_c_enum_descriptor_get_value(&pg_query__keyword_kind__descriptor, scan_token->keyword_kind); 53 | 54 | Napi::Object token = Napi::Object::New(env); 55 | token.Set("kind", token_kind->name); 56 | token.Set("start", scan_token->start); 57 | token.Set("end", scan_token->end); 58 | token.Set("keyword", keyword_kind->name); 59 | 60 | tokens[i] = token; 61 | } 62 | 63 | pg_query__scan_result__free_unpacked(scan_result, NULL); 64 | 65 | pg_query_free_scan_result(result); 66 | 67 | return tokens; 68 | } 69 | 70 | Napi::Value SplitWithScannerSync(const Napi::CallbackInfo& info) { 71 | Napi::Env env = info.Env(); 72 | 73 | if (info.Length() < 1 || !info[0].IsString()) { 74 | Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException(); 75 | return env.Null(); 76 | } 77 | 78 | std::string input = info[0].As().Utf8Value(); 79 | PgQuerySplitResult result = pg_query_split_with_scanner(input.c_str()); 80 | 81 | if (result.error) { 82 | Napi::Error::New(env, result.error->message).ThrowAsJavaScriptException(); 83 | pg_query_free_split_result(result); 84 | return env.Null(); 85 | } 86 | 87 | Napi::Array statements = Napi::Array::New(env, result.n_stmts); 88 | 89 | for (int i = 0; i < result.n_stmts; i++) { 90 | Napi::Object stmt = Napi::Object::New(env); 91 | stmt.Set("location", result.stmts[i]->stmt_location); 92 | stmt.Set("length", result.stmts[i]->stmt_len); 93 | statements[i] = stmt; 94 | } 95 | 96 | pg_query_free_split_result(result); 97 | 98 | return statements; 99 | } 100 | 101 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "defines": [ 3 | "NAPI_VERSION=<(napi_build_version)" 4 | ], 5 | "targets": [ 6 | { 7 | "target_name": "queryparser", 8 | "sources": [ 9 | "src/addon.cc", 10 | "src/helpers.cc", 11 | "src/sync.cc", 12 | "src/async.cc" 13 | ], 14 | "dependencies": [ 15 | " line 4 1`] = ` 4 | [ 5 | "DO $doblock$ 6 | declare 7 | fk record; 8 | nkeys integer; 9 | cmd text; 10 | err record; 11 | begin 12 | for fk in select * from pg_get_catalog_foreign_keys() 13 | loop 14 | raise notice 'checking % % => % %', 15 | fk.fktable, fk.fkcols, fk.pktable, fk.pkcols; 16 | nkeys := array_length(fk.fkcols, 1); 17 | cmd := 'SELECT ctid'; 18 | for i in 1 .. nkeys loop 19 | cmd := cmd || ', ' || quote_ident(fk.fkcols[i]); 20 | end loop; 21 | if fk.is_array then 22 | cmd := cmd || ' FROM (SELECT ctid'; 23 | for i in 1 .. nkeys-1 loop 24 | cmd := cmd || ', ' || quote_ident(fk.fkcols[i]); 25 | end loop; 26 | cmd := cmd || ', unnest(' || quote_ident(fk.fkcols[nkeys]); 27 | cmd := cmd || ') as ' || quote_ident(fk.fkcols[nkeys]); 28 | cmd := cmd || ' FROM ' || fk.fktable::text || ') fk WHERE '; 29 | else 30 | cmd := cmd || ' FROM ' || fk.fktable::text || ' fk WHERE '; 31 | end if; 32 | if fk.is_opt then 33 | for i in 1 .. nkeys loop 34 | cmd := cmd || quote_ident(fk.fkcols[i]) || ' != 0 AND '; 35 | end loop; 36 | end if; 37 | cmd := cmd || 'NOT EXISTS(SELECT 1 FROM ' || fk.pktable::text || ' pk WHERE '; 38 | for i in 1 .. nkeys loop 39 | if i > 1 then cmd := cmd || ' AND '; end if; 40 | cmd := cmd || 'pk.' || quote_ident(fk.pkcols[i]); 41 | cmd := cmd || ' = fk.' || quote_ident(fk.fkcols[i]); 42 | end loop; 43 | cmd := cmd || ')'; 44 | -- raise notice 'cmd = %', cmd; 45 | for err in execute cmd loop 46 | raise warning 'FK VIOLATION IN %(%): %', fk.fktable, fk.fkcols, err; 47 | end loop; 48 | end loop; 49 | end 50 | $doblock$", 51 | { 52 | "stmts": [ 53 | { 54 | "stmt": { 55 | "DoStmt": { 56 | "args": [ 57 | { 58 | "DefElem": { 59 | "arg": { 60 | "String": { 61 | "sval": " 62 | declare 63 | fk record; 64 | nkeys integer; 65 | cmd text; 66 | err record; 67 | begin 68 | for fk in select * from pg_get_catalog_foreign_keys() 69 | loop 70 | raise notice 'checking % % => % %', 71 | fk.fktable, fk.fkcols, fk.pktable, fk.pkcols; 72 | nkeys := array_length(fk.fkcols, 1); 73 | cmd := 'SELECT ctid'; 74 | for i in 1 .. nkeys loop 75 | cmd := cmd || ', ' || quote_ident(fk.fkcols[i]); 76 | end loop; 77 | if fk.is_array then 78 | cmd := cmd || ' FROM (SELECT ctid'; 79 | for i in 1 .. nkeys-1 loop 80 | cmd := cmd || ', ' || quote_ident(fk.fkcols[i]); 81 | end loop; 82 | cmd := cmd || ', unnest(' || quote_ident(fk.fkcols[nkeys]); 83 | cmd := cmd || ') as ' || quote_ident(fk.fkcols[nkeys]); 84 | cmd := cmd || ' FROM ' || fk.fktable::text || ') fk WHERE '; 85 | else 86 | cmd := cmd || ' FROM ' || fk.fktable::text || ' fk WHERE '; 87 | end if; 88 | if fk.is_opt then 89 | for i in 1 .. nkeys loop 90 | cmd := cmd || quote_ident(fk.fkcols[i]) || ' != 0 AND '; 91 | end loop; 92 | end if; 93 | cmd := cmd || 'NOT EXISTS(SELECT 1 FROM ' || fk.pktable::text || ' pk WHERE '; 94 | for i in 1 .. nkeys loop 95 | if i > 1 then cmd := cmd || ' AND '; end if; 96 | cmd := cmd || 'pk.' || quote_ident(fk.pkcols[i]); 97 | cmd := cmd || ' = fk.' || quote_ident(fk.fkcols[i]); 98 | end loop; 99 | cmd := cmd || ')'; 100 | -- raise notice 'cmd = %', cmd; 101 | for err in execute cmd loop 102 | raise warning 'FK VIOLATION IN %(%): %', fk.fktable, fk.fkcols, err; 103 | end loop; 104 | end loop; 105 | end 106 | ", 107 | }, 108 | }, 109 | "defaction": "DEFELEM_UNSPEC", 110 | "defname": "as", 111 | "location": 3, 112 | }, 113 | }, 114 | ], 115 | }, 116 | }, 117 | }, 118 | ], 119 | "version": 160001, 120 | }, 121 | ] 122 | `; 123 | -------------------------------------------------------------------------------- /src/async.cc: -------------------------------------------------------------------------------- 1 | #include "helpers.h" // NOLINT(build/include) 2 | #include "async.h" // NOLINT(build/include) 3 | #include "pg_query.h" 4 | #include 5 | 6 | class QueryWorker : public Napi::AsyncWorker { 7 | public: 8 | QueryWorker(Napi::Function& callback, const std::string& query) 9 | : Napi::AsyncWorker(callback), query(query) {} 10 | ~QueryWorker() {} 11 | 12 | // Executed inside the worker-thread. 13 | // It is not safe to access JS engine data structure 14 | // here, so everything we need for input and output 15 | // should go on `this`. 16 | void Execute () { 17 | result = pg_query_parse(query.c_str()); 18 | } 19 | 20 | // Executed when the async work is complete 21 | // this function will be run inside the main event loop 22 | // so it is safe to use JS engine data again 23 | void OnOK() { 24 | Napi::HandleScope scope(Env()); 25 | try { 26 | Callback().Call({Env().Undefined(), QueryParseResult(Env(), result) }); 27 | } catch (const Napi::Error& e) { 28 | Callback().Call({ e.Value(), Env().Undefined() }); 29 | } 30 | } 31 | 32 | private: 33 | std::string query; 34 | PgQueryParseResult result; 35 | }; 36 | 37 | class PgPlQSLWorker : public Napi::AsyncWorker { 38 | public: 39 | PgPlQSLWorker(Napi::Function& callback, const std::string& query) 40 | : Napi::AsyncWorker(callback), query(query) {} 41 | ~PgPlQSLWorker() {} 42 | 43 | // Executed inside the worker-thread. 44 | // It is not safe to access JS engine data structure 45 | // here, so everything we need for input and output 46 | // should go on `this`. 47 | void Execute () { 48 | result = pg_query_parse_plpgsql(query.c_str()); 49 | } 50 | 51 | // Executed when the async work is complete 52 | // this function will be run inside the main event loop 53 | // so it is safe to use JS engine data again 54 | void OnOK() { 55 | Napi::HandleScope scope(Env()); 56 | try { 57 | Callback().Call({Env().Undefined(), PlPgSQLParseResult(Env(), result) }); 58 | } catch (const Napi::Error& e) { 59 | Callback().Call({ e.Value(), Env().Undefined() }); 60 | } 61 | } 62 | 63 | private: 64 | std::string query; 65 | PgQueryPlpgsqlParseResult result; 66 | }; 67 | 68 | 69 | class FingeprintWorker : public Napi::AsyncWorker { 70 | public: 71 | FingeprintWorker(Napi::Function& callback, const std::string& query) 72 | : Napi::AsyncWorker(callback), query(query) {} 73 | ~FingeprintWorker() {} 74 | 75 | // Executed inside the worker-thread. 76 | // It is not safe to access JS engine data structure 77 | // here, so everything we need for input and output 78 | // should go on `this`. 79 | void Execute () { 80 | result = pg_query_fingerprint(query.c_str()); 81 | } 82 | 83 | // Executed when the async work is complete 84 | // this function will be run inside the main event loop 85 | // so it is safe to use JS engine data again 86 | void OnOK() { 87 | Napi::HandleScope scope(Env()); 88 | try { 89 | Callback().Call({Env().Undefined(), FingerprintResult(Env(), result) }); 90 | } catch (const Napi::Error& e) { 91 | Callback().Call({ e.Value(), Env().Undefined() }); 92 | } 93 | } 94 | 95 | private: 96 | std::string query; 97 | PgQueryFingerprintResult result; 98 | }; 99 | 100 | Napi::Value ParseQueryAsync(const Napi::CallbackInfo& info) { 101 | std::string query = info[0].As(); 102 | Napi::Function callback = info[1].As(); 103 | QueryWorker* worker = new QueryWorker(callback, query); 104 | worker->Queue(); 105 | return info.Env().Undefined(); 106 | } 107 | 108 | Napi::Value ParsePlPgSQLAsync(const Napi::CallbackInfo& info) { 109 | std::string query = info[0].As(); 110 | Napi::Function callback = info[1].As(); 111 | PgPlQSLWorker* worker = new PgPlQSLWorker(callback, query); 112 | worker->Queue(); 113 | return info.Env().Undefined(); 114 | } 115 | 116 | Napi::Value FingerprintAsync(const Napi::CallbackInfo& info) { 117 | std::string query = info[0].As(); 118 | Napi::Function callback = info[1].As(); 119 | FingeprintWorker* worker = new FingeprintWorker(callback, query); 120 | worker->Queue(); 121 | return info.Env().Undefined(); 122 | } 123 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/init_privs.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`init_privs.sql > line 4 1`] = ` 4 | [ 5 | "SELECT count(*) > 0 FROM pg_init_privs", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "SelectStmt": { 11 | "fromClause": [ 12 | { 13 | "RangeVar": { 14 | "inh": true, 15 | "location": 25, 16 | "relname": "pg_init_privs", 17 | "relpersistence": "p", 18 | }, 19 | }, 20 | ], 21 | "limitOption": "LIMIT_OPTION_DEFAULT", 22 | "op": "SETOP_NONE", 23 | "targetList": [ 24 | { 25 | "ResTarget": { 26 | "location": 7, 27 | "val": { 28 | "A_Expr": { 29 | "kind": "AEXPR_OP", 30 | "lexpr": { 31 | "FuncCall": { 32 | "agg_star": true, 33 | "funcformat": "COERCE_EXPLICIT_CALL", 34 | "funcname": [ 35 | { 36 | "String": { 37 | "sval": "count", 38 | }, 39 | }, 40 | ], 41 | "location": 7, 42 | }, 43 | }, 44 | "location": 16, 45 | "name": [ 46 | { 47 | "String": { 48 | "sval": ">", 49 | }, 50 | }, 51 | ], 52 | "rexpr": { 53 | "A_Const": { 54 | "ival": {}, 55 | "location": 18, 56 | }, 57 | }, 58 | }, 59 | }, 60 | }, 61 | }, 62 | ], 63 | }, 64 | }, 65 | }, 66 | ], 67 | "version": 160001, 68 | }, 69 | ] 70 | `; 71 | 72 | exports[`init_privs.sql > line 7 1`] = ` 73 | [ 74 | "GRANT SELECT ON pg_proc TO CURRENT_USER", 75 | { 76 | "stmts": [ 77 | { 78 | "stmt": { 79 | "GrantStmt": { 80 | "behavior": "DROP_RESTRICT", 81 | "grantees": [ 82 | { 83 | "RoleSpec": { 84 | "location": 27, 85 | "roletype": "ROLESPEC_CURRENT_USER", 86 | }, 87 | }, 88 | ], 89 | "is_grant": true, 90 | "objects": [ 91 | { 92 | "RangeVar": { 93 | "inh": true, 94 | "location": 16, 95 | "relname": "pg_proc", 96 | "relpersistence": "p", 97 | }, 98 | }, 99 | ], 100 | "objtype": "OBJECT_TABLE", 101 | "privileges": [ 102 | { 103 | "AccessPriv": { 104 | "priv_name": "select", 105 | }, 106 | }, 107 | ], 108 | "targtype": "ACL_TARGET_OBJECT", 109 | }, 110 | }, 111 | }, 112 | ], 113 | "version": 160001, 114 | }, 115 | ] 116 | `; 117 | 118 | exports[`init_privs.sql > line 10 1`] = ` 119 | [ 120 | "GRANT SELECT (rolname, rolsuper) ON pg_authid TO CURRENT_USER", 121 | { 122 | "stmts": [ 123 | { 124 | "stmt": { 125 | "GrantStmt": { 126 | "behavior": "DROP_RESTRICT", 127 | "grantees": [ 128 | { 129 | "RoleSpec": { 130 | "location": 49, 131 | "roletype": "ROLESPEC_CURRENT_USER", 132 | }, 133 | }, 134 | ], 135 | "is_grant": true, 136 | "objects": [ 137 | { 138 | "RangeVar": { 139 | "inh": true, 140 | "location": 36, 141 | "relname": "pg_authid", 142 | "relpersistence": "p", 143 | }, 144 | }, 145 | ], 146 | "objtype": "OBJECT_TABLE", 147 | "privileges": [ 148 | { 149 | "AccessPriv": { 150 | "cols": [ 151 | { 152 | "String": { 153 | "sval": "rolname", 154 | }, 155 | }, 156 | { 157 | "String": { 158 | "sval": "rolsuper", 159 | }, 160 | }, 161 | ], 162 | "priv_name": "select", 163 | }, 164 | }, 165 | ], 166 | "targtype": "ACL_TARGET_OBJECT", 167 | }, 168 | }, 169 | }, 170 | ], 171 | "version": 160001, 172 | }, 173 | ] 174 | `; 175 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/lseg.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`lseg.sql > line 7 1`] = ` 4 | [ 5 | "CREATE TABLE LSEG_TBL (s lseg)", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "CreateStmt": { 11 | "oncommit": "ONCOMMIT_NOOP", 12 | "relation": { 13 | "inh": true, 14 | "location": 13, 15 | "relname": "lseg_tbl", 16 | "relpersistence": "p", 17 | }, 18 | "tableElts": [ 19 | { 20 | "ColumnDef": { 21 | "colname": "s", 22 | "is_local": true, 23 | "location": 23, 24 | "typeName": { 25 | "location": 25, 26 | "names": [ 27 | { 28 | "String": { 29 | "sval": "lseg", 30 | }, 31 | }, 32 | ], 33 | "typemod": -1, 34 | }, 35 | }, 36 | }, 37 | ], 38 | }, 39 | }, 40 | }, 41 | ], 42 | "version": 160001, 43 | }, 44 | ] 45 | `; 46 | 47 | exports[`lseg.sql > line 9 1`] = ` 48 | [ 49 | "INSERT INTO LSEG_TBL VALUES ('[(1,2),(3,4)]')", 50 | { 51 | "stmts": [ 52 | { 53 | "stmt": { 54 | "InsertStmt": { 55 | "override": "OVERRIDING_NOT_SET", 56 | "relation": { 57 | "inh": true, 58 | "location": 12, 59 | "relname": "lseg_tbl", 60 | "relpersistence": "p", 61 | }, 62 | "selectStmt": { 63 | "SelectStmt": { 64 | "limitOption": "LIMIT_OPTION_DEFAULT", 65 | "op": "SETOP_NONE", 66 | "valuesLists": [ 67 | { 68 | "List": { 69 | "items": [ 70 | { 71 | "A_Const": { 72 | "location": 29, 73 | "sval": { 74 | "sval": "[(1,2),(3,4)]", 75 | }, 76 | }, 77 | }, 78 | ], 79 | }, 80 | }, 81 | ], 82 | }, 83 | }, 84 | }, 85 | }, 86 | }, 87 | ], 88 | "version": 160001, 89 | }, 90 | ] 91 | `; 92 | 93 | exports[`lseg.sql > line 24 1`] = ` 94 | [ 95 | "select * from LSEG_TBL", 96 | { 97 | "stmts": [ 98 | { 99 | "stmt": { 100 | "SelectStmt": { 101 | "fromClause": [ 102 | { 103 | "RangeVar": { 104 | "inh": true, 105 | "location": 14, 106 | "relname": "lseg_tbl", 107 | "relpersistence": "p", 108 | }, 109 | }, 110 | ], 111 | "limitOption": "LIMIT_OPTION_DEFAULT", 112 | "op": "SETOP_NONE", 113 | "targetList": [ 114 | { 115 | "ResTarget": { 116 | "location": 7, 117 | "val": { 118 | "ColumnRef": { 119 | "fields": [ 120 | { 121 | "A_Star": {}, 122 | }, 123 | ], 124 | "location": 7, 125 | }, 126 | }, 127 | }, 128 | }, 129 | ], 130 | }, 131 | }, 132 | }, 133 | ], 134 | "version": 160001, 135 | }, 136 | ] 137 | `; 138 | 139 | exports[`lseg.sql > line 27 1`] = ` 140 | [ 141 | "SELECT pg_input_is_valid('[(1,2),(3)]', 'lseg')", 142 | { 143 | "stmts": [ 144 | { 145 | "stmt": { 146 | "SelectStmt": { 147 | "limitOption": "LIMIT_OPTION_DEFAULT", 148 | "op": "SETOP_NONE", 149 | "targetList": [ 150 | { 151 | "ResTarget": { 152 | "location": 7, 153 | "val": { 154 | "FuncCall": { 155 | "args": [ 156 | { 157 | "A_Const": { 158 | "location": 25, 159 | "sval": { 160 | "sval": "[(1,2),(3)]", 161 | }, 162 | }, 163 | }, 164 | { 165 | "A_Const": { 166 | "location": 40, 167 | "sval": { 168 | "sval": "lseg", 169 | }, 170 | }, 171 | }, 172 | ], 173 | "funcformat": "COERCE_EXPLICIT_CALL", 174 | "funcname": [ 175 | { 176 | "String": { 177 | "sval": "pg_input_is_valid", 178 | }, 179 | }, 180 | ], 181 | "location": 7, 182 | }, 183 | }, 184 | }, 185 | }, 186 | ], 187 | }, 188 | }, 189 | }, 190 | ], 191 | "version": 160001, 192 | }, 193 | ] 194 | `; 195 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/portals_p2.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`portals_p2.sql > line 7 1`] = ` 4 | [ 5 | "DECLARE foo13 CURSOR FOR 6 | SELECT * FROM onek WHERE unique1 = 50", 7 | { 8 | "stmts": [ 9 | { 10 | "stmt": { 11 | "DeclareCursorStmt": { 12 | "options": 256, 13 | "portalname": "foo13", 14 | "query": { 15 | "SelectStmt": { 16 | "fromClause": [ 17 | { 18 | "RangeVar": { 19 | "inh": true, 20 | "location": 42, 21 | "relname": "onek", 22 | "relpersistence": "p", 23 | }, 24 | }, 25 | ], 26 | "limitOption": "LIMIT_OPTION_DEFAULT", 27 | "op": "SETOP_NONE", 28 | "targetList": [ 29 | { 30 | "ResTarget": { 31 | "location": 35, 32 | "val": { 33 | "ColumnRef": { 34 | "fields": [ 35 | { 36 | "A_Star": {}, 37 | }, 38 | ], 39 | "location": 35, 40 | }, 41 | }, 42 | }, 43 | }, 44 | ], 45 | "whereClause": { 46 | "A_Expr": { 47 | "kind": "AEXPR_OP", 48 | "lexpr": { 49 | "ColumnRef": { 50 | "fields": [ 51 | { 52 | "String": { 53 | "sval": "unique1", 54 | }, 55 | }, 56 | ], 57 | "location": 53, 58 | }, 59 | }, 60 | "location": 61, 61 | "name": [ 62 | { 63 | "String": { 64 | "sval": "=", 65 | }, 66 | }, 67 | ], 68 | "rexpr": { 69 | "A_Const": { 70 | "ival": { 71 | "ival": 50, 72 | }, 73 | "location": 63, 74 | }, 75 | }, 76 | }, 77 | }, 78 | }, 79 | }, 80 | }, 81 | }, 82 | }, 83 | ], 84 | "version": 160001, 85 | }, 86 | ] 87 | `; 88 | 89 | exports[`portals_p2.sql > line 40 1`] = ` 90 | [ 91 | "DECLARE foo24 CURSOR FOR 92 | SELECT * FROM onek2 WHERE unique1 = 50", 93 | { 94 | "stmts": [ 95 | { 96 | "stmt": { 97 | "DeclareCursorStmt": { 98 | "options": 256, 99 | "portalname": "foo24", 100 | "query": { 101 | "SelectStmt": { 102 | "fromClause": [ 103 | { 104 | "RangeVar": { 105 | "inh": true, 106 | "location": 42, 107 | "relname": "onek2", 108 | "relpersistence": "p", 109 | }, 110 | }, 111 | ], 112 | "limitOption": "LIMIT_OPTION_DEFAULT", 113 | "op": "SETOP_NONE", 114 | "targetList": [ 115 | { 116 | "ResTarget": { 117 | "location": 35, 118 | "val": { 119 | "ColumnRef": { 120 | "fields": [ 121 | { 122 | "A_Star": {}, 123 | }, 124 | ], 125 | "location": 35, 126 | }, 127 | }, 128 | }, 129 | }, 130 | ], 131 | "whereClause": { 132 | "A_Expr": { 133 | "kind": "AEXPR_OP", 134 | "lexpr": { 135 | "ColumnRef": { 136 | "fields": [ 137 | { 138 | "String": { 139 | "sval": "unique1", 140 | }, 141 | }, 142 | ], 143 | "location": 54, 144 | }, 145 | }, 146 | "location": 62, 147 | "name": [ 148 | { 149 | "String": { 150 | "sval": "=", 151 | }, 152 | }, 153 | ], 154 | "rexpr": { 155 | "A_Const": { 156 | "ival": { 157 | "ival": 50, 158 | }, 159 | "location": 64, 160 | }, 161 | }, 162 | }, 163 | }, 164 | }, 165 | }, 166 | }, 167 | }, 168 | }, 169 | ], 170 | "version": 160001, 171 | }, 172 | ] 173 | `; 174 | 175 | exports[`portals_p2.sql > line 46 1`] = ` 176 | [ 177 | "FETCH all in foo13", 178 | { 179 | "stmts": [ 180 | { 181 | "stmt": { 182 | "FetchStmt": { 183 | "direction": "FETCH_FORWARD", 184 | "howMany": 9223372036854776000, 185 | "portalname": "foo13", 186 | }, 187 | }, 188 | }, 189 | ], 190 | "version": 160001, 191 | }, 192 | ] 193 | `; 194 | 195 | exports[`portals_p2.sql > line 72 1`] = ` 196 | [ 197 | "CLOSE foo13", 198 | { 199 | "stmts": [ 200 | { 201 | "stmt": { 202 | "ClosePortalStmt": { 203 | "portalname": "foo13", 204 | }, 205 | }, 206 | }, 207 | ], 208 | "version": 160001, 209 | }, 210 | ] 211 | `; 212 | 213 | exports[`portals_p2.sql > line 98 1`] = ` 214 | [ 215 | "END", 216 | { 217 | "stmts": [ 218 | { 219 | "stmt": { 220 | "TransactionStmt": { 221 | "kind": "TRANS_STMT_COMMIT", 222 | }, 223 | }, 224 | }, 225 | ], 226 | "version": 160001, 227 | }, 228 | ] 229 | `; 230 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # @pg-nano/pg-parser 2 | 3 | A fork of [libpg-query](https://github.com/launchql/libpg-query-node) with best-in-class type definitions and AST utilities. 4 | 5 | ```ts 6 | import { parseQuery } from "@pg-nano/pg-parser" 7 | 8 | const ast = await parseQuery("SELECT 1; SELECT 2") 9 | // ^? ParseResult 10 | 11 | ast.version // => 160001 12 | ast.stmts // => [{ stmt: SelectStmt, stmt_len: 8 }, { stmt: SelectStmt, stmt_location: 9 }] 13 | ``` 14 | 15 | ## Install 16 | 17 | ``` 18 | pnpm add @pg-nano/pg-parser 19 | ``` 20 | 21 | The major and minor version of this package is meant to be aligned with the supported PostgreSQL major and minor version. Older and newer versions of PostgreSQL may be compatible, but this is not guaranteed. 22 | 23 | > [!WARNING] 24 | > This package may be improved in ways that break your type guards. You'll probably want to pin to a specific version to avoid unexpected type changes. 25 | 26 | Upon install, the pre-compiled binary for your operating system and architecture will be pulled from GitHub Releases. 27 | 28 | ## API 29 | 30 | This package exports the following native functions: 31 | 32 | - `parseQuery` (for async parsing a SQL string of one or more statements) 33 | - `parseQuerySync` 34 | - `parsePlPgSQL` (for async parsing a plpgsql string) 35 | - `parsePlPgSQLSync` 36 | - `fingerprint` (for generating a unique string for a SQL string) 37 | - `fingerprintSync` 38 | - `scanSync` (for scanning a SQL string and returning a list of tokens) 39 | - `splitWithScannerSync` (for splitting a SQL string into one or more statements) 40 | 41 | **Note:** There is no `deparse` function (for turning an AST back into a string) included, as this isn't needed for my use case. 42 | 43 | #### AST utilities 44 | 45 | I've implemented some TypeScript utilities for working with the AST: 46 | 47 | - `walk` (for traversing the AST) 48 | - `select` (for type-safe, deep field access through dot-notation) 49 | - `$` (for type-safe node proxy and type guards) 50 | 51 | ### Walking the AST 52 | 53 | Let's explore the `walk` function, ideal for AST traversal where you're only concerned with specific node types. You can pass a callback or a visitor object. You can return false to not walk into the children of the current node. 54 | 55 | Each node passed to your visitor is wrapped in a `NodePath` instance, which tracks the parent node and provides type guards (e.g. `isSelectStmt`) for type narrowing. You can access the underlying node with `path.node`. 56 | 57 | ```ts 58 | import { parseQuerySync, walk, $ } from "@pg-nano/pg-parser" 59 | 60 | walk(parseQuerySync(`SELECT foo FROM bar`), (path) => { 61 | path.tag // string 62 | path.node // the node object 63 | path.parent // the parent node 64 | 65 | if (path.isSelectStmt()) { 66 | // The visitor pattern is also supported. 67 | walk(path.node.targetList, { 68 | ColumnRef(path) { 69 | const id = path.node.fields 70 | .map((f) => ($.isString(f) ? f.String.sval : "*")) 71 | .join(".") 72 | 73 | console.log(id) 74 | }, 75 | }) 76 | 77 | // don't walk into the children 78 | return false 79 | } 80 | }) 81 | ``` 82 | 83 | Don't forget the `select` function, which excels at type-safe field access via dot-notation. 84 | 85 | Note: You must not include the *node types* (i.e. the capitalized names) in the field path. 86 | 87 | ```ts 88 | import { select, Expr } from "@pg-nano/pg-parser" 89 | 90 | // Say we have an expression node of many possible types. 91 | declare const expr: Expr 92 | 93 | // By using the `select` function, we can access the `typeName` field 94 | // without having to use a bunch of type guards. 95 | const typeName = select(expr, 'typeName') 96 | // ^? TypeName | undefined 97 | ``` 98 | 99 | Similar to `select`, you may like the `$` function for field access. It returns a proxy that makes field access less verbose. It also comes with type guards for all nodes. 100 | 101 | ```ts 102 | import { $, walk } from "@pg-nano/pg-parser" 103 | 104 | walk(ast, { 105 | SelectStmt(path) { 106 | for (const target of path.node.targetList) { 107 | const { name, val } = $(target) 108 | 109 | if ($.isColumnRef(val)) { 110 | console.log( 111 | name, 112 | $(val).fields.map(field => { 113 | return $.isA_Star(field) ? "*" : field.String.sval 114 | }).join("."), 115 | ) 116 | } 117 | } 118 | } 119 | }) 120 | ``` 121 | 122 | ### Type definitions 123 | 124 | Every possible type that could be returned from libpg_query is defined in [ast.ts](https://github.com/pg-nano/pg-parser/blob/16-latest/src/lib/ast.ts). If a type is missing, it's probably because libpg_query didn't tell us about it (otherwise, please [file an issue](https://github.com/pg-nano/pg-parser/issues)). 125 | 126 | The type definitions are generated from the [srcdata](https://github.com/pganalyze/libpg_query/tree/16-latest/srcdata) of `libpg_query` (the C library this package binds to). If you're interested in how they're generated, see [scripts/generateTypes.ts](https://github.com/pg-nano/pg-parser/blob/16-latest/scripts/generateTypes.ts) and [scripts/inferFieldMetadata.ts](https://github.com/pg-nano/pg-parser/blob/16-latest/scripts/inferFieldMetadata.ts). For some cases, type definitions are manually specified in [scripts/typeMappings.ts](https://github.com/pg-nano/pg-parser/blob/16-latest/scripts/typeMappings.ts). 127 | 128 | ### Other improvements 129 | 130 | - Uses `prebuild-install` to avoid bundling every platform's binaries into the package. 131 | - Added `splitWithScannerSync` for SQL statement splitting. 132 | - Added `scanSync` for token scanning. 133 | - [Generated](https://github.com/pg-nano/pg-parser/blob/16-latest/scripts/generateTests.ts) unit tests (see [snapshots](https://github.com/pg-nano/pg-parser/tree/16-latest/test/postgres_regress/__snapshots__) of every SQL case supported by `libpg_query`). 134 | 135 | ## Contributing 136 | 137 | To generate the type definitions, you can use this command: 138 | 139 | ```sh 140 | pnpm prepare:types 141 | ``` 142 | 143 | To compile the TypeScript bindings and the C++ addon (and recompile them on file changes), you can use this command: 144 | 145 | ```sh 146 | pnpm dev 147 | ``` 148 | 149 | Otherwise, `pnpm build` will compile just once. 150 | 151 | If you're editing C++ code, you'll want to have [compiledb](https://github.com/nickdiego/compiledb) installed and the [clangd extension](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) in VSCode. This enables the `clangd` language server for features like autocomplete, static analysis, and code navigation. 152 | 153 | ```sh 154 | brew install compiledb 155 | ``` 156 | 157 | **⚠️ Windows support:** The `binding.gyp` file is currently broken for Windows builds. Any help would be appreciated! 158 | 159 | ## License 160 | 161 | MIT 162 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/reindex_catalog.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`reindex_catalog.sql > line 27 1`] = ` 4 | [ 5 | "REINDEX TABLE pg_class", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "ReindexStmt": { 11 | "kind": "REINDEX_OBJECT_TABLE", 12 | "relation": { 13 | "inh": true, 14 | "location": 14, 15 | "relname": "pg_class", 16 | "relpersistence": "p", 17 | }, 18 | }, 19 | }, 20 | }, 21 | ], 22 | "version": 160001, 23 | }, 24 | ] 25 | `; 26 | 27 | exports[`reindex_catalog.sql > line 27 2`] = ` 28 | [ 29 | " -- mapped, non-shared, critical 30 | REINDEX TABLE pg_index", 31 | { 32 | "stmts": [ 33 | { 34 | "stmt": { 35 | "ReindexStmt": { 36 | "kind": "REINDEX_OBJECT_TABLE", 37 | "relation": { 38 | "inh": true, 39 | "location": 47, 40 | "relname": "pg_index", 41 | "relpersistence": "p", 42 | }, 43 | }, 44 | }, 45 | }, 46 | ], 47 | "version": 160001, 48 | }, 49 | ] 50 | `; 51 | 52 | exports[`reindex_catalog.sql > line 28 1`] = ` 53 | [ 54 | " -- non-mapped, non-shared, critical 55 | REINDEX TABLE pg_operator", 56 | { 57 | "stmts": [ 58 | { 59 | "stmt": { 60 | "ReindexStmt": { 61 | "kind": "REINDEX_OBJECT_TABLE", 62 | "relation": { 63 | "inh": true, 64 | "location": 51, 65 | "relname": "pg_operator", 66 | "relpersistence": "p", 67 | }, 68 | }, 69 | }, 70 | }, 71 | ], 72 | "version": 160001, 73 | }, 74 | ] 75 | `; 76 | 77 | exports[`reindex_catalog.sql > line 29 1`] = ` 78 | [ 79 | " -- non-mapped, non-shared, critical 80 | REINDEX TABLE pg_database", 81 | { 82 | "stmts": [ 83 | { 84 | "stmt": { 85 | "ReindexStmt": { 86 | "kind": "REINDEX_OBJECT_TABLE", 87 | "relation": { 88 | "inh": true, 89 | "location": 51, 90 | "relname": "pg_database", 91 | "relpersistence": "p", 92 | }, 93 | }, 94 | }, 95 | }, 96 | ], 97 | "version": 160001, 98 | }, 99 | ] 100 | `; 101 | 102 | exports[`reindex_catalog.sql > line 30 1`] = ` 103 | [ 104 | " -- mapped, shared, critical 105 | REINDEX TABLE pg_shdescription", 106 | { 107 | "stmts": [ 108 | { 109 | "stmt": { 110 | "ReindexStmt": { 111 | "kind": "REINDEX_OBJECT_TABLE", 112 | "relation": { 113 | "inh": true, 114 | "location": 43, 115 | "relname": "pg_shdescription", 116 | "relpersistence": "p", 117 | }, 118 | }, 119 | }, 120 | }, 121 | ], 122 | "version": 160001, 123 | }, 124 | ] 125 | `; 126 | 127 | exports[`reindex_catalog.sql > line 31 1`] = ` 128 | [ 129 | " -- mapped, shared non-critical 130 | 131 | -- Check that individual system indexes can be reindexed. That's a bit 132 | -- different from the entire-table case because reindex_relation 133 | -- treats e.g. pg_class special. 134 | REINDEX INDEX pg_class_oid_index", 135 | { 136 | "stmts": [ 137 | { 138 | "stmt": { 139 | "ReindexStmt": { 140 | "kind": "REINDEX_OBJECT_INDEX", 141 | "relation": { 142 | "inh": true, 143 | "location": 216, 144 | "relname": "pg_class_oid_index", 145 | "relpersistence": "p", 146 | }, 147 | }, 148 | }, 149 | }, 150 | ], 151 | "version": 160001, 152 | }, 153 | ] 154 | `; 155 | 156 | exports[`reindex_catalog.sql > line 36 1`] = ` 157 | [ 158 | " -- mapped, non-shared, critical 159 | REINDEX INDEX pg_class_relname_nsp_index", 160 | { 161 | "stmts": [ 162 | { 163 | "stmt": { 164 | "ReindexStmt": { 165 | "kind": "REINDEX_OBJECT_INDEX", 166 | "relation": { 167 | "inh": true, 168 | "location": 47, 169 | "relname": "pg_class_relname_nsp_index", 170 | "relpersistence": "p", 171 | }, 172 | }, 173 | }, 174 | }, 175 | ], 176 | "version": 160001, 177 | }, 178 | ] 179 | `; 180 | 181 | exports[`reindex_catalog.sql > line 37 1`] = ` 182 | [ 183 | " -- mapped, non-shared, non-critical 184 | REINDEX INDEX pg_index_indexrelid_index", 185 | { 186 | "stmts": [ 187 | { 188 | "stmt": { 189 | "ReindexStmt": { 190 | "kind": "REINDEX_OBJECT_INDEX", 191 | "relation": { 192 | "inh": true, 193 | "location": 51, 194 | "relname": "pg_index_indexrelid_index", 195 | "relpersistence": "p", 196 | }, 197 | }, 198 | }, 199 | }, 200 | ], 201 | "version": 160001, 202 | }, 203 | ] 204 | `; 205 | 206 | exports[`reindex_catalog.sql > line 38 1`] = ` 207 | [ 208 | " -- non-mapped, non-shared, critical 209 | REINDEX INDEX pg_index_indrelid_index", 210 | { 211 | "stmts": [ 212 | { 213 | "stmt": { 214 | "ReindexStmt": { 215 | "kind": "REINDEX_OBJECT_INDEX", 216 | "relation": { 217 | "inh": true, 218 | "location": 51, 219 | "relname": "pg_index_indrelid_index", 220 | "relpersistence": "p", 221 | }, 222 | }, 223 | }, 224 | }, 225 | ], 226 | "version": 160001, 227 | }, 228 | ] 229 | `; 230 | 231 | exports[`reindex_catalog.sql > line 39 1`] = ` 232 | [ 233 | " -- non-mapped, non-shared, non-critical 234 | REINDEX INDEX pg_database_oid_index", 235 | { 236 | "stmts": [ 237 | { 238 | "stmt": { 239 | "ReindexStmt": { 240 | "kind": "REINDEX_OBJECT_INDEX", 241 | "relation": { 242 | "inh": true, 243 | "location": 55, 244 | "relname": "pg_database_oid_index", 245 | "relpersistence": "p", 246 | }, 247 | }, 248 | }, 249 | }, 250 | ], 251 | "version": 160001, 252 | }, 253 | ] 254 | `; 255 | 256 | exports[`reindex_catalog.sql > line 40 1`] = ` 257 | [ 258 | " -- mapped, shared, critical 259 | REINDEX INDEX pg_shdescription_o_c_index", 260 | { 261 | "stmts": [ 262 | { 263 | "stmt": { 264 | "ReindexStmt": { 265 | "kind": "REINDEX_OBJECT_INDEX", 266 | "relation": { 267 | "inh": true, 268 | "location": 43, 269 | "relname": "pg_shdescription_o_c_index", 270 | "relpersistence": "p", 271 | }, 272 | }, 273 | }, 274 | }, 275 | ], 276 | "version": 160001, 277 | }, 278 | ] 279 | `; 280 | 281 | exports[`reindex_catalog.sql > line 41 1`] = ` 282 | [ 283 | " -- mapped, shared, non-critical 284 | 285 | -- Check the same REINDEX INDEX statements under parallelism. 286 | BEGIN", 287 | { 288 | "stmts": [ 289 | { 290 | "stmt": { 291 | "TransactionStmt": { 292 | "kind": "TRANS_STMT_BEGIN", 293 | }, 294 | }, 295 | }, 296 | ], 297 | "version": 160001, 298 | }, 299 | ] 300 | `; 301 | 302 | exports[`reindex_catalog.sql > line 51 1`] = ` 303 | [ 304 | " -- mapped, shared, non-critical 305 | ROLLBACK", 306 | { 307 | "stmts": [ 308 | { 309 | "stmt": { 310 | "TransactionStmt": { 311 | "kind": "TRANS_STMT_ROLLBACK", 312 | }, 313 | }, 314 | }, 315 | ], 316 | "version": 160001, 317 | }, 318 | ] 319 | `; 320 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/regproc.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`regproc.sql > line 19 1`] = ` 4 | [ 5 | "SELECT to_regoper('||/')", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "SelectStmt": { 11 | "limitOption": "LIMIT_OPTION_DEFAULT", 12 | "op": "SETOP_NONE", 13 | "targetList": [ 14 | { 15 | "ResTarget": { 16 | "location": 7, 17 | "val": { 18 | "FuncCall": { 19 | "args": [ 20 | { 21 | "A_Const": { 22 | "location": 18, 23 | "sval": { 24 | "sval": "||/", 25 | }, 26 | }, 27 | }, 28 | ], 29 | "funcformat": "COERCE_EXPLICIT_CALL", 30 | "funcname": [ 31 | { 32 | "String": { 33 | "sval": "to_regoper", 34 | }, 35 | }, 36 | ], 37 | "location": 7, 38 | }, 39 | }, 40 | }, 41 | }, 42 | ], 43 | }, 44 | }, 45 | }, 46 | ], 47 | "version": 160001, 48 | }, 49 | ] 50 | `; 51 | 52 | exports[`regproc.sql > line 51 1`] = ` 53 | [ 54 | "SELECT to_regrole('regress_regrole_test')", 55 | { 56 | "stmts": [ 57 | { 58 | "stmt": { 59 | "SelectStmt": { 60 | "limitOption": "LIMIT_OPTION_DEFAULT", 61 | "op": "SETOP_NONE", 62 | "targetList": [ 63 | { 64 | "ResTarget": { 65 | "location": 7, 66 | "val": { 67 | "FuncCall": { 68 | "args": [ 69 | { 70 | "A_Const": { 71 | "location": 18, 72 | "sval": { 73 | "sval": "regress_regrole_test", 74 | }, 75 | }, 76 | }, 77 | ], 78 | "funcformat": "COERCE_EXPLICIT_CALL", 79 | "funcname": [ 80 | { 81 | "String": { 82 | "sval": "to_regrole", 83 | }, 84 | }, 85 | ], 86 | "location": 7, 87 | }, 88 | }, 89 | }, 90 | }, 91 | ], 92 | }, 93 | }, 94 | }, 95 | ], 96 | "version": 160001, 97 | }, 98 | ] 99 | `; 100 | 101 | exports[`regproc.sql > line 56 1`] = ` 102 | [ 103 | "/* If objects don't exist, raise errors. */ 104 | 105 | DROP ROLE regress_regrole_test", 106 | { 107 | "stmts": [ 108 | { 109 | "stmt": { 110 | "DropRoleStmt": { 111 | "roles": [ 112 | { 113 | "RoleSpec": { 114 | "location": 55, 115 | "rolename": "regress_regrole_test", 116 | "roletype": "ROLESPEC_CSTRING", 117 | }, 118 | }, 119 | ], 120 | }, 121 | }, 122 | }, 123 | ], 124 | "version": 160001, 125 | }, 126 | ] 127 | `; 128 | 129 | exports[`regproc.sql > line 78 1`] = ` 130 | [ 131 | "SELECT regcollation('ng_catalog."POSIX"')", 132 | { 133 | "stmts": [ 134 | { 135 | "stmt": { 136 | "SelectStmt": { 137 | "limitOption": "LIMIT_OPTION_DEFAULT", 138 | "op": "SETOP_NONE", 139 | "targetList": [ 140 | { 141 | "ResTarget": { 142 | "location": 7, 143 | "val": { 144 | "FuncCall": { 145 | "args": [ 146 | { 147 | "A_Const": { 148 | "location": 20, 149 | "sval": { 150 | "sval": "ng_catalog."POSIX"", 151 | }, 152 | }, 153 | }, 154 | ], 155 | "funcformat": "COERCE_EXPLICIT_CALL", 156 | "funcname": [ 157 | { 158 | "String": { 159 | "sval": "regcollation", 160 | }, 161 | }, 162 | ], 163 | "location": 7, 164 | }, 165 | }, 166 | }, 167 | }, 168 | ], 169 | }, 170 | }, 171 | }, 172 | ], 173 | "version": 160001, 174 | }, 175 | ] 176 | `; 177 | 178 | exports[`regproc.sql > line 144 1`] = ` 179 | [ 180 | "SELECT * FROM pg_input_error_info('incorrect type name syntax', 'regtype')", 181 | { 182 | "stmts": [ 183 | { 184 | "stmt": { 185 | "SelectStmt": { 186 | "fromClause": [ 187 | { 188 | "RangeFunction": { 189 | "functions": [ 190 | { 191 | "List": { 192 | "items": [ 193 | { 194 | "FuncCall": { 195 | "args": [ 196 | { 197 | "A_Const": { 198 | "location": 34, 199 | "sval": { 200 | "sval": "incorrect type name syntax", 201 | }, 202 | }, 203 | }, 204 | { 205 | "A_Const": { 206 | "location": 64, 207 | "sval": { 208 | "sval": "regtype", 209 | }, 210 | }, 211 | }, 212 | ], 213 | "funcformat": "COERCE_EXPLICIT_CALL", 214 | "funcname": [ 215 | { 216 | "String": { 217 | "sval": "pg_input_error_info", 218 | }, 219 | }, 220 | ], 221 | "location": 14, 222 | }, 223 | }, 224 | {}, 225 | ], 226 | }, 227 | }, 228 | ], 229 | }, 230 | }, 231 | ], 232 | "limitOption": "LIMIT_OPTION_DEFAULT", 233 | "op": "SETOP_NONE", 234 | "targetList": [ 235 | { 236 | "ResTarget": { 237 | "location": 7, 238 | "val": { 239 | "ColumnRef": { 240 | "fields": [ 241 | { 242 | "A_Star": {}, 243 | }, 244 | ], 245 | "location": 7, 246 | }, 247 | }, 248 | }, 249 | }, 250 | ], 251 | }, 252 | }, 253 | }, 254 | ], 255 | "version": 160001, 256 | }, 257 | ] 258 | `; 259 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/copydml.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`copydml.sql > line 4 1`] = ` 4 | [ 5 | "create table copydml_test (id serial, t text)", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "CreateStmt": { 11 | "oncommit": "ONCOMMIT_NOOP", 12 | "relation": { 13 | "inh": true, 14 | "location": 13, 15 | "relname": "copydml_test", 16 | "relpersistence": "p", 17 | }, 18 | "tableElts": [ 19 | { 20 | "ColumnDef": { 21 | "colname": "id", 22 | "is_local": true, 23 | "location": 27, 24 | "typeName": { 25 | "location": 30, 26 | "names": [ 27 | { 28 | "String": { 29 | "sval": "serial", 30 | }, 31 | }, 32 | ], 33 | "typemod": -1, 34 | }, 35 | }, 36 | }, 37 | { 38 | "ColumnDef": { 39 | "colname": "t", 40 | "is_local": true, 41 | "location": 38, 42 | "typeName": { 43 | "location": 40, 44 | "names": [ 45 | { 46 | "String": { 47 | "sval": "text", 48 | }, 49 | }, 50 | ], 51 | "typemod": -1, 52 | }, 53 | }, 54 | }, 55 | ], 56 | }, 57 | }, 58 | }, 59 | ], 60 | "version": 160001, 61 | }, 62 | ] 63 | `; 64 | 65 | exports[`copydml.sql > line 14 1`] = ` 66 | [ 67 | "copy (insert into copydml_test (t) values ('f') returning id) to stdout", 68 | { 69 | "stmts": [ 70 | { 71 | "stmt": { 72 | "CopyStmt": { 73 | "query": { 74 | "InsertStmt": { 75 | "cols": [ 76 | { 77 | "ResTarget": { 78 | "location": 32, 79 | "name": "t", 80 | }, 81 | }, 82 | ], 83 | "override": "OVERRIDING_NOT_SET", 84 | "relation": { 85 | "inh": true, 86 | "location": 18, 87 | "relname": "copydml_test", 88 | "relpersistence": "p", 89 | }, 90 | "returningList": [ 91 | { 92 | "ResTarget": { 93 | "location": 58, 94 | "val": { 95 | "ColumnRef": { 96 | "fields": [ 97 | { 98 | "String": { 99 | "sval": "id", 100 | }, 101 | }, 102 | ], 103 | "location": 58, 104 | }, 105 | }, 106 | }, 107 | }, 108 | ], 109 | "selectStmt": { 110 | "SelectStmt": { 111 | "limitOption": "LIMIT_OPTION_DEFAULT", 112 | "op": "SETOP_NONE", 113 | "valuesLists": [ 114 | { 115 | "List": { 116 | "items": [ 117 | { 118 | "A_Const": { 119 | "location": 43, 120 | "sval": { 121 | "sval": "f", 122 | }, 123 | }, 124 | }, 125 | ], 126 | }, 127 | }, 128 | ], 129 | }, 130 | }, 131 | }, 132 | }, 133 | }, 134 | }, 135 | }, 136 | ], 137 | "version": 160001, 138 | }, 139 | ] 140 | `; 141 | 142 | exports[`copydml.sql > line 26 1`] = ` 143 | [ 144 | "copy (insert into copydml_test default values) to stdout", 145 | { 146 | "stmts": [ 147 | { 148 | "stmt": { 149 | "CopyStmt": { 150 | "query": { 151 | "InsertStmt": { 152 | "override": "OVERRIDING_NOT_SET", 153 | "relation": { 154 | "inh": true, 155 | "location": 18, 156 | "relname": "copydml_test", 157 | "relpersistence": "p", 158 | }, 159 | }, 160 | }, 161 | }, 162 | }, 163 | }, 164 | ], 165 | "version": 160001, 166 | }, 167 | ] 168 | `; 169 | 170 | exports[`copydml.sql > line 30 1`] = ` 171 | [ 172 | "create rule qqq as on insert to copydml_test do instead nothing", 173 | { 174 | "stmts": [ 175 | { 176 | "stmt": { 177 | "RuleStmt": { 178 | "event": "CMD_INSERT", 179 | "instead": true, 180 | "relation": { 181 | "inh": true, 182 | "location": 32, 183 | "relname": "copydml_test", 184 | "relpersistence": "p", 185 | }, 186 | "rulename": "qqq", 187 | }, 188 | }, 189 | }, 190 | ], 191 | "version": 160001, 192 | }, 193 | ] 194 | `; 195 | 196 | exports[`copydml.sql > line 43 1`] = ` 197 | [ 198 | "create rule qqq as on update to copydml_test do instead nothing", 199 | { 200 | "stmts": [ 201 | { 202 | "stmt": { 203 | "RuleStmt": { 204 | "event": "CMD_UPDATE", 205 | "instead": true, 206 | "relation": { 207 | "inh": true, 208 | "location": 32, 209 | "relname": "copydml_test", 210 | "relpersistence": "p", 211 | }, 212 | "rulename": "qqq", 213 | }, 214 | }, 215 | }, 216 | ], 217 | "version": 160001, 218 | }, 219 | ] 220 | `; 221 | 222 | exports[`copydml.sql > line 56 1`] = ` 223 | [ 224 | "create rule qqq as on delete to copydml_test do instead nothing", 225 | { 226 | "stmts": [ 227 | { 228 | "stmt": { 229 | "RuleStmt": { 230 | "event": "CMD_DELETE", 231 | "instead": true, 232 | "relation": { 233 | "inh": true, 234 | "location": 32, 235 | "relname": "copydml_test", 236 | "relpersistence": "p", 237 | }, 238 | "rulename": "qqq", 239 | }, 240 | }, 241 | }, 242 | ], 243 | "version": 160001, 244 | }, 245 | ] 246 | `; 247 | 248 | exports[`copydml.sql > line 70 1`] = ` 249 | [ 250 | "create function qqq_trig() returns trigger as $$ 251 | begin 252 | if tg_op in ('INSERT', 'UPDATE') then 253 | raise notice '% % %', tg_when, tg_op, new.id; 254 | return new; 255 | else 256 | raise notice '% % %', tg_when, tg_op, old.id; 257 | return old; 258 | end if; 259 | end 260 | $$ language plpgsql", 261 | { 262 | "stmts": [ 263 | { 264 | "stmt": { 265 | "CreateFunctionStmt": { 266 | "funcname": [ 267 | { 268 | "String": { 269 | "sval": "qqq_trig", 270 | }, 271 | }, 272 | ], 273 | "options": [ 274 | { 275 | "DefElem": { 276 | "arg": { 277 | "List": { 278 | "items": [ 279 | { 280 | "String": { 281 | "sval": " 282 | begin 283 | if tg_op in ('INSERT', 'UPDATE') then 284 | raise notice '% % %', tg_when, tg_op, new.id; 285 | return new; 286 | else 287 | raise notice '% % %', tg_when, tg_op, old.id; 288 | return old; 289 | end if; 290 | end 291 | ", 292 | }, 293 | }, 294 | ], 295 | }, 296 | }, 297 | "defaction": "DEFELEM_UNSPEC", 298 | "defname": "as", 299 | "location": 43, 300 | }, 301 | }, 302 | { 303 | "DefElem": { 304 | "arg": { 305 | "String": { 306 | "sval": "plpgsql", 307 | }, 308 | }, 309 | "defaction": "DEFELEM_UNSPEC", 310 | "defname": "language", 311 | "location": 245, 312 | }, 313 | }, 314 | ], 315 | "returnType": { 316 | "location": 35, 317 | "names": [ 318 | { 319 | "String": { 320 | "sval": "trigger", 321 | }, 322 | }, 323 | ], 324 | "typemod": -1, 325 | }, 326 | }, 327 | }, 328 | }, 329 | ], 330 | "version": 160001, 331 | }, 332 | ] 333 | `; 334 | 335 | exports[`copydml.sql > line 90 1`] = ` 336 | [ 337 | "drop table copydml_test", 338 | { 339 | "stmts": [ 340 | { 341 | "stmt": { 342 | "DropStmt": { 343 | "behavior": "DROP_RESTRICT", 344 | "objects": [ 345 | { 346 | "List": { 347 | "items": [ 348 | { 349 | "String": { 350 | "sval": "copydml_test", 351 | }, 352 | }, 353 | ], 354 | }, 355 | }, 356 | ], 357 | "removeType": "OBJECT_TABLE", 358 | }, 359 | }, 360 | }, 361 | ], 362 | "version": 160001, 363 | }, 364 | ] 365 | `; 366 | -------------------------------------------------------------------------------- /src/lib/tokens.ts: -------------------------------------------------------------------------------- 1 | // Synced from https://github.com/pganalyze/libpg_query/blob/16-latest/protobuf/pg_query.proto 2 | 3 | export type KeywordKind = 4 | | 'NO_KEYWORD' 5 | | 'UNRESERVED_KEYWORD' 6 | | 'COL_NAME_KEYWORD' 7 | | 'TYPE_FUNC_NAME_KEYWORD' 8 | | 'RESERVED_KEYWORD' 9 | 10 | export type TokenKind = 11 | | 'NUL' 12 | // Single-character tokens that are returned 1:1 (identical with "self" list in scan.l) 13 | // Either supporting syntax, or single-character operators (some can be both) 14 | // Also see https://www.postgresql.org/docs/12/sql-syntax-lexical.html#SQL-SYNTAX-SPECIAL-CHARS 15 | | 'ASCII_36' // "$" 16 | | 'ASCII_37' // "%" 17 | | 'ASCII_40' // "(" 18 | | 'ASCII_41' // ")" 19 | | 'ASCII_42' // "*" 20 | | 'ASCII_43' // "+" 21 | | 'ASCII_44' // "," 22 | | 'ASCII_45' // "-" 23 | | 'ASCII_46' // "." 24 | | 'ASCII_47' // "/" 25 | | 'ASCII_58' // ":" 26 | | 'ASCII_59' // ";" 27 | | 'ASCII_60' // "<" 28 | | 'ASCII_61' // "=" 29 | | 'ASCII_62' // ">" 30 | | 'ASCII_63' // "?" 31 | | 'ASCII_91' // "[" 32 | | 'ASCII_92' // "\" 33 | | 'ASCII_93' // "]" 34 | | 'ASCII_94' // "^" 35 | // Named tokens in scan.l 36 | | 'IDENT' 37 | | 'UIDENT' 38 | | 'FCONST' 39 | | 'SCONST' 40 | | 'USCONST' 41 | | 'BCONST' 42 | | 'XCONST' 43 | | 'Op' 44 | | 'ICONST' 45 | | 'PARAM' 46 | | 'TYPECAST' 47 | | 'DOT_DOT' 48 | | 'COLON_EQUALS' 49 | | 'EQUALS_GREATER' 50 | | 'LESS_EQUALS' 51 | | 'GREATER_EQUALS' 52 | | 'NOT_EQUALS' 53 | | 'SQL_COMMENT' 54 | | 'C_COMMENT' 55 | | 'ABORT_P' 56 | | 'ABSENT' 57 | | 'ABSOLUTE_P' 58 | | 'ACCESS' 59 | | 'ACTION' 60 | | 'ADD_P' 61 | | 'ADMIN' 62 | | 'AFTER' 63 | | 'AGGREGATE' 64 | | 'ALL' 65 | | 'ALSO' 66 | | 'ALTER' 67 | | 'ALWAYS' 68 | | 'ANALYSE' 69 | | 'ANALYZE' 70 | | 'AND' 71 | | 'ANY' 72 | | 'ARRAY' 73 | | 'AS' 74 | | 'ASC' 75 | | 'ASENSITIVE' 76 | | 'ASSERTION' 77 | | 'ASSIGNMENT' 78 | | 'ASYMMETRIC' 79 | | 'ATOMIC' 80 | | 'AT' 81 | | 'ATTACH' 82 | | 'ATTRIBUTE' 83 | | 'AUTHORIZATION' 84 | | 'BACKWARD' 85 | | 'BEFORE' 86 | | 'BEGIN_P' 87 | | 'BETWEEN' 88 | | 'BIGINT' 89 | | 'BINARY' 90 | | 'BIT' 91 | | 'BOOLEAN_P' 92 | | 'BOTH' 93 | | 'BREADTH' 94 | | 'BY' 95 | | 'CACHE' 96 | | 'CALL' 97 | | 'CALLED' 98 | | 'CASCADE' 99 | | 'CASCADED' 100 | | 'CASE' 101 | | 'CAST' 102 | | 'CATALOG_P' 103 | | 'CHAIN' 104 | | 'CHAR_P' 105 | | 'CHARACTER' 106 | | 'CHARACTERISTICS' 107 | | 'CHECK' 108 | | 'CHECKPOINT' 109 | | 'CLASS' 110 | | 'CLOSE' 111 | | 'CLUSTER' 112 | | 'COALESCE' 113 | | 'COLLATE' 114 | | 'COLLATION' 115 | | 'COLUMN' 116 | | 'COLUMNS' 117 | | 'COMMENT' 118 | | 'COMMENTS' 119 | | 'COMMIT' 120 | | 'COMMITTED' 121 | | 'COMPRESSION' 122 | | 'CONCURRENTLY' 123 | | 'CONFIGURATION' 124 | | 'CONFLICT' 125 | | 'CONNECTION' 126 | | 'CONSTRAINT' 127 | | 'CONSTRAINTS' 128 | | 'CONTENT_P' 129 | | 'CONTINUE_P' 130 | | 'CONVERSION_P' 131 | | 'COPY' 132 | | 'COST' 133 | | 'CREATE' 134 | | 'CROSS' 135 | | 'CSV' 136 | | 'CUBE' 137 | | 'CURRENT_P' 138 | | 'CURRENT_CATALOG' 139 | | 'CURRENT_DATE' 140 | | 'CURRENT_ROLE' 141 | | 'CURRENT_SCHEMA' 142 | | 'CURRENT_TIME' 143 | | 'CURRENT_TIMESTAMP' 144 | | 'CURRENT_USER' 145 | | 'CURSOR' 146 | | 'CYCLE' 147 | | 'DATA_P' 148 | | 'DATABASE' 149 | | 'DAY_P' 150 | | 'DEALLOCATE' 151 | | 'DEC' 152 | | 'DECIMAL_P' 153 | | 'DECLARE' 154 | | 'DEFAULT' 155 | | 'DEFAULTS' 156 | | 'DEFERRABLE' 157 | | 'DEFERRED' 158 | | 'DEFINER' 159 | | 'DELETE_P' 160 | | 'DELIMITER' 161 | | 'DELIMITERS' 162 | | 'DEPENDS' 163 | | 'DEPTH' 164 | | 'DESC' 165 | | 'DETACH' 166 | | 'DICTIONARY' 167 | | 'DISABLE_P' 168 | | 'DISCARD' 169 | | 'DISTINCT' 170 | | 'DO' 171 | | 'DOCUMENT_P' 172 | | 'DOMAIN_P' 173 | | 'DOUBLE_P' 174 | | 'DROP' 175 | | 'EACH' 176 | | 'ELSE' 177 | | 'ENABLE_P' 178 | | 'ENCODING' 179 | | 'ENCRYPTED' 180 | | 'END_P' 181 | | 'ENUM_P' 182 | | 'ESCAPE' 183 | | 'EVENT' 184 | | 'EXCEPT' 185 | | 'EXCLUDE' 186 | | 'EXCLUDING' 187 | | 'EXCLUSIVE' 188 | | 'EXECUTE' 189 | | 'EXISTS' 190 | | 'EXPLAIN' 191 | | 'EXPRESSION' 192 | | 'EXTENSION' 193 | | 'EXTERNAL' 194 | | 'EXTRACT' 195 | | 'FALSE_P' 196 | | 'FAMILY' 197 | | 'FETCH' 198 | | 'FILTER' 199 | | 'FINALIZE' 200 | | 'FIRST_P' 201 | | 'FLOAT_P' 202 | | 'FOLLOWING' 203 | | 'FOR' 204 | | 'FORCE' 205 | | 'FOREIGN' 206 | | 'FORMAT' 207 | | 'FORWARD' 208 | | 'FREEZE' 209 | | 'FROM' 210 | | 'FULL' 211 | | 'FUNCTION' 212 | | 'FUNCTIONS' 213 | | 'GENERATED' 214 | | 'GLOBAL' 215 | | 'GRANT' 216 | | 'GRANTED' 217 | | 'GREATEST' 218 | | 'GROUP_P' 219 | | 'GROUPING' 220 | | 'GROUPS' 221 | | 'HANDLER' 222 | | 'HAVING' 223 | | 'HEADER_P' 224 | | 'HOLD' 225 | | 'HOUR_P' 226 | | 'IDENTITY_P' 227 | | 'IF_P' 228 | | 'ILIKE' 229 | | 'IMMEDIATE' 230 | | 'IMMUTABLE' 231 | | 'IMPLICIT_P' 232 | | 'IMPORT_P' 233 | | 'IN_P' 234 | | 'INCLUDE' 235 | | 'INCLUDING' 236 | | 'INCREMENT' 237 | | 'INDENT' 238 | | 'INDEX' 239 | | 'INDEXES' 240 | | 'INHERIT' 241 | | 'INHERITS' 242 | | 'INITIALLY' 243 | | 'INLINE_P' 244 | | 'INNER_P' 245 | | 'INOUT' 246 | | 'INPUT_P' 247 | | 'INSENSITIVE' 248 | | 'INSERT' 249 | | 'INSTEAD' 250 | | 'INT_P' 251 | | 'INTEGER' 252 | | 'INTERSECT' 253 | | 'INTERVAL' 254 | | 'INTO' 255 | | 'INVOKER' 256 | | 'IS' 257 | | 'ISNULL' 258 | | 'ISOLATION' 259 | | 'JOIN' 260 | | 'JSON' 261 | | 'JSON_ARRAY' 262 | | 'JSON_ARRAYAGG' 263 | | 'JSON_OBJECT' 264 | | 'JSON_OBJECTAGG' 265 | | 'KEY' 266 | | 'KEYS' 267 | | 'LABEL' 268 | | 'LANGUAGE' 269 | | 'LARGE_P' 270 | | 'LAST_P' 271 | | 'LATERAL_P' 272 | | 'LEADING' 273 | | 'LEAKPROOF' 274 | | 'LEAST' 275 | | 'LEFT' 276 | | 'LEVEL' 277 | | 'LIKE' 278 | | 'LIMIT' 279 | | 'LISTEN' 280 | | 'LOAD' 281 | | 'LOCAL' 282 | | 'LOCALTIME' 283 | | 'LOCALTIMESTAMP' 284 | | 'LOCATION' 285 | | 'LOCK_P' 286 | | 'LOCKED' 287 | | 'LOGGED' 288 | | 'MAPPING' 289 | | 'MATCH' 290 | | 'MATCHED' 291 | | 'MATERIALIZED' 292 | | 'MAXVALUE' 293 | | 'MERGE' 294 | | 'METHOD' 295 | | 'MINUTE_P' 296 | | 'MINVALUE' 297 | | 'MODE' 298 | | 'MONTH_P' 299 | | 'MOVE' 300 | | 'NAME_P' 301 | | 'NAMES' 302 | | 'NATIONAL' 303 | | 'NATURAL' 304 | | 'NCHAR' 305 | | 'NEW' 306 | | 'NEXT' 307 | | 'NFC' 308 | | 'NFD' 309 | | 'NFKC' 310 | | 'NFKD' 311 | | 'NO' 312 | | 'NONE' 313 | | 'NORMALIZE' 314 | | 'NORMALIZED' 315 | | 'NOT' 316 | | 'NOTHING' 317 | | 'NOTIFY' 318 | | 'NOTNULL' 319 | | 'NOWAIT' 320 | | 'NULL_P' 321 | | 'NULLIF' 322 | | 'NULLS_P' 323 | | 'NUMERIC' 324 | | 'OBJECT_P' 325 | | 'OF' 326 | | 'OFF' 327 | | 'OFFSET' 328 | | 'OIDS' 329 | | 'OLD' 330 | | 'ON' 331 | | 'ONLY' 332 | | 'OPERATOR' 333 | | 'OPTION' 334 | | 'OPTIONS' 335 | | 'OR' 336 | | 'ORDER' 337 | | 'ORDINALITY' 338 | | 'OTHERS' 339 | | 'OUT_P' 340 | | 'OUTER_P' 341 | | 'OVER' 342 | | 'OVERLAPS' 343 | | 'OVERLAY' 344 | | 'OVERRIDING' 345 | | 'OWNED' 346 | | 'OWNER' 347 | | 'PARALLEL' 348 | | 'PARAMETER' 349 | | 'PARSER' 350 | | 'PARTIAL' 351 | | 'PARTITION' 352 | | 'PASSING' 353 | | 'PASSWORD' 354 | | 'PLACING' 355 | | 'PLANS' 356 | | 'POLICY' 357 | | 'POSITION' 358 | | 'PRECEDING' 359 | | 'PRECISION' 360 | | 'PRESERVE' 361 | | 'PREPARE' 362 | | 'PREPARED' 363 | | 'PRIMARY' 364 | | 'PRIOR' 365 | | 'PRIVILEGES' 366 | | 'PROCEDURAL' 367 | | 'PROCEDURE' 368 | | 'PROCEDURES' 369 | | 'PROGRAM' 370 | | 'PUBLICATION' 371 | | 'QUOTE' 372 | | 'RANGE' 373 | | 'READ' 374 | | 'REAL' 375 | | 'REASSIGN' 376 | | 'RECHECK' 377 | | 'RECURSIVE' 378 | | 'REF_P' 379 | | 'REFERENCES' 380 | | 'REFERENCING' 381 | | 'REFRESH' 382 | | 'REINDEX' 383 | | 'RELATIVE_P' 384 | | 'RELEASE' 385 | | 'RENAME' 386 | | 'REPEATABLE' 387 | | 'REPLACE' 388 | | 'REPLICA' 389 | | 'RESET' 390 | | 'RESTART' 391 | | 'RESTRICT' 392 | | 'RETURN' 393 | | 'RETURNING' 394 | | 'RETURNS' 395 | | 'REVOKE' 396 | | 'RIGHT' 397 | | 'ROLE' 398 | | 'ROLLBACK' 399 | | 'ROLLUP' 400 | | 'ROUTINE' 401 | | 'ROUTINES' 402 | | 'ROW' 403 | | 'ROWS' 404 | | 'RULE' 405 | | 'SAVEPOINT' 406 | | 'SCALAR' 407 | | 'SCHEMA' 408 | | 'SCHEMAS' 409 | | 'SCROLL' 410 | | 'SEARCH' 411 | | 'SECOND_P' 412 | | 'SECURITY' 413 | | 'SELECT' 414 | | 'SEQUENCE' 415 | | 'SEQUENCES' 416 | | 'SERIALIZABLE' 417 | | 'SERVER' 418 | | 'SESSION' 419 | | 'SESSION_USER' 420 | | 'SET' 421 | | 'SETS' 422 | | 'SETOF' 423 | | 'SHARE' 424 | | 'SHOW' 425 | | 'SIMILAR' 426 | | 'SIMPLE' 427 | | 'SKIP' 428 | | 'SMALLINT' 429 | | 'SNAPSHOT' 430 | | 'SOME' 431 | | 'SQL_P' 432 | | 'STABLE' 433 | | 'STANDALONE_P' 434 | | 'START' 435 | | 'STATEMENT' 436 | | 'STATISTICS' 437 | | 'STDIN' 438 | | 'STDOUT' 439 | | 'STORAGE' 440 | | 'STORED' 441 | | 'STRICT_P' 442 | | 'STRIP_P' 443 | | 'SUBSCRIPTION' 444 | | 'SUBSTRING' 445 | | 'SUPPORT' 446 | | 'SYMMETRIC' 447 | | 'SYSID' 448 | | 'SYSTEM_P' 449 | | 'SYSTEM_USER' 450 | | 'TABLE' 451 | | 'TABLES' 452 | | 'TABLESAMPLE' 453 | | 'TABLESPACE' 454 | | 'TEMP' 455 | | 'TEMPLATE' 456 | | 'TEMPORARY' 457 | | 'TEXT_P' 458 | | 'THEN' 459 | | 'TIES' 460 | | 'TIME' 461 | | 'TIMESTAMP' 462 | | 'TO' 463 | | 'TRAILING' 464 | | 'TRANSACTION' 465 | | 'TRANSFORM' 466 | | 'TREAT' 467 | | 'TRIGGER' 468 | | 'TRIM' 469 | | 'TRUE_P' 470 | | 'TRUNCATE' 471 | | 'TRUSTED' 472 | | 'TYPE_P' 473 | | 'TYPES_P' 474 | | 'UESCAPE' 475 | | 'UNBOUNDED' 476 | | 'UNCOMMITTED' 477 | | 'UNENCRYPTED' 478 | | 'UNION' 479 | | 'UNIQUE' 480 | | 'UNKNOWN' 481 | | 'UNLISTEN' 482 | | 'UNLOGGED' 483 | | 'UNTIL' 484 | | 'UPDATE' 485 | | 'USER' 486 | | 'USING' 487 | | 'VACUUM' 488 | | 'VALID' 489 | | 'VALIDATE' 490 | | 'VALIDATOR' 491 | | 'VALUE_P' 492 | | 'VALUES' 493 | | 'VARCHAR' 494 | | 'VARIADIC' 495 | | 'VARYING' 496 | | 'VERBOSE' 497 | | 'VERSION_P' 498 | | 'VIEW' 499 | | 'VIEWS' 500 | | 'VOLATILE' 501 | | 'WHEN' 502 | | 'WHERE' 503 | | 'WHITESPACE_P' 504 | | 'WINDOW' 505 | | 'WITH' 506 | | 'WITHIN' 507 | | 'WITHOUT' 508 | | 'WORK' 509 | | 'WRAPPER' 510 | | 'WRITE' 511 | | 'XML_P' 512 | | 'XMLATTRIBUTES' 513 | | 'XMLCONCAT' 514 | | 'XMLELEMENT' 515 | | 'XMLEXISTS' 516 | | 'XMLFOREST' 517 | | 'XMLNAMESPACES' 518 | | 'XMLPARSE' 519 | | 'XMLPI' 520 | | 'XMLROOT' 521 | | 'XMLSERIALIZE' 522 | | 'XMLTABLE' 523 | | 'YEAR_P' 524 | | 'YES_P' 525 | | 'ZONE' 526 | | 'FORMAT_LA' 527 | | 'NOT_LA' 528 | | 'NULLS_LA' 529 | | 'WITH_LA' 530 | | 'WITHOUT_LA' 531 | | 'MODE_TYPE_NAME' 532 | | 'MODE_PLPGSQL_EXPR' 533 | | 'MODE_PLPGSQL_ASSIGN1' 534 | | 'MODE_PLPGSQL_ASSIGN2' 535 | | 'MODE_PLPGSQL_ASSIGN3' 536 | | 'UMINUS' 537 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/path.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`path.sql > line 9 1`] = ` 4 | [ 5 | "INSERT INTO PATH_TBL VALUES ('[(1,2),(3,4)]')", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "InsertStmt": { 11 | "override": "OVERRIDING_NOT_SET", 12 | "relation": { 13 | "inh": true, 14 | "location": 12, 15 | "relname": "path_tbl", 16 | "relpersistence": "p", 17 | }, 18 | "selectStmt": { 19 | "SelectStmt": { 20 | "limitOption": "LIMIT_OPTION_DEFAULT", 21 | "op": "SETOP_NONE", 22 | "valuesLists": [ 23 | { 24 | "List": { 25 | "items": [ 26 | { 27 | "A_Const": { 28 | "location": 29, 29 | "sval": { 30 | "sval": "[(1,2),(3,4)]", 31 | }, 32 | }, 33 | }, 34 | ], 35 | }, 36 | }, 37 | ], 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | ], 44 | "version": 160001, 45 | }, 46 | ] 47 | `; 48 | 49 | exports[`path.sql > line 38 1`] = ` 50 | [ 51 | "SELECT f1 AS open_path FROM PATH_TBL WHERE isopen(f1)", 52 | { 53 | "stmts": [ 54 | { 55 | "stmt": { 56 | "SelectStmt": { 57 | "fromClause": [ 58 | { 59 | "RangeVar": { 60 | "inh": true, 61 | "location": 28, 62 | "relname": "path_tbl", 63 | "relpersistence": "p", 64 | }, 65 | }, 66 | ], 67 | "limitOption": "LIMIT_OPTION_DEFAULT", 68 | "op": "SETOP_NONE", 69 | "targetList": [ 70 | { 71 | "ResTarget": { 72 | "location": 7, 73 | "name": "open_path", 74 | "val": { 75 | "ColumnRef": { 76 | "fields": [ 77 | { 78 | "String": { 79 | "sval": "f1", 80 | }, 81 | }, 82 | ], 83 | "location": 7, 84 | }, 85 | }, 86 | }, 87 | }, 88 | ], 89 | "whereClause": { 90 | "FuncCall": { 91 | "args": [ 92 | { 93 | "ColumnRef": { 94 | "fields": [ 95 | { 96 | "String": { 97 | "sval": "f1", 98 | }, 99 | }, 100 | ], 101 | "location": 50, 102 | }, 103 | }, 104 | ], 105 | "funcformat": "COERCE_EXPLICIT_CALL", 106 | "funcname": [ 107 | { 108 | "String": { 109 | "sval": "isopen", 110 | }, 111 | }, 112 | ], 113 | "location": 43, 114 | }, 115 | }, 116 | }, 117 | }, 118 | }, 119 | ], 120 | "version": 160001, 121 | }, 122 | ] 123 | `; 124 | 125 | exports[`path.sql > line 40 1`] = ` 126 | [ 127 | "SELECT f1 AS closed_path FROM PATH_TBL WHERE isclosed(f1)", 128 | { 129 | "stmts": [ 130 | { 131 | "stmt": { 132 | "SelectStmt": { 133 | "fromClause": [ 134 | { 135 | "RangeVar": { 136 | "inh": true, 137 | "location": 30, 138 | "relname": "path_tbl", 139 | "relpersistence": "p", 140 | }, 141 | }, 142 | ], 143 | "limitOption": "LIMIT_OPTION_DEFAULT", 144 | "op": "SETOP_NONE", 145 | "targetList": [ 146 | { 147 | "ResTarget": { 148 | "location": 7, 149 | "name": "closed_path", 150 | "val": { 151 | "ColumnRef": { 152 | "fields": [ 153 | { 154 | "String": { 155 | "sval": "f1", 156 | }, 157 | }, 158 | ], 159 | "location": 7, 160 | }, 161 | }, 162 | }, 163 | }, 164 | ], 165 | "whereClause": { 166 | "FuncCall": { 167 | "args": [ 168 | { 169 | "ColumnRef": { 170 | "fields": [ 171 | { 172 | "String": { 173 | "sval": "f1", 174 | }, 175 | }, 176 | ], 177 | "location": 54, 178 | }, 179 | }, 180 | ], 181 | "funcformat": "COERCE_EXPLICIT_CALL", 182 | "funcname": [ 183 | { 184 | "String": { 185 | "sval": "isclosed", 186 | }, 187 | }, 188 | ], 189 | "location": 45, 190 | }, 191 | }, 192 | }, 193 | }, 194 | }, 195 | ], 196 | "version": 160001, 197 | }, 198 | ] 199 | `; 200 | 201 | exports[`path.sql > line 42 1`] = ` 202 | [ 203 | "SELECT pclose(f1) AS closed_path FROM PATH_TBL", 204 | { 205 | "stmts": [ 206 | { 207 | "stmt": { 208 | "SelectStmt": { 209 | "fromClause": [ 210 | { 211 | "RangeVar": { 212 | "inh": true, 213 | "location": 38, 214 | "relname": "path_tbl", 215 | "relpersistence": "p", 216 | }, 217 | }, 218 | ], 219 | "limitOption": "LIMIT_OPTION_DEFAULT", 220 | "op": "SETOP_NONE", 221 | "targetList": [ 222 | { 223 | "ResTarget": { 224 | "location": 7, 225 | "name": "closed_path", 226 | "val": { 227 | "FuncCall": { 228 | "args": [ 229 | { 230 | "ColumnRef": { 231 | "fields": [ 232 | { 233 | "String": { 234 | "sval": "f1", 235 | }, 236 | }, 237 | ], 238 | "location": 14, 239 | }, 240 | }, 241 | ], 242 | "funcformat": "COERCE_EXPLICIT_CALL", 243 | "funcname": [ 244 | { 245 | "String": { 246 | "sval": "pclose", 247 | }, 248 | }, 249 | ], 250 | "location": 7, 251 | }, 252 | }, 253 | }, 254 | }, 255 | ], 256 | }, 257 | }, 258 | }, 259 | ], 260 | "version": 160001, 261 | }, 262 | ] 263 | `; 264 | 265 | exports[`path.sql > line 44 1`] = ` 266 | [ 267 | "SELECT popen(f1) AS open_path FROM PATH_TBL", 268 | { 269 | "stmts": [ 270 | { 271 | "stmt": { 272 | "SelectStmt": { 273 | "fromClause": [ 274 | { 275 | "RangeVar": { 276 | "inh": true, 277 | "location": 35, 278 | "relname": "path_tbl", 279 | "relpersistence": "p", 280 | }, 281 | }, 282 | ], 283 | "limitOption": "LIMIT_OPTION_DEFAULT", 284 | "op": "SETOP_NONE", 285 | "targetList": [ 286 | { 287 | "ResTarget": { 288 | "location": 7, 289 | "name": "open_path", 290 | "val": { 291 | "FuncCall": { 292 | "args": [ 293 | { 294 | "ColumnRef": { 295 | "fields": [ 296 | { 297 | "String": { 298 | "sval": "f1", 299 | }, 300 | }, 301 | ], 302 | "location": 13, 303 | }, 304 | }, 305 | ], 306 | "funcformat": "COERCE_EXPLICIT_CALL", 307 | "funcname": [ 308 | { 309 | "String": { 310 | "sval": "popen", 311 | }, 312 | }, 313 | ], 314 | "location": 7, 315 | }, 316 | }, 317 | }, 318 | }, 319 | ], 320 | }, 321 | }, 322 | }, 323 | ], 324 | "version": 160001, 325 | }, 326 | ] 327 | `; 328 | 329 | exports[`path.sql > line 47 1`] = ` 330 | [ 331 | "SELECT pg_input_is_valid('[(1,2),(3)]', 'path')", 332 | { 333 | "stmts": [ 334 | { 335 | "stmt": { 336 | "SelectStmt": { 337 | "limitOption": "LIMIT_OPTION_DEFAULT", 338 | "op": "SETOP_NONE", 339 | "targetList": [ 340 | { 341 | "ResTarget": { 342 | "location": 7, 343 | "val": { 344 | "FuncCall": { 345 | "args": [ 346 | { 347 | "A_Const": { 348 | "location": 25, 349 | "sval": { 350 | "sval": "[(1,2),(3)]", 351 | }, 352 | }, 353 | }, 354 | { 355 | "A_Const": { 356 | "location": 40, 357 | "sval": { 358 | "sval": "path", 359 | }, 360 | }, 361 | }, 362 | ], 363 | "funcformat": "COERCE_EXPLICIT_CALL", 364 | "funcname": [ 365 | { 366 | "String": { 367 | "sval": "pg_input_is_valid", 368 | }, 369 | }, 370 | ], 371 | "location": 7, 372 | }, 373 | }, 374 | }, 375 | }, 376 | ], 377 | }, 378 | }, 379 | }, 380 | ], 381 | "version": 160001, 382 | }, 383 | ] 384 | `; 385 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/security_label.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`security_label.sql > line 6 1`] = ` 4 | [ 5 | "SET client_min_messages TO 'warning'", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "VariableSetStmt": { 11 | "args": [ 12 | { 13 | "A_Const": { 14 | "location": 27, 15 | "sval": { 16 | "sval": "warning", 17 | }, 18 | }, 19 | }, 20 | ], 21 | "kind": "VAR_SET_VALUE", 22 | "name": "client_min_messages", 23 | }, 24 | }, 25 | }, 26 | ], 27 | "version": 160001, 28 | }, 29 | ] 30 | `; 31 | 32 | exports[`security_label.sql > line 8 1`] = ` 33 | [ 34 | "DROP ROLE IF EXISTS regress_seclabel_user1", 35 | { 36 | "stmts": [ 37 | { 38 | "stmt": { 39 | "DropRoleStmt": { 40 | "missing_ok": true, 41 | "roles": [ 42 | { 43 | "RoleSpec": { 44 | "location": 20, 45 | "rolename": "regress_seclabel_user1", 46 | "roletype": "ROLESPEC_CSTRING", 47 | }, 48 | }, 49 | ], 50 | }, 51 | }, 52 | }, 53 | ], 54 | "version": 160001, 55 | }, 56 | ] 57 | `; 58 | 59 | exports[`security_label.sql > line 11 1`] = ` 60 | [ 61 | "RESET client_min_messages", 62 | { 63 | "stmts": [ 64 | { 65 | "stmt": { 66 | "VariableSetStmt": { 67 | "kind": "VAR_RESET", 68 | "name": "client_min_messages", 69 | }, 70 | }, 71 | }, 72 | ], 73 | "version": 160001, 74 | }, 75 | ] 76 | `; 77 | 78 | exports[`security_label.sql > line 13 1`] = ` 79 | [ 80 | "CREATE USER regress_seclabel_user1 WITH CREATEROLE", 81 | { 82 | "stmts": [ 83 | { 84 | "stmt": { 85 | "CreateRoleStmt": { 86 | "options": [ 87 | { 88 | "DefElem": { 89 | "arg": { 90 | "Boolean": { 91 | "boolval": true, 92 | }, 93 | }, 94 | "defaction": "DEFELEM_UNSPEC", 95 | "defname": "createrole", 96 | "location": 40, 97 | }, 98 | }, 99 | ], 100 | "role": "regress_seclabel_user1", 101 | "stmt_type": "ROLESTMT_USER", 102 | }, 103 | }, 104 | }, 105 | ], 106 | "version": 160001, 107 | }, 108 | ] 109 | `; 110 | 111 | exports[`security_label.sql > line 16 1`] = ` 112 | [ 113 | "CREATE TABLE seclabel_tbl1 (a int, b text)", 114 | { 115 | "stmts": [ 116 | { 117 | "stmt": { 118 | "CreateStmt": { 119 | "oncommit": "ONCOMMIT_NOOP", 120 | "relation": { 121 | "inh": true, 122 | "location": 13, 123 | "relname": "seclabel_tbl1", 124 | "relpersistence": "p", 125 | }, 126 | "tableElts": [ 127 | { 128 | "ColumnDef": { 129 | "colname": "a", 130 | "is_local": true, 131 | "location": 28, 132 | "typeName": { 133 | "location": 30, 134 | "names": [ 135 | { 136 | "String": { 137 | "sval": "pg_catalog", 138 | }, 139 | }, 140 | { 141 | "String": { 142 | "sval": "int4", 143 | }, 144 | }, 145 | ], 146 | "typemod": -1, 147 | }, 148 | }, 149 | }, 150 | { 151 | "ColumnDef": { 152 | "colname": "b", 153 | "is_local": true, 154 | "location": 35, 155 | "typeName": { 156 | "location": 37, 157 | "names": [ 158 | { 159 | "String": { 160 | "sval": "text", 161 | }, 162 | }, 163 | ], 164 | "typemod": -1, 165 | }, 166 | }, 167 | }, 168 | ], 169 | }, 170 | }, 171 | }, 172 | ], 173 | "version": 160001, 174 | }, 175 | ] 176 | `; 177 | 178 | exports[`security_label.sql > line 22 1`] = ` 179 | [ 180 | "ALTER TABLE seclabel_tbl1 OWNER TO regress_seclabel_user1", 181 | { 182 | "stmts": [ 183 | { 184 | "stmt": { 185 | "AlterTableStmt": { 186 | "cmds": [ 187 | { 188 | "AlterTableCmd": { 189 | "behavior": "DROP_RESTRICT", 190 | "newowner": { 191 | "location": 35, 192 | "rolename": "regress_seclabel_user1", 193 | "roletype": "ROLESPEC_CSTRING", 194 | }, 195 | "subtype": "AT_ChangeOwner", 196 | }, 197 | }, 198 | ], 199 | "objtype": "OBJECT_TABLE", 200 | "relation": { 201 | "inh": true, 202 | "location": 12, 203 | "relname": "seclabel_tbl1", 204 | "relpersistence": "p", 205 | }, 206 | }, 207 | }, 208 | }, 209 | ], 210 | "version": 160001, 211 | }, 212 | ] 213 | `; 214 | 215 | exports[`security_label.sql > line 28 1`] = ` 216 | [ 217 | "SECURITY LABEL ON TABLE seclabel_tbl1 IS 'classified'", 218 | { 219 | "stmts": [ 220 | { 221 | "stmt": { 222 | "SecLabelStmt": { 223 | "label": "classified", 224 | "object": { 225 | "List": { 226 | "items": [ 227 | { 228 | "String": { 229 | "sval": "seclabel_tbl1", 230 | }, 231 | }, 232 | ], 233 | }, 234 | }, 235 | "objtype": "OBJECT_TABLE", 236 | }, 237 | }, 238 | }, 239 | ], 240 | "version": 160001, 241 | }, 242 | ] 243 | `; 244 | 245 | exports[`security_label.sql > line 28 2`] = ` 246 | [ 247 | " -- fail 248 | SECURITY LABEL FOR 'dummy' ON TABLE seclabel_tbl1 IS 'classified'", 249 | { 250 | "stmts": [ 251 | { 252 | "stmt": { 253 | "SecLabelStmt": { 254 | "label": "classified", 255 | "object": { 256 | "List": { 257 | "items": [ 258 | { 259 | "String": { 260 | "sval": "seclabel_tbl1", 261 | }, 262 | }, 263 | ], 264 | }, 265 | }, 266 | "objtype": "OBJECT_TABLE", 267 | "provider": "dummy", 268 | }, 269 | }, 270 | }, 271 | ], 272 | "version": 160001, 273 | }, 274 | ] 275 | `; 276 | 277 | exports[`security_label.sql > line 29 1`] = ` 278 | [ 279 | " -- fail 280 | SECURITY LABEL ON TABLE seclabel_tbl1 IS '...invalid label...'", 281 | { 282 | "stmts": [ 283 | { 284 | "stmt": { 285 | "SecLabelStmt": { 286 | "label": "...invalid label...", 287 | "object": { 288 | "List": { 289 | "items": [ 290 | { 291 | "String": { 292 | "sval": "seclabel_tbl1", 293 | }, 294 | }, 295 | ], 296 | }, 297 | }, 298 | "objtype": "OBJECT_TABLE", 299 | }, 300 | }, 301 | }, 302 | ], 303 | "version": 160001, 304 | }, 305 | ] 306 | `; 307 | 308 | exports[`security_label.sql > line 30 1`] = ` 309 | [ 310 | " -- fail 311 | SECURITY LABEL ON TABLE seclabel_tbl3 IS 'unclassified'", 312 | { 313 | "stmts": [ 314 | { 315 | "stmt": { 316 | "SecLabelStmt": { 317 | "label": "unclassified", 318 | "object": { 319 | "List": { 320 | "items": [ 321 | { 322 | "String": { 323 | "sval": "seclabel_tbl3", 324 | }, 325 | }, 326 | ], 327 | }, 328 | }, 329 | "objtype": "OBJECT_TABLE", 330 | }, 331 | }, 332 | }, 333 | ], 334 | "version": 160001, 335 | }, 336 | ] 337 | `; 338 | 339 | exports[`security_label.sql > line 31 1`] = ` 340 | [ 341 | " -- fail 342 | 343 | SECURITY LABEL ON ROLE regress_seclabel_user1 IS 'classified'", 344 | { 345 | "stmts": [ 346 | { 347 | "stmt": { 348 | "SecLabelStmt": { 349 | "label": "classified", 350 | "object": { 351 | "String": { 352 | "sval": "regress_seclabel_user1", 353 | }, 354 | }, 355 | "objtype": "OBJECT_ROLE", 356 | }, 357 | }, 358 | }, 359 | ], 360 | "version": 160001, 361 | }, 362 | ] 363 | `; 364 | 365 | exports[`security_label.sql > line 33 1`] = ` 366 | [ 367 | " -- fail 368 | SECURITY LABEL FOR 'dummy' ON ROLE regress_seclabel_user1 IS 'classified'", 369 | { 370 | "stmts": [ 371 | { 372 | "stmt": { 373 | "SecLabelStmt": { 374 | "label": "classified", 375 | "object": { 376 | "String": { 377 | "sval": "regress_seclabel_user1", 378 | }, 379 | }, 380 | "objtype": "OBJECT_ROLE", 381 | "provider": "dummy", 382 | }, 383 | }, 384 | }, 385 | ], 386 | "version": 160001, 387 | }, 388 | ] 389 | `; 390 | 391 | exports[`security_label.sql > line 34 1`] = ` 392 | [ 393 | " -- fail 394 | SECURITY LABEL ON ROLE regress_seclabel_user1 IS '...invalid label...'", 395 | { 396 | "stmts": [ 397 | { 398 | "stmt": { 399 | "SecLabelStmt": { 400 | "label": "...invalid label...", 401 | "object": { 402 | "String": { 403 | "sval": "regress_seclabel_user1", 404 | }, 405 | }, 406 | "objtype": "OBJECT_ROLE", 407 | }, 408 | }, 409 | }, 410 | ], 411 | "version": 160001, 412 | }, 413 | ] 414 | `; 415 | 416 | exports[`security_label.sql > line 35 1`] = ` 417 | [ 418 | " -- fail 419 | SECURITY LABEL ON ROLE regress_seclabel_user3 IS 'unclassified'", 420 | { 421 | "stmts": [ 422 | { 423 | "stmt": { 424 | "SecLabelStmt": { 425 | "label": "unclassified", 426 | "object": { 427 | "String": { 428 | "sval": "regress_seclabel_user3", 429 | }, 430 | }, 431 | "objtype": "OBJECT_ROLE", 432 | }, 433 | }, 434 | }, 435 | ], 436 | "version": 160001, 437 | }, 438 | ] 439 | `; 440 | 441 | exports[`security_label.sql > line 36 1`] = ` 442 | [ 443 | " -- fail 444 | 445 | -- clean up objects 446 | DROP FUNCTION seclabel_four()", 447 | { 448 | "stmts": [ 449 | { 450 | "stmt": { 451 | "DropStmt": { 452 | "behavior": "DROP_RESTRICT", 453 | "objects": [ 454 | { 455 | "ObjectWithArgs": { 456 | "objname": [ 457 | { 458 | "String": { 459 | "sval": "seclabel_four", 460 | }, 461 | }, 462 | ], 463 | }, 464 | }, 465 | ], 466 | "removeType": "OBJECT_FUNCTION", 467 | }, 468 | }, 469 | }, 470 | ], 471 | "version": 160001, 472 | }, 473 | ] 474 | `; 475 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/roleattributes.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`roleattributes.sql > line 2 1`] = ` 4 | [ 5 | "CREATE ROLE regress_test_def_superuser", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "CreateRoleStmt": { 11 | "role": "regress_test_def_superuser", 12 | "stmt_type": "ROLESTMT_ROLE", 13 | }, 14 | }, 15 | }, 16 | ], 17 | "version": 160001, 18 | }, 19 | ] 20 | `; 21 | 22 | exports[`roleattributes.sql > line 4 1`] = ` 23 | [ 24 | "SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname = 'regress_test_def_superuser'", 25 | { 26 | "stmts": [ 27 | { 28 | "stmt": { 29 | "SelectStmt": { 30 | "fromClause": [ 31 | { 32 | "RangeVar": { 33 | "inh": true, 34 | "location": 155, 35 | "relname": "pg_authid", 36 | "relpersistence": "p", 37 | }, 38 | }, 39 | ], 40 | "limitOption": "LIMIT_OPTION_DEFAULT", 41 | "op": "SETOP_NONE", 42 | "targetList": [ 43 | { 44 | "ResTarget": { 45 | "location": 7, 46 | "val": { 47 | "ColumnRef": { 48 | "fields": [ 49 | { 50 | "String": { 51 | "sval": "rolname", 52 | }, 53 | }, 54 | ], 55 | "location": 7, 56 | }, 57 | }, 58 | }, 59 | }, 60 | { 61 | "ResTarget": { 62 | "location": 16, 63 | "val": { 64 | "ColumnRef": { 65 | "fields": [ 66 | { 67 | "String": { 68 | "sval": "rolsuper", 69 | }, 70 | }, 71 | ], 72 | "location": 16, 73 | }, 74 | }, 75 | }, 76 | }, 77 | { 78 | "ResTarget": { 79 | "location": 26, 80 | "val": { 81 | "ColumnRef": { 82 | "fields": [ 83 | { 84 | "String": { 85 | "sval": "rolinherit", 86 | }, 87 | }, 88 | ], 89 | "location": 26, 90 | }, 91 | }, 92 | }, 93 | }, 94 | { 95 | "ResTarget": { 96 | "location": 38, 97 | "val": { 98 | "ColumnRef": { 99 | "fields": [ 100 | { 101 | "String": { 102 | "sval": "rolcreaterole", 103 | }, 104 | }, 105 | ], 106 | "location": 38, 107 | }, 108 | }, 109 | }, 110 | }, 111 | { 112 | "ResTarget": { 113 | "location": 53, 114 | "val": { 115 | "ColumnRef": { 116 | "fields": [ 117 | { 118 | "String": { 119 | "sval": "rolcreatedb", 120 | }, 121 | }, 122 | ], 123 | "location": 53, 124 | }, 125 | }, 126 | }, 127 | }, 128 | { 129 | "ResTarget": { 130 | "location": 66, 131 | "val": { 132 | "ColumnRef": { 133 | "fields": [ 134 | { 135 | "String": { 136 | "sval": "rolcanlogin", 137 | }, 138 | }, 139 | ], 140 | "location": 66, 141 | }, 142 | }, 143 | }, 144 | }, 145 | { 146 | "ResTarget": { 147 | "location": 79, 148 | "val": { 149 | "ColumnRef": { 150 | "fields": [ 151 | { 152 | "String": { 153 | "sval": "rolreplication", 154 | }, 155 | }, 156 | ], 157 | "location": 79, 158 | }, 159 | }, 160 | }, 161 | }, 162 | { 163 | "ResTarget": { 164 | "location": 95, 165 | "val": { 166 | "ColumnRef": { 167 | "fields": [ 168 | { 169 | "String": { 170 | "sval": "rolbypassrls", 171 | }, 172 | }, 173 | ], 174 | "location": 95, 175 | }, 176 | }, 177 | }, 178 | }, 179 | { 180 | "ResTarget": { 181 | "location": 109, 182 | "val": { 183 | "ColumnRef": { 184 | "fields": [ 185 | { 186 | "String": { 187 | "sval": "rolconnlimit", 188 | }, 189 | }, 190 | ], 191 | "location": 109, 192 | }, 193 | }, 194 | }, 195 | }, 196 | { 197 | "ResTarget": { 198 | "location": 123, 199 | "val": { 200 | "ColumnRef": { 201 | "fields": [ 202 | { 203 | "String": { 204 | "sval": "rolpassword", 205 | }, 206 | }, 207 | ], 208 | "location": 123, 209 | }, 210 | }, 211 | }, 212 | }, 213 | { 214 | "ResTarget": { 215 | "location": 136, 216 | "val": { 217 | "ColumnRef": { 218 | "fields": [ 219 | { 220 | "String": { 221 | "sval": "rolvaliduntil", 222 | }, 223 | }, 224 | ], 225 | "location": 136, 226 | }, 227 | }, 228 | }, 229 | }, 230 | ], 231 | "whereClause": { 232 | "A_Expr": { 233 | "kind": "AEXPR_OP", 234 | "lexpr": { 235 | "ColumnRef": { 236 | "fields": [ 237 | { 238 | "String": { 239 | "sval": "rolname", 240 | }, 241 | }, 242 | ], 243 | "location": 171, 244 | }, 245 | }, 246 | "location": 179, 247 | "name": [ 248 | { 249 | "String": { 250 | "sval": "=", 251 | }, 252 | }, 253 | ], 254 | "rexpr": { 255 | "A_Const": { 256 | "location": 181, 257 | "sval": { 258 | "sval": "regress_test_def_superuser", 259 | }, 260 | }, 261 | }, 262 | }, 263 | }, 264 | }, 265 | }, 266 | }, 267 | ], 268 | "version": 160001, 269 | }, 270 | ] 271 | `; 272 | 273 | exports[`roleattributes.sql > line 13 1`] = ` 274 | [ 275 | "CREATE ROLE regress_test_def_inherit", 276 | { 277 | "stmts": [ 278 | { 279 | "stmt": { 280 | "CreateRoleStmt": { 281 | "role": "regress_test_def_inherit", 282 | "stmt_type": "ROLESTMT_ROLE", 283 | }, 284 | }, 285 | }, 286 | ], 287 | "version": 160001, 288 | }, 289 | ] 290 | `; 291 | 292 | exports[`roleattributes.sql > line 23 1`] = ` 293 | [ 294 | "CREATE ROLE regress_test_def_createrole", 295 | { 296 | "stmts": [ 297 | { 298 | "stmt": { 299 | "CreateRoleStmt": { 300 | "role": "regress_test_def_createrole", 301 | "stmt_type": "ROLESTMT_ROLE", 302 | }, 303 | }, 304 | }, 305 | ], 306 | "version": 160001, 307 | }, 308 | ] 309 | `; 310 | 311 | exports[`roleattributes.sql > line 33 1`] = ` 312 | [ 313 | "CREATE ROLE regress_test_def_createdb", 314 | { 315 | "stmts": [ 316 | { 317 | "stmt": { 318 | "CreateRoleStmt": { 319 | "role": "regress_test_def_createdb", 320 | "stmt_type": "ROLESTMT_ROLE", 321 | }, 322 | }, 323 | }, 324 | ], 325 | "version": 160001, 326 | }, 327 | ] 328 | `; 329 | 330 | exports[`roleattributes.sql > line 43 1`] = ` 331 | [ 332 | "CREATE ROLE regress_test_def_role_canlogin", 333 | { 334 | "stmts": [ 335 | { 336 | "stmt": { 337 | "CreateRoleStmt": { 338 | "role": "regress_test_def_role_canlogin", 339 | "stmt_type": "ROLESTMT_ROLE", 340 | }, 341 | }, 342 | }, 343 | ], 344 | "version": 160001, 345 | }, 346 | ] 347 | `; 348 | 349 | exports[`roleattributes.sql > line 53 1`] = ` 350 | [ 351 | "CREATE USER regress_test_def_user_canlogin", 352 | { 353 | "stmts": [ 354 | { 355 | "stmt": { 356 | "CreateRoleStmt": { 357 | "role": "regress_test_def_user_canlogin", 358 | "stmt_type": "ROLESTMT_USER", 359 | }, 360 | }, 361 | }, 362 | ], 363 | "version": 160001, 364 | }, 365 | ] 366 | `; 367 | 368 | exports[`roleattributes.sql > line 63 1`] = ` 369 | [ 370 | "CREATE ROLE regress_test_def_replication", 371 | { 372 | "stmts": [ 373 | { 374 | "stmt": { 375 | "CreateRoleStmt": { 376 | "role": "regress_test_def_replication", 377 | "stmt_type": "ROLESTMT_ROLE", 378 | }, 379 | }, 380 | }, 381 | ], 382 | "version": 160001, 383 | }, 384 | ] 385 | `; 386 | 387 | exports[`roleattributes.sql > line 73 1`] = ` 388 | [ 389 | "CREATE ROLE regress_test_def_bypassrls", 390 | { 391 | "stmts": [ 392 | { 393 | "stmt": { 394 | "CreateRoleStmt": { 395 | "role": "regress_test_def_bypassrls", 396 | "stmt_type": "ROLESTMT_ROLE", 397 | }, 398 | }, 399 | }, 400 | ], 401 | "version": 160001, 402 | }, 403 | ] 404 | `; 405 | 406 | exports[`roleattributes.sql > line 83 1`] = ` 407 | [ 408 | "DROP ROLE regress_test_def_superuser", 409 | { 410 | "stmts": [ 411 | { 412 | "stmt": { 413 | "DropRoleStmt": { 414 | "roles": [ 415 | { 416 | "RoleSpec": { 417 | "location": 10, 418 | "rolename": "regress_test_def_superuser", 419 | "roletype": "ROLESPEC_CSTRING", 420 | }, 421 | }, 422 | ], 423 | }, 424 | }, 425 | }, 426 | ], 427 | "version": 160001, 428 | }, 429 | ] 430 | `; 431 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/prepared_xacts.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`prepared_xacts.sql > line 11 1`] = ` 4 | [ 5 | "CREATE TABLE pxtest1 (foobar VARCHAR(10))", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "CreateStmt": { 11 | "oncommit": "ONCOMMIT_NOOP", 12 | "relation": { 13 | "inh": true, 14 | "location": 13, 15 | "relname": "pxtest1", 16 | "relpersistence": "p", 17 | }, 18 | "tableElts": [ 19 | { 20 | "ColumnDef": { 21 | "colname": "foobar", 22 | "is_local": true, 23 | "location": 22, 24 | "typeName": { 25 | "location": 29, 26 | "names": [ 27 | { 28 | "String": { 29 | "sval": "pg_catalog", 30 | }, 31 | }, 32 | { 33 | "String": { 34 | "sval": "varchar", 35 | }, 36 | }, 37 | ], 38 | "typemod": -1, 39 | "typmods": [ 40 | { 41 | "A_Const": { 42 | "ival": { 43 | "ival": 10, 44 | }, 45 | "location": 37, 46 | }, 47 | }, 48 | ], 49 | }, 50 | }, 51 | }, 52 | ], 53 | }, 54 | }, 55 | }, 56 | ], 57 | "version": 160001, 58 | }, 59 | ] 60 | `; 61 | 62 | exports[`prepared_xacts.sql > line 13 1`] = ` 63 | [ 64 | "INSERT INTO pxtest1 VALUES ('aaa')", 65 | { 66 | "stmts": [ 67 | { 68 | "stmt": { 69 | "InsertStmt": { 70 | "override": "OVERRIDING_NOT_SET", 71 | "relation": { 72 | "inh": true, 73 | "location": 12, 74 | "relname": "pxtest1", 75 | "relpersistence": "p", 76 | }, 77 | "selectStmt": { 78 | "SelectStmt": { 79 | "limitOption": "LIMIT_OPTION_DEFAULT", 80 | "op": "SETOP_NONE", 81 | "valuesLists": [ 82 | { 83 | "List": { 84 | "items": [ 85 | { 86 | "A_Const": { 87 | "location": 28, 88 | "sval": { 89 | "sval": "aaa", 90 | }, 91 | }, 92 | }, 93 | ], 94 | }, 95 | }, 96 | ], 97 | }, 98 | }, 99 | }, 100 | }, 101 | }, 102 | ], 103 | "version": 160001, 104 | }, 105 | ] 106 | `; 107 | 108 | exports[`prepared_xacts.sql > line 17 1`] = ` 109 | [ 110 | "BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE", 111 | { 112 | "stmts": [ 113 | { 114 | "stmt": { 115 | "TransactionStmt": { 116 | "kind": "TRANS_STMT_BEGIN", 117 | "options": [ 118 | { 119 | "DefElem": { 120 | "arg": { 121 | "A_Const": { 122 | "location": 34, 123 | "sval": { 124 | "sval": "serializable", 125 | }, 126 | }, 127 | }, 128 | "defaction": "DEFELEM_UNSPEC", 129 | "defname": "transaction_isolation", 130 | "location": 18, 131 | }, 132 | }, 133 | ], 134 | }, 135 | }, 136 | }, 137 | ], 138 | "version": 160001, 139 | }, 140 | ] 141 | `; 142 | 143 | exports[`prepared_xacts.sql > line 22 1`] = ` 144 | [ 145 | "SELECT * FROM pxtest1", 146 | { 147 | "stmts": [ 148 | { 149 | "stmt": { 150 | "SelectStmt": { 151 | "fromClause": [ 152 | { 153 | "RangeVar": { 154 | "inh": true, 155 | "location": 14, 156 | "relname": "pxtest1", 157 | "relpersistence": "p", 158 | }, 159 | }, 160 | ], 161 | "limitOption": "LIMIT_OPTION_DEFAULT", 162 | "op": "SETOP_NONE", 163 | "targetList": [ 164 | { 165 | "ResTarget": { 166 | "location": 7, 167 | "val": { 168 | "ColumnRef": { 169 | "fields": [ 170 | { 171 | "A_Star": {}, 172 | }, 173 | ], 174 | "location": 7, 175 | }, 176 | }, 177 | }, 178 | }, 179 | ], 180 | }, 181 | }, 182 | }, 183 | ], 184 | "version": 160001, 185 | }, 186 | ] 187 | `; 188 | 189 | exports[`prepared_xacts.sql > line 25 1`] = ` 190 | [ 191 | "SELECT gid FROM pg_prepared_xacts", 192 | { 193 | "stmts": [ 194 | { 195 | "stmt": { 196 | "SelectStmt": { 197 | "fromClause": [ 198 | { 199 | "RangeVar": { 200 | "inh": true, 201 | "location": 16, 202 | "relname": "pg_prepared_xacts", 203 | "relpersistence": "p", 204 | }, 205 | }, 206 | ], 207 | "limitOption": "LIMIT_OPTION_DEFAULT", 208 | "op": "SETOP_NONE", 209 | "targetList": [ 210 | { 211 | "ResTarget": { 212 | "location": 7, 213 | "val": { 214 | "ColumnRef": { 215 | "fields": [ 216 | { 217 | "String": { 218 | "sval": "gid", 219 | }, 220 | }, 221 | ], 222 | "location": 7, 223 | }, 224 | }, 225 | }, 226 | }, 227 | ], 228 | }, 229 | }, 230 | }, 231 | ], 232 | "version": 160001, 233 | }, 234 | ] 235 | `; 236 | 237 | exports[`prepared_xacts.sql > line 28 1`] = ` 238 | [ 239 | "ROLLBACK PREPARED 'foo1'", 240 | { 241 | "stmts": [ 242 | { 243 | "stmt": { 244 | "TransactionStmt": { 245 | "gid": "foo1", 246 | "kind": "TRANS_STMT_ROLLBACK_PREPARED", 247 | }, 248 | }, 249 | }, 250 | ], 251 | "version": 160001, 252 | }, 253 | ] 254 | `; 255 | 256 | exports[`prepared_xacts.sql > line 43 1`] = ` 257 | [ 258 | "COMMIT PREPARED 'foo2'", 259 | { 260 | "stmts": [ 261 | { 262 | "stmt": { 263 | "TransactionStmt": { 264 | "gid": "foo2", 265 | "kind": "TRANS_STMT_COMMIT_PREPARED", 266 | }, 267 | }, 268 | }, 269 | ], 270 | "version": 160001, 271 | }, 272 | ] 273 | `; 274 | 275 | exports[`prepared_xacts.sql > line 59 1`] = ` 276 | [ 277 | "PREPARE TRANSACTION 'foo3'", 278 | { 279 | "stmts": [ 280 | { 281 | "stmt": { 282 | "TransactionStmt": { 283 | "gid": "foo3", 284 | "kind": "TRANS_STMT_PREPARE", 285 | }, 286 | }, 287 | }, 288 | ], 289 | "version": 160001, 290 | }, 291 | ] 292 | `; 293 | 294 | exports[`prepared_xacts.sql > line 89 1`] = ` 295 | [ 296 | "DROP TABLE pxtest1", 297 | { 298 | "stmts": [ 299 | { 300 | "stmt": { 301 | "DropStmt": { 302 | "behavior": "DROP_RESTRICT", 303 | "objects": [ 304 | { 305 | "List": { 306 | "items": [ 307 | { 308 | "String": { 309 | "sval": "pxtest1", 310 | }, 311 | }, 312 | ], 313 | }, 314 | }, 315 | ], 316 | "removeType": "OBJECT_TABLE", 317 | }, 318 | }, 319 | }, 320 | ], 321 | "version": 160001, 322 | }, 323 | ] 324 | `; 325 | 326 | exports[`prepared_xacts.sql > line 108 1`] = ` 327 | [ 328 | "CREATE TABLE pxtest3(fff int)", 329 | { 330 | "stmts": [ 331 | { 332 | "stmt": { 333 | "CreateStmt": { 334 | "oncommit": "ONCOMMIT_NOOP", 335 | "relation": { 336 | "inh": true, 337 | "location": 13, 338 | "relname": "pxtest3", 339 | "relpersistence": "p", 340 | }, 341 | "tableElts": [ 342 | { 343 | "ColumnDef": { 344 | "colname": "fff", 345 | "is_local": true, 346 | "location": 21, 347 | "typeName": { 348 | "location": 25, 349 | "names": [ 350 | { 351 | "String": { 352 | "sval": "pg_catalog", 353 | }, 354 | }, 355 | { 356 | "String": { 357 | "sval": "int4", 358 | }, 359 | }, 360 | ], 361 | "typemod": -1, 362 | }, 363 | }, 364 | }, 365 | ], 366 | }, 367 | }, 368 | }, 369 | ], 370 | "version": 160001, 371 | }, 372 | ] 373 | `; 374 | 375 | exports[`prepared_xacts.sql > line 118 1`] = ` 376 | [ 377 | " FETCH 1 FROM foo", 378 | { 379 | "stmts": [ 380 | { 381 | "stmt": { 382 | "FetchStmt": { 383 | "direction": "FETCH_FORWARD", 384 | "howMany": 1, 385 | "portalname": "foo", 386 | }, 387 | }, 388 | }, 389 | ], 390 | "version": 160001, 391 | }, 392 | ] 393 | `; 394 | 395 | exports[`prepared_xacts.sql > line 125 1`] = ` 396 | [ 397 | "SELECT * FROM pxtest2", 398 | { 399 | "stmts": [ 400 | { 401 | "stmt": { 402 | "SelectStmt": { 403 | "fromClause": [ 404 | { 405 | "RangeVar": { 406 | "inh": true, 407 | "location": 14, 408 | "relname": "pxtest2", 409 | "relpersistence": "p", 410 | }, 411 | }, 412 | ], 413 | "limitOption": "LIMIT_OPTION_DEFAULT", 414 | "op": "SETOP_NONE", 415 | "targetList": [ 416 | { 417 | "ResTarget": { 418 | "location": 7, 419 | "val": { 420 | "ColumnRef": { 421 | "fields": [ 422 | { 423 | "A_Star": {}, 424 | }, 425 | ], 426 | "location": 7, 427 | }, 428 | }, 429 | }, 430 | }, 431 | ], 432 | }, 433 | }, 434 | }, 435 | ], 436 | "version": 160001, 437 | }, 438 | ] 439 | `; 440 | 441 | exports[`prepared_xacts.sql > line 162 1`] = ` 442 | [ 443 | "DROP TABLE pxtest2", 444 | { 445 | "stmts": [ 446 | { 447 | "stmt": { 448 | "DropStmt": { 449 | "behavior": "DROP_RESTRICT", 450 | "objects": [ 451 | { 452 | "List": { 453 | "items": [ 454 | { 455 | "String": { 456 | "sval": "pxtest2", 457 | }, 458 | }, 459 | ], 460 | }, 461 | }, 462 | ], 463 | "removeType": "OBJECT_TABLE", 464 | }, 465 | }, 466 | }, 467 | ], 468 | "version": 160001, 469 | }, 470 | ] 471 | `; 472 | 473 | exports[`prepared_xacts.sql > line 163 1`] = ` 474 | [ 475 | " -- will still be there if prepared xacts are disabled 476 | DROP TABLE pxtest4", 477 | { 478 | "stmts": [ 479 | { 480 | "stmt": { 481 | "DropStmt": { 482 | "behavior": "DROP_RESTRICT", 483 | "objects": [ 484 | { 485 | "List": { 486 | "items": [ 487 | { 488 | "String": { 489 | "sval": "pxtest4", 490 | }, 491 | }, 492 | ], 493 | }, 494 | }, 495 | ], 496 | "removeType": "OBJECT_TABLE", 497 | }, 498 | }, 499 | }, 500 | ], 501 | "version": 160001, 502 | }, 503 | ] 504 | `; 505 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/delete.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`delete.sql > line 1 1`] = ` 4 | [ 5 | "CREATE TABLE delete_test ( 6 | id SERIAL PRIMARY KEY, 7 | a INT, 8 | b text 9 | )", 10 | { 11 | "stmts": [ 12 | { 13 | "stmt": { 14 | "CreateStmt": { 15 | "oncommit": "ONCOMMIT_NOOP", 16 | "relation": { 17 | "inh": true, 18 | "location": 13, 19 | "relname": "delete_test", 20 | "relpersistence": "p", 21 | }, 22 | "tableElts": [ 23 | { 24 | "ColumnDef": { 25 | "colname": "id", 26 | "constraints": [ 27 | { 28 | "Constraint": { 29 | "contype": "CONSTR_PRIMARY", 30 | "location": 41, 31 | }, 32 | }, 33 | ], 34 | "is_local": true, 35 | "location": 31, 36 | "typeName": { 37 | "location": 34, 38 | "names": [ 39 | { 40 | "String": { 41 | "sval": "serial", 42 | }, 43 | }, 44 | ], 45 | "typemod": -1, 46 | }, 47 | }, 48 | }, 49 | { 50 | "ColumnDef": { 51 | "colname": "a", 52 | "is_local": true, 53 | "location": 58, 54 | "typeName": { 55 | "location": 60, 56 | "names": [ 57 | { 58 | "String": { 59 | "sval": "pg_catalog", 60 | }, 61 | }, 62 | { 63 | "String": { 64 | "sval": "int4", 65 | }, 66 | }, 67 | ], 68 | "typemod": -1, 69 | }, 70 | }, 71 | }, 72 | { 73 | "ColumnDef": { 74 | "colname": "b", 75 | "is_local": true, 76 | "location": 69, 77 | "typeName": { 78 | "location": 71, 79 | "names": [ 80 | { 81 | "String": { 82 | "sval": "text", 83 | }, 84 | }, 85 | ], 86 | "typemod": -1, 87 | }, 88 | }, 89 | }, 90 | ], 91 | }, 92 | }, 93 | }, 94 | ], 95 | "version": 160001, 96 | }, 97 | ] 98 | `; 99 | 100 | exports[`delete.sql > line 7 1`] = ` 101 | [ 102 | "INSERT INTO delete_test (a) VALUES (10)", 103 | { 104 | "stmts": [ 105 | { 106 | "stmt": { 107 | "InsertStmt": { 108 | "cols": [ 109 | { 110 | "ResTarget": { 111 | "location": 25, 112 | "name": "a", 113 | }, 114 | }, 115 | ], 116 | "override": "OVERRIDING_NOT_SET", 117 | "relation": { 118 | "inh": true, 119 | "location": 12, 120 | "relname": "delete_test", 121 | "relpersistence": "p", 122 | }, 123 | "selectStmt": { 124 | "SelectStmt": { 125 | "limitOption": "LIMIT_OPTION_DEFAULT", 126 | "op": "SETOP_NONE", 127 | "valuesLists": [ 128 | { 129 | "List": { 130 | "items": [ 131 | { 132 | "A_Const": { 133 | "ival": { 134 | "ival": 10, 135 | }, 136 | "location": 36, 137 | }, 138 | }, 139 | ], 140 | }, 141 | }, 142 | ], 143 | }, 144 | }, 145 | }, 146 | }, 147 | }, 148 | ], 149 | "version": 160001, 150 | }, 151 | ] 152 | `; 153 | 154 | exports[`delete.sql > line 12 1`] = ` 155 | [ 156 | "DELETE FROM delete_test AS dt WHERE dt.a > 75", 157 | { 158 | "stmts": [ 159 | { 160 | "stmt": { 161 | "DeleteStmt": { 162 | "relation": { 163 | "alias": { 164 | "aliasname": "dt", 165 | }, 166 | "inh": true, 167 | "location": 12, 168 | "relname": "delete_test", 169 | "relpersistence": "p", 170 | }, 171 | "whereClause": { 172 | "A_Expr": { 173 | "kind": "AEXPR_OP", 174 | "lexpr": { 175 | "ColumnRef": { 176 | "fields": [ 177 | { 178 | "String": { 179 | "sval": "dt", 180 | }, 181 | }, 182 | { 183 | "String": { 184 | "sval": "a", 185 | }, 186 | }, 187 | ], 188 | "location": 36, 189 | }, 190 | }, 191 | "location": 41, 192 | "name": [ 193 | { 194 | "String": { 195 | "sval": ">", 196 | }, 197 | }, 198 | ], 199 | "rexpr": { 200 | "A_Const": { 201 | "ival": { 202 | "ival": 75, 203 | }, 204 | "location": 43, 205 | }, 206 | }, 207 | }, 208 | }, 209 | }, 210 | }, 211 | }, 212 | ], 213 | "version": 160001, 214 | }, 215 | ] 216 | `; 217 | 218 | exports[`delete.sql > line 16 1`] = ` 219 | [ 220 | "DELETE FROM delete_test dt WHERE delete_test.a > 25", 221 | { 222 | "stmts": [ 223 | { 224 | "stmt": { 225 | "DeleteStmt": { 226 | "relation": { 227 | "alias": { 228 | "aliasname": "dt", 229 | }, 230 | "inh": true, 231 | "location": 12, 232 | "relname": "delete_test", 233 | "relpersistence": "p", 234 | }, 235 | "whereClause": { 236 | "A_Expr": { 237 | "kind": "AEXPR_OP", 238 | "lexpr": { 239 | "ColumnRef": { 240 | "fields": [ 241 | { 242 | "String": { 243 | "sval": "delete_test", 244 | }, 245 | }, 246 | { 247 | "String": { 248 | "sval": "a", 249 | }, 250 | }, 251 | ], 252 | "location": 33, 253 | }, 254 | }, 255 | "location": 47, 256 | "name": [ 257 | { 258 | "String": { 259 | "sval": ">", 260 | }, 261 | }, 262 | ], 263 | "rexpr": { 264 | "A_Const": { 265 | "ival": { 266 | "ival": 25, 267 | }, 268 | "location": 49, 269 | }, 270 | }, 271 | }, 272 | }, 273 | }, 274 | }, 275 | }, 276 | ], 277 | "version": 160001, 278 | }, 279 | ] 280 | `; 281 | 282 | exports[`delete.sql > line 18 1`] = ` 283 | [ 284 | "SELECT id, a, char_length(b) FROM delete_test", 285 | { 286 | "stmts": [ 287 | { 288 | "stmt": { 289 | "SelectStmt": { 290 | "fromClause": [ 291 | { 292 | "RangeVar": { 293 | "inh": true, 294 | "location": 34, 295 | "relname": "delete_test", 296 | "relpersistence": "p", 297 | }, 298 | }, 299 | ], 300 | "limitOption": "LIMIT_OPTION_DEFAULT", 301 | "op": "SETOP_NONE", 302 | "targetList": [ 303 | { 304 | "ResTarget": { 305 | "location": 7, 306 | "val": { 307 | "ColumnRef": { 308 | "fields": [ 309 | { 310 | "String": { 311 | "sval": "id", 312 | }, 313 | }, 314 | ], 315 | "location": 7, 316 | }, 317 | }, 318 | }, 319 | }, 320 | { 321 | "ResTarget": { 322 | "location": 11, 323 | "val": { 324 | "ColumnRef": { 325 | "fields": [ 326 | { 327 | "String": { 328 | "sval": "a", 329 | }, 330 | }, 331 | ], 332 | "location": 11, 333 | }, 334 | }, 335 | }, 336 | }, 337 | { 338 | "ResTarget": { 339 | "location": 14, 340 | "val": { 341 | "FuncCall": { 342 | "args": [ 343 | { 344 | "ColumnRef": { 345 | "fields": [ 346 | { 347 | "String": { 348 | "sval": "b", 349 | }, 350 | }, 351 | ], 352 | "location": 26, 353 | }, 354 | }, 355 | ], 356 | "funcformat": "COERCE_EXPLICIT_CALL", 357 | "funcname": [ 358 | { 359 | "String": { 360 | "sval": "char_length", 361 | }, 362 | }, 363 | ], 364 | "location": 14, 365 | }, 366 | }, 367 | }, 368 | }, 369 | ], 370 | }, 371 | }, 372 | }, 373 | ], 374 | "version": 160001, 375 | }, 376 | ] 377 | `; 378 | 379 | exports[`delete.sql > line 21 1`] = ` 380 | [ 381 | "DELETE FROM delete_test WHERE a > 25", 382 | { 383 | "stmts": [ 384 | { 385 | "stmt": { 386 | "DeleteStmt": { 387 | "relation": { 388 | "inh": true, 389 | "location": 12, 390 | "relname": "delete_test", 391 | "relpersistence": "p", 392 | }, 393 | "whereClause": { 394 | "A_Expr": { 395 | "kind": "AEXPR_OP", 396 | "lexpr": { 397 | "ColumnRef": { 398 | "fields": [ 399 | { 400 | "String": { 401 | "sval": "a", 402 | }, 403 | }, 404 | ], 405 | "location": 30, 406 | }, 407 | }, 408 | "location": 32, 409 | "name": [ 410 | { 411 | "String": { 412 | "sval": ">", 413 | }, 414 | }, 415 | ], 416 | "rexpr": { 417 | "A_Const": { 418 | "ival": { 419 | "ival": 25, 420 | }, 421 | "location": 34, 422 | }, 423 | }, 424 | }, 425 | }, 426 | }, 427 | }, 428 | }, 429 | ], 430 | "version": 160001, 431 | }, 432 | ] 433 | `; 434 | 435 | exports[`delete.sql > line 25 1`] = ` 436 | [ 437 | "DROP TABLE delete_test", 438 | { 439 | "stmts": [ 440 | { 441 | "stmt": { 442 | "DropStmt": { 443 | "behavior": "DROP_RESTRICT", 444 | "objects": [ 445 | { 446 | "List": { 447 | "items": [ 448 | { 449 | "String": { 450 | "sval": "delete_test", 451 | }, 452 | }, 453 | ], 454 | }, 455 | }, 456 | ], 457 | "removeType": "OBJECT_TABLE", 458 | }, 459 | }, 460 | }, 461 | ], 462 | "version": 160001, 463 | }, 464 | ] 465 | `; 466 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/regex.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`regex.sql > line 6 1`] = ` 4 | [ 5 | "set standard_conforming_strings = on", 6 | { 7 | "stmts": [ 8 | { 9 | "stmt": { 10 | "VariableSetStmt": { 11 | "args": [ 12 | { 13 | "A_Const": { 14 | "location": 34, 15 | "sval": { 16 | "sval": "on", 17 | }, 18 | }, 19 | }, 20 | ], 21 | "kind": "VAR_SET_VALUE", 22 | "name": "standard_conforming_strings", 23 | }, 24 | }, 25 | }, 26 | ], 27 | "version": 160001, 28 | }, 29 | ] 30 | `; 31 | 32 | exports[`regex.sql > line 9 1`] = ` 33 | [ 34 | "select 'bbbbb' ~ '^([bc])\\1*$' as t", 35 | { 36 | "stmts": [ 37 | { 38 | "stmt": { 39 | "SelectStmt": { 40 | "limitOption": "LIMIT_OPTION_DEFAULT", 41 | "op": "SETOP_NONE", 42 | "targetList": [ 43 | { 44 | "ResTarget": { 45 | "location": 7, 46 | "name": "t", 47 | "val": { 48 | "A_Expr": { 49 | "kind": "AEXPR_OP", 50 | "lexpr": { 51 | "A_Const": { 52 | "location": 7, 53 | "sval": { 54 | "sval": "bbbbb", 55 | }, 56 | }, 57 | }, 58 | "location": 15, 59 | "name": [ 60 | { 61 | "String": { 62 | "sval": "~", 63 | }, 64 | }, 65 | ], 66 | "rexpr": { 67 | "A_Const": { 68 | "location": 17, 69 | "sval": { 70 | "sval": "^([bc])\\1*$", 71 | }, 72 | }, 73 | }, 74 | }, 75 | }, 76 | }, 77 | }, 78 | ], 79 | }, 80 | }, 81 | }, 82 | ], 83 | "version": 160001, 84 | }, 85 | ] 86 | `; 87 | 88 | exports[`regex.sql > line 24 1`] = ` 89 | [ 90 | "select substring('asd TO foo' from ' TO (([a-z0-9._]+|"([^"]+|"")+")+)')", 91 | { 92 | "stmts": [ 93 | { 94 | "stmt": { 95 | "SelectStmt": { 96 | "limitOption": "LIMIT_OPTION_DEFAULT", 97 | "op": "SETOP_NONE", 98 | "targetList": [ 99 | { 100 | "ResTarget": { 101 | "location": 7, 102 | "val": { 103 | "FuncCall": { 104 | "args": [ 105 | { 106 | "A_Const": { 107 | "location": 17, 108 | "sval": { 109 | "sval": "asd TO foo", 110 | }, 111 | }, 112 | }, 113 | { 114 | "A_Const": { 115 | "location": 35, 116 | "sval": { 117 | "sval": " TO (([a-z0-9._]+|"([^"]+|"")+")+)", 118 | }, 119 | }, 120 | }, 121 | ], 122 | "funcformat": "COERCE_SQL_SYNTAX", 123 | "funcname": [ 124 | { 125 | "String": { 126 | "sval": "pg_catalog", 127 | }, 128 | }, 129 | { 130 | "String": { 131 | "sval": "substring", 132 | }, 133 | }, 134 | ], 135 | "location": 7, 136 | }, 137 | }, 138 | }, 139 | }, 140 | ], 141 | }, 142 | }, 143 | }, 144 | ], 145 | "version": 160001, 146 | }, 147 | ] 148 | `; 149 | 150 | exports[`regex.sql > line 29 1`] = ` 151 | [ 152 | "select regexp_match('abc', '')", 153 | { 154 | "stmts": [ 155 | { 156 | "stmt": { 157 | "SelectStmt": { 158 | "limitOption": "LIMIT_OPTION_DEFAULT", 159 | "op": "SETOP_NONE", 160 | "targetList": [ 161 | { 162 | "ResTarget": { 163 | "location": 7, 164 | "val": { 165 | "FuncCall": { 166 | "args": [ 167 | { 168 | "A_Const": { 169 | "location": 20, 170 | "sval": { 171 | "sval": "abc", 172 | }, 173 | }, 174 | }, 175 | { 176 | "A_Const": { 177 | "location": 27, 178 | "sval": { 179 | "sval": "", 180 | }, 181 | }, 182 | }, 183 | ], 184 | "funcformat": "COERCE_EXPLICIT_CALL", 185 | "funcname": [ 186 | { 187 | "String": { 188 | "sval": "regexp_match", 189 | }, 190 | }, 191 | ], 192 | "location": 7, 193 | }, 194 | }, 195 | }, 196 | }, 197 | ], 198 | }, 199 | }, 200 | }, 201 | ], 202 | "version": 160001, 203 | }, 204 | ] 205 | `; 206 | 207 | exports[`regex.sql > line 33 1`] = ` 208 | [ 209 | " -- error 210 | 211 | -- Test lookahead constraints 212 | select regexp_matches('ab', 'a(?=b)b*')", 213 | { 214 | "stmts": [ 215 | { 216 | "stmt": { 217 | "SelectStmt": { 218 | "limitOption": "LIMIT_OPTION_DEFAULT", 219 | "op": "SETOP_NONE", 220 | "targetList": [ 221 | { 222 | "ResTarget": { 223 | "location": 48, 224 | "val": { 225 | "FuncCall": { 226 | "args": [ 227 | { 228 | "A_Const": { 229 | "location": 63, 230 | "sval": { 231 | "sval": "ab", 232 | }, 233 | }, 234 | }, 235 | { 236 | "A_Const": { 237 | "location": 69, 238 | "sval": { 239 | "sval": "a(?=b)b*", 240 | }, 241 | }, 242 | }, 243 | ], 244 | "funcformat": "COERCE_EXPLICIT_CALL", 245 | "funcname": [ 246 | { 247 | "String": { 248 | "sval": "regexp_matches", 249 | }, 250 | }, 251 | ], 252 | "location": 48, 253 | }, 254 | }, 255 | }, 256 | }, 257 | ], 258 | }, 259 | }, 260 | }, 261 | ], 262 | "version": 160001, 263 | }, 264 | ] 265 | `; 266 | 267 | exports[`regex.sql > line 71 1`] = ` 268 | [ 269 | "explain (costs off) select * from pg_proc where proname ~ 'abc'", 270 | { 271 | "stmts": [ 272 | { 273 | "stmt": { 274 | "ExplainStmt": { 275 | "options": [ 276 | { 277 | "DefElem": { 278 | "arg": { 279 | "String": { 280 | "sval": "off", 281 | }, 282 | }, 283 | "defaction": "DEFELEM_UNSPEC", 284 | "defname": "costs", 285 | "location": 9, 286 | }, 287 | }, 288 | ], 289 | "query": { 290 | "SelectStmt": { 291 | "fromClause": [ 292 | { 293 | "RangeVar": { 294 | "inh": true, 295 | "location": 34, 296 | "relname": "pg_proc", 297 | "relpersistence": "p", 298 | }, 299 | }, 300 | ], 301 | "limitOption": "LIMIT_OPTION_DEFAULT", 302 | "op": "SETOP_NONE", 303 | "targetList": [ 304 | { 305 | "ResTarget": { 306 | "location": 27, 307 | "val": { 308 | "ColumnRef": { 309 | "fields": [ 310 | { 311 | "A_Star": {}, 312 | }, 313 | ], 314 | "location": 27, 315 | }, 316 | }, 317 | }, 318 | }, 319 | ], 320 | "whereClause": { 321 | "A_Expr": { 322 | "kind": "AEXPR_OP", 323 | "lexpr": { 324 | "ColumnRef": { 325 | "fields": [ 326 | { 327 | "String": { 328 | "sval": "proname", 329 | }, 330 | }, 331 | ], 332 | "location": 48, 333 | }, 334 | }, 335 | "location": 56, 336 | "name": [ 337 | { 338 | "String": { 339 | "sval": "~", 340 | }, 341 | }, 342 | ], 343 | "rexpr": { 344 | "A_Const": { 345 | "location": 58, 346 | "sval": { 347 | "sval": "abc", 348 | }, 349 | }, 350 | }, 351 | }, 352 | }, 353 | }, 354 | }, 355 | }, 356 | }, 357 | }, 358 | ], 359 | "version": 160001, 360 | }, 361 | ] 362 | `; 363 | 364 | exports[`regex.sql > line 110 1`] = ` 365 | [ 366 | "select 'x' ~ repeat('x*y*z*', 1000)", 367 | { 368 | "stmts": [ 369 | { 370 | "stmt": { 371 | "SelectStmt": { 372 | "limitOption": "LIMIT_OPTION_DEFAULT", 373 | "op": "SETOP_NONE", 374 | "targetList": [ 375 | { 376 | "ResTarget": { 377 | "location": 7, 378 | "val": { 379 | "A_Expr": { 380 | "kind": "AEXPR_OP", 381 | "lexpr": { 382 | "A_Const": { 383 | "location": 7, 384 | "sval": { 385 | "sval": "x", 386 | }, 387 | }, 388 | }, 389 | "location": 11, 390 | "name": [ 391 | { 392 | "String": { 393 | "sval": "~", 394 | }, 395 | }, 396 | ], 397 | "rexpr": { 398 | "FuncCall": { 399 | "args": [ 400 | { 401 | "A_Const": { 402 | "location": 20, 403 | "sval": { 404 | "sval": "x*y*z*", 405 | }, 406 | }, 407 | }, 408 | { 409 | "A_Const": { 410 | "ival": { 411 | "ival": 1000, 412 | }, 413 | "location": 30, 414 | }, 415 | }, 416 | ], 417 | "funcformat": "COERCE_EXPLICIT_CALL", 418 | "funcname": [ 419 | { 420 | "String": { 421 | "sval": "repeat", 422 | }, 423 | }, 424 | ], 425 | "location": 13, 426 | }, 427 | }, 428 | }, 429 | }, 430 | }, 431 | }, 432 | ], 433 | }, 434 | }, 435 | }, 436 | ], 437 | "version": 160001, 438 | }, 439 | ] 440 | `; 441 | -------------------------------------------------------------------------------- /test/postgres_regress/__snapshots__/json_encoding.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`json_encoding.sql > line 13 1`] = ` 4 | [ 5 | " -- just to label the results files 6 | 7 | -- first json 8 | 9 | -- basic unicode input 10 | SELECT '"\\u"'::json", 11 | { 12 | "stmts": [ 13 | { 14 | "stmt": { 15 | "SelectStmt": { 16 | "limitOption": "LIMIT_OPTION_DEFAULT", 17 | "op": "SETOP_NONE", 18 | "targetList": [ 19 | { 20 | "ResTarget": { 21 | "location": 92, 22 | "val": { 23 | "TypeCast": { 24 | "arg": { 25 | "A_Const": { 26 | "location": 92, 27 | "sval": { 28 | "sval": ""\\u"", 29 | }, 30 | }, 31 | }, 32 | "location": 98, 33 | "typeName": { 34 | "location": 100, 35 | "names": [ 36 | { 37 | "String": { 38 | "sval": "json", 39 | }, 40 | }, 41 | ], 42 | "typemod": -1, 43 | }, 44 | }, 45 | }, 46 | }, 47 | }, 48 | ], 49 | }, 50 | }, 51 | }, 52 | ], 53 | "version": 160001, 54 | }, 55 | ] 56 | `; 57 | 58 | exports[`json_encoding.sql > line 22 1`] = ` 59 | [ 60 | " -- OK, uppercase and lower case both OK 61 | 62 | -- handling of unicode surrogate pairs 63 | 64 | select json '{ "a": "\\ud83d\\ude04\\ud83d\\udc36" }' -> 'a' as correct_in_utf8", 65 | { 66 | "stmts": [ 67 | { 68 | "stmt": { 69 | "SelectStmt": { 70 | "limitOption": "LIMIT_OPTION_DEFAULT", 71 | "op": "SETOP_NONE", 72 | "targetList": [ 73 | { 74 | "ResTarget": { 75 | "location": 90, 76 | "name": "correct_in_utf8", 77 | "val": { 78 | "A_Expr": { 79 | "kind": "AEXPR_OP", 80 | "lexpr": { 81 | "TypeCast": { 82 | "arg": { 83 | "A_Const": { 84 | "location": 95, 85 | "sval": { 86 | "sval": "{ "a": "\\ud83d\\ude04\\ud83d\\udc36" }", 87 | }, 88 | }, 89 | }, 90 | "location": -1, 91 | "typeName": { 92 | "location": 90, 93 | "names": [ 94 | { 95 | "String": { 96 | "sval": "json", 97 | }, 98 | }, 99 | ], 100 | "typemod": -1, 101 | }, 102 | }, 103 | }, 104 | "location": 134, 105 | "name": [ 106 | { 107 | "String": { 108 | "sval": "->", 109 | }, 110 | }, 111 | ], 112 | "rexpr": { 113 | "A_Const": { 114 | "location": 137, 115 | "sval": { 116 | "sval": "a", 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | }, 123 | }, 124 | ], 125 | }, 126 | }, 127 | }, 128 | ], 129 | "version": 160001, 130 | }, 131 | ] 132 | `; 133 | 134 | exports[`json_encoding.sql > line 40 1`] = ` 135 | [ 136 | "select json '{ "a": "the Copyright \\u00a9 sign" }' ->> 'a' as correct_in_utf8", 137 | { 138 | "stmts": [ 139 | { 140 | "stmt": { 141 | "SelectStmt": { 142 | "limitOption": "LIMIT_OPTION_DEFAULT", 143 | "op": "SETOP_NONE", 144 | "targetList": [ 145 | { 146 | "ResTarget": { 147 | "location": 7, 148 | "name": "correct_in_utf8", 149 | "val": { 150 | "A_Expr": { 151 | "kind": "AEXPR_OP", 152 | "lexpr": { 153 | "TypeCast": { 154 | "arg": { 155 | "A_Const": { 156 | "location": 12, 157 | "sval": { 158 | "sval": "{ "a": "the Copyright \\u00a9 sign" }", 159 | }, 160 | }, 161 | }, 162 | "location": -1, 163 | "typeName": { 164 | "location": 7, 165 | "names": [ 166 | { 167 | "String": { 168 | "sval": "json", 169 | }, 170 | }, 171 | ], 172 | "typemod": -1, 173 | }, 174 | }, 175 | }, 176 | "location": 52, 177 | "name": [ 178 | { 179 | "String": { 180 | "sval": "->>", 181 | }, 182 | }, 183 | ], 184 | "rexpr": { 185 | "A_Const": { 186 | "location": 56, 187 | "sval": { 188 | "sval": "a", 189 | }, 190 | }, 191 | }, 192 | }, 193 | }, 194 | }, 195 | }, 196 | ], 197 | }, 198 | }, 199 | }, 200 | ], 201 | "version": 160001, 202 | }, 203 | ] 204 | `; 205 | 206 | exports[`json_encoding.sql > line 53 1`] = ` 207 | [ 208 | " -- ERROR, we don't support U+0000 209 | -- use octet_length here so we don't get an odd unicode char in the 210 | -- output 211 | SELECT octet_length('"\\uaBcD"'::jsonb::text)", 212 | { 213 | "stmts": [ 214 | { 215 | "stmt": { 216 | "SelectStmt": { 217 | "limitOption": "LIMIT_OPTION_DEFAULT", 218 | "op": "SETOP_NONE", 219 | "targetList": [ 220 | { 221 | "ResTarget": { 222 | "location": 121, 223 | "val": { 224 | "FuncCall": { 225 | "args": [ 226 | { 227 | "TypeCast": { 228 | "arg": { 229 | "TypeCast": { 230 | "arg": { 231 | "A_Const": { 232 | "location": 134, 233 | "sval": { 234 | "sval": ""\\uaBcD"", 235 | }, 236 | }, 237 | }, 238 | "location": 144, 239 | "typeName": { 240 | "location": 146, 241 | "names": [ 242 | { 243 | "String": { 244 | "sval": "jsonb", 245 | }, 246 | }, 247 | ], 248 | "typemod": -1, 249 | }, 250 | }, 251 | }, 252 | "location": 151, 253 | "typeName": { 254 | "location": 153, 255 | "names": [ 256 | { 257 | "String": { 258 | "sval": "text", 259 | }, 260 | }, 261 | ], 262 | "typemod": -1, 263 | }, 264 | }, 265 | }, 266 | ], 267 | "funcformat": "COERCE_EXPLICIT_CALL", 268 | "funcname": [ 269 | { 270 | "String": { 271 | "sval": "octet_length", 272 | }, 273 | }, 274 | ], 275 | "location": 121, 276 | }, 277 | }, 278 | }, 279 | }, 280 | ], 281 | }, 282 | }, 283 | }, 284 | ], 285 | "version": 160001, 286 | }, 287 | ] 288 | `; 289 | 290 | exports[`json_encoding.sql > line 56 1`] = ` 291 | [ 292 | " -- OK, uppercase and lower case both OK 293 | 294 | -- handling of unicode surrogate pairs 295 | 296 | SELECT octet_length((jsonb '{ "a": "\\ud83d\\ude04\\ud83d\\udc36" }' -> 'a')::text) AS correct_in_utf8", 297 | { 298 | "stmts": [ 299 | { 300 | "stmt": { 301 | "SelectStmt": { 302 | "limitOption": "LIMIT_OPTION_DEFAULT", 303 | "op": "SETOP_NONE", 304 | "targetList": [ 305 | { 306 | "ResTarget": { 307 | "location": 89, 308 | "name": "correct_in_utf8", 309 | "val": { 310 | "FuncCall": { 311 | "args": [ 312 | { 313 | "TypeCast": { 314 | "arg": { 315 | "A_Expr": { 316 | "kind": "AEXPR_OP", 317 | "lexpr": { 318 | "TypeCast": { 319 | "arg": { 320 | "A_Const": { 321 | "location": 109, 322 | "sval": { 323 | "sval": "{ "a": "\\ud83d\\ude04\\ud83d\\udc36" }", 324 | }, 325 | }, 326 | }, 327 | "location": -1, 328 | "typeName": { 329 | "location": 103, 330 | "names": [ 331 | { 332 | "String": { 333 | "sval": "jsonb", 334 | }, 335 | }, 336 | ], 337 | "typemod": -1, 338 | }, 339 | }, 340 | }, 341 | "location": 148, 342 | "name": [ 343 | { 344 | "String": { 345 | "sval": "->", 346 | }, 347 | }, 348 | ], 349 | "rexpr": { 350 | "A_Const": { 351 | "location": 151, 352 | "sval": { 353 | "sval": "a", 354 | }, 355 | }, 356 | }, 357 | }, 358 | }, 359 | "location": 155, 360 | "typeName": { 361 | "location": 157, 362 | "names": [ 363 | { 364 | "String": { 365 | "sval": "text", 366 | }, 367 | }, 368 | ], 369 | "typemod": -1, 370 | }, 371 | }, 372 | }, 373 | ], 374 | "funcformat": "COERCE_EXPLICIT_CALL", 375 | "funcname": [ 376 | { 377 | "String": { 378 | "sval": "octet_length", 379 | }, 380 | }, 381 | ], 382 | "location": 89, 383 | }, 384 | }, 385 | }, 386 | }, 387 | ], 388 | }, 389 | }, 390 | }, 391 | ], 392 | "version": 160001, 393 | }, 394 | ] 395 | `; 396 | --------------------------------------------------------------------------------