"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.__tsfTypeAliases = exports.QueryBuilder = void 0;
const tslib_1 = require("tslib");
const _a = tslib_1.__importStar(require("@zyno-io/ts-server-foundation/type-metadata-runtime"));
const reflection_1 = require("../reflection");
const primitive_conversion_1 = require("../reflection/primitive-conversion");
const sql_1 = require("./sql");
const entity_1 = require("./entity");
const metadata_1 = require("./metadata");
const values_1 = require("./values");
const __tsf_metadata_type = _a.createCompactMetadataRegistryV1("[1,[{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"$in\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$nin\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$ne\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$like\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$notLike\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true}]}],\"typeName\":\"FilterValue\"}],\"index\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"$in\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$nin\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$ne\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$like\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$notLike\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true}]}],\"typeName\":\"FilterValue\"},\"types\":[],\"typeName\":\"FilterRecord\"},{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"keyof T\"},{\"kind\":6}]},{\"kind\":1,\"typeName\":\"FilterValue\"},{\"kind\":14,\"type\":{\"kind\":6}},{\"kind\":6},{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":\"asc\"},{\"kind\":10,\"literal\":\"desc\"}],\"typeName\":\"Direction\"},{\"kind\":10,\"literal\":\"ASC\"},{\"kind\":10,\"literal\":\"DESC\"},{\"kind\":10,\"literal\":1},{\"kind\":10,\"literal\":-1}],\"typeName\":\"DirectionInput\"},{\"kind\":12,\"types\":[{\"kind\":6},{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":\"asc\"},{\"kind\":10,\"literal\":\"desc\"}],\"typeName\":\"Direction\"},{\"kind\":10,\"literal\":\"ASC\"},{\"kind\":10,\"literal\":\"DESC\"},{\"kind\":10,\"literal\":1},{\"kind\":10,\"literal\":-1}],\"typeName\":\"DirectionInput\"}],\"index\":{\"kind\":12,\"types\":[{\"kind\":12,\"types\":[{\"kind\":10,\"literal\":\"asc\"},{\"kind\":10,\"literal\":\"desc\"}],\"typeName\":\"Direction\"},{\"kind\":10,\"literal\":\"ASC\"},{\"kind\":10,\"literal\":\"DESC\"},{\"kind\":10,\"literal\":1},{\"kind\":10,\"literal\":-1}],\"typeName\":\"DirectionInput\"},\"types\":[]}]},{\"kind\":7},{\"kind\":22,\"type\":{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"T\"}}},{\"kind\":2,\"typeName\":\"K\"},{\"kind\":22,\"type\":{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"T[K]\"}}},{\"kind\":22,\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"T[K]\"},{\"kind\":4}]}},{\"kind\":22,\"type\":{\"kind\":2,\"typeName\":\"T[K]\"}},{\"kind\":22,\"type\":{\"kind\":12,\"types\":[{\"kind\":2,\"typeName\":\"T\"},{\"kind\":4}]}},{\"kind\":22,\"type\":{\"kind\":2,\"typeName\":\"T\"}},{\"kind\":22,\"type\":{\"kind\":8}},{\"kind\":22,\"type\":{\"kind\":7}},{\"kind\":18,\"typeName\":\"PatchRecord\\u003cT\\u003e\",\"index\":{\"kind\":1},\"types\":[]},{\"kind\":22,\"type\":{\"kind\":18,\"typeName\":\"QueryMutationResult\",\"types\":[{\"kind\":20,\"name\":\"affectedRows\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"modified\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"primaryKeys\",\"type\":{\"kind\":14,\"type\":{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":2}],\"index\":{\"kind\":2},\"types\":[]}},\"optional\":false}]}},{\"kind\":18,\"typeName\":\"SqlQuery\",\"types\":[{\"kind\":20,\"name\":\"kind\",\"type\":{\"kind\":10,\"literal\":\"sql\"},\"optional\":false},{\"kind\":20,\"name\":\"chunks\",\"type\":{\"kind\":14,\"type\":{\"kind\":6}},\"optional\":false},{\"kind\":20,\"name\":\"values\",\"type\":{\"kind\":14,\"type\":{\"kind\":2,\"typeName\":\"SqlValue\"}},\"optional\":false}]},{\"kind\":2,\"typeName\":\"EntityClass\",\"typeArguments\":[{\"kind\":2,\"typeName\":\"T\"}]}]]", []);
const MYSQL_OFFSET_ONLY_LIMIT = '18446744073709551615';
let _QueryBuilderMetadata_1;
class QueryBuilder {
    db;
    Entity;
    session;
    filters = {};
    selected;
    aggregateSelections = [];
    order = [];
    limitValue;
    offsetValue;
    lockForUpdate = false;
    constructor(db, Entity, session) {
        this.db = db;
        this.Entity = Entity;
        this.session = session;
    }
    filter(filter) {
        Object.assign(this.filters, filter);
        return this;
    }
    filterField(field, value) {
        return this.filter({ [String(field)]: value });
    }
    select(...fields) {
        this.selected = fields;
        return this;
    }
    withMax(field) {
        this.aggregateSelections.push({ field: String(field), fn: 'MAX' });
        return this;
    }
    orderBy(field, direction = 'asc') {
        this.order.push({ field, direction: normalizeDirection(direction) });
        return this;
    }
    sort(field, direction = 'asc') {
        if (typeof field === 'string')
            return this.orderBy(field, direction);
        for (const [name, value] of Object.entries(field))
            this.orderBy(name, value);
        return this;
    }
    limit(limit) {
        this.limitValue = limit;
        return this;
    }
    offset(offset) {
        this.offsetValue = offset;
        return this;
    }
    skip(offset) {
        return this.offset(offset);
    }
    /** Locks selected rows until the active transaction completes. */
    forUpdate() {
        if (!this.session?.isTransactional)
            throw new Error('FOR UPDATE requires an active transaction');
        this.lockForUpdate = true;
        return this;
    }
    async find() {
        const rows = await this.runRead(() => this.db.rawFind(this.toSelectSql(), this.session));
        return rows.map(row => this.hydrate(row));
    }
    async findField(field) {
        const fieldName = String(field);
        const columnName = this.resolveColumnName(fieldName);
        const aggregate = this.aggregateSelections.find(item => item.field === fieldName || this.resolveColumnName(item.field) === columnName);
        const fields = aggregate
            ? [(0, sql_1.sql) `${sql_1.sql.rawTrusted(aggregate.fn)}(${sql_1.sql.identifier(columnName)}) AS ${sql_1.sql.identifier(columnName)}`]
            : [sql_1.sql.identifier(columnName)];
        const rows = await this.runRead(() => this.db.rawFind(this.buildSelectSql(fields, { includeOrderAndPaging: !aggregate }), this.session));
        const column = (0, metadata_1.getEntityMetadata)(this.Entity).columns.find(item => item.propertyName === fieldName || item.columnName === columnName);
        return rows.map(row => coerceColumnValue(getRowValue(row, columnName, fieldName), column, this.db.driver.dialect));
    }
    async findOneFieldOrUndefined(field) {
        return this.withTemporaryLimit(1, async () => (await this.findField(field))[0]);
    }
    async findOneField(field) {
        const value = await this.findOneFieldOrUndefined(field);
        if (value === undefined)
            throw new Error('Item not found');
        return value;
    }
    async findOneOrUndefined() {
        return this.withTemporaryLimit(1, async () => (await this.find())[0]);
    }
    async findOne() {
        const entity = await this.findOneOrUndefined();
        if (!entity)
            throw new Error('Item not found');
        return entity;
    }
    async has() {
        return !!(await this.findOneOrUndefined());
    }
    async count() {
        const row = await this.runRead(() => this.db.rawFindOne(this.toCountSql(), this.session));
        const value = row ? getRowValue(row, 'count', 'count') : 0;
        if (typeof value === 'bigint')
            return Number(value);
        if (typeof value === 'number')
            return value;
        return Number(value ?? 0);
    }
    async patchMany(patch) {
        return this.runMutation(async (session) => {
            const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
            this.assertObservedPrimaryKeyIsStable(metadata, patch);
            const patchSet = this.renderPatchSet(patch);
            if (!patchSet)
                return createMutationResult(0, []);
            const { primaryKeys, before } = await this.findMutationTargets(session);
            if (!primaryKeys.length)
                return createMutationResult(0, []);
            const result = await this.db.rawExecute((0, sql_1.sql) `UPDATE ${sql_1.sql.identifier(metadata.tableName)} SET ${patchSet} WHERE ${renderPrimaryKeyRowsWhere(metadata.primaryKeys, primaryKeys, this.db.driver.dialect)}`, session);
            if (session && this.db.hasMutationInterceptorFor(metadata) && result.affectedRows) {
                session.recordQueryMutation({
                    kind: 'query',
                    operation: 'update',
                    metadata,
                    primaryKeys,
                    before,
                    changedFields: getPatchFields(metadata, patch),
                    patch
                });
            }
            return createMutationResult(result.affectedRows, primaryKeys);
        });
    }
    async patchOne(patch) {
        return this.runMutation(async (session) => {
            const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
            this.assertObservedPrimaryKeyIsStable(metadata, patch);
            const primaryKey = this.requirePrimaryKeyFilter('patchOne');
            const patchSet = this.renderPatchSet(patch);
            if (!patchSet)
                return createMutationResult(0, []);
            const snapshotFields = this.db.getMutationQuerySnapshotFields(metadata);
            const targets = snapshotFields.length ? await this.findMutationTargets(session) : { primaryKeys: [primaryKey], before: [primaryKey] };
            if (!targets.primaryKeys.length)
                return createMutationResult(0, []);
            const result = await this.db.rawExecute((0, sql_1.sql) `UPDATE ${sql_1.sql.identifier(metadata.tableName)} SET ${patchSet} WHERE ${this.renderWhere()}`, session);
            if (session && this.db.hasMutationInterceptorFor(metadata) && result.affectedRows) {
                session.recordQueryMutation({
                    kind: 'query',
                    operation: 'update',
                    metadata,
                    primaryKeys: targets.primaryKeys,
                    before: targets.before,
                    changedFields: getPatchFields(metadata, patch),
                    patch
                });
            }
            return createMutationResult(result.affectedRows, result.affectedRows ? targets.primaryKeys : []);
        });
    }
    async deleteMany() {
        return this.runMutation(async (session) => {
            const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
            const { primaryKeys, before } = await this.findMutationTargets(session);
            if (!primaryKeys.length)
                return createMutationResult(0, []);
            const result = await this.db.rawExecute((0, sql_1.sql) `DELETE FROM ${sql_1.sql.identifier(metadata.tableName)} WHERE ${renderPrimaryKeyRowsWhere(metadata.primaryKeys, primaryKeys, this.db.driver.dialect)}`, session);
            if (session && this.db.hasMutationInterceptorFor(metadata) && result.affectedRows) {
                session.recordQueryMutation({ kind: 'query', operation: 'delete', metadata, primaryKeys, before, changedFields: [] });
            }
            return createMutationResult(result.affectedRows, primaryKeys);
        });
    }
    async deleteOne() {
        return this.runMutation(async (session) => {
            const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
            const primaryKey = this.requirePrimaryKeyFilter('deleteOne');
            const snapshotFields = this.db.getMutationQuerySnapshotFields(metadata);
            const targets = snapshotFields.length ? await this.findMutationTargets(session) : { primaryKeys: [primaryKey], before: [primaryKey] };
            if (!targets.primaryKeys.length)
                return createMutationResult(0, []);
            const result = await this.db.rawExecute((0, sql_1.sql) `DELETE FROM ${sql_1.sql.identifier(metadata.tableName)} WHERE ${this.renderWhere()}`, session);
            if (session && this.db.hasMutationInterceptorFor(metadata) && result.affectedRows) {
                session.recordQueryMutation({
                    kind: 'query',
                    operation: 'delete',
                    metadata,
                    primaryKeys: targets.primaryKeys,
                    before: targets.before,
                    changedFields: []
                });
            }
            return createMutationResult(result.affectedRows, result.affectedRows ? targets.primaryKeys : []);
        });
    }
    toSelectSql() {
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        const fields = this.aggregateSelections.length
            ? this.aggregateSelections.map(item => {
                const columnName = this.resolveColumnName(item.field);
                return (0, sql_1.sql) `${sql_1.sql.rawTrusted(item.fn)}(${sql_1.sql.identifier(columnName)}) AS ${sql_1.sql.identifier(columnName)}`;
            })
            : this.selected?.length
                ? this.selected.map(field => sql_1.sql.identifier(this.resolveColumnName(field)))
                : metadata.columns.map(column => sql_1.sql.identifier(column.columnName));
        return this.buildSelectSql(fields, { includeOrderAndPaging: !this.aggregateSelections.length });
    }
    toCountSql() {
        return this.buildSelectSql([(0, sql_1.sql) `COUNT(*) AS ${sql_1.sql.identifier('count')}`], {
            includeOrderAndPaging: false
        });
    }
    buildSelectSql(fields, options) {
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        let query = (0, sql_1.sql) `SELECT ${sql_1.sql.join(fields)} FROM ${sql_1.sql.identifier(metadata.tableName)}`;
        const where = this.renderWhere();
        if (where)
            query = (0, sql_1.sql) `${query} WHERE ${where}`;
        if (options.includeOrderAndPaging) {
            if (this.order.length) {
                query = (0, sql_1.sql) `${query} ORDER BY ${sql_1.sql.join(this.order.map(item => (0, sql_1.sql) `${sql_1.sql.identifier(this.resolveColumnName(item.field))} ${sql_1.sql.rawTrusted(item.direction.toUpperCase())}`), (0, sql_1.sql) `, `)}`;
            }
            if (this.limitValue !== undefined)
                query = (0, sql_1.sql) `${query} LIMIT ${this.limitValue}`;
            else if (this.offsetValue !== undefined && this.db.driver.dialect === 'mysql')
                query = (0, sql_1.sql) `${query} LIMIT ${sql_1.sql.rawTrusted(MYSQL_OFFSET_ONLY_LIMIT)}`;
            if (this.offsetValue !== undefined)
                query = (0, sql_1.sql) `${query} OFFSET ${this.offsetValue}`;
        }
        if (this.lockForUpdate)
            query = (0, sql_1.sql) `${query} FOR UPDATE`;
        return query;
    }
    renderWhere() {
        return renderFilterRecord(this.filters, field => this.resolveColumn(field), this.db.driver.dialect);
    }
    async findMutationTargets(session = this.session) {
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        const requestedFields = this.db.getMutationQuerySnapshotFields(metadata);
        const selectedColumns = [...metadata.primaryKeys];
        for (const field of requestedFields) {
            const column = metadata.columns.find(candidate => candidate.propertyName === field || candidate.columnName === field);
            if (!column)
                throw new Error(`Mutation interceptor requested unknown field ${metadata.classType.name}.${field}`);
            if (!selectedColumns.includes(column))
                selectedColumns.push(column);
        }
        let targetQuery = this.buildSelectSql(selectedColumns.map(column => sql_1.sql.identifier(column.columnName)), { includeOrderAndPaging: true });
        if (session?.isTransactional)
            targetQuery = (0, sql_1.sql) `${targetQuery} FOR UPDATE`;
        const rows = await this.db.rawFind(targetQuery, session);
        const before = rows.map(row => Object.fromEntries(selectedColumns.map(column => [
            column.propertyName,
            coerceColumnValue(getRowValue(row, column.columnName, column.propertyName), column, this.db.driver.dialect)
        ])));
        return {
            primaryKeys: before.map(snapshot => Object.fromEntries(metadata.primaryKeys.map(column => [column.propertyName, snapshot[column.propertyName]]))),
            before
        };
    }
    hydrate(row) {
        const entity = new this.Entity();
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        for (const column of metadata.columns) {
            entity[column.propertyName] = coerceColumnValue(getRowValue(row, column.columnName, column.propertyName), column, this.db.driver.dialect);
        }
        (0, entity_1.markEntityClean)(entity);
        if (this.selected === undefined && this.aggregateSelections.length === 0)
            this.session?.manage(entity);
        return entity;
    }
    resolveColumnName(field) {
        return this.resolveColumn(field)?.columnName ?? field;
    }
    resolveColumn(field) {
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        return metadata.columns.find(column => column.propertyName === field || column.columnName === field);
    }
    renderPatchSet(patch) {
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        const patchRecord = patch;
        const generatedColumn = metadata.columns.find(column => column.generated &&
            (Object.hasOwn(patchRecord, column.propertyName) ||
                (isRecord(patchRecord.$inc) && Object.hasOwn(patchRecord.$inc, column.propertyName))));
        if (generatedColumn)
            throw new Error(`Cannot update generated column ${metadata.classType.name}.${generatedColumn.propertyName}`);
        const patchColumns = metadata.columns.filter(column => Object.hasOwn(patchRecord, column.propertyName));
        const incPatch = isRecord(patchRecord.$inc) ? patchRecord.$inc : undefined;
        const incrementColumns = incPatch
            ? metadata.columns.filter(column => !column.primaryKey && !Object.hasOwn(patchRecord, column.propertyName) && Object.hasOwn(incPatch, column.propertyName))
            : [];
        const updateColumns = [...patchColumns, ...incrementColumns];
        if (!updateColumns.length)
            return undefined;
        return sql_1.sql.join(updateColumns.map(column => {
            if (incPatch && Object.hasOwn(incPatch, column.propertyName)) {
                return (0, sql_1.sql) `${sql_1.sql.identifier(column.columnName)} = ${sql_1.sql.identifier(column.columnName)} + ${incPatch[column.propertyName]}`;
            }
            return (0, sql_1.sql) `${sql_1.sql.identifier(column.columnName)} = ${(0, values_1.serializeColumnValue)(column, patchRecord[column.propertyName], this.db.driver.dialect)}`;
        }), (0, sql_1.sql) `, `);
    }
    requirePrimaryKeyFilter(operation) {
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        const result = {};
        for (const primaryKey of metadata.primaryKeys) {
            const filterKey = Object.hasOwn(this.filters, primaryKey.propertyName)
                ? primaryKey.propertyName
                : Object.hasOwn(this.filters, primaryKey.columnName)
                    ? primaryKey.columnName
                    : undefined;
            const value = filterKey === undefined ? undefined : this.filters[filterKey];
            if (filterKey === undefined || value === null || value === undefined || isFilterOperator(value)) {
                throw new Error(`${operation} requires an exact filter for primary key ${metadata.classType.name}.${primaryKey.propertyName}`);
            }
            result[primaryKey.propertyName] = value;
        }
        return result;
    }
    assertObservedPrimaryKeyIsStable(metadata, patch) {
        if (!this.db.hasMutationInterceptorFor(metadata))
            return;
        const changedPrimaryKey = metadata.primaryKeys.find(column => Object.hasOwn(patch, column.propertyName));
        if (changedPrimaryKey) {
            throw new Error(`Observed entity ${metadata.classType.name} cannot patch primary key ${changedPrimaryKey.propertyName}; load and replace the entity explicitly`);
        }
    }
    async withTemporaryLimit(limit, worker) {
        const previousLimit = this.limitValue;
        this.limitValue = limit;
        try {
            return await worker();
        }
        finally {
            this.limitValue = previousLimit;
        }
    }
    runMutation(worker) {
        const metadata = (0, metadata_1.getEntityMetadata)(this.Entity);
        const observed = this.db.hasMutationInterceptorFor(metadata);
        if (this.session) {
            if (this.session.db !== this.db)
                throw new Error(`Cannot mutate ${metadata.classType.name} through a session owned by another database`);
            if (observed && !this.session.isTransactional) {
                throw new Error(`Observed entity ${metadata.classType.name} requires a transactional database session`);
            }
            return this.session.trackOperation(() => worker(this.session));
        }
        if (observed)
            return this.db.transaction(session => session.trackOperation(() => worker(session)));
        return worker();
    }
    runRead(worker) {
        return this.session ? this.session.withoutAutoFlush(worker) : worker();
    }
    static __tsfType = _QueryBuilderMetadata_1 = _a.decodeCompactMetadataV1("[1,{\"kind\":16,\"name\":\"QueryBuilder\",\"typeName\":\"QueryBuilder\",\"classType\":{\"$tsf\":0},\"properties\":[],\"methods\":[{\"name\":\"filter\",\"parameters\":[{\"name\":\"filter\",\"type\":{\"$tsfType\":0},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"filterField\",\"parameters\":[{\"name\":\"field\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false},{\"name\":\"value\",\"type\":{\"$tsfType\":2},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"select\",\"parameters\":[{\"name\":\"fields\",\"type\":{\"$tsfType\":3},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"withMax\",\"parameters\":[{\"name\":\"field\",\"type\":{\"$tsfType\":1},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"orderBy\",\"parameters\":[{\"name\":\"field\",\"type\":{\"$tsfType\":4},\"optional\":false,\"default\":false},{\"name\":\"direction\",\"type\":{\"$tsfType\":5},\"optional\":true,\"default\":true}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"sort\",\"parameters\":[{\"name\":\"field\",\"type\":{\"$tsfType\":6},\"optional\":false,\"default\":false},{\"name\":\"direction\",\"type\":{\"$tsfType\":5},\"optional\":true,\"default\":true}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"limit\",\"parameters\":[{\"name\":\"limit\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"offset\",\"parameters\":[{\"name\":\"offset\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"skip\",\"parameters\":[{\"name\":\"offset\",\"type\":{\"$tsfType\":7},\"optional\":false,\"default\":false}],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}}},{\"name\":\"forUpdate\",\"parameters\":[],\"returnType\":{\"kind\":16,\"typeName\":\"this\",\"classType\":{\"$tsf\":1}},\"description\":\"Locks selected rows until the active transaction completes.\"},{\"name\":\"find\",\"parameters\":[],\"returnType\":{\"$tsfType\":8}},{\"name\":\"findField\",\"parameters\":[{\"name\":\"field\",\"type\":{\"$tsfType\":9},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":10}},{\"name\":\"findOneFieldOrUndefined\",\"parameters\":[{\"name\":\"field\",\"type\":{\"$tsfType\":9},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":11}},{\"name\":\"findOneField\",\"parameters\":[{\"name\":\"field\",\"type\":{\"$tsfType\":9},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":12}},{\"name\":\"findOneOrUndefined\",\"parameters\":[],\"returnType\":{\"$tsfType\":13}},{\"name\":\"findOne\",\"parameters\":[],\"returnType\":{\"$tsfType\":14}},{\"name\":\"has\",\"parameters\":[],\"returnType\":{\"$tsfType\":15}},{\"name\":\"count\",\"parameters\":[],\"returnType\":{\"$tsfType\":16}},{\"name\":\"patchMany\",\"parameters\":[{\"name\":\"patch\",\"type\":{\"$tsfType\":17},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":18}},{\"name\":\"patchOne\",\"parameters\":[{\"name\":\"patch\",\"type\":{\"$tsfType\":17},\"optional\":false,\"default\":false}],\"returnType\":{\"$tsfType\":18}},{\"name\":\"deleteMany\",\"parameters\":[],\"returnType\":{\"$tsfType\":18}},{\"name\":\"deleteOne\",\"parameters\":[],\"returnType\":{\"$tsfType\":18}},{\"name\":\"toSelectSql\",\"parameters\":[],\"returnType\":{\"$tsfType\":19}},{\"name\":\"toCountSql\",\"parameters\":[],\"returnType\":{\"$tsfType\":19}}],\"hasConstructor\":true,\"constructorParameters\":[{\"name\":\"db\",\"type\":{\"kind\":16,\"typeName\":\"BaseDatabase\",\"classType\":{\"$tsfImport\":[2,\"./database\",\"BaseDatabase\"]}},\"optional\":false,\"default\":false},{\"name\":\"Entity\",\"type\":{\"$tsfType\":20},\"optional\":false,\"default\":false},{\"name\":\"session\",\"type\":{\"kind\":16,\"typeName\":\"DatabaseSession\",\"classType\":{\"$tsfImport\":[2,\"./session\",\"DatabaseSession\"]}},\"optional\":true,\"default\":false}]}]", [() => QueryBuilder, () => (typeof this !== "undefined" ? this : (typeof exports !== "undefined" ? exports.this : undefined)), require], __tsf_metadata_type);
}
exports.QueryBuilder = QueryBuilder;
_QueryBuilderMetadata_1.classType = QueryBuilder;
QueryBuilder.__tsfType = _QueryBuilderMetadata_1;
function getPatchFields(metadata, patch) {
    const fields = new Set();
    const increment = isRecord(patch.$inc) ? patch.$inc : undefined;
    for (const column of metadata.columns) {
        if (Object.hasOwn(patch, column.propertyName) || (increment && Object.hasOwn(increment, column.propertyName))) {
            fields.add(column.propertyName);
        }
    }
    return [...fields];
}
function createMutationResult(affectedRows, primaryKeys) {
    const result = { affectedRows, primaryKeys };
    Object.defineProperty(result, 'modified', {
        enumerable: false,
        value: affectedRows
    });
    return result;
}
function normalizeDirection(direction) {
    if (direction === -1)
        return 'desc';
    if (direction === 1)
        return 'asc';
    return direction.toLowerCase() === 'desc' ? 'desc' : 'asc';
}
function renderPrimaryKeyRowsWhere(primaryKeys, rows, dialect) {
    if (primaryKeys.length === 1) {
        const primaryKey = primaryKeys[0];
        return (0, sql_1.sql) `${sql_1.sql.identifier(primaryKey.columnName)} IN (${sql_1.sql.join(rows.map(row => (0, sql_1.sql) `${(0, values_1.serializeColumnValue)(primaryKey, row[primaryKey.propertyName], dialect)}`))})`;
    }
    return sql_1.sql.join(rows.map(row => (0, sql_1.sql) `(${sql_1.sql.join(primaryKeys.map(primaryKey => (0, sql_1.sql) `${sql_1.sql.identifier(primaryKey.columnName)} = ${(0, values_1.serializeColumnValue)(primaryKey, row[primaryKey.propertyName], dialect)}`), (0, sql_1.sql) ` AND `)})`), (0, sql_1.sql) ` OR `);
}
function getRowValue(row, columnName, propertyName) {
    if (Object.hasOwn(row, columnName))
        return row[columnName];
    return row[propertyName];
}
function coerceColumnValue(value, column, dialect) {
    if (!column)
        return value;
    if (value === null)
        return null;
    if (value === undefined)
        return value;
    value = (0, values_1.deserializeColumnValue)(column, value, dialect);
    if (isBooleanColumn(column)) {
        const coerced = (0, primitive_conversion_1.coerceBooleanValue)(typeof value === 'string' ? value.toLowerCase() : value);
        if (typeof coerced === 'boolean')
            return coerced;
        if (typeof value === 'string')
            return false;
    }
    if (isBigIntType(column.type)) {
        if (typeof value === 'bigint')
            return value;
        if (typeof value === 'number' && Number.isFinite(value))
            return BigInt(value);
        if (typeof value === 'string' && value.trim() !== '') {
            try {
                return BigInt(value);
            }
            catch {
                return value;
            }
        }
    }
    if (isNumberType(column.type) && typeof value === 'string' && value.trim() !== '') {
        const numberValue = Number(value);
        if (Number.isFinite(numberValue))
            return numberValue;
    }
    return value;
}
function isBooleanColumn(column) {
    return typeof column.defaultValue === 'boolean' || isBooleanType(column.type);
}
function isBooleanType(type) {
    if (type.kind === reflection_1.ReflectionKind.boolean)
        return true;
    if ((type.kind === reflection_1.ReflectionKind.intersection || type.kind === reflection_1.ReflectionKind.union) && 'types' in type) {
        return type.types.some(child => isBooleanType(child));
    }
    return false;
}
function isNumberType(type) {
    if (type.kind === reflection_1.ReflectionKind.number)
        return true;
    if ((type.kind === reflection_1.ReflectionKind.intersection || type.kind === reflection_1.ReflectionKind.union) && 'types' in type) {
        return type.types.some(child => isNumberType(child));
    }
    return false;
}
function isBigIntType(type) {
    if (type.kind === reflection_1.ReflectionKind.bigint)
        return true;
    if ((type.kind === reflection_1.ReflectionKind.intersection || type.kind === reflection_1.ReflectionKind.union) && 'types' in type) {
        return type.types.some(child => isBigIntType(child));
    }
    return false;
}
function isRecord(value) {
    return !!value && typeof value === 'object' && !Array.isArray(value);
}
function isFilterOperator(value) {
    return isRecord(value) && Object.keys(value).some(key => key.startsWith('$'));
}
function renderFilter(field, value, column, dialect) {
    if (value && typeof value === 'object' && !Array.isArray(value)) {
        const op = value;
        const clauses = [];
        if ('$in' in op) {
            const values = op.$in ?? [];
            clauses.push(values.length
                ? (0, sql_1.sql) `${sql_1.sql.identifier(field)} IN (${sql_1.sql.join(values.map(v => (0, sql_1.sql) `${(0, values_1.serializeColumnValue)(column, v, dialect)}`))})`
                : (0, sql_1.sql) `1 = 0`);
        }
        if ('$nin' in op) {
            const values = op.$nin ?? [];
            const nonNullValues = values.filter(value => value !== null && value !== undefined);
            if (!nonNullValues.length)
                clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} IS NOT NULL`);
            else if (nonNullValues.length === values.length) {
                clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} NOT IN (${sql_1.sql.join(nonNullValues.map(v => (0, sql_1.sql) `${(0, values_1.serializeColumnValue)(column, v, dialect)}`))})`);
            }
            else {
                clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} IS NOT NULL AND ${sql_1.sql.identifier(field)} NOT IN (${sql_1.sql.join(nonNullValues.map(v => (0, sql_1.sql) `${(0, values_1.serializeColumnValue)(column, v, dialect)}`))})`);
            }
        }
        if ('$ne' in op)
            clauses.push(op.$ne === null
                ? (0, sql_1.sql) `${sql_1.sql.identifier(field)} IS NOT NULL`
                : (0, sql_1.sql) `${sql_1.sql.identifier(field)} <> ${(0, values_1.serializeColumnValue)(column, op.$ne, dialect)}`);
        if ('$gt' in op)
            clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} > ${(0, values_1.serializeColumnValue)(column, op.$gt, dialect)}`);
        if ('$gte' in op)
            clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} >= ${(0, values_1.serializeColumnValue)(column, op.$gte, dialect)}`);
        if ('$lt' in op)
            clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} < ${(0, values_1.serializeColumnValue)(column, op.$lt, dialect)}`);
        if ('$lte' in op)
            clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} <= ${(0, values_1.serializeColumnValue)(column, op.$lte, dialect)}`);
        if ('$like' in op)
            clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} LIKE ${(0, values_1.serializeColumnValue)(column, op.$like, dialect)}`);
        if ('$notLike' in op)
            clauses.push((0, sql_1.sql) `${sql_1.sql.identifier(field)} NOT LIKE ${(0, values_1.serializeColumnValue)(column, op.$notLike, dialect)}`);
        if (clauses.length)
            return sql_1.sql.join(clauses, (0, sql_1.sql) ` AND `);
    }
    if (value === null || value === undefined)
        return (0, sql_1.sql) `${sql_1.sql.identifier(field)} IS NULL`;
    return (0, sql_1.sql) `${sql_1.sql.identifier(field)} = ${(0, values_1.serializeColumnValue)(column, value, dialect)}`;
}
function renderFilterRecord(filters, resolveColumn, dialect) {
    const clauses = [];
    for (const [field, value] of Object.entries(filters)) {
        if (field === '$and' || field === '$or') {
            const children = Array.isArray(value) ? value : [];
            const rendered = children
                .map(child => renderFilterRecord(child, resolveColumn, dialect))
                .filter((child) => child !== undefined);
            if (rendered.length)
                clauses.push((0, sql_1.sql) `(${sql_1.sql.join(rendered, field === '$and' ? (0, sql_1.sql) ` AND ` : (0, sql_1.sql) ` OR `)})`);
            else if (field === '$or')
                clauses.push((0, sql_1.sql) `1 = 0`);
            continue;
        }
        const column = resolveColumn(field);
        clauses.push(renderFilter(column?.columnName ?? field, value, column, dialect));
    }
    return clauses.length ? sql_1.sql.join(clauses, (0, sql_1.sql) ` AND `) : undefined;
}
exports.__tsfTypeAliases = JSON.parse("[1,{\"FilterRecord\":{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"$in\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$nin\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$ne\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$like\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$notLike\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true}]}],\"typeName\":\"FilterValue\"}],\"index\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"$in\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$nin\",\"type\":{\"kind\":12,\"types\":[{\"kind\":14,\"type\":{\"kind\":2}},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$ne\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$gte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lt\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$lte\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$like\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true},{\"kind\":20,\"name\":\"$notLike\",\"type\":{\"kind\":12,\"types\":[{\"kind\":2},{\"kind\":4}]},\"optional\":true}]}],\"typeName\":\"FilterValue\"},\"types\":[],\"typeName\":\"FilterRecord\"},\"FilterValue\":{\"kind\":1,\"typeName\":\"FilterValue\"},\"PatchOperator\":{\"kind\":18,\"types\":[{\"kind\":20,\"name\":\"$inc\",\"type\":{\"kind\":12,\"types\":[{\"kind\":7},{\"kind\":4}]},\"optional\":true}],\"typeName\":\"PatchOperator\"},\"QueryMutationResult\":{\"kind\":18,\"typeName\":\"QueryMutationResult\",\"types\":[{\"kind\":20,\"name\":\"affectedRows\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"modified\",\"type\":{\"kind\":7},\"optional\":false},{\"kind\":20,\"name\":\"primaryKeys\",\"type\":{\"kind\":14,\"type\":{\"kind\":18,\"typeName\":\"Record\",\"utilityType\":\"Record\",\"typeArguments\":[{\"kind\":6},{\"kind\":2}],\"index\":{\"kind\":2},\"types\":[]}},\"optional\":false}]}}]")[1];
//# sourceMappingURL=query.js.map