文章目录
一、程序启动
-
Clojure有多种启动方式,推荐使用 lein run
1. lein ring server
- 加载 ring 环境,包括 exclude-tests、include-all-drivers、lein-ring
include-all-drivers 会对所有数据库驱动进行加载,并在终端输出进度,耗时较长;即使在环境中注释掉include-all-drivers,后续init时依然会加载,即注释掉不会产生影响;
- 加载metabase.core引用的命名空间,耗时较长。某些命名空间在加载过程中会输出到终端;
示例:2020-12-11 18:21:38,879 INFO metabase.util :: Maximum memory available to JVM: 3.6 GB
- 加载 metabase.core,加载过程中有输出
示例:Copyright © 2020 Metabase, Inc.
- 运行 metabase.core/init!
- 启动ring服务,监听端口(默认3000),并将收到的请求交给handler处理
2. lein run
- 加载 run 环境,包括 exclude-tests、include-all-drivers
- 加载 :main 指定的入口命名空间,此处为metabase.core
- 运行 metabase.core/-main,如果带有cmd指令会进入 metabase.cmd/run-cmd 运行相应指令,否则进入 metabase.core/start-normally 启动程序
- 在 start-normally 中,会运行 metabase.core/init!并通过jetty启动服务器,相关运行配置在 metabase.config中配置
3. lein repl
- 打开一个交互服务器,可以执行输入的代码并实时返回结果。
- 启动后命名空间为user,即不会主动加载项目代码,需要手动 require 以加载环境
- 在project.clj中配置 :repl-options { :init-ns test.core }, 可以指定默认加载空间
- 加载 ring 环境,包括 exclude-tests、include-all-drivers、lein-ring
二、API请求
-
API官方文档可参阅 API Document
-
如需追踪工作流,可以在
resources/log4j2.xml
中调低相关日志等级1. 统一API请求入口
- 收到API请求后,统一调用 metabase.handler/app 处理
- 调用 metabase.routes/routes 进行请求分流,最核心的是处理数据请求的 /api 接口,其它接口主要是辅助功能
- 对于api请求,进入 metabase.api.routes/routes 对于不同的接口进行二次分流
- 回到metabase.handler/app,对结果进行一系列的封装,如捕获异常、输出日志、转JSON等等
2. 数据查询接口
-
临时查询基本都走/api/dataset接口,保存的图表会有不同的接口入口,但是都会从card表中查SQL,然后通过
qp/process-query-and-save-execution!
执行GET /api/pulse/preview_card_info/4
- 在pulse中查看数据预览时调用该接口
- GET接口,有效参数仅card_id,后端通过数据库获取query
- 调用
metabase.api.pulse
中GET "/preview_card_info/:id"
路由;- 将card_id传入
api/read-check
函数,查询card信息(包括sql语句) - 将card传入
pulse-card-query-results
函数,获取数据;- 调用
qp/process-query-and-save-execution!
执行查询
- 调用
- 将数据直接转换成HTML格式的图表,放在 pulse_card_html中返回给前端;
- 将card_id传入
- 前端直接渲染,无需调用图表方法(邮件中也可以直接渲染)。
POST /api/card/1/query
- 打开已保存的数据看板/Question时调用该接口,但如果对card条件进行更改,刷新数据时会调用dataset
- 接口调用上传参数为空,有效参数仅card_id,后端通过数据库获取query
- 调用
metabase.api.card
中POST "/:card-id/query"
路由;- 将card_id传入
run-query-for-card-async
函数,获取数据;- 将card_id传入
api/read-check
,查询card信息(包括sql语句) - 调用
qp/process-query-and-save-execution!
执行查询
- 将card_id传入
- 将card_id传入
POST /api/dataset
- 所有其它调用都使用该接口,包括x-ray,ask question, SQL取数等
- 调用
metabase.api.dataset
中POST "/"
路由;- 增加中间件 :js-int-to-string?
- 调用
qp/process-query-and-save-with-max-results-constraints!
组装SQL语句- 增加中间件 :add-default-userland-constraints?
- 调用
qp/process-query-and-save-execution!
组装SQL语句
- 调用
qp.streaming/streaming-response
执行并组装返回结果
3. DP流程
qp/process-query-and-save-execution!
之后进入DP流程,以下为流程详解qp/process-query-and-save-execution!
,此时收到的query可能为SQL语句,也可能是HoneySQL- 调用
qp/process-userland-query
,在此区分同步和异步查询,但之后其实都是调异步查询函数qp/process-userland-query-async
,只是同步查询会在这里先加上一个 wait-for-async-result 方法 - 调用
qp/base-qp
,这里定义并调用了一个内部函数qp,里面运行了两个最关键的函数:
qp.reducible/combine-middleware
,这个函数会先用 qp.reducible/pivot 生成一个基础执行器,然后用 qp/userland-middleware 中定义的40个中间件逐个去包装它,最后返回包装好的执行器qp.reducible/async-qp
, 这里定义了一个内部函数 thunk 用于把query放入执行器执行,并捕获错误,并且可以另起一个线程来运行执行器。
- 出于二次开发需要,需进一步了解执行器的流程,展开如下:
- 当
qp.reducible/async-qp
调用执行器之后,会将原始query传递给qp.reducible/pivot
, - 44个中间件对执行器进行加工
- 调用
qp.context/runf
,然后再调用qp.context/executef
进行取数,此时的query已经被组装为可执行的SQL- 由于数据可能存在不同的数据源中,因此实际取数要调用driver去执行,主程序中只调用了REST接口,然后等待数据返回
- 主程序中虽然引入了toucan,但只用于读取元数据,并不用于业务取数,因此主流程中找不到toucan操作。
- 当
4. Middleware
-
一个普通的查询会遍历44个middleware,先遍历3个userland附加的middleware,如下表从下往上执行:
metabase.query-processor.middleware.constraints/add-default-userland-constraints
- 如果参数中 :add-default-userland-constraints? 为 true,则在query中添加 :constraints {:max-results 10000, :max-results-bare-rows 2000}
metabase.query-processor.middleware.process-userland-query/process-userland-query
- 对于面向用户的查询(userland query),记录查询日志并包装返回的结果
metabase.query-processor.middleware.catch-exceptions/catch-exceptions
- 捕获qp过程中的异常
-
然后是41个 process-query 默认的middleware, 从下往上执行
#'metabase.query-processor.middleware.mbql-to-native/mbql->native
#'metabase.query-processor.middleware.check-features/check-features
#'metabase.query-processor.middleware.optimize-datetime-filters/optimize-datetime-filters
#'metabase.query-processor.middleware.auto-parse-filter-values/auto-parse-filter-values
#'metabase.query-processor.middleware.wrap-value-literals/wrap-value-literals
#'metabase.query-processor.middleware.annotate/add-column-info
#'metabase.query-processor.middleware.permissions/check-query-permissions
#'metabase.query-processor.middleware.pre-alias-aggregations/pre-alias-aggregations
#'metabase.query-processor.middleware.cumulative-aggregations/handle-cumulative-aggregations
#'metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions/apply-row-level-permissions
#'metabase.query-processor.middleware.resolve-joined-fields/resolve-joined-fields
#'metabase.query-processor.middleware.resolve-joins/resolve-joins
#'metabase.query-processor.middleware.add-implicit-joins/add-implicit-joins
#'metabase.query-processor.middleware.large-int-id/convert-id-to-string
#'metabase.query-processor.middleware.limit/limit
#'metabase.query-processor.middleware.format-rows/format-rows
#'metabase.query-processor.middleware.desugar/desugar
#'metabase.query-processor.middleware.binning/update-binning-strategy
#'metabase.query-processor.middleware.resolve-fields/resolve-fields
#'metabase.query-processor.middleware.add-dimension-projections/add-remapping
#'metabase.query-processor.middleware.add-implicit-clauses/add-implicit-clauses
#'metabase-enterprise.sandbox.query-processor.middleware.row-level-restrictions/apply-row-level-permissions
#'metabase.query-processor.middleware.add-source-metadata/add-source-metadata-for-source-queries
#'metabase-enterprise.sandbox.query-processor.middleware.column-level-perms-check/maybe-apply-column-level-perms-check
#'metabase.query-processor.middleware.reconcile-breakout-and-order-by-bucketing/reconcile-breakout-and-order-by-bucketing
#'metabase.query-processor.middleware.auto-bucket-datetimes/auto-bucket-datetimes
#'metabase.query-processor.middleware.resolve-source-table/resolve-source-tables
#'metabase.query-processor.middleware.parameters/substitute-parameters
#'metabase.query-processor.middleware.resolve-referenced/resolve-referenced-card-resources
#'metabase.query-processor.middleware.expand-macros/expand-macros
#'metabase.query-processor.middleware.add-timezone-info/add-timezone-info
#'metabase.query-processor.middleware.splice-params-in-response/splice-params-in-response
#'metabase.query-processor.middleware.resolve-database-and-driver/resolve-database-and-driver
#'metabase.query-processor.middleware.fetch-source-query/resolve-card-id-source-tables
#'metabase.query-processor.middleware.store/initialize-store
#'metabase.query-processor.middleware.cache/maybe-return-cached-results
#'metabase.query-processor.middleware.validate/validate-query
#'metabase.query-processor.middleware.normalize-query/normalize
#'metabase.query-processor.middleware.add-rows-truncated/add-rows-truncated
#'metabase-enterprise.audit.query-processor.middleware.handle-audit-queries/handle-internal-queries
#'metabase.query-processor.middleware.results-metadata/record-and-return-metadata!