libpg-query
Version:
The real PostgreSQL query parser
378 lines (377 loc) • 13.6 kB
JavaScript
/**
* DO NOT MODIFY MANUALLY — this is generated from the templates dir
*
* To make changes, edit the files in the templates/ directory and run:
* npm run copy:templates
*/
export * from "@pgsql/types";
export class SqlError extends Error {
sqlDetails;
constructor(message, details) {
super(message);
this.name = 'SqlError';
this.sqlDetails = details;
}
}
export function hasSqlDetails(error) {
return error instanceof SqlError && error.sqlDetails !== undefined;
}
export function formatSqlError(error, query, options = {}) {
const { showPosition = true, showQuery = true, color = false, maxQueryLength } = options;
const lines = [];
// ANSI color codes
const red = color ? '\x1b[31m' : '';
const yellow = color ? '\x1b[33m' : '';
const reset = color ? '\x1b[0m' : '';
// Add error message
lines.push(`${red}Error: ${error.message}${reset}`);
// Add SQL details if available
if (error.sqlDetails) {
const { cursorPosition, fileName, functionName, lineNumber } = error.sqlDetails;
if (cursorPosition !== undefined && cursorPosition >= 0) {
lines.push(`Position: ${cursorPosition}`);
}
if (fileName || functionName || lineNumber) {
const details = [];
if (fileName)
details.push(`file: ${fileName}`);
if (functionName)
details.push(`function: ${functionName}`);
if (lineNumber)
details.push(`line: ${lineNumber}`);
lines.push(`Source: ${details.join(', ')}`);
}
// Show query with position marker
if (showQuery && showPosition && cursorPosition !== undefined && cursorPosition >= 0) {
let displayQuery = query;
let adjustedPosition = cursorPosition;
// Truncate if needed
if (maxQueryLength && query.length > maxQueryLength) {
const start = Math.max(0, cursorPosition - Math.floor(maxQueryLength / 2));
const end = Math.min(query.length, start + maxQueryLength);
displayQuery = (start > 0 ? '...' : '') +
query.substring(start, end) +
(end < query.length ? '...' : '');
// Adjust cursor position for truncation
adjustedPosition = cursorPosition - start + (start > 0 ? 3 : 0);
}
lines.push(displayQuery);
lines.push(' '.repeat(adjustedPosition) + `${yellow}^${reset}`);
}
}
else if (showQuery) {
// No SQL details, just show the query if requested
let displayQuery = query;
if (maxQueryLength && query.length > maxQueryLength) {
displayQuery = query.substring(0, maxQueryLength) + '...';
}
lines.push(`Query: ${displayQuery}`);
}
return lines.join('\n');
}
// @ts-ignore
import PgQueryModule from './libpg-query.js';
let wasmModule;
const initPromise = PgQueryModule().then((module) => {
wasmModule = module;
});
function ensureLoaded() {
if (!wasmModule)
throw new Error("WASM module not initialized. Call `loadModule()` first.");
}
export async function loadModule() {
if (!wasmModule) {
await initPromise;
}
}
function awaitInit(fn) {
return (async (...args) => {
await initPromise;
return fn(...args);
});
}
function stringToPtr(str) {
ensureLoaded();
if (typeof str !== 'string') {
throw new TypeError(`Expected a string, got ${typeof str}`);
}
const len = wasmModule.lengthBytesUTF8(str) + 1;
const ptr = wasmModule._malloc(len);
try {
wasmModule.stringToUTF8(str, ptr, len);
return ptr;
}
catch (error) {
wasmModule._free(ptr);
throw error;
}
}
function ptrToString(ptr) {
ensureLoaded();
if (typeof ptr !== 'number') {
throw new TypeError(`Expected a number, got ${typeof ptr}`);
}
return wasmModule.UTF8ToString(ptr);
}
export const parse = awaitInit(async (query) => {
// Input validation
if (query === null || query === undefined) {
throw new Error('Query cannot be null or undefined');
}
if (query === '') {
throw new Error('Query cannot be empty');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_parse_query_raw(queryPtr);
if (!resultPtr) {
throw new Error('Failed to parse query: memory allocation failed');
}
// Read the PgQueryParseResult struct
const parseTreePtr = wasmModule.getValue(resultPtr, 'i32');
const stderrBufferPtr = wasmModule.getValue(resultPtr + 4, 'i32');
const errorPtr = wasmModule.getValue(resultPtr + 8, 'i32');
if (errorPtr) {
// Read PgQueryError struct
const messagePtr = wasmModule.getValue(errorPtr, 'i32');
const funcnamePtr = wasmModule.getValue(errorPtr + 4, 'i32');
const filenamePtr = wasmModule.getValue(errorPtr + 8, 'i32');
const lineno = wasmModule.getValue(errorPtr + 12, 'i32');
const cursorpos = wasmModule.getValue(errorPtr + 16, 'i32');
const message = messagePtr ? wasmModule.UTF8ToString(messagePtr) : 'Unknown error';
const funcname = funcnamePtr ? wasmModule.UTF8ToString(funcnamePtr) : undefined;
const filename = filenamePtr ? wasmModule.UTF8ToString(filenamePtr) : undefined;
throw new SqlError(message, {
message,
cursorPosition: cursorpos > 0 ? cursorpos - 1 : 0, // Convert to 0-based
fileName: filename,
functionName: funcname,
lineNumber: lineno > 0 ? lineno : undefined
});
}
if (!parseTreePtr) {
throw new Error('No parse tree generated');
}
const parseTreeStr = wasmModule.UTF8ToString(parseTreePtr);
return JSON.parse(parseTreeStr);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_parse_result(resultPtr);
}
}
});
export const parsePlPgSQL = awaitInit(async (query) => {
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_parse_plpgsql(queryPtr);
const resultStr = ptrToString(resultPtr);
// Success is always a JSON object; anything else is an error message
if (!resultStr.startsWith('{')) {
throw new Error(resultStr);
}
return JSON.parse(resultStr);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
});
export const fingerprint = awaitInit(async (query) => {
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_fingerprint(queryPtr);
const resultStr = ptrToString(resultPtr);
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
throw new Error(resultStr);
}
return resultStr;
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
});
export const normalize = awaitInit(async (query) => {
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_normalize_query(queryPtr);
const resultStr = ptrToString(resultPtr);
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
throw new Error(resultStr);
}
return resultStr;
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
});
// Sync versions
export function parseSync(query) {
if (!wasmModule) {
throw new Error('WASM module not initialized. Call loadModule() first.');
}
// Input validation
if (query === null || query === undefined) {
throw new Error('Query cannot be null or undefined');
}
if (query === '') {
throw new Error('Query cannot be empty');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_parse_query_raw(queryPtr);
if (!resultPtr) {
throw new Error('Failed to parse query: memory allocation failed');
}
// Read the PgQueryParseResult struct
const parseTreePtr = wasmModule.getValue(resultPtr, 'i32');
const stderrBufferPtr = wasmModule.getValue(resultPtr + 4, 'i32');
const errorPtr = wasmModule.getValue(resultPtr + 8, 'i32');
if (errorPtr) {
// Read PgQueryError struct
const messagePtr = wasmModule.getValue(errorPtr, 'i32');
const funcnamePtr = wasmModule.getValue(errorPtr + 4, 'i32');
const filenamePtr = wasmModule.getValue(errorPtr + 8, 'i32');
const lineno = wasmModule.getValue(errorPtr + 12, 'i32');
const cursorpos = wasmModule.getValue(errorPtr + 16, 'i32');
const message = messagePtr ? wasmModule.UTF8ToString(messagePtr) : 'Unknown error';
const funcname = funcnamePtr ? wasmModule.UTF8ToString(funcnamePtr) : undefined;
const filename = filenamePtr ? wasmModule.UTF8ToString(filenamePtr) : undefined;
throw new SqlError(message, {
message,
cursorPosition: cursorpos > 0 ? cursorpos - 1 : 0, // Convert to 0-based
fileName: filename,
functionName: funcname,
lineNumber: lineno > 0 ? lineno : undefined
});
}
if (!parseTreePtr) {
throw new Error('No parse tree generated');
}
const parseTreeStr = wasmModule.UTF8ToString(parseTreePtr);
return JSON.parse(parseTreeStr);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_parse_result(resultPtr);
}
}
}
export function parsePlPgSQLSync(query) {
if (!wasmModule) {
throw new Error('WASM module not initialized. Call loadModule() first.');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_parse_plpgsql(queryPtr);
const resultStr = ptrToString(resultPtr);
// Success is always a JSON object; anything else is an error message
if (!resultStr.startsWith('{')) {
throw new Error(resultStr);
}
return JSON.parse(resultStr);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
}
export function fingerprintSync(query) {
if (!wasmModule) {
throw new Error('WASM module not initialized. Call loadModule() first.');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_fingerprint(queryPtr);
const resultStr = ptrToString(resultPtr);
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
throw new Error(resultStr);
}
return resultStr;
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
}
export function normalizeSync(query) {
if (!wasmModule) {
throw new Error('WASM module not initialized. Call loadModule() first.');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_normalize_query(queryPtr);
const resultStr = ptrToString(resultPtr);
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
throw new Error(resultStr);
}
return resultStr;
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
}
export const scan = awaitInit(async (query) => {
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_scan(queryPtr);
const resultStr = ptrToString(resultPtr);
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
throw new Error(resultStr);
}
return JSON.parse(resultStr);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
});
export function scanSync(query) {
if (!wasmModule) {
throw new Error('WASM module not initialized. Call loadModule() first.');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
resultPtr = wasmModule._wasm_scan(queryPtr);
const resultStr = ptrToString(resultPtr);
if (resultStr.startsWith('syntax error') || resultStr.startsWith('deparse error') || resultStr.startsWith('ERROR')) {
throw new Error(resultStr);
}
return JSON.parse(resultStr);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_string(resultPtr);
}
}
}