libpg-query
Version:
The real PostgreSQL query parser
279 lines (278 loc) • 11.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
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = exports.SqlError = void 0;
exports.formatSqlError = formatSqlError;
exports.hasSqlDetails = hasSqlDetails;
exports.loadModule = loadModule;
exports.parseSync = parseSync;
__exportStar(require("@pgsql/types"), exports);
// @ts-ignore
const libpg_query_js_1 = __importDefault(require("./libpg-query.js"));
let wasmModule;
class SqlError extends Error {
sqlDetails;
constructor(message, details) {
super(message);
this.name = 'SqlError';
this.sqlDetails = details;
}
}
exports.SqlError = SqlError;
// Helper function to classify error source
function getErrorSource(filename) {
if (!filename)
return 'unknown';
if (filename === 'scan.l')
return 'lexer'; // Lexical analysis errors
if (filename === 'gram.y')
return 'parser'; // Grammar/parsing errors
return filename;
}
// Format SQL error with visual position indicator
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;
// 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
const adjustedPosition = cursorPosition - start + (start > 0 ? 3 : 0);
lines.push(displayQuery);
lines.push(' '.repeat(adjustedPosition) + `${yellow}^${reset}`);
}
else {
lines.push(displayQuery);
lines.push(' '.repeat(cursorPosition) + `${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');
}
// Check if an error has SQL details
function hasSqlDetails(error) {
return error instanceof Error &&
'sqlDetails' in error &&
typeof error.sqlDetails === 'object' &&
error.sqlDetails !== null &&
'message' in error.sqlDetails &&
'cursorPosition' in error.sqlDetails;
}
const initPromise = (0, libpg_query_js_1.default)().then((module) => {
wasmModule = module;
});
function ensureLoaded() {
if (!wasmModule)
throw new Error("WASM module not initialized. Call `loadModule()` first.");
}
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);
}
exports.parse = awaitInit(async (query) => {
// Pre-validation
if (query === null || query === undefined) {
throw new Error('Query cannot be null or undefined');
}
if (typeof query !== 'string') {
throw new Error(`Query must be a string, got ${typeof query}`);
}
if (query.trim() === '') {
throw new Error('Query cannot be empty');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
// Call the raw function that returns a struct pointer
resultPtr = wasmModule._wasm_parse_query_raw(queryPtr);
if (!resultPtr) {
throw new Error('Failed to allocate memory for parse result');
}
// Read the PgQueryParseResult struct fields
// struct { char* parse_tree; char* stderr_buffer; PgQueryError* error; }
const parseTreePtr = wasmModule.getValue(resultPtr, 'i32'); // offset 0
const stderrBufferPtr = wasmModule.getValue(resultPtr + 4, 'i32'); // offset 4
const errorPtr = wasmModule.getValue(resultPtr + 8, 'i32'); // offset 8
// Check for error
if (errorPtr) {
// Read PgQueryError struct fields
// struct { char* message; char* funcname; char* filename; int lineno; int cursorpos; char* context; }
const messagePtr = wasmModule.getValue(errorPtr, 'i32'); // offset 0
const funcnamePtr = wasmModule.getValue(errorPtr + 4, 'i32'); // offset 4
const filenamePtr = wasmModule.getValue(errorPtr + 8, 'i32'); // offset 8
const lineno = wasmModule.getValue(errorPtr + 12, 'i32'); // offset 12
const cursorpos = wasmModule.getValue(errorPtr + 16, 'i32'); // offset 16
const contextPtr = wasmModule.getValue(errorPtr + 20, 'i32'); // offset 20
const message = messagePtr ? wasmModule.UTF8ToString(messagePtr) : 'Unknown error';
const filename = filenamePtr ? wasmModule.UTF8ToString(filenamePtr) : null;
const errorDetails = {
message: message,
cursorPosition: cursorpos > 0 ? cursorpos - 1 : 0, // Convert to 0-based
fileName: filename || undefined,
functionName: funcnamePtr ? wasmModule.UTF8ToString(funcnamePtr) : undefined,
lineNumber: lineno > 0 ? lineno : undefined,
context: contextPtr ? wasmModule.UTF8ToString(contextPtr) : undefined
};
throw new SqlError(message, errorDetails);
}
if (!parseTreePtr) {
throw new Error('Parse result is null');
}
const parseTree = wasmModule.UTF8ToString(parseTreePtr);
return JSON.parse(parseTree);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_parse_result(resultPtr);
}
}
});
function parseSync(query) {
// Pre-validation
if (query === null || query === undefined) {
throw new Error('Query cannot be null or undefined');
}
if (typeof query !== 'string') {
throw new Error(`Query must be a string, got ${typeof query}`);
}
if (query.trim() === '') {
throw new Error('Query cannot be empty');
}
const queryPtr = stringToPtr(query);
let resultPtr = 0;
try {
// Call the raw function that returns a struct pointer
resultPtr = wasmModule._wasm_parse_query_raw(queryPtr);
if (!resultPtr) {
throw new Error('Failed to allocate memory for parse result');
}
// Read the PgQueryParseResult struct fields
// struct { char* parse_tree; char* stderr_buffer; PgQueryError* error; }
const parseTreePtr = wasmModule.getValue(resultPtr, 'i32'); // offset 0
const stderrBufferPtr = wasmModule.getValue(resultPtr + 4, 'i32'); // offset 4
const errorPtr = wasmModule.getValue(resultPtr + 8, 'i32'); // offset 8
// Check for error
if (errorPtr) {
// Read PgQueryError struct fields
// struct { char* message; char* funcname; char* filename; int lineno; int cursorpos; char* context; }
const messagePtr = wasmModule.getValue(errorPtr, 'i32'); // offset 0
const funcnamePtr = wasmModule.getValue(errorPtr + 4, 'i32'); // offset 4
const filenamePtr = wasmModule.getValue(errorPtr + 8, 'i32'); // offset 8
const lineno = wasmModule.getValue(errorPtr + 12, 'i32'); // offset 12
const cursorpos = wasmModule.getValue(errorPtr + 16, 'i32'); // offset 16
const contextPtr = wasmModule.getValue(errorPtr + 20, 'i32'); // offset 20
const message = messagePtr ? wasmModule.UTF8ToString(messagePtr) : 'Unknown error';
const filename = filenamePtr ? wasmModule.UTF8ToString(filenamePtr) : null;
const errorDetails = {
message: message,
cursorPosition: cursorpos > 0 ? cursorpos - 1 : 0, // Convert to 0-based
fileName: filename || undefined,
functionName: funcnamePtr ? wasmModule.UTF8ToString(funcnamePtr) : undefined,
lineNumber: lineno > 0 ? lineno : undefined,
context: contextPtr ? wasmModule.UTF8ToString(contextPtr) : undefined
};
throw new SqlError(message, errorDetails);
}
if (!parseTreePtr) {
throw new Error('Parse result is null');
}
const parseTree = wasmModule.UTF8ToString(parseTreePtr);
return JSON.parse(parseTree);
}
finally {
wasmModule._free(queryPtr);
if (resultPtr) {
wasmModule._wasm_free_parse_result(resultPtr);
}
}
}
;