import type { MongoAggExpr } from '@prisma-next/mongo-query-ast/execution'; import { MongoAggCond, MongoAggLiteral, MongoAggOperator, } from '@prisma-next/mongo-query-ast/execution'; import type { ArrayField, BooleanField, DateField, DocField, LiteralValue, NullableDocField, NumericField, StringField, TypedAggExpr, } from './types'; // --------------------------------------------------------------------------- // Internal factory helpers // --------------------------------------------------------------------------- function numericExpr(op: string, args: TypedAggExpr[]): TypedAggExpr { return { _field: { codecId: 'mongo/double@1', nullable: false } as NumericField, node: MongoAggOperator.of( op, args.map((a) => a.node), ), }; } function numericUnaryExpr(op: string, arg: TypedAggExpr): TypedAggExpr { return { _field: { codecId: 'mongo/double@1', nullable: false } as NumericField, node: MongoAggOperator.of(op, arg.node), }; } function stringExpr(op: string, args: TypedAggExpr[]): TypedAggExpr { return { _field: { codecId: 'mongo/string@1', nullable: false } as StringField, node: MongoAggOperator.of( op, args.map((a) => a.node), ), }; } function stringUnaryExpr(op: string, arg: TypedAggExpr): TypedAggExpr { return { _field: { codecId: 'mongo/string@1', nullable: false } as StringField, node: MongoAggOperator.of(op, arg.node), }; } function booleanExpr(op: string, args: TypedAggExpr[]): TypedAggExpr { return { _field: { codecId: 'mongo/bool@1', nullable: false } as BooleanField, node: MongoAggOperator.of( op, args.map((a) => a.node), ), }; } function booleanUnaryExpr(op: string, arg: TypedAggExpr): TypedAggExpr { return { _field: { codecId: 'mongo/bool@1', nullable: false } as BooleanField, node: MongoAggOperator.of(op, arg.node), }; } function dateUnaryExpr(op: string, arg: TypedAggExpr): TypedAggExpr { return { _field: { codecId: 'mongo/date@1', nullable: false } as DateField, node: MongoAggOperator.of(op, arg.node), }; } function arrayExpr(op: string, args: TypedAggExpr[]): TypedAggExpr { return { _field: { codecId: 'mongo/array@1', nullable: false } as ArrayField, node: MongoAggOperator.of( op, args.map((a) => a.node), ), }; } function arrayUnaryExpr(op: string, arg: TypedAggExpr): TypedAggExpr { return { _field: { codecId: 'mongo/array@1', nullable: false } as ArrayField, node: MongoAggOperator.of(op, arg.node), }; } function docUnaryExpr(op: string, arg: TypedAggExpr): TypedAggExpr { return { _field: { codecId: arg._field.codecId, nullable: false }, node: MongoAggOperator.of(op, arg.node), }; } function namedArgsExpr( op: string, args: Readonly | undefined>>, _field: F, ): TypedAggExpr { const nodeArgs: Record = {}; for (const [key, val] of Object.entries(args)) { if (val !== undefined) { nodeArgs[key] = val.node; } } return { _field, node: MongoAggOperator.of(op, nodeArgs) }; } const NUMERIC: NumericField = { codecId: 'mongo/double@1', nullable: false } as NumericField; const STRING: StringField = { codecId: 'mongo/string@1', nullable: false } as StringField; const BOOLEAN: BooleanField = { codecId: 'mongo/bool@1', nullable: false } as BooleanField; const DATE: DateField = { codecId: 'mongo/date@1', nullable: false } as DateField; const ARRAY: ArrayField = { codecId: 'mongo/array@1', nullable: false } as ArrayField; const DOC: DocField = { codecId: 'mongo/document@1', nullable: false }; function literal(value: string): TypedAggExpr; function literal(value: number): TypedAggExpr; function literal(value: boolean): TypedAggExpr; function literal(value: Date): TypedAggExpr; function literal(value: LiteralValue): TypedAggExpr; function literal(value: unknown): TypedAggExpr { return { _field: undefined as never, node: MongoAggLiteral.of(value) }; } // --------------------------------------------------------------------------- // Public helpers // --------------------------------------------------------------------------- export const fn = { // -- Arithmetic (existing) ------------------------------------------------ add(...args: TypedAggExpr[]): TypedAggExpr { return numericExpr('$add', args); }, subtract(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return numericExpr('$subtract', [a, b]); }, multiply(...args: TypedAggExpr[]): TypedAggExpr { return numericExpr('$multiply', args); }, divide(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return numericExpr('$divide', [a, b]); }, // -- String (existing) ---------------------------------------------------- concat(...args: TypedAggExpr[]): TypedAggExpr { return stringExpr('$concat', args); }, toLower(a: TypedAggExpr): TypedAggExpr { return stringUnaryExpr('$toLower', a); }, toUpper(a: TypedAggExpr): TypedAggExpr { return stringUnaryExpr('$toUpper', a); }, // -- Size (existing) ------------------------------------------------------ size(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$size', a); }, // -- Control flow (existing) ---------------------------------------------- cond( condition: MongoAggExpr, thenExpr: TypedAggExpr, elseExpr: TypedAggExpr, ): TypedAggExpr { return { _field: thenExpr._field, node: new MongoAggCond(condition, thenExpr.node, elseExpr.node), }; }, literal, // -- Date helpers --------------------------------------------------------- year(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$year', a); }, month(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$month', a); }, dayOfMonth(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$dayOfMonth', a); }, hour(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$hour', a); }, minute(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$minute', a); }, second(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$second', a); }, millisecond(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$millisecond', a); }, dateToString(args: { date: TypedAggExpr; format?: TypedAggExpr; timezone?: TypedAggExpr; onNull?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$dateToString', args, STRING); }, dateFromString(args: { dateString: TypedAggExpr; format?: TypedAggExpr; timezone?: TypedAggExpr; onError?: TypedAggExpr; onNull?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$dateFromString', args, DATE); }, dateDiff(args: { startDate: TypedAggExpr; endDate: TypedAggExpr; unit: TypedAggExpr; timezone?: TypedAggExpr; startOfWeek?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$dateDiff', args, NUMERIC); }, dateAdd(args: { startDate: TypedAggExpr; unit: TypedAggExpr; amount: TypedAggExpr; timezone?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$dateAdd', args, DATE); }, dateSubtract(args: { startDate: TypedAggExpr; unit: TypedAggExpr; amount: TypedAggExpr; timezone?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$dateSubtract', args, DATE); }, dateTrunc(args: { date: TypedAggExpr; unit: TypedAggExpr; binSize?: TypedAggExpr; timezone?: TypedAggExpr; startOfWeek?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$dateTrunc', args, DATE); }, // -- String helpers ------------------------------------------------------- substr( str: TypedAggExpr, start: TypedAggExpr, length: TypedAggExpr, ): TypedAggExpr { return stringExpr('$substr', [str, start, length]); }, substrBytes( str: TypedAggExpr, start: TypedAggExpr, count: TypedAggExpr, ): TypedAggExpr { return stringExpr('$substrBytes', [str, start, count]); }, trim(args: { input: TypedAggExpr; chars?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$trim', args, STRING); }, ltrim(args: { input: TypedAggExpr; chars?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$ltrim', args, STRING); }, rtrim(args: { input: TypedAggExpr; chars?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$rtrim', args, STRING); }, split(str: TypedAggExpr, delimiter: TypedAggExpr): TypedAggExpr { return arrayExpr('$split', [str, delimiter]); }, strLenCP(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$strLenCP', a); }, strLenBytes(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$strLenBytes', a); }, regexMatch(args: { input: TypedAggExpr; regex: TypedAggExpr; options?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$regexMatch', args, BOOLEAN); }, regexFind(args: { input: TypedAggExpr; regex: TypedAggExpr; options?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$regexFind', args, DOC); }, regexFindAll(args: { input: TypedAggExpr; regex: TypedAggExpr; options?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$regexFindAll', args, ARRAY); }, replaceOne(args: { input: TypedAggExpr; find: TypedAggExpr; replacement: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$replaceOne', args, STRING); }, replaceAll(args: { input: TypedAggExpr; find: TypedAggExpr; replacement: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$replaceAll', args, STRING); }, // -- Comparison helpers --------------------------------------------------- cmp(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return numericExpr('$cmp', [a, b]); }, eq(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return booleanExpr('$eq', [a, b]); }, ne(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return booleanExpr('$ne', [a, b]); }, gt(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return booleanExpr('$gt', [a, b]); }, gte(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return booleanExpr('$gte', [a, b]); }, lt(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return booleanExpr('$lt', [a, b]); }, lte(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return booleanExpr('$lte', [a, b]); }, // -- Array helpers -------------------------------------------------------- arrayElemAt( arr: TypedAggExpr, idx: TypedAggExpr, ): TypedAggExpr { return { _field: { codecId: DOC.codecId, nullable: true }, node: MongoAggOperator.of('$arrayElemAt', [arr.node, idx.node]), }; }, concatArrays(...args: TypedAggExpr[]): TypedAggExpr { return arrayExpr('$concatArrays', args); }, firstElem(a: TypedAggExpr): TypedAggExpr { return { _field: { codecId: DOC.codecId, nullable: true }, node: MongoAggOperator.of('$first', a.node), }; }, lastElem(a: TypedAggExpr): TypedAggExpr { return { _field: { codecId: DOC.codecId, nullable: true }, node: MongoAggOperator.of('$last', a.node), }; }, isIn(elem: TypedAggExpr, arr: TypedAggExpr): TypedAggExpr { return booleanExpr('$in', [elem, arr]); }, indexOfArray( arr: TypedAggExpr, value: TypedAggExpr, ...rest: TypedAggExpr[] ): TypedAggExpr { return numericExpr('$indexOfArray', [arr, value, ...rest]); }, isArray(a: TypedAggExpr): TypedAggExpr { return booleanUnaryExpr('$isArray', a); }, reverseArray(a: TypedAggExpr): TypedAggExpr { return arrayUnaryExpr('$reverseArray', a); }, slice(arr: TypedAggExpr, ...rest: TypedAggExpr[]): TypedAggExpr { return arrayExpr('$slice', [arr, ...rest]); }, zip(args: { inputs: TypedAggExpr[]; useLongestLength?: TypedAggExpr; defaults?: TypedAggExpr; }): TypedAggExpr { const nodeArgs: Record> = { inputs: args.inputs.map((a) => a.node), }; if (args.useLongestLength) nodeArgs['useLongestLength'] = args.useLongestLength.node; if (args.defaults) nodeArgs['defaults'] = args.defaults.node; return { _field: ARRAY, node: MongoAggOperator.of('$zip', nodeArgs) }; }, range( start: TypedAggExpr, end: TypedAggExpr, step: TypedAggExpr, ): TypedAggExpr { return arrayExpr('$range', [start, end, step]); }, // -- Set helpers ---------------------------------------------------------- setUnion(...args: TypedAggExpr[]): TypedAggExpr { return arrayExpr('$setUnion', args); }, setIntersection(...args: TypedAggExpr[]): TypedAggExpr { return arrayExpr('$setIntersection', args); }, setDifference(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return arrayExpr('$setDifference', [a, b]); }, setEquals(...args: TypedAggExpr[]): TypedAggExpr { return booleanExpr('$setEquals', args); }, setIsSubset(a: TypedAggExpr, b: TypedAggExpr): TypedAggExpr { return booleanExpr('$setIsSubset', [a, b]); }, anyElementTrue(a: TypedAggExpr): TypedAggExpr { return booleanUnaryExpr('$anyElementTrue', a); }, allElementsTrue(a: TypedAggExpr): TypedAggExpr { return booleanUnaryExpr('$allElementsTrue', a); }, // -- Type helpers --------------------------------------------------------- typeOf(a: TypedAggExpr): TypedAggExpr { return stringUnaryExpr('$type', a); }, convert(args: { input: TypedAggExpr; to: TypedAggExpr; onError?: TypedAggExpr; onNull?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$convert', args, DOC); }, toInt(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$toInt', a); }, toLong(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$toLong', a); }, toDouble(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$toDouble', a); }, toDecimal(a: TypedAggExpr): TypedAggExpr { return numericUnaryExpr('$toDecimal', a); }, toString_(a: TypedAggExpr): TypedAggExpr { return stringUnaryExpr('$toString', a); }, toObjectId(a: TypedAggExpr): TypedAggExpr { return docUnaryExpr('$toObjectId', a); }, toBool(a: TypedAggExpr): TypedAggExpr { return booleanUnaryExpr('$toBool', a); }, toDate(a: TypedAggExpr): TypedAggExpr { return dateUnaryExpr('$toDate', a); }, // -- Object helpers ------------------------------------------------------- objectToArray(a: TypedAggExpr): TypedAggExpr { return arrayUnaryExpr('$objectToArray', a); }, arrayToObject(a: TypedAggExpr): TypedAggExpr { return { _field: DOC, node: MongoAggOperator.of('$arrayToObject', a.node) }; }, getField(args: { field: TypedAggExpr; input?: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$getField', args, DOC); }, setField(args: { field: TypedAggExpr; input: TypedAggExpr; value: TypedAggExpr; }): TypedAggExpr { return namedArgsExpr('$setField', args, DOC); }, };