pgsql-deparser 17.8.3 → 17.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/package.json +10 -3
- package/esm/utils/index.js +0 -90
- package/esm/utils/statement-splitter.js +0 -125
- package/utils/index.d.ts +0 -4
- package/utils/index.js +0 -97
- package/utils/statement-splitter.d.ts +0 -38
- package/utils/statement-splitter.js +0 -131
package/README.md
CHANGED
|
@@ -69,6 +69,23 @@ console.log(deparse(stmt));
|
|
|
69
69
|
// Output: SELECT * FROM another_table
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
### Latest Version (PostgreSQL 17)
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
npm install pgsql-deparser
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Version-Specific Packages (PostgreSQL 13-16)
|
|
79
|
+
|
|
80
|
+
While we highly recommend using PG17, for PostgreSQL versions 13-16, use the version-specific packages:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
npm install pgsql-deparser@v13 # PostgreSQL 13
|
|
84
|
+
npm install pgsql-deparser@v14 # PostgreSQL 14
|
|
85
|
+
npm install pgsql-deparser@v15 # PostgreSQL 15
|
|
86
|
+
npm install pgsql-deparser@v16 # PostgreSQL 16
|
|
87
|
+
```
|
|
88
|
+
|
|
72
89
|
## Options
|
|
73
90
|
|
|
74
91
|
The deparser accepts optional configuration for formatting and output control:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgsql-deparser",
|
|
3
|
-
"version": "17.8.
|
|
3
|
+
"version": "17.8.4",
|
|
4
4
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
5
|
"description": "PostgreSQL AST Deparser",
|
|
6
6
|
"main": "index.js",
|
|
@@ -33,7 +33,14 @@
|
|
|
33
33
|
"fixtures:upstream-diff": "ts-node scripts/make-upstream-diff.ts",
|
|
34
34
|
"lint": "eslint . --fix",
|
|
35
35
|
"test": "jest",
|
|
36
|
-
"test:watch": "jest --watch"
|
|
36
|
+
"test:watch": "jest --watch",
|
|
37
|
+
"strip-transformer-types": "ts-node scripts/strip-transformer-types.ts",
|
|
38
|
+
"strip-direct-transformer-types": "ts-node scripts/strip-direct-transformer-types.ts",
|
|
39
|
+
"strip-deparser-types": "ts-node scripts/strip-deparser-types.ts",
|
|
40
|
+
"organize-transformers": "ts-node scripts/organize-transformers-by-version.ts",
|
|
41
|
+
"generate-version-deparsers": "ts-node scripts/generate-version-deparsers.ts",
|
|
42
|
+
"generate-packages": "ts-node scripts/generate-version-packages.ts",
|
|
43
|
+
"prepare-versions": "npm run strip-transformer-types && npm run strip-direct-transformer-types && npm run strip-deparser-types && npm run organize-transformers && npm run generate-version-deparsers && npm run generate-packages"
|
|
37
44
|
},
|
|
38
45
|
"keywords": [
|
|
39
46
|
"sql",
|
|
@@ -51,5 +58,5 @@
|
|
|
51
58
|
"dependencies": {
|
|
52
59
|
"@pgsql/types": "^17.6.1"
|
|
53
60
|
},
|
|
54
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "db3e424332bcb223c34a1caca3f37fb6fc9a1964"
|
|
55
62
|
}
|
package/esm/utils/index.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-restricted-syntax */
|
|
2
|
-
export const cleanLines = (sql) => {
|
|
3
|
-
return sql
|
|
4
|
-
.split('\n')
|
|
5
|
-
.map((l) => l.trim())
|
|
6
|
-
.filter((a) => a)
|
|
7
|
-
.join('\n');
|
|
8
|
-
};
|
|
9
|
-
export const transform = (obj, props) => {
|
|
10
|
-
let copy = null;
|
|
11
|
-
// Handle the 3 simple types, and null or undefined
|
|
12
|
-
if (obj == null || typeof obj !== 'object') {
|
|
13
|
-
return obj;
|
|
14
|
-
}
|
|
15
|
-
// Handle Date
|
|
16
|
-
if (obj instanceof Date) {
|
|
17
|
-
copy = new Date();
|
|
18
|
-
copy.setTime(obj.getTime());
|
|
19
|
-
return copy;
|
|
20
|
-
}
|
|
21
|
-
// Handle Array
|
|
22
|
-
if (obj instanceof Array) {
|
|
23
|
-
copy = [];
|
|
24
|
-
for (let i = 0, len = obj.length; i < len; i++) {
|
|
25
|
-
copy[i] = transform(obj[i], props);
|
|
26
|
-
}
|
|
27
|
-
return copy;
|
|
28
|
-
}
|
|
29
|
-
// Handle Object
|
|
30
|
-
if (obj instanceof Object || typeof obj === 'object') {
|
|
31
|
-
copy = {};
|
|
32
|
-
for (const attr in obj) {
|
|
33
|
-
if (obj.hasOwnProperty(attr)) {
|
|
34
|
-
if (props.hasOwnProperty(attr)) {
|
|
35
|
-
if (typeof props[attr] === 'function') {
|
|
36
|
-
copy[attr] = props[attr](obj[attr]);
|
|
37
|
-
}
|
|
38
|
-
else if (props[attr].hasOwnProperty(obj[attr])) {
|
|
39
|
-
copy[attr] = props[attr][obj[attr]];
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
copy[attr] = transform(obj[attr], props);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
copy[attr] = transform(obj[attr], props);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
copy[attr] = transform(obj[attr], props);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return copy;
|
|
54
|
-
}
|
|
55
|
-
throw new Error("Unable to copy obj! Its type isn't supported.");
|
|
56
|
-
};
|
|
57
|
-
const noop = () => undefined;
|
|
58
|
-
export const cleanTree = (tree) => {
|
|
59
|
-
return transform(tree, {
|
|
60
|
-
stmt_len: noop,
|
|
61
|
-
stmt_location: noop,
|
|
62
|
-
location: noop,
|
|
63
|
-
DefElem: (obj) => {
|
|
64
|
-
if (obj.defname === 'as') {
|
|
65
|
-
if (Array.isArray(obj.arg) && obj.arg.length) {
|
|
66
|
-
// function
|
|
67
|
-
obj.arg[0].String.sval = obj.arg[0].String.sval.trim();
|
|
68
|
-
}
|
|
69
|
-
else if (obj.arg.List && obj.arg.List.items) {
|
|
70
|
-
// function
|
|
71
|
-
obj.arg.List.items[0].String.sval = obj.arg.List.items[0].String.sval.trim();
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
// do stmt
|
|
75
|
-
obj.arg.String.sval = obj.arg.String.sval.trim();
|
|
76
|
-
}
|
|
77
|
-
return cleanTree(obj);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
return cleanTree(obj);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
};
|
|
85
|
-
export const cleanTreeWithStmt = (tree) => {
|
|
86
|
-
return transform(tree, {
|
|
87
|
-
stmt_location: noop,
|
|
88
|
-
location: noop
|
|
89
|
-
});
|
|
90
|
-
};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { parse } from 'libpg-query';
|
|
2
|
-
/**
|
|
3
|
-
* Extracts a single statement from SQL using PostgreSQL's location information.
|
|
4
|
-
* Handles Unicode properly by using byte positions instead of character positions.
|
|
5
|
-
*/
|
|
6
|
-
export function extractStatement(originalSQL, rawStmt, isFirst = false, options = {}) {
|
|
7
|
-
let extracted = null;
|
|
8
|
-
// Convert string to buffer to handle byte positions correctly (for Unicode)
|
|
9
|
-
const sqlBuffer = Buffer.from(originalSQL, 'utf8');
|
|
10
|
-
if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len !== undefined) {
|
|
11
|
-
// Use byte positions as provided by PostgreSQL
|
|
12
|
-
const startByte = rawStmt.stmt_location;
|
|
13
|
-
const endByte = rawStmt.stmt_location + rawStmt.stmt_len;
|
|
14
|
-
// Extract using byte positions and convert back to string
|
|
15
|
-
const extractedBuffer = sqlBuffer.slice(startByte, endByte);
|
|
16
|
-
extracted = extractedBuffer.toString('utf8');
|
|
17
|
-
}
|
|
18
|
-
else if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len === undefined) {
|
|
19
|
-
// We have location but no length - extract from location to end of file
|
|
20
|
-
const extractedBuffer = sqlBuffer.slice(rawStmt.stmt_location);
|
|
21
|
-
extracted = extractedBuffer.toString('utf8');
|
|
22
|
-
}
|
|
23
|
-
else if (isFirst && rawStmt.stmt_len !== undefined) {
|
|
24
|
-
// For first statement when location is missing but we have length
|
|
25
|
-
const extractedBuffer = sqlBuffer.slice(0, rawStmt.stmt_len);
|
|
26
|
-
extracted = extractedBuffer.toString('utf8');
|
|
27
|
-
}
|
|
28
|
-
else if (isFirst && rawStmt.stmt_location === undefined && rawStmt.stmt_len === undefined) {
|
|
29
|
-
// For first statement when both location and length are missing, use entire SQL
|
|
30
|
-
extracted = originalSQL;
|
|
31
|
-
}
|
|
32
|
-
if (extracted && options.stripComments !== false) {
|
|
33
|
-
// Split into lines to handle leading whitespace and comments properly
|
|
34
|
-
const lines = extracted.split('\n');
|
|
35
|
-
let startLineIndex = 0;
|
|
36
|
-
// Find the first line that contains actual SQL content
|
|
37
|
-
for (let i = 0; i < lines.length; i++) {
|
|
38
|
-
const line = lines[i].trim();
|
|
39
|
-
// Skip empty lines and comment-only lines
|
|
40
|
-
if (line === '' || line.startsWith('--')) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
startLineIndex = i;
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
// Reconstruct from the first SQL line, preserving the original indentation of that line
|
|
47
|
-
if (startLineIndex < lines.length) {
|
|
48
|
-
const resultLines = lines.slice(startLineIndex);
|
|
49
|
-
extracted = resultLines.join('\n').trim();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// Final validation unless skipped
|
|
53
|
-
if (extracted && !options.skipValidation) {
|
|
54
|
-
const firstLine = extracted.split('\n')[0].trim();
|
|
55
|
-
const firstWord = firstLine.split(/\s+/)[0].toUpperCase();
|
|
56
|
-
// Only check for most obvious malformed patterns at the BEGINNING
|
|
57
|
-
if (
|
|
58
|
-
// Check if it starts with truncated patterns (not just contains anywhere)
|
|
59
|
-
extracted.trim().startsWith('ELECT ') || // Missing S from SELECT
|
|
60
|
-
extracted.trim().startsWith('REATE ') || // Missing C from CREATE
|
|
61
|
-
extracted.trim().startsWith('NSERT ') || // Missing I from INSERT
|
|
62
|
-
// Completely empty or whitespace only
|
|
63
|
-
extracted.trim().length === 0) {
|
|
64
|
-
return null; // Invalid extraction, skip this statement
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return extracted;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Splits SQL text into individual statements using PostgreSQL's parser.
|
|
71
|
-
* Handles Unicode characters properly and provides detailed location information.
|
|
72
|
-
*/
|
|
73
|
-
export async function splitStatements(sql, options = {}) {
|
|
74
|
-
const parseResult = await parse(sql);
|
|
75
|
-
const statements = [];
|
|
76
|
-
if (!parseResult.stmts) {
|
|
77
|
-
return statements;
|
|
78
|
-
}
|
|
79
|
-
for (let idx = 0; idx < parseResult.stmts.length; idx++) {
|
|
80
|
-
const stmt = parseResult.stmts[idx];
|
|
81
|
-
const extracted = extractStatement(sql, stmt, idx === 0, options);
|
|
82
|
-
if (extracted) {
|
|
83
|
-
statements.push({
|
|
84
|
-
statement: extracted,
|
|
85
|
-
index: idx,
|
|
86
|
-
location: stmt.stmt_location,
|
|
87
|
-
length: stmt.stmt_len
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return statements;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Utility to generate statement keys for fixtures
|
|
95
|
-
*/
|
|
96
|
-
export function generateStatementKey(relativePath, statementIndex, extension = 'sql') {
|
|
97
|
-
return `${relativePath.replace(/\.sql$/, '')}-${statementIndex + 1}.${extension}`;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Test utility to compare byte vs character extraction for debugging Unicode issues
|
|
101
|
-
*/
|
|
102
|
-
export function debugUnicodeExtraction(sql, rawStmt) {
|
|
103
|
-
const charLength = sql.length;
|
|
104
|
-
const byteLength = Buffer.from(sql, 'utf8').length;
|
|
105
|
-
// Character-based extraction (old way)
|
|
106
|
-
let characterBased = '';
|
|
107
|
-
if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len !== undefined) {
|
|
108
|
-
characterBased = sql.substring(rawStmt.stmt_location, rawStmt.stmt_location + rawStmt.stmt_len);
|
|
109
|
-
}
|
|
110
|
-
// Byte-based extraction (new way)
|
|
111
|
-
let byteBased = '';
|
|
112
|
-
if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len !== undefined) {
|
|
113
|
-
const sqlBuffer = Buffer.from(sql, 'utf8');
|
|
114
|
-
const extractedBuffer = sqlBuffer.slice(rawStmt.stmt_location, rawStmt.stmt_location + rawStmt.stmt_len);
|
|
115
|
-
byteBased = extractedBuffer.toString('utf8');
|
|
116
|
-
}
|
|
117
|
-
return {
|
|
118
|
-
characterBased,
|
|
119
|
-
byteBased,
|
|
120
|
-
matches: characterBased === byteBased,
|
|
121
|
-
unicodeChars: byteLength - charLength,
|
|
122
|
-
byteLength,
|
|
123
|
-
charLength
|
|
124
|
-
};
|
|
125
|
-
}
|
package/utils/index.d.ts
DELETED
package/utils/index.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/* eslint-disable no-restricted-syntax */
|
|
3
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.cleanTreeWithStmt = exports.cleanTree = exports.transform = exports.cleanLines = void 0;
|
|
5
|
-
const cleanLines = (sql) => {
|
|
6
|
-
return sql
|
|
7
|
-
.split('\n')
|
|
8
|
-
.map((l) => l.trim())
|
|
9
|
-
.filter((a) => a)
|
|
10
|
-
.join('\n');
|
|
11
|
-
};
|
|
12
|
-
exports.cleanLines = cleanLines;
|
|
13
|
-
const transform = (obj, props) => {
|
|
14
|
-
let copy = null;
|
|
15
|
-
// Handle the 3 simple types, and null or undefined
|
|
16
|
-
if (obj == null || typeof obj !== 'object') {
|
|
17
|
-
return obj;
|
|
18
|
-
}
|
|
19
|
-
// Handle Date
|
|
20
|
-
if (obj instanceof Date) {
|
|
21
|
-
copy = new Date();
|
|
22
|
-
copy.setTime(obj.getTime());
|
|
23
|
-
return copy;
|
|
24
|
-
}
|
|
25
|
-
// Handle Array
|
|
26
|
-
if (obj instanceof Array) {
|
|
27
|
-
copy = [];
|
|
28
|
-
for (let i = 0, len = obj.length; i < len; i++) {
|
|
29
|
-
copy[i] = (0, exports.transform)(obj[i], props);
|
|
30
|
-
}
|
|
31
|
-
return copy;
|
|
32
|
-
}
|
|
33
|
-
// Handle Object
|
|
34
|
-
if (obj instanceof Object || typeof obj === 'object') {
|
|
35
|
-
copy = {};
|
|
36
|
-
for (const attr in obj) {
|
|
37
|
-
if (obj.hasOwnProperty(attr)) {
|
|
38
|
-
if (props.hasOwnProperty(attr)) {
|
|
39
|
-
if (typeof props[attr] === 'function') {
|
|
40
|
-
copy[attr] = props[attr](obj[attr]);
|
|
41
|
-
}
|
|
42
|
-
else if (props[attr].hasOwnProperty(obj[attr])) {
|
|
43
|
-
copy[attr] = props[attr][obj[attr]];
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
copy[attr] = (0, exports.transform)(obj[attr], props);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
copy[attr] = (0, exports.transform)(obj[attr], props);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
copy[attr] = (0, exports.transform)(obj[attr], props);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
return copy;
|
|
58
|
-
}
|
|
59
|
-
throw new Error("Unable to copy obj! Its type isn't supported.");
|
|
60
|
-
};
|
|
61
|
-
exports.transform = transform;
|
|
62
|
-
const noop = () => undefined;
|
|
63
|
-
const cleanTree = (tree) => {
|
|
64
|
-
return (0, exports.transform)(tree, {
|
|
65
|
-
stmt_len: noop,
|
|
66
|
-
stmt_location: noop,
|
|
67
|
-
location: noop,
|
|
68
|
-
DefElem: (obj) => {
|
|
69
|
-
if (obj.defname === 'as') {
|
|
70
|
-
if (Array.isArray(obj.arg) && obj.arg.length) {
|
|
71
|
-
// function
|
|
72
|
-
obj.arg[0].String.sval = obj.arg[0].String.sval.trim();
|
|
73
|
-
}
|
|
74
|
-
else if (obj.arg.List && obj.arg.List.items) {
|
|
75
|
-
// function
|
|
76
|
-
obj.arg.List.items[0].String.sval = obj.arg.List.items[0].String.sval.trim();
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
// do stmt
|
|
80
|
-
obj.arg.String.sval = obj.arg.String.sval.trim();
|
|
81
|
-
}
|
|
82
|
-
return (0, exports.cleanTree)(obj);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
return (0, exports.cleanTree)(obj);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
exports.cleanTree = cleanTree;
|
|
91
|
-
const cleanTreeWithStmt = (tree) => {
|
|
92
|
-
return (0, exports.transform)(tree, {
|
|
93
|
-
stmt_location: noop,
|
|
94
|
-
location: noop
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
exports.cleanTreeWithStmt = cleanTreeWithStmt;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { RawStmt } from '@pgsql/types';
|
|
2
|
-
export interface ExtractedStatement {
|
|
3
|
-
statement: string;
|
|
4
|
-
index: number;
|
|
5
|
-
location?: number;
|
|
6
|
-
length?: number;
|
|
7
|
-
}
|
|
8
|
-
export interface StatementSplitterOptions {
|
|
9
|
-
/** Skip validation for malformed statements */
|
|
10
|
-
skipValidation?: boolean;
|
|
11
|
-
/** Strip leading comments from extracted statements */
|
|
12
|
-
stripComments?: boolean;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Extracts a single statement from SQL using PostgreSQL's location information.
|
|
16
|
-
* Handles Unicode properly by using byte positions instead of character positions.
|
|
17
|
-
*/
|
|
18
|
-
export declare function extractStatement(originalSQL: string, rawStmt: RawStmt, isFirst?: boolean, options?: StatementSplitterOptions): string | null;
|
|
19
|
-
/**
|
|
20
|
-
* Splits SQL text into individual statements using PostgreSQL's parser.
|
|
21
|
-
* Handles Unicode characters properly and provides detailed location information.
|
|
22
|
-
*/
|
|
23
|
-
export declare function splitStatements(sql: string, options?: StatementSplitterOptions): Promise<ExtractedStatement[]>;
|
|
24
|
-
/**
|
|
25
|
-
* Utility to generate statement keys for fixtures
|
|
26
|
-
*/
|
|
27
|
-
export declare function generateStatementKey(relativePath: string, statementIndex: number, extension?: string): string;
|
|
28
|
-
/**
|
|
29
|
-
* Test utility to compare byte vs character extraction for debugging Unicode issues
|
|
30
|
-
*/
|
|
31
|
-
export declare function debugUnicodeExtraction(sql: string, rawStmt: RawStmt): {
|
|
32
|
-
characterBased: string;
|
|
33
|
-
byteBased: string;
|
|
34
|
-
matches: boolean;
|
|
35
|
-
unicodeChars: number;
|
|
36
|
-
byteLength: number;
|
|
37
|
-
charLength: number;
|
|
38
|
-
};
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.extractStatement = extractStatement;
|
|
4
|
-
exports.splitStatements = splitStatements;
|
|
5
|
-
exports.generateStatementKey = generateStatementKey;
|
|
6
|
-
exports.debugUnicodeExtraction = debugUnicodeExtraction;
|
|
7
|
-
const libpg_query_1 = require("libpg-query");
|
|
8
|
-
/**
|
|
9
|
-
* Extracts a single statement from SQL using PostgreSQL's location information.
|
|
10
|
-
* Handles Unicode properly by using byte positions instead of character positions.
|
|
11
|
-
*/
|
|
12
|
-
function extractStatement(originalSQL, rawStmt, isFirst = false, options = {}) {
|
|
13
|
-
let extracted = null;
|
|
14
|
-
// Convert string to buffer to handle byte positions correctly (for Unicode)
|
|
15
|
-
const sqlBuffer = Buffer.from(originalSQL, 'utf8');
|
|
16
|
-
if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len !== undefined) {
|
|
17
|
-
// Use byte positions as provided by PostgreSQL
|
|
18
|
-
const startByte = rawStmt.stmt_location;
|
|
19
|
-
const endByte = rawStmt.stmt_location + rawStmt.stmt_len;
|
|
20
|
-
// Extract using byte positions and convert back to string
|
|
21
|
-
const extractedBuffer = sqlBuffer.slice(startByte, endByte);
|
|
22
|
-
extracted = extractedBuffer.toString('utf8');
|
|
23
|
-
}
|
|
24
|
-
else if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len === undefined) {
|
|
25
|
-
// We have location but no length - extract from location to end of file
|
|
26
|
-
const extractedBuffer = sqlBuffer.slice(rawStmt.stmt_location);
|
|
27
|
-
extracted = extractedBuffer.toString('utf8');
|
|
28
|
-
}
|
|
29
|
-
else if (isFirst && rawStmt.stmt_len !== undefined) {
|
|
30
|
-
// For first statement when location is missing but we have length
|
|
31
|
-
const extractedBuffer = sqlBuffer.slice(0, rawStmt.stmt_len);
|
|
32
|
-
extracted = extractedBuffer.toString('utf8');
|
|
33
|
-
}
|
|
34
|
-
else if (isFirst && rawStmt.stmt_location === undefined && rawStmt.stmt_len === undefined) {
|
|
35
|
-
// For first statement when both location and length are missing, use entire SQL
|
|
36
|
-
extracted = originalSQL;
|
|
37
|
-
}
|
|
38
|
-
if (extracted && options.stripComments !== false) {
|
|
39
|
-
// Split into lines to handle leading whitespace and comments properly
|
|
40
|
-
const lines = extracted.split('\n');
|
|
41
|
-
let startLineIndex = 0;
|
|
42
|
-
// Find the first line that contains actual SQL content
|
|
43
|
-
for (let i = 0; i < lines.length; i++) {
|
|
44
|
-
const line = lines[i].trim();
|
|
45
|
-
// Skip empty lines and comment-only lines
|
|
46
|
-
if (line === '' || line.startsWith('--')) {
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
startLineIndex = i;
|
|
50
|
-
break;
|
|
51
|
-
}
|
|
52
|
-
// Reconstruct from the first SQL line, preserving the original indentation of that line
|
|
53
|
-
if (startLineIndex < lines.length) {
|
|
54
|
-
const resultLines = lines.slice(startLineIndex);
|
|
55
|
-
extracted = resultLines.join('\n').trim();
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// Final validation unless skipped
|
|
59
|
-
if (extracted && !options.skipValidation) {
|
|
60
|
-
const firstLine = extracted.split('\n')[0].trim();
|
|
61
|
-
const firstWord = firstLine.split(/\s+/)[0].toUpperCase();
|
|
62
|
-
// Only check for most obvious malformed patterns at the BEGINNING
|
|
63
|
-
if (
|
|
64
|
-
// Check if it starts with truncated patterns (not just contains anywhere)
|
|
65
|
-
extracted.trim().startsWith('ELECT ') || // Missing S from SELECT
|
|
66
|
-
extracted.trim().startsWith('REATE ') || // Missing C from CREATE
|
|
67
|
-
extracted.trim().startsWith('NSERT ') || // Missing I from INSERT
|
|
68
|
-
// Completely empty or whitespace only
|
|
69
|
-
extracted.trim().length === 0) {
|
|
70
|
-
return null; // Invalid extraction, skip this statement
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return extracted;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Splits SQL text into individual statements using PostgreSQL's parser.
|
|
77
|
-
* Handles Unicode characters properly and provides detailed location information.
|
|
78
|
-
*/
|
|
79
|
-
async function splitStatements(sql, options = {}) {
|
|
80
|
-
const parseResult = await (0, libpg_query_1.parse)(sql);
|
|
81
|
-
const statements = [];
|
|
82
|
-
if (!parseResult.stmts) {
|
|
83
|
-
return statements;
|
|
84
|
-
}
|
|
85
|
-
for (let idx = 0; idx < parseResult.stmts.length; idx++) {
|
|
86
|
-
const stmt = parseResult.stmts[idx];
|
|
87
|
-
const extracted = extractStatement(sql, stmt, idx === 0, options);
|
|
88
|
-
if (extracted) {
|
|
89
|
-
statements.push({
|
|
90
|
-
statement: extracted,
|
|
91
|
-
index: idx,
|
|
92
|
-
location: stmt.stmt_location,
|
|
93
|
-
length: stmt.stmt_len
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return statements;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Utility to generate statement keys for fixtures
|
|
101
|
-
*/
|
|
102
|
-
function generateStatementKey(relativePath, statementIndex, extension = 'sql') {
|
|
103
|
-
return `${relativePath.replace(/\.sql$/, '')}-${statementIndex + 1}.${extension}`;
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Test utility to compare byte vs character extraction for debugging Unicode issues
|
|
107
|
-
*/
|
|
108
|
-
function debugUnicodeExtraction(sql, rawStmt) {
|
|
109
|
-
const charLength = sql.length;
|
|
110
|
-
const byteLength = Buffer.from(sql, 'utf8').length;
|
|
111
|
-
// Character-based extraction (old way)
|
|
112
|
-
let characterBased = '';
|
|
113
|
-
if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len !== undefined) {
|
|
114
|
-
characterBased = sql.substring(rawStmt.stmt_location, rawStmt.stmt_location + rawStmt.stmt_len);
|
|
115
|
-
}
|
|
116
|
-
// Byte-based extraction (new way)
|
|
117
|
-
let byteBased = '';
|
|
118
|
-
if (rawStmt.stmt_location !== undefined && rawStmt.stmt_len !== undefined) {
|
|
119
|
-
const sqlBuffer = Buffer.from(sql, 'utf8');
|
|
120
|
-
const extractedBuffer = sqlBuffer.slice(rawStmt.stmt_location, rawStmt.stmt_location + rawStmt.stmt_len);
|
|
121
|
-
byteBased = extractedBuffer.toString('utf8');
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
characterBased,
|
|
125
|
-
byteBased,
|
|
126
|
-
matches: characterBased === byteBased,
|
|
127
|
-
unicodeChars: byteLength - charLength,
|
|
128
|
-
byteLength,
|
|
129
|
-
charLength
|
|
130
|
-
};
|
|
131
|
-
}
|