Superset如何拼接SQL并执行(下)
在上半章之中,我们看了下Superset如何利用入参中的参数拼接SQL。
在下半章之中,我们会查看Superset如何将不同类型的图表封装为一个框架。
这里我们以Bar图为例,查看下如何实现的
涉及到的框架相关的接口是http://localhost:9000/superset/explore_json/
对应的代码在core.py文件中explore_json函数中
其中主要的入口在
viz_obj = get_viz(
datasource_type=cast(str, datasource_type), datasource_id=datasource_id, form_data=form_data, force_cached=True, force=force, ) payload = viz_obj.get_payload() |
获取到查询结果。
在get_viz函数其中则是根据图表类型,获取到BaseViz的子类,初始化类对象返回
def get_viz(
form_data: FormData, datasource_type: str, datasource_id: int, force: bool = False, force_cached: bool = False, ) -> BaseViz: viz_type = form_data.get(“viz_type”, “table”) datasource = DatasourceDAO.get_datasource( db.session, DatasourceType(datasource_type), datasource_id, ) viz_obj = viz.viz_types[viz_type]( datasource, form_data=form_data, force=force, force_cached=force_cached ) return viz_obj |
viz.viz_types 则是所有BaseViz的子类
def get_subclasses(cls: Type[BaseViz]) -> Set[Type[BaseViz]]:
return set(cls.__subclasses__()).union( [sc for c in cls.__subclasses__() for sc in get_subclasses(c)] ) viz_types = { o.viz_type: o for o in get_subclasses(BaseViz) if o.viz_type not in config[“VIZ_TYPE_DENYLIST”] } |
主要的查询接口在get_payload函数中,而这个函数位于BaseViz类中,方便继承。
在get_payload函数中,主要核心的代码则是调用本类中的get_df_payload函数
payload = self.get_df_payload(query_obj)
在get_df_payload之中,则是首先拼接 query_obj对象
然后拼接cache_key,查看缓存。
当获取不到缓存的时候,利用get_df接口进行查询,并在查询之后放入缓存。
我们贴上述的相关代码。
if not query_obj:
query_obj = self.query_obj() |
if cache_key and cache_manager.data_cache and not self.force:
cache_value = cache_manager.data_cache.get(cache_key) if cache_value: stats_logger.incr(“loading_from_cache”) try: df = cache_value[“df”] self.query = cache_value[“query”] self.applied_template_filters = cache_value.get( “applied_template_filters”, [] ) self.status = QueryStatus.SUCCESS is_loaded = True stats_logger.incr(“loaded_from_cache”) except Exception as ex: # pylint: disable=broad-except logger.exception(ex) logger.error( “Error reading cache: %s”, utils.error_msg_from_exception(ex), exc_info=True, ) logger.info(“Serving from cache”) |
df = self.get_df(query_obj)
if self.status != QueryStatus.FAILED: stats_logger.incr(“loaded_from_source”) if not self.force: stats_logger.incr(“loaded_from_source_without_force”) is_loaded = True |
对于query_obj函数,是类似拼接query_context的函数,在父类中分别获取到了groupby,metics,时间字段,时间相关filter,order by,limit等信息,最后拼接为一个dict返回
这个函数也可以被子类进行继承,诸如在Bar图之中,子类实现的query_obj 除了调用父类接口之外,还进行了自己校验逻辑的封装
query_obj = super().query_obj()
if len(query_obj[“groupby”]) < len(self.form_data.get(“groupby”) or []) + len( self.form_data.get(“columns”) or [] ): raise QueryObjectValidationError( _(“Can’t have overlap between Series and Breakdowns”) ) if not self.form_data.get(“metrics”): raise QueryObjectValidationError(_(“Pick at least one metric”)) if not self.form_data.get(“groupby”): raise QueryObjectValidationError(_(“Pick at least one field for [Series]”)) |
然后到了get_df函数之中
则是直接调用了数据集相关的datasource的query函数进行查询后返回
self.results = self.datasource.query(query_obj)
这里的query函数,我们在上一节中讲解过了,这里不再赘述。
在获取到了df之后,就可以进行返回。
如果想要对df进行一些后置处理操作,比如bar图中的breakdown操作,就可以实现get_data函数。
if self.status != QueryStatus.FAILED:
payload[“data”] = self.get_data(df) |
那么从上面框架中可以看出
如果我们想要增加一个属于自己的chart type,可以在这个框架之中。通过继承BaseViz,声明自己的viz_type
并可以进行客制化操作,诸如实现query_obj 函数修改,get_df函数修改查询逻辑。
cache_key函数修改缓存生成。 get_data修改查询后的dataframe格式。