• cube.js 上线文 filter 处理的原理


    cube.js 支持基于上下文的filter 处理,核心是依赖了js 的proxy

    参考filter 使用模式

    cube(`demoapp`, {
        sql: ` SELECT
        *
    FROM 
        transactions AS ts
        where   ${SECURITY_CONTEXT.user_id.filter("total_amount")}
    `,
        measures: {
            average_spend_per_customer: {
                sql: `${sum}/${count}`,
                type: `number`
            },
            count: {
                sql: `total_amount`,
                type: `count`,
            },
            sum: {
                sql: `total_amount`,
                type: `sum`,
            }
        },
        dimensions: {
            total_amount: {
                sql: `total_amount`,
                type: `number`
            },
            customer_id: {
                sql: `customer_id`,
                type: `string`
            },
            transaction_date: {
                sql: `transaction_date`,
                type: `time`,
                shown: false
            },
            event_id: {
                sql: `event_id`,
                type: `string`
            }
        }
    });

    简单说明

    因为请求是基于api-gateway的,需要通过编译器进行预编译处理,之后会获取sql,然后进行执行
    参考gateway 处理

     
    const sqlQuery = await this.getCompilerApi(context).getSql(this.coerceForSqlQuery(normalizedQuery, context));
     

    同时会使用到编译api 提供的withQuery 函数(进行维度以及数据的处理)

     return compilers.compiler.withQuery(sqlGenerator, () => ({
                external: sqlGenerator.externalPreAggregationQuery(),
                sql: sqlGenerator.buildSqlAndParams(),
                timeDimensionAlias: sqlGenerator.timeDimensions[0] && sqlGenerator.timeDimensions[0].unescapedAliasName(),
                timeDimensionField: sqlGenerator.timeDimensions[0] && 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()
            }));

    核心代码说明

    @cubejs-backend/schema-compiler/dust/src/adapter/BaseQuery.js

     evaluateSql(cubeName, sql, options) {
            options = options || {};
            const self = this;
            const { cubeEvaluator } = this;
            this.pushCubeNameForCollectionIfNecessary(cubeName);
            return cubeEvaluator.resolveSymbolsCall(sql, (name) => {
                const nextCubeName = cubeEvaluator.symbols[name] && name || cubeName;
                this.pushCubeNameForCollectionIfNecessary(nextCubeName);
                const resolvedSymbol = cubeEvaluator.resolveSymbol(cubeName, name);
                // eslint-disable-next-line no-underscore-dangle
                if (resolvedSymbol._objectWithResolvedProperties) {
                    return resolvedSymbol;
                }
                return self.evaluateSymbolSql(nextCubeName, name, resolvedSymbol);
            }, {
                sqlResolveFn: options.sqlResolveFn || ((symbol, cube, n) => self.evaluateSymbolSql(cube, n, symbol)),
                cubeAliasFn: self.cubeAlias.bind(self),
                contextSymbols: this.parametrizedContextSymbols(),
                query: this
            });
        }

    parametrizedContextSymbols 函数

      parametrizedContextSymbols() {
            if (!this.parametrizedContextSymbolsValue) {
                this.parametrizedContextSymbolsValue = Object.assign({
                    filterParams: this.filtersProxy(),
                    sqlUtils: {
                        convertTz: this.convertTz.bind(this)
                    }
                }, ramda_1.default.map((symbols) => this.contextSymbolsProxy(symbols), this.contextSymbols));
            }
            return this.parametrizedContextSymbolsValue;
        }

    proxy 函数

    contextSymbolsProxy(symbols) {
            return new Proxy(symbols, {
                get: (target, name) => {
                    const propValue = target[name];
                    const methods = (paramValue) => ({
                        filter: (column) => {
                            if (paramValue) {
                                const value = Array.isArray(paramValue) ?
                                    paramValue.map(this.paramAllocator.allocateParam.bind(this.paramAllocator)) :
                                    this.paramAllocator.allocateParam(paramValue);
                                if (typeof column === 'function') {
                                    return column(value);
                                }
                                else {
                                    return `${column} = ${value}`;
                                }
                            }
                            else {
                                return '1 = 1';
                            }
                        },
                        requiredFilter: (column) => {
                            if (!paramValue) {
                                throw new UserError_1.UserError(`Filter for ${column} is required`);
                            }
                            return methods(paramValue).filter(column);
                        },
                        unsafeValue: () => paramValue
                    });
                    return methods(target)[name] ||
                        typeof propValue === 'object' && this.contextSymbolsProxy(propValue) ||
                        methods(propValue);
                }
            });
        }
        filtersProxy() {
            const { allFilters } = this;
            return new Proxy({}, {
                get: (target, name) => {
                    if (name === '_objectWithResolvedProperties') {
                        return true;
                    }
                    const cubeName = this.cubeEvaluator.cubeNameFromPath(name);
                    return new Proxy({ cube: cubeName }, {
                        get: (cubeNameObj, propertyName) => {
                            const filter = allFilters.find(f => f.dimension === this.cubeEvaluator.pathFromArray([cubeNameObj.cube, propertyName]));
                            return {
                                filter: (column) => {
                                    const filterParams = filter && filter.filterParams();
                                    if (filterParams && filterParams.length) {
                                        if (typeof column === 'function') {
                                            // eslint-disable-next-line prefer-spread
                                            return column.apply(null, filterParams.map(this.paramAllocator.allocateParam.bind(this.paramAllocator)));
                                        }
                                        else {
                                            return filter.conditionSql(column);
                                        }
                                    }
                                    else {
                                        return '1 = 1';
                                    }
                                }
                            };
                        }
                    });
                }
            });
        }

    cube.js 基于babel 的一些扩展

    同时为了进行sql 函数的生成,cube.js 基于babel 开发了好多transpiler,比如import 的prop,validator 的


    比如一些通用的属性转sql 函数的处理

     
    protected knownIdentifiersInjectVisitor(field, resolveSymbol) {
        const self = this;
        return {
          ObjectProperty(path) {
            if (path.node.key.type === 'Identifier' && path.node.key.name.match(field)) {
              const knownIds = self.collectKnownIdentifiers(
                resolveSymbol,
                path.get('value')
              );
              path.get('value').replaceWith(
                t.arrowFunctionExpression(knownIds.map(i => t.identifier(i)), path.node.value, false)
              );
            }
          }
        };
      }

    编译使用babel 扩展

    transpileFile(file, errorsReport) {
        try {
          const ast = parse(
            file.content,
            {
              sourceFilename: file.fileName,
              sourceType: 'module',
              plugins: ['objectRestSpread']
            },
          );
          // 此处使用了编写的babel扩展
          this.transpilers.forEach((t) => {
            errorsReport.inFile(file);
            babelTraverse(ast, t.traverseObject(errorsReport));
            errorsReport.exitFile();
          });
          const content = babelGenerator(ast, {}, file.content).code;
          return Object.assign({}, file, { content });
        } catch (e) {
          if (e.toString().indexOf('SyntaxError') !== -1) {
            const line = file.content.split('
    ')[e.loc.line - 1];
            const spaces = Array(e.loc.column).fill(' ').join('');
            errorsReport.error(`Syntax error during '${file.fileName}' parsing: ${e.message}:
    ${line}
    ${spaces}^`);
          } else {
            errorsReport.error(e);
          }
        }

    说明

    以上是一个简单的使用说明,具体的可以多看看源码

    参考资料

    https://github.com/cube-js/cube.js

  • 相关阅读:
    循环事件绑定和原型的应用
    小知识随手记(四)
    JavaScript数组与字符串常用方法总结
    jquery获得select option的值和对select option的操作
    前端图片上传前预览
    CSS 的优先级机制总结
    汇编语言学习笔记(8)——数据处理的基本问题
    SPOJ 1811LCS Longest Common Substring
    mysql 安装完毕后登陆不了mysql的 shell 即mysql>遇到:ERROR 1045 (28000): Access denied for user 'root'@'localhost‘
    [LeetCode]Power of Two
  • 原文地址:https://www.cnblogs.com/rongfengliang/p/14364134.html
Copyright © 2020-2023  润新知