Skip to content

Commit 899250e

Browse files
committed
parser
1 parent 89b5e6b commit 899250e

File tree

7 files changed

+169
-24
lines changed

7 files changed

+169
-24
lines changed

parser/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pgsql/parser",
3-
"version": "1.0.5",
3+
"version": "1.1.0",
44
"author": "Dan Lynch <pyramation@gmail.com>",
55
"description": "Multi-version PostgreSQL parser with dynamic version selection",
66
"main": "./wasm/index.cjs",

parser/scripts/prepare.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,27 @@ function generateTemplateVars(versions) {
5454
// For TypeScript exports: export * as v13 from './v13/index';
5555
const versionTypeExports = versions.map(v => `export * as v${v} from './v${v}/index';`).join('\n');
5656

57+
// For TypeScript type imports
58+
const versionTypeImports = versions.map(v =>
59+
`import type { ParseResult as ParseResult${v}, Node as Node${v} } from './v${v}/types';`
60+
).join('\n');
61+
62+
// For ParseResult version map
63+
const versionParseResultMap = versions.map(v => ` ${v}: ParseResult${v};`).join('\n');
64+
65+
// For Node version map
66+
const versionNodeMap = versions.map(v => ` ${v}: Node${v};`).join('\n');
67+
5768
return {
5869
DEFAULT_VERSION: defaultVersion,
5970
VERSIONS: versionsArray,
6071
VERSION_UNION: versionUnion,
6172
VERSION_EXPORTS: versionExports,
6273
VERSION_REQUIRES: versionRequires,
63-
VERSION_TYPE_EXPORTS: versionTypeExports
74+
VERSION_TYPE_EXPORTS: versionTypeExports,
75+
VERSION_TYPE_IMPORTS: versionTypeImports,
76+
VERSION_PARSE_RESULT_MAP: versionParseResultMap,
77+
VERSION_NODE_MAP: versionNodeMap
6478
};
6579
}
6680

parser/templates/index.cjs.template

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,53 @@
11
// CommonJS entry point for @pgsql/parser
22
// Provides dynamic version loading for PostgreSQL parsers
33

4+
const SUPPORTED_VERSIONS = [${VERSIONS}];
5+
46
class Parser {
5-
constructor(version = ${DEFAULT_VERSION}) {
6-
if (![${VERSIONS}].includes(version)) {
7+
constructor(options = {}) {
8+
const version = options.version || ${DEFAULT_VERSION};
9+
10+
if (!SUPPORTED_VERSIONS.includes(version)) {
711
throw new Error(`Unsupported PostgreSQL version: ${version}. Supported versions are ${VERSIONS}.`);
812
}
13+
914
this.version = version;
1015
this.parser = null;
16+
this._loadPromise = null;
17+
18+
// Create the ready promise
19+
this.ready = new Promise((resolve) => {
20+
this._resolveReady = resolve;
21+
});
1122
}
1223

1324
async loadParser() {
1425
if (this.parser) return;
1526

27+
// Ensure we only load once
28+
if (!this._loadPromise) {
29+
this._loadPromise = this._doLoad();
30+
}
31+
32+
return this._loadPromise;
33+
}
34+
35+
async _doLoad() {
1636
// Dynamic require for CommonJS
1737
this.parser = require(`./v${this.version}/index.cjs`);
1838

1939
if (this.parser.loadModule) {
2040
await this.parser.loadModule();
2141
}
42+
43+
// Resolve the ready promise
44+
this._resolveReady();
2245
}
2346

2447
async parse(query) {
2548
await this.loadParser();
2649
try {
27-
return this.parser.parse(query);
50+
return await this.parser.parse(query);
2851
} catch (error) {
2952
// Preserve the original error if it's a SqlError
3053
if (error.name === 'SqlError') {
@@ -36,7 +59,7 @@ class Parser {
3659

3760
parseSync(query) {
3861
if (!this.parser) {
39-
throw new Error('Parser not loaded. Call loadParser() first or use parseSync after loading.');
62+
throw new Error('Parser not loaded. Call loadParser() first or use parse() for automatic loading.');
4063
}
4164
try {
4265
return this.parser.parseSync(query);
@@ -48,12 +71,22 @@ class Parser {
4871
throw new Error(`Parse error in PostgreSQL ${this.version}: ${error.message}`);
4972
}
5073
}
74+
}
75+
76+
// Utility functions
77+
function isSupportedVersion(version) {
78+
return SUPPORTED_VERSIONS.includes(version);
79+
}
5180

81+
function getSupportedVersions() {
82+
return [...SUPPORTED_VERSIONS];
5283
}
5384

5485
// Export versions
5586
module.exports = {
5687
Parser,
5788
default: Parser,
89+
isSupportedVersion,
90+
getSupportedVersions,
5891
${VERSION_REQUIRES}
5992
};

parser/templates/index.d.ts.template

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,76 @@
11
// TypeScript definitions for @pgsql/parser
22

3-
export interface ParseResult {
3+
// Supported versions
4+
export type SupportedVersion = ${VERSION_UNION};
5+
6+
// Version-specific type imports
7+
${VERSION_TYPE_IMPORTS}
8+
9+
// Version-specific type mappings
10+
type ParseResultVersionMap = {
11+
${VERSION_PARSE_RESULT_MAP}
12+
};
13+
14+
type NodeVersionMap = {
15+
${VERSION_NODE_MAP}
16+
};
17+
18+
// Generic types with version constraints
19+
export type ParseResult<Version extends SupportedVersion = SupportedVersion> =
20+
ParseResultVersionMap[Version];
21+
22+
export type Node<Version extends SupportedVersion = SupportedVersion> =
23+
NodeVersionMap[Version];
24+
25+
// SQL Error types
26+
export interface SqlErrorDetails {
27+
message: string;
28+
cursorPosition: number;
29+
fileName?: string;
30+
functionName?: string;
31+
lineNumber?: number;
32+
context?: string;
33+
}
34+
35+
export declare class SqlError extends Error {
36+
readonly name: 'SqlError';
37+
sqlDetails?: SqlErrorDetails;
38+
constructor(message: string, details?: SqlErrorDetails);
39+
}
40+
41+
// Parser options
42+
export interface ParserOptions<Version extends SupportedVersion> {
43+
version?: Version;
44+
}
45+
46+
// Main Parser class with generic version support
47+
export declare class Parser<Version extends SupportedVersion = ${DEFAULT_VERSION}> {
48+
readonly version: Version;
49+
readonly ready: Promise<void>;
50+
51+
constructor(options?: ParserOptions<Version>);
52+
53+
/**
54+
* Parse SQL asynchronously. Returns a properly typed ParseResult for the parser version.
55+
* @throws {SqlError} if parsing fails
56+
*/
57+
parse(query: string): Promise<ParseResult<Version>>;
58+
59+
/**
60+
* Parse SQL synchronously. Returns a properly typed ParseResult for the parser version.
61+
* @throws {SqlError} if parsing fails
62+
*/
63+
parseSync(query: string): ParseResult<Version>;
64+
65+
/**
66+
* Load the parser module. This is called automatically on first parse,
67+
* but can be called manually to pre-load the WASM module.
68+
*/
69+
loadParser(): Promise<void>;
70+
}
71+
72+
// Legacy compatibility interface (for backward compatibility)
73+
export interface LegacyParseResult {
474
parse_tree?: any;
575
stderr_buffer?: string;
676
error?: {
@@ -13,12 +83,9 @@ export interface ParseResult {
1383
};
1484
}
1585

16-
export declare class Parser {
17-
constructor(version?: ${VERSION_UNION});
18-
loadParser(): Promise<void>;
19-
parse(query: string): Promise<ParseResult>;
20-
parseSync(query: string): ParseResult;
21-
}
86+
// Utility functions
87+
export declare function isSupportedVersion(version: unknown): version is SupportedVersion;
88+
export declare function getSupportedVersions(): readonly SupportedVersion[];
2289

2390
export default Parser;
2491

parser/templates/index.js.template

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,53 @@
11
// Main entry point for @pgsql/parser
22
// Provides dynamic version loading for PostgreSQL parsers
33

4+
const SUPPORTED_VERSIONS = [${VERSIONS}];
5+
46
export class Parser {
5-
constructor(version = ${DEFAULT_VERSION}) {
6-
if (![${VERSIONS}].includes(version)) {
7+
constructor(options = {}) {
8+
const version = options.version || ${DEFAULT_VERSION};
9+
10+
if (!SUPPORTED_VERSIONS.includes(version)) {
711
throw new Error(`Unsupported PostgreSQL version: ${version}. Supported versions are ${VERSIONS}.`);
812
}
13+
914
this.version = version;
1015
this.parser = null;
16+
this._loadPromise = null;
17+
18+
// Create the ready promise
19+
this.ready = new Promise((resolve) => {
20+
this._resolveReady = resolve;
21+
});
1122
}
1223

1324
async loadParser() {
1425
if (this.parser) return;
1526

27+
// Ensure we only load once
28+
if (!this._loadPromise) {
29+
this._loadPromise = this._doLoad();
30+
}
31+
32+
return this._loadPromise;
33+
}
34+
35+
async _doLoad() {
1636
const module = await import(`./v${this.version}/index.js`);
1737
this.parser = module;
1838

1939
if (this.parser.loadModule) {
2040
await this.parser.loadModule();
2141
}
42+
43+
// Resolve the ready promise
44+
this._resolveReady();
2245
}
2346

2447
async parse(query) {
2548
await this.loadParser();
2649
try {
27-
return this.parser.parse(query);
50+
return await this.parser.parse(query);
2851
} catch (error) {
2952
// Preserve the original error if it's a SqlError
3053
if (error.name === 'SqlError') {
@@ -36,7 +59,7 @@ export class Parser {
3659

3760
parseSync(query) {
3861
if (!this.parser) {
39-
throw new Error('Parser not loaded. Call loadParser() first or use parseSync after loading.');
62+
throw new Error('Parser not loaded. Call loadParser() first or use parse() for automatic loading.');
4063
}
4164
try {
4265
return this.parser.parseSync(query);
@@ -48,7 +71,15 @@ export class Parser {
4871
throw new Error(`Parse error in PostgreSQL ${this.version}: ${error.message}`);
4972
}
5073
}
74+
}
75+
76+
// Utility functions
77+
export function isSupportedVersion(version) {
78+
return SUPPORTED_VERSIONS.includes(version);
79+
}
5180

81+
export function getSupportedVersions() {
82+
return [...SUPPORTED_VERSIONS];
5283
}
5384

5485
// Re-export all versions for direct access

parser/test/errors.test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ describe('Parser Error Handling', () => {
99

1010
for (const version of versions) {
1111
it(`should handle parse errors in PostgreSQL v${version}`, async () => {
12-
const parser = new Parser(version);
12+
const parser = new Parser({version});
1313

1414
// Test async parse
1515
await assert.rejects(
@@ -45,7 +45,7 @@ describe('Parser Error Handling', () => {
4545

4646
describe('Error details preservation', () => {
4747
it('should preserve error details from underlying parser', async () => {
48-
const parser = new Parser(17);
48+
const parser = new Parser({ version: 17 });
4949
await parser.loadParser();
5050

5151
try {
@@ -65,7 +65,7 @@ describe('Parser Error Handling', () => {
6565
describe('Invalid version handling', () => {
6666
it('should throw error for unsupported version', () => {
6767
assert.throws(
68-
() => new Parser(12),
68+
() => new Parser({ version: 12 }),
6969
{
7070
message: 'Unsupported PostgreSQL version: 12. Supported versions are 13, 14, 15, 16, 17.'
7171
}
@@ -75,12 +75,12 @@ describe('Parser Error Handling', () => {
7575

7676
describe('Parser not loaded error', () => {
7777
it('should throw error when using parseSync without loading', () => {
78-
const parser = new Parser(17);
78+
const parser = new Parser({ version: 17 });
7979

8080
assert.throws(
8181
() => parser.parseSync('SELECT 1'),
8282
{
83-
message: 'Parser not loaded. Call loadParser() first or use parseSync after loading.'
83+
message: 'Parser not loaded. Call loadParser() first or use parse() for automatic loading.'
8484
}
8585
);
8686
});

parser/test/parsing.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ describe('Parser', () => {
2020
// Test with a different version if available
2121
const testVersion = defaultVersion === 17 ? 16 : 15;
2222
try {
23-
const versionParser = new Parser(testVersion);
23+
const versionParser = new Parser({ version: testVersion });
2424
const result = await versionParser.parse('SELECT 1+1 as sum');
2525
assert.equal(versionParser.version, testVersion);
2626
assert.ok(result);
@@ -51,7 +51,7 @@ describe('Parser', () => {
5151
it('should validate version in constructor', () => {
5252
// Test invalid version
5353
assert.throws(() => {
54-
new Parser(99);
54+
new Parser({ version: 99 });
5555
}, /Unsupported PostgreSQL version/);
5656
});
5757

0 commit comments

Comments
 (0)