@algochad/prisma-core
Version:
A comprehensive NestJS library that provides EF-Core-like operations using Prisma and GraphQL. Features LINQ-style query builders, advanced data manipulation, GraphQL integration with genql, and a unified API for both Prisma and GraphQL operations. Includ
371 lines • 13 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LinqExpressionParser = void 0;
const database_dialect_1 = require("./database-dialect");
class LinqExpressionParser {
static databaseDialect = 'sqlite';
static setDatabaseDialect(dialect) {
this.databaseDialect = dialect;
}
static supportsCaseInsensitiveMode() {
const config = database_dialect_1.DatabaseDialectDetector.getConfig(this.databaseDialect);
return (config.supportsCaseInsensitive && this.databaseDialect !== 'sqlite');
}
static parseLambdaExpression(lambdaFn) {
const fnString = lambdaFn.toString();
let expression = fnString
.replace(/^\s*\(?[^)]*\)?\s*=>\s*/, '')
.replace(/^function\s*\([^)]*\)\s*{\s*return\s*/, '')
.replace(/;\s*}$/, '')
.trim();
try {
const parsedExpression = this.parseExpression(expression);
if (parsedExpression.type === 'member') {
console.warn(`Cannot convert bare member access '${parsedExpression.member}' to where clause. ` +
`Use explicit comparison like 'item.${parsedExpression.member} === value'`);
return null;
}
return this.convertToWhereObject(parsedExpression);
}
catch (error) {
console.warn('Failed to parse lambda expression, falling back to function execution:', error);
return null;
}
}
static parseExpression(expr) {
expr = expr.trim();
if (expr.includes('&&') || expr.includes('||')) {
return this.parseLogicalExpression(expr);
}
const comparisonOps = ['===', '!==', '==', '!=', '<=', '>=', '<', '>'];
for (const op of comparisonOps) {
const index = expr.lastIndexOf(op);
if (index > 0) {
const left = expr.substring(0, index).trim();
const right = expr.substring(index + op.length).trim();
return {
type: 'binary',
operator: op,
left: this.parseExpression(left),
right: this.parseExpression(right),
};
}
}
if (expr.includes('(') && expr.includes(')')) {
return this.parseMethodCall(expr);
}
if (expr.includes('.')) {
const parts = expr.split('.');
if (parts.length >= 2) {
return {
type: 'member',
member: parts.slice(1).join('.'),
};
}
}
if (expr.startsWith('"') && expr.endsWith('"')) {
return {
type: 'constant',
value: expr.slice(1, -1),
};
}
if (expr.startsWith("'") && expr.endsWith("'")) {
return {
type: 'constant',
value: expr.slice(1, -1),
};
}
if (!isNaN(Number(expr))) {
return {
type: 'constant',
value: Number(expr),
};
}
if (expr === 'true' || expr === 'false') {
return {
type: 'constant',
value: expr === 'true',
};
}
return {
type: 'constant',
value: expr,
};
}
static parseLogicalExpression(expr) {
const andIndex = expr.lastIndexOf('&&');
const orIndex = expr.lastIndexOf('||');
let operator;
let splitIndex;
if (andIndex > orIndex) {
operator = '&&';
splitIndex = andIndex;
}
else {
operator = '||';
splitIndex = orIndex;
}
const left = expr.substring(0, splitIndex).trim();
const right = expr.substring(splitIndex + 2).trim();
return {
type: 'logical',
operator,
expressions: [
this.parseExpression(left),
this.parseExpression(right),
],
};
}
static parseMethodCall(expr) {
const parenIndex = expr.indexOf('(');
const beforeParen = expr.substring(0, parenIndex);
const argsString = expr.substring(parenIndex + 1, expr.lastIndexOf(')'));
if (beforeParen.includes('.')) {
const parts = beforeParen.split('.');
const memberPath = parts.slice(0, -1).join('.');
const methodName = parts[parts.length - 1];
return {
type: 'call',
left: this.parseExpression(memberPath),
method: methodName,
arguments: argsString ? [this.parseExpression(argsString)] : [],
};
}
return {
type: 'call',
method: beforeParen,
arguments: argsString ? [this.parseExpression(argsString)] : [],
};
}
static convertToWhereObject(expression) {
switch (expression.type) {
case 'binary':
return this.convertBinaryExpression(expression);
case 'logical':
return this.convertLogicalExpression(expression);
case 'call':
return this.convertMethodCall(expression);
case 'member':
return null;
case 'constant':
return expression.value;
default:
throw new Error(`Unsupported expression type: ${expression.type}`);
}
}
static convertBinaryExpression(expression) {
const left = expression.left;
const right = expression.right;
const operator = expression.operator;
let fieldName;
let value;
let effectiveOperator = operator;
if (left.type === 'member' &&
(right.type === 'constant' || right.type === 'member')) {
fieldName = left.member;
if (right.type === 'constant') {
value = right.value;
}
else {
return null;
}
}
else if (left.type === 'call' &&
(right.type === 'constant' || typeof right.value !== 'undefined')) {
return this.convertMethodCallComparison(left, right, operator);
}
else if (left.type === 'call' && right.type === 'call') {
return this.convertMethodCallComparison(left, right, operator);
}
else if (left.type === 'constant' && right.type === 'member') {
fieldName = right.member;
value = left.value;
effectiveOperator = this.reverseOperator(operator);
}
else {
return null;
}
switch (effectiveOperator) {
case '===':
case '==':
return { [fieldName]: { equals: value } };
case '!==':
case '!=':
return { [fieldName]: { not: value } };
case '>':
return { [fieldName]: { gt: value } };
case '>=':
return { [fieldName]: { gte: value } };
case '<':
return { [fieldName]: { lt: value } };
case '<=':
return { [fieldName]: { lte: value } };
default:
throw new Error(`Unsupported operator: ${effectiveOperator}`);
}
}
static reverseOperator(operator) {
switch (operator) {
case '>':
return '<';
case '>=':
return '<=';
case '<':
return '>';
case '<=':
return '>=';
default:
return operator;
}
}
static convertLogicalExpression(expression) {
const operator = expression.operator;
const expressions = expression.expressions;
const conditions = expressions
.map((expr) => this.convertToWhereObject(expr))
.filter((condition) => {
return (condition !== null &&
condition !== undefined &&
typeof condition === 'object' &&
!Array.isArray(condition));
});
if (conditions.length === 0) {
return null;
}
if (conditions.length === 1) {
return conditions[0];
}
if (operator === '&&') {
return { AND: conditions };
}
else if (operator === '||') {
return { OR: conditions };
}
else {
throw new Error(`Unsupported logical operator: ${operator}`);
}
}
static convertMethodCall(expression) {
if (!expression.left || expression.left.type !== 'member') {
throw new Error('Method calls must be on object members');
}
const fieldName = expression.left.member;
const methodName = expression.method;
switch (methodName) {
case 'toLowerCase':
return {
type: 'call',
field: fieldName,
method: 'toLowerCase',
};
case 'toUpperCase':
return {
type: 'call',
field: fieldName,
method: 'toUpperCase',
};
case 'includes':
case 'contains':
const searchValue = expression.arguments?.[0];
if (searchValue?.type === 'constant') {
return {
[fieldName]: {
contains: searchValue.value,
mode: 'insensitive',
},
};
}
break;
case 'startsWith':
const startsValue = expression.arguments?.[0];
if (startsValue?.type === 'constant') {
return {
[fieldName]: {
startsWith: startsValue.value,
mode: 'insensitive',
},
};
}
break;
case 'endsWith':
const endsValue = expression.arguments?.[0];
if (endsValue?.type === 'constant') {
return {
[fieldName]: {
endsWith: endsValue.value,
mode: 'insensitive',
},
};
}
break;
}
throw new Error(`Unsupported method: ${methodName}`);
}
static convertMethodCallComparison(left, right, operator) {
if (left.method === 'toLowerCase' && left.left?.type === 'member') {
const fieldName = left.left.member;
let compareValue;
if (right.type === 'constant') {
compareValue = right.value;
}
else if (right.type === 'call' &&
right.method === 'toLowerCase') {
if (right.left?.type === 'constant') {
compareValue = right.left.value;
}
else {
return null;
}
}
else {
return null;
}
switch (operator) {
case '===':
case '==':
return {
[fieldName]: {
equals: compareValue,
mode: 'insensitive',
},
};
case '!==':
case '!=':
return {
[fieldName]: {
not: { equals: compareValue, mode: 'insensitive' },
},
};
default:
throw new Error(`Unsupported operator for string comparison: ${operator}`);
}
}
return null;
}
static extractVariables(lambdaFn) {
const fnString = lambdaFn.toString();
const variables = [];
const variableRegex = /(?<![a-zA-Z_$])[a-zA-Z_$][a-zA-Z0-9_$]*(?=\s*[^.(])/g;
const matches = fnString.match(variableRegex) || [];
const keywords = [
'return',
'true',
'false',
'null',
'undefined',
'q',
'item',
'x',
'toLowerCase',
'toUpperCase',
];
for (const match of matches) {
if (!keywords.includes(match) && !variables.includes(match)) {
variables.push(match);
}
}
return variables;
}
}
exports.LinqExpressionParser = LinqExpressionParser;
//# sourceMappingURL=linq-expression-parser.js.map