"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CompilerApi = void 0;
const crypto_1 = __importDefault(require("crypto"));
const schema_compiler_1 = require("@cubejs-backend/schema-compiler");
const uuid_1 = require("uuid");
const lru_cache_1 = require("lru-cache");
const native_1 = require("@cubejs-backend/native");
const shared_1 = require("@cubejs-backend/shared");
class CompilerApi {
    repository;
    dbType;
    dialectClass;
    options;
    allowNodeRequire;
    logger;
    preAggregationsSchema;
    allowUngroupedWithoutPrimaryKey;
    convertTzForRawTimeDimension;
    schemaVersion;
    contextToRoles;
    contextToGroups;
    compileContext;
    allowJsDuplicatePropsInSchema;
    sqlCache;
    standalone;
    nativeInstance;
    compiledScriptCache;
    compiledYamlCache;
    compiledJinjaCache;
    compiledScriptCacheInterval;
    graphqlSchema;
    compilers;
    compilerVersion;
    queryFactory;
    constructor(repository, dbType, options) {
        this.repository = repository;
        this.dbType = dbType;
        this.dialectClass = options.dialectClass;
        this.options = options || {};
        this.allowNodeRequire = options.allowNodeRequire == null ? true : options.allowNodeRequire;
        // eslint-disable-next-line @typescript-eslint/no-empty-function
        this.logger = this.options.logger || (() => { });
        this.preAggregationsSchema = this.options.preAggregationsSchema;
        this.allowUngroupedWithoutPrimaryKey = this.options.allowUngroupedWithoutPrimaryKey;
        this.convertTzForRawTimeDimension = this.options.convertTzForRawTimeDimension;
        this.schemaVersion = this.options.schemaVersion;
        this.contextToRoles = this.options.contextToRoles;
        this.contextToGroups = this.options.contextToGroups;
        this.compileContext = options.compileContext;
        this.allowJsDuplicatePropsInSchema = options.allowJsDuplicatePropsInSchema;
        this.sqlCache = options.sqlCache;
        this.standalone = options.standalone;
        this.nativeInstance = this.createNativeInstance();
        // Caching stuff
        this.compiledScriptCache = new lru_cache_1.LRUCache({
            max: options.compilerCacheSize || 250,
            ttl: options.maxCompilerCacheKeepAlive,
            updateAgeOnGet: options.updateCompilerCacheKeepAlive
        });
        this.compiledYamlCache = new lru_cache_1.LRUCache({
            max: options.compilerCacheSize || 250,
            ttl: options.maxCompilerCacheKeepAlive,
            updateAgeOnGet: options.updateCompilerCacheKeepAlive
        });
        this.compiledJinjaCache = new lru_cache_1.LRUCache({
            max: options.compilerCacheSize || 250,
            ttl: options.maxCompilerCacheKeepAlive,
            updateAgeOnGet: options.updateCompilerCacheKeepAlive
        });
        // proactively free up old cache values occasionally
        if (this.options.maxCompilerCacheKeepAlive) {
            this.compiledScriptCacheInterval = setInterval(() => {
                this.compiledScriptCache.purgeStale();
                this.compiledYamlCache.purgeStale();
                this.compiledJinjaCache.purgeStale();
            }, this.options.maxCompilerCacheKeepAlive);
        }
    }
    dispose() {
        if (this.compiledScriptCacheInterval) {
            clearInterval(this.compiledScriptCacheInterval);
            this.compiledScriptCacheInterval = null;
        }
        // freeing memory-heavy allocated instances
        // using safeguard for potential dangling references.
        this.compilers = (0, shared_1.disposedProxy)('compilers', 'disposed CompilerApi instance');
        this.queryFactory = (0, shared_1.disposedProxy)('queryFactory', 'disposed CompilerApi instance');
        this.graphqlSchema = undefined;
    }
    setGraphQLSchema(schema) {
        this.graphqlSchema = schema;
    }
    getGraphQLSchema() {
        return this.graphqlSchema;
    }
    createNativeInstance() {
        return new native_1.NativeInstance();
    }
    async getCompilers(options = {}) {
        let compilerVersion = (this.schemaVersion && await this.schemaVersion() ||
            'default_schema_version');
        if (typeof compilerVersion === 'object') {
            compilerVersion = JSON.stringify(compilerVersion);
        }
        if (this.options.devServer || this.options.fastReload) {
            const files = await this.repository.dataSchemaFiles();
            compilerVersion += `_${crypto_1.default.createHash('md5').update(JSON.stringify(files)).digest('hex')}`;
        }
        if (!this.compilers || this.compilerVersion !== compilerVersion) {
            this.compilers = this.compileSchema(compilerVersion, options.requestId).catch(e => {
                this.compilers = undefined;
                throw e;
            });
            this.compilerVersion = compilerVersion;
        }
        return this.compilers;
    }
    /**
     * Creates the compilers instances without model compilation,
     * because it could fail and no compilers will be returned.
     */
    createCompilerInstances() {
        return (0, schema_compiler_1.prepareCompiler)(this.repository, {
            allowNodeRequire: this.allowNodeRequire,
            compileContext: this.compileContext,
            allowJsDuplicatePropsInSchema: this.allowJsDuplicatePropsInSchema,
            standalone: this.standalone,
            nativeInstance: this.nativeInstance,
            compiledScriptCache: this.compiledScriptCache,
        });
    }
    async compileSchema(compilerVersion, requestId) {
        const startCompilingTime = new Date().getTime();
        try {
            this.logger(this.compilers ? 'Recompiling schema' : 'Compiling schema', {
                version: compilerVersion,
                requestId
            });
            const compilers = await (0, schema_compiler_1.compile)(this.repository, {
                allowNodeRequire: this.allowNodeRequire,
                compileContext: this.compileContext,
                allowJsDuplicatePropsInSchema: this.allowJsDuplicatePropsInSchema,
                standalone: this.standalone,
                nativeInstance: this.nativeInstance,
                compiledScriptCache: this.compiledScriptCache,
                compiledJinjaCache: this.compiledJinjaCache,
                compiledYamlCache: this.compiledYamlCache,
            });
            this.queryFactory = await this.createQueryFactory(compilers);
            this.logger('Compiling schema completed', {
                version: compilerVersion,
                requestId,
                duration: ((new Date()).getTime() - startCompilingTime),
            });
            return compilers;
        }
        catch (e) {
            this.logger('Compiling schema error', {
                version: compilerVersion,
                requestId,
                duration: ((new Date()).getTime() - startCompilingTime),
                error: (e.stack || e).toString()
            });
            throw e;
        }
    }
    async createQueryFactory(compilers) {
        const { cubeEvaluator } = compilers;
        const cubeToQueryClass = Object.fromEntries(await Promise.all(cubeEvaluator.cubeNames().map(async (cube) => {
            const dataSource = cubeEvaluator.cubeFromPath(cube).dataSource ?? 'default';
            const dbType = await this.getDbType(dataSource);
            const dialectClass = this.getDialectClass(dataSource, dbType);
            return [cube, (0, schema_compiler_1.queryClass)(dbType, dialectClass)];
        })));
        return new schema_compiler_1.QueryFactory(cubeToQueryClass);
    }
    async getDbType(dataSource = 'default') {
        return this.dbType({ dataSource });
    }
    getDialectClass(dataSource = 'default', dbType) {
        return this.dialectClass?.({ dataSource, dbType });
    }
    async getSqlGenerator(query, dataSource) {
        const dbType = await this.getDbType(dataSource);
        const compilers = await this.getCompilers({ requestId: query.requestId });
        let sqlGenerator = await this.createQueryByDataSource(compilers, query, dataSource, dbType);
        if (!sqlGenerator) {
            throw new Error(`Unknown dbType: ${dbType}`);
        }
        // sqlGenerator.dataSource can return undefined for query without members
        // Queries like this are used by api-gateway to initialize SQL API
        // At the same time, those queries should use concrete dataSource, so we should be good to go with it
        dataSource = compilers.compiler.withQuery(sqlGenerator, () => sqlGenerator.dataSource);
        if (dataSource !== undefined) {
            const _dbType = await this.getDbType(dataSource);
            if (dataSource !== 'default' && dbType !== _dbType) {
                // TODO consider more efficient way than instantiating query
                sqlGenerator = await this.createQueryByDataSource(compilers, query, dataSource, _dbType);
                if (!sqlGenerator) {
                    throw new Error(`Can't find dialect for '${dataSource}' data source: ${_dbType}`);
                }
            }
        }
        return { sqlGenerator, compilers };
    }
    async getSql(query, options = {}) {
        const { includeDebugInfo, exportAnnotatedSql } = options;
        const { sqlGenerator, compilers } = await this.getSqlGenerator(query);
        const getSqlFn = () => compilers.compiler.withQuery(sqlGenerator, () => ({
            external: sqlGenerator.externalPreAggregationQuery(),
            sql: sqlGenerator.buildSqlAndParams(exportAnnotatedSql),
            lambdaQueries: sqlGenerator.buildLambdaQuery(),
            timeDimensionAlias: sqlGenerator.timeDimensions[0]?.unescapedAliasName(),
            timeDimensionField: sqlGenerator.timeDimensions[0]?.dimension,
            order: sqlGenerator.order,
            cacheKeyQueries: sqlGenerator.cacheKeyQueries(),
            preAggregations: sqlGenerator.preAggregations.preAggregationsDescription(),
            dataSource: sqlGenerator.dataSource,
            aliasNameToMember: sqlGenerator.aliasNameToMember,
            rollupMatchResults: includeDebugInfo ?
                sqlGenerator.preAggregations.rollupMatchResultDescriptions() : undefined,
            canUseTransformedQuery: sqlGenerator.preAggregations.canUseTransformedQuery(),
            memberNames: sqlGenerator.collectAllMemberNames(),
        }));
        if (this.sqlCache) {
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const { requestId, ...keyOptions } = query;
            const key = { query: keyOptions, options };
            return compilers.compilerCache.getQueryCache(key).cache(['sql'], getSqlFn);
        }
        else {
            return getSqlFn();
        }
    }
    async getRolesFromContext(context) {
        if (!this.contextToRoles) {
            return new Set();
        }
        return new Set(await this.contextToRoles(context));
    }
    async getGroupsFromContext(context) {
        if (!this.contextToGroups) {
            return new Set();
        }
        return new Set(await this.contextToGroups(context));
    }
    userHasRole(userRoles, role) {
        return userRoles.has(role) || role === '*';
    }
    userHasGroup(userGroups, group) {
        if (Array.isArray(group)) {
            return group.some(g => userGroups.has(g) || g === '*');
        }
        return userGroups.has(group) || group === '*';
    }
    roleMeetsConditions(evaluatedConditions) {
        if (evaluatedConditions?.length) {
            return evaluatedConditions.reduce((a, b) => {
                if (typeof b !== 'boolean') {
                    throw new Error(`Access policy condition must return boolean, got ${JSON.stringify(b)}`);
                }
                return a && b;
            });
        }
        return true;
    }
    async getCubesFromQuery(query, context) {
        const sql = await this.getSql(query, { requestId: context?.requestId });
        return new Set(sql.memberNames.map(memberName => memberName.split('.')[0]));
    }
    hashRequestContext(context) {
        if (!context.__hash) {
            context.__hash = crypto_1.default.createHash('md5').update(JSON.stringify(context)).digest('hex');
        }
        return context.__hash;
    }
    async getApplicablePolicies(cube, context, compilers) {
        const cache = compilers.compilerCache.getRbacCacheInstance();
        const cacheKey = `${cube.name}_${this.hashRequestContext(context)}`;
        if (!cache.has(cacheKey)) {
            const userRoles = await this.getRolesFromContext(context);
            const userGroups = await this.getGroupsFromContext(context);
            const policies = cube.accessPolicy.filter((policy) => {
                // Validate that policy doesn't have both role and group/groups - this is invalid
                if (policy.role && (policy.group || policy.groups)) {
                    const groupValue = policy.group || policy.groups;
                    const groupDisplay = Array.isArray(groupValue) ? groupValue.join(', ') : groupValue;
                    const groupProp = policy.group ? 'group' : 'groups';
                    throw new Error(`Access policy cannot have both 'role' and '${groupProp}' properties.\nPolicy in cube '${cube.name}' has role '${policy.role}' and ${groupProp} '${groupDisplay}'.\nUse either 'role' or '${groupProp}', not both.`);
                }
                // Validate that policy doesn't have both group and groups
                if (policy.group && policy.groups) {
                    const groupDisplay = Array.isArray(policy.group) ? policy.group.join(', ') : policy.group;
                    const groupsDisplay = Array.isArray(policy.groups) ? policy.groups.join(', ') : policy.groups;
                    throw new Error(`Access policy cannot have both 'group' and 'groups' properties.\nPolicy in cube '${cube.name}' has group '${groupDisplay}' and groups '${groupsDisplay}'.\nUse either 'group' or 'groups', not both.`);
                }
                const evaluatedConditions = (policy.conditions || []).map((condition) => compilers.cubeEvaluator.evaluateContextFunction(cube, condition.if, context));
                // Check if policy matches by role, group, or groups
                let hasAccess = false;
                if (policy.role) {
                    hasAccess = this.userHasRole(userRoles, policy.role);
                }
                else if (policy.group) {
                    hasAccess = this.userHasGroup(userGroups, policy.group);
                }
                else if (policy.groups) {
                    hasAccess = this.userHasGroup(userGroups, policy.groups);
                }
                else {
                    // If policy has neither role nor group/groups, default to checking role for backward compatibility
                    hasAccess = this.userHasRole(userRoles, '*');
                }
                const res = hasAccess && this.roleMeetsConditions(evaluatedConditions);
                return res;
            });
            cache.set(cacheKey, policies);
        }
        return cache.get(cacheKey);
    }
    evaluateNestedFilter(filter, cube, context, cubeEvaluator) {
        const result = {};
        if (filter.memberReference) {
            const evaluatedValues = cubeEvaluator.evaluateContextFunction(cube, filter.values || (() => undefined), context);
            result.member = filter.memberReference;
            result.operator = filter.operator;
            result.values = evaluatedValues;
        }
        if (filter.or) {
            result.or = filter.or.map((f) => this.evaluateNestedFilter(f, cube, context, cubeEvaluator));
        }
        if (filter.and) {
            result.and = filter.and.map((f) => this.evaluateNestedFilter(f, cube, context, cubeEvaluator));
        }
        return result;
    }
    /**
     * This method rewrites the query according to RBAC row level security policies.
     *
     * If RBAC is enabled, it looks at all the Cubes from the query with accessPolicy defined.
     * It extracts all policies applicable to for the current user context (contextToRoles() + conditions).
     * It then generates a rls filter by
     * - combining all filters for the same role with AND
     * - combining all filters for different roles with OR
     * - combining cube and view filters with AND
    */
    async applyRowLevelSecurity(query, evaluatedQuery, context) {
        const compilers = await this.getCompilers({ requestId: context.requestId });
        const { cubeEvaluator } = compilers;
        if (!cubeEvaluator.isRbacEnabled()) {
            return { query, denied: false };
        }
        // Get the SQL to extract member names from the query
        const sql = await this.getSql(evaluatedQuery, { requestId: context?.requestId });
        const queryMemberNames = new Set(sql.memberNames);
        const queryCubes = new Set(sql.memberNames.map(memberName => memberName.split('.')[0]));
        // Identify cubes that are accessed through views.
        // Similar to PostgreSQL views: views act as a security boundary for member access.
        // When a cube is accessed via a view, we skip the cube's member-level restrictions
        // and only apply row-level filters. The view controls what members are exposed.
        const cubesAccessedViaView = new Set();
        for (const cubeName of queryCubes) {
            const cube = cubeEvaluator.cubeFromPath(cubeName);
            if (cube.isView) {
                // Track which underlying cubes are accessed through this view
                const underlyingCubes = new Set((cube.includedMembers || []).map((m) => m.memberPath.split('.')[0]));
                underlyingCubes.forEach(c => cubesAccessedViaView.add(c));
            }
        }
        // We collect Cube and View filters separately because they have to be
        // applied in "two layers": first Cube filters, then View filters on top
        const cubeFiltersPerCubePerRole = {};
        const viewFiltersPerCubePerRole = {};
        const hasAllowAllForCube = {};
        const maskedMembersSet = new Set();
        for (const cubeName of queryCubes) {
            const cube = cubeEvaluator.cubeFromPath(cubeName);
            const filtersMap = cube.isView ? viewFiltersPerCubePerRole : cubeFiltersPerCubePerRole;
            if (cubeEvaluator.isRbacEnabledForCube(cube)) {
                let hasAccessPermission = false;
                const userPolicies = await this.getApplicablePolicies(cube, context, compilers);
                // Filter out policies that don't grant member-level access to query members
                //
                // Policies define access in two dimensions: Members (columns) and Rows.
                // We first filter by member access, then apply row-level filters.
                //
                // Example setup:
                //   - Policy 1 covers members: a, b (with row filter R1)
                //   - Policy 2 covers members: b, c (with row filter R2)
                //
                //   Members
                //     ^
                //     |       ┌─────────────────────────────┐
                //   c |       │          Policy 2           │
                //     |   ┌───┼─────────────┐               │
                //   b |   │   │  (overlap)  │               │
                //     |   │   └─────────────┼───────────────┘
                //   a |   │    Policy 1     │
                //     |   └─────────────────┘
                //     └──────────────────────────────────────────> Rows
                //              R1 rows        R2 rows
                //
                // ═══════════════════════════════════════════════════════════════════
                // Case 1: Query members (a, b)
                //         Only Policy 1 covers ALL queried members → R1 rows visible
                //
                //   Members
                //     ^
                //     |       ┌─────────────────────────────┐
                //   c |       │          Policy 2           │
                //     |   ┌───┼─────────────┐               │
                //   b |   │░░░│░░(query)░░░░│               │
                //     |   │░░░└─────────────┼───────────────┘
                //   a |   │░░░░Policy 1░░░░░│
                //     |   └─────────────────┘
                //     └──────────────────────────────────────────> Rows
                //         ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
                //         R1 rows visible
                //
                // ═══════════════════════════════════════════════════════════════════
                // Case 2: Query members (b, c)
                //         Only Policy 2 covers ALL queried members → R2 rows visible
                //
                //   Members
                //     ^
                //     |       ┌─────────────────────────────┐
                //   c |       │░░░░░░░░░░Policy 2░░░░░░░░░░░│
                //     |   ┌───┼─────────────┐░░░░░░░░░░░░░░░│
                //   b |   │   │░░(query)░░░░│░░░░░░░░░░░░░░░│
                //     |   │   └─────────────┼───────────────┘
                //   a |   │    Policy 1     │
                //     |   └─────────────────┘
                //     └──────────────────────────────────────────> Rows
                //             ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
                //             R2 rows visible
                //
                // ═══════════════════════════════════════════════════════════════════
                // Case 3: Query member (b) only
                //         Both policies cover member b → Union of R1 ∪ R2 rows visible
                //
                //   Members
                //     ^
                //     |       ┌─────────────────────────────┐
                //   c |       │          Policy 2           │
                //     |   ┌───┼─────────────┐               │
                //   b |   │░░░│░░(query)░░░░│░░░░░░░░░░░░░░░│
                //     |   │   └─────────────┼───────────────┘
                //   a |   │    Policy 1     │
                //     |   └─────────────────┘
                //     └──────────────────────────────────────────> Rows
                //         ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
                //         R1 ∪ R2 rows visible (union)
                //
                // ═══════════════════════════════════════════════════════════════════
                // Case 4: Query members (a, b, c)
                //         Neither policy covers ALL three → NO rows visible (denied)
                //
                //   Members
                //     ^
                //     |       ┌─────────────────────────────┐
                //   c |       │          Policy 2           │
                //     |   ┌───┼─────────────┐               │
                //   b |   │   │  (query)    │               │
                //     |   │   └─────────────┼───────────────┘
                //   a |   │    Policy 1     │
                //     |   └─────────────────┘
                //     └──────────────────────────────────────────> Rows
                //
                //         No policy covers {a,b,c} → Access denied, empty result
                //
                const policiesWithMemberAccess = userPolicies.filter((policy) => {
                    // If there's no memberLevel and no memberMasking policy, all members are accessible
                    if (!policy.memberLevel && !policy.memberMasking) {
                        return true;
                    }
                    // PostgreSQL-style view behavior: if this cube is accessed through a view,
                    // the view grants access to all members it exposes.
                    // We only apply row-level filters from the cube, not member-level restrictions.
                    if (cubesAccessedViaView.has(cubeName)) {
                        return true;
                    }
                    const cubeMembersInQuery = Array.from(queryMemberNames).filter(memberName => memberName.startsWith(`${cubeName}.`));
                    // A policy covers a member if it's in memberLevel includes (full access)
                    // or in memberMasking includes (masked access)
                    return [...cubeMembersInQuery].every(memberName => {
                        const hasFullAccess = !policy.memberLevel ||
                            (policy.memberLevel.includesMembers.includes(memberName) &&
                                !policy.memberLevel.excludesMembers.includes(memberName));
                        if (hasFullAccess)
                            return true;
                        if (policy.memberMasking) {
                            return policy.memberMasking.includesMembers.includes(memberName) &&
                                !policy.memberMasking.excludesMembers.includes(memberName);
                        }
                        return false;
                    });
                });
                // Determine which members need masking: a member is masked if no covering
                // policy grants it full access via memberLevel AND at least one covering
                // policy defines memberMasking that includes the member.
                // Masking follows the same pattern as row-level security: it is applied
                // at both cube and view levels. When a cube is accessed through a view,
                // both the cube's and the view's masking policies are evaluated.
                const cubeMembersInQuery = Array.from(queryMemberNames).filter(memberName => memberName.startsWith(`${cubeName}.`));
                for (const memberName of cubeMembersInQuery) {
                    const hasFullAccessInAnyPolicy = policiesWithMemberAccess.some(policy => {
                        if (!policy.memberLevel)
                            return true;
                        return policy.memberLevel.includesMembers.includes(memberName) &&
                            !policy.memberLevel.excludesMembers.includes(memberName);
                    });
                    if (!hasFullAccessInAnyPolicy && policiesWithMemberAccess.length > 0) {
                        const isMaskedByAnyPolicy = policiesWithMemberAccess.some((policy) => policy.memberMasking && policy.memberMasking.includesMembers.includes(memberName) && !policy.memberMasking.excludesMembers.includes(memberName));
                        if (isMaskedByAnyPolicy) {
                            maskedMembersSet.add(memberName);
                        }
                    }
                }
                for (const policy of policiesWithMemberAccess) {
                    hasAccessPermission = true;
                    (policy?.rowLevel?.filters || []).forEach((filter) => {
                        filtersMap[cubeName] = filtersMap[cubeName] || {};
                        // Create a unique key for the policy (either role, group, or groups)
                        const groupValue = policy.group || policy.groups;
                        const policyKey = policy.role ||
                            (Array.isArray(groupValue) ? groupValue.join(',') : groupValue) ||
                            'default';
                        filtersMap[cubeName][policyKey] = filtersMap[cubeName][policyKey] || [];
                        filtersMap[cubeName][policyKey].push(this.evaluateNestedFilter(filter, cube, context, cubeEvaluator));
                    });
                    if (!policy?.rowLevel || policy?.rowLevel?.allowAll) {
                        hasAllowAllForCube[cubeName] = true;
                        break;
                    }
                }
                if (!hasAccessPermission) {
                    query.segments = query.segments || [];
                    query.segments.push({
                        expression: () => '1 = 0',
                        cubeName: cube.name,
                        name: 'rlsAccessDenied',
                    });
                    return { query, denied: true };
                }
            }
        }
        const rlsFilter = this.buildFinalRlsFilter(cubeFiltersPerCubePerRole, viewFiltersPerCubePerRole, hasAllowAllForCube);
        if (rlsFilter) {
            query.filters = query.filters || [];
            query.filters.push(rlsFilter);
        }
        if (maskedMembersSet.size > 0) {
            query.maskedMembers = Array.from(maskedMembersSet);
        }
        return { query, denied: false };
    }
    removeEmptyFilters(filter) {
        if (filter?.and) {
            const and = filter.and.map((f) => this.removeEmptyFilters(f)).filter((f) => f);
            return and.length > 1 ? { and } : and.at(0) || null;
        }
        if (filter?.or) {
            const or = filter.or.map((f) => this.removeEmptyFilters(f)).filter((f) => f);
            return or.length > 1 ? { or } : or.at(0) || null;
        }
        return filter;
    }
    buildFinalRlsFilter(cubeFiltersPerCubePerRole, viewFiltersPerCubePerRole, hasAllowAllForCube) {
        // - delete all filters for cubes where the user has allowAll
        // - combine the rest into per policy maps (policies can be role-based or group-based)
        // - join all filters for the same policy with AND
        // - join all filters for different policies with OR
        // - join cube and view filters with AND
        const policyReducer = (filtersMap) => (acc, cubeName) => {
            if (!hasAllowAllForCube[cubeName]) {
                Object.keys(filtersMap[cubeName]).forEach(policyKey => {
                    acc[policyKey] = (acc[policyKey] || []).concat(filtersMap[cubeName][policyKey]);
                });
            }
            return acc;
        };
        const cubeFiltersPerPolicy = Object.keys(cubeFiltersPerCubePerRole).reduce(policyReducer(cubeFiltersPerCubePerRole), {});
        const viewFiltersPerPolicy = Object.keys(viewFiltersPerCubePerRole).reduce(policyReducer(viewFiltersPerCubePerRole), {});
        return this.removeEmptyFilters({
            and: [{
                    or: Object.keys(cubeFiltersPerPolicy).map(policyKey => ({
                        and: cubeFiltersPerPolicy[policyKey]
                    }))
                }, {
                    or: Object.keys(viewFiltersPerPolicy).map(policyKey => ({
                        and: viewFiltersPerPolicy[policyKey]
                    }))
                }]
        });
    }
    async compilerCacheFn(requestId, key, path) {
        const compilers = await this.getCompilers({ requestId });
        if (this.sqlCache) {
            return (subKey, cacheFn) => compilers.compilerCache.getQueryCache(key).cache(path.concat(subKey), cacheFn);
        }
        else {
            return (subKey, cacheFn) => cacheFn();
        }
    }
    async preAggregations(filter) {
        const { cubeEvaluator } = await this.getCompilers();
        return cubeEvaluator.preAggregations(filter);
    }
    async scheduledPreAggregations() {
        const { cubeEvaluator } = await this.getCompilers();
        return cubeEvaluator.scheduledPreAggregations();
    }
    async createQueryByDataSource(compilers, query, dataSource, dbType) {
        if (!dbType) {
            dbType = await this.getDbType(dataSource);
        }
        return this.createQuery(compilers, dbType, this.getDialectClass(dataSource, dbType), query);
    }
    createQuery(compilers, dbType, dialectClass, query) {
        return (0, schema_compiler_1.createQuery)(compilers, dbType, {
            ...query,
            dialectClass,
            externalDialectClass: this.options.externalDialectClass,
            externalDbType: this.options.externalDbType,
            preAggregationsSchema: this.preAggregationsSchema,
            allowUngroupedWithoutPrimaryKey: this.allowUngroupedWithoutPrimaryKey,
            convertTzForRawTimeDimension: this.convertTzForRawTimeDimension,
            queryFactory: this.queryFactory,
        });
    }
    /**
     * if RBAC is enabled, this method is used to patch isVisible property of cube members
     * based on access policies.
    */
    async patchVisibilityByAccessPolicy(compilers, context, cubes) {
        const isMemberVisibleInContext = {};
        const { cubeEvaluator } = compilers;
        if (!cubeEvaluator.isRbacEnabled()) {
            return { cubes, visibilityMaskHash: null };
        }
        for (const cube of cubes) {
            const evaluatedCube = cubeEvaluator.cubeFromPath(cube.config.name);
            if (cubeEvaluator.isRbacEnabledForCube(evaluatedCube)) {
                const applicablePolicies = await this.getApplicablePolicies(evaluatedCube, context, compilers);
                const computeMemberVisibility = (item) => {
                    for (const policy of applicablePolicies) {
                        if (policy.memberLevel) {
                            if (policy.memberLevel.includesMembers.includes(item.name) &&
                                !policy.memberLevel.excludesMembers.includes(item.name)) {
                                return true;
                            }
                        }
                        else if (!policy.memberMasking) {
                            // If there's no memberLevel and no memberMasking policy, all members are visible
                            return true;
                        }
                        if (policy.memberMasking) {
                            if (policy.memberMasking.includesMembers.includes(item.name) &&
                                !policy.memberMasking.excludesMembers.includes(item.name)) {
                                return true;
                            }
                        }
                    }
                    return false;
                };
                for (const dimension of cube.config.dimensions) {
                    isMemberVisibleInContext[dimension.name] = computeMemberVisibility(dimension);
                }
                for (const measure of cube.config.measures) {
                    isMemberVisibleInContext[measure.name] = computeMemberVisibility(measure);
                }
                for (const segment of cube.config.segments) {
                    isMemberVisibleInContext[segment.name] = computeMemberVisibility(segment);
                }
                for (const hierarchy of cube.config.hierarchies) {
                    isMemberVisibleInContext[hierarchy.name] = computeMemberVisibility(hierarchy);
                }
            }
        }
        const visibilityPatcherForCube = (cube) => {
            const evaluatedCube = cubeEvaluator.cubeFromPath(cube.config.name);
            if (!cubeEvaluator.isRbacEnabledForCube(evaluatedCube)) {
                return (item) => item;
            }
            return (item) => ({
                ...item,
                isVisible: item.isVisible && isMemberVisibleInContext[item.name],
                public: item.public && isMemberVisibleInContext[item.name]
            });
        };
        const visibilityMask = JSON.stringify(isMemberVisibleInContext, Object.keys(isMemberVisibleInContext).sort());
        // This hash will be returned along the modified meta config and can be used
        // to distinguish between different "schema versions" after DAP visibility is applied
        const visibilityMaskHash = crypto_1.default.createHash('sha256').update(visibilityMask).digest('hex');
        return {
            cubes: cubes
                .map((cube) => ({
                config: {
                    ...cube.config,
                    measures: cube.config.measures?.map(visibilityPatcherForCube(cube)),
                    dimensions: cube.config.dimensions?.map(visibilityPatcherForCube(cube)),
                    segments: cube.config.segments?.map(visibilityPatcherForCube(cube)),
                    hierarchies: cube.config.hierarchies?.map(visibilityPatcherForCube(cube)),
                },
            })),
            visibilityMaskHash
        };
    }
    mixInVisibilityMaskHash(compilerId, visibilityMaskHash) {
        const uuidBytes = Buffer.from((0, uuid_1.parse)(compilerId));
        const hashBytes = Buffer.from(visibilityMaskHash, 'hex');
        return (0, uuid_1.v4)({ random: crypto_1.default.createHash('sha256').update(uuidBytes).update(hashBytes).digest()
                .subarray(0, 16) });
    }
    async metaConfig(requestContext, options = {}) {
        const { includeCompilerId, ...restOptions } = options;
        const compilers = await this.getCompilers(restOptions);
        const { cubes } = compilers.metaTransformer;
        const { visibilityMaskHash, cubes: patchedCubes } = await this.patchVisibilityByAccessPolicy(compilers, requestContext, cubes);
        if (includeCompilerId) {
            return {
                cubes: patchedCubes,
                // This compilerId is primarily used by the cubejs-backend-native or caching purposes.
                // By default, it doesn't account for member visibility changes introduced above by DAP.
                // Here we're modifying the original compilerId in a way that it's distinct for
                // distinct schema versions while still being a valid UUID.
                compilerId: visibilityMaskHash ? this.mixInVisibilityMaskHash(compilers.compilerId, visibilityMaskHash) : compilers.compilerId,
            };
        }
        return patchedCubes;
    }
    async metaConfigExtended(requestContext, options) {
        const compilers = await this.getCompilers(options);
        const { cubes: patchedCubes } = await this.patchVisibilityByAccessPolicy(compilers, requestContext, compilers.metaTransformer?.cubes);
        return {
            metaConfig: patchedCubes,
            cubeDefinitions: compilers.metaTransformer?.cubeEvaluator?.cubeDefinitions,
        };
    }
    async compilerId(options = {}) {
        return (await this.getCompilers(options)).compilerId;
    }
    async cubeNameToDataSource(query) {
        const { cubeEvaluator } = await this.getCompilers({ requestId: query.requestId });
        return cubeEvaluator
            .cubeNames()
            .map((cube) => ({ [cube]: cubeEvaluator.cubeFromPath(cube).dataSource || 'default' })).reduce((a, b) => ({ ...a, ...b }), {});
    }
    async memberToDataSource(query) {
        const { cubeEvaluator } = await this.getCompilers({ requestId: query.requestId });
        const entries = cubeEvaluator
            .cubeNames()
            .flatMap(cube => {
            const cubeDef = cubeEvaluator.cubeFromPath(cube);
            if (cubeDef.isView) {
                const viewName = cubeDef.name;
                return cubeDef.includedMembers?.map((included) => {
                    const memberName = `${viewName}.${included.name}`;
                    const refCubeDef = cubeEvaluator.cubeFromPath(included.memberPath);
                    const dataSource = refCubeDef.dataSource ?? 'default';
                    return [memberName, dataSource];
                }) || [];
            }
            else {
                const cubeName = cubeDef.name;
                const dataSource = cubeDef.dataSource ?? 'default';
                return [
                    ...Object.keys(cubeDef.dimensions),
                    ...Object.keys(cubeDef.measures),
                    ...Object.keys(cubeDef.segments),
                ].map(mem => [`${cubeName}.${mem}`, dataSource]);
            }
        });
        return Object.fromEntries(entries);
    }
    async dataSources(orchestratorApi, query, _dataSourceFromEnvs) {
        const cubeNameToDataSource = await this.cubeNameToDataSource(query || { requestId: `datasources-${(0, uuid_1.v4)()}` });
        let dataSources = Object.keys(cubeNameToDataSource).map(c => cubeNameToDataSource[c]);
        dataSources = [...new Set(dataSources)];
        const dataSourcesInfo = await Promise.all(dataSources.map(async (dataSource) => {
            try {
                await orchestratorApi.driverFactory(dataSource);
                const dbType = await this.getDbType(dataSource);
                return { dataSource, dbType };
            }
            catch (err) {
                return null;
            }
        }));
        return {
            dataSources: dataSourcesInfo.filter((source) => !!source),
        };
    }
    canUsePreAggregationForTransformedQuery(transformedQuery, refs = null) {
        return schema_compiler_1.PreAggregations.canUsePreAggregationForTransformedQueryFn(transformedQuery, refs);
    }
}
exports.CompilerApi = CompilerApi;
//# sourceMappingURL=CompilerApi.js.map