pip install mindsdb_sql
Parser(解析器)
- 接收字符串输入,解析为抽象语法树(AST)
Planner(规划器)
- 接收 AST 输入,将其转换为执行查询所需的一系列步骤
Render(渲染器)
- 接收 AST 输入,将其转换为指定 SQL 方言的字符串
from mindsdb_sql import parse_sql
query = parse_sql('select b from aaa where c=1', dialect='mindsdb')
# 结果是一个抽象语法树 (AST)
query
# AST 的字符串表示形式
query.to_tree()
# 将树结构转换为 SQL 字符串,可能与原始 SQL 不完全一致
query.to_string()
mysql
- MySQL 服务器的 SQL 方言。尚未完全实现,正在持续改进中
sqlite
- 目前尚未完全实现,是 mysql 语法的简化版本
mindsdb
- 扩展的 MySQL 方言,支持 mindsdb SQL 命令和运算符 [https://docs.mindsdb.com/]
使用 SLY 库进行解析。
解析分为两个阶段(每个方言有独立模块):
- 在 lexer.py 模块中定义关键字,主要通过正则表达式实现
- 在 parser.py 模块中定义语法规则,通过 BNF 范式 描述规则
- 语法在函数的装饰器中定义,装饰器内可使用关键字本身或解析器中的其他函数
- 函数的输出可作为解析器其他函数的输入
- 解析器的输出列为"顶层语句",必须是抽象语法树(AST)对象
由于 SLY 不支持继承,每个方言需完整独立描述,无法互相扩展。
为提供更好的用户体验,解析错误会包含问题位置及可能的解决方案。
1. 当以下情况发生时显示错误位置:
- 字符无法被解析(由词法分析器处理)
- 出现意外的标记(由解析器处理)
2. 尝试在错误位置附近建议正确的标记。可能的选项:
- 关键字会原样显示
- '[number]' - 预期为浮点数或整数
- '[string]' - 预期为字符串
- '[identifier]' - 预期为对象名称。例如,以下加粗词汇即为标识符:
- "select x as name from tbl1 where col=1"
建议机制说明:
使用语法规则定义的下一个可能的标记。
如果这是查询的末尾:仅显示这些标记。
否则:
- 尝试用可能的标记列表中的其他标记替换错误标记
- 再次尝试解析查询,如果无错误:
- 将该标记添加到建议列表
- 第二次迭代:在错误标记之前插入可能的标记(而非替换),重复相同操作。
示例:
初始化规划器
from mindsdb_sql.planner import query_planner
# 所有参数均为可选
planner = query_planner.QueryPlanner(
ast_query, # 查询的 AST 树
integrations=['mysql'], # 可用集成列表
predictor_namespace='mindsdb', # 查找预测器的命名空间
default_namespace='mindsdb', # 查询中未指定命名空间时的默认命名空间
predictor_metadata={ # 预测器信息
'tp3': { # 预测器名称
'timeseries': True, # 是否为时间序列预测器
'order_by_column': 'pickup_hour', # 时间序列列
'group_by_columns': ['day', 'type'], # 分区列(仅用于时间序列)
'window': 10 # 窗口大小(仅用于时间序列)
}
}
)
时间序列预测器的详细说明:[https://docs.mindsdb.com/sql/create/predictor/]
预编译语句计划
规划器可用于带参数的查询:查询未完整,无法执行,但可以获取查询的列和参数列表。
for step in planner.prepare_steps(ast_query):
data = do_execute_step(step)
step.set_result(data)
statement_info = planner.get_statement_info()
# 列列表
print(statement_info['columns'])
# 参数列表
print(statement_info['parameters'])
目前此功能仅在 MySQL 二进制协议的 COM_STMT_PREPARE 命令中使用。
执行计划
# 如果已执行 prepare_steps,则需要传递参数。
# 否则,params=None
for step in planner.execute_steps(params):
data = do_execute_step(step)
step.set_result(data)
查询结果数据将在最后一步的输出中。
另一种执行方式
目前执行计划不依赖上一步的结果,但未来可能会改变。
在当前行为下,可以以列表形式获取查询计划:
from mindsdb_sql.planner import plan_query
plan = plan_query(
ast_query,
integrations=['mysql'],
predictor_namespace='mindsdb',
default_namespace='mindsdb',
predictor_metadata={
'tp3': {
'timeseries': False,
}
}
)
# 步骤列表
print(plan.steps)
规划器分析 AST 查询,并返回执行查询所需的一系列步骤。
步骤定义在 planner/steps.py 中。步骤可引用上一步的将来结果(使用 planner/step_results.py 中的 Result 类)。
查询规划器包含两个不同的规划器:
预编译语句:query_prepare.py 中的 PreparedStatementPlanner 类
执行:query_panner.py 中的 QueryPlanner 类
规划器中最复杂的部分是处理时间序列预测器的连接表查询。逻辑简要说明:
- 提取集成的查询(不含预测器)
- 选择所有可能的分组字段值(查询范围内)
- 对于每个分组字段值
- 根据过滤条件和窗口大小选择数据部分
- 将所有数据合并为一个 DataFrame
- 将其传递到预测器输入
- 将预测结果与预测前的数据合并
实用函数
可用于分析 AST 树的组成。示例:
query_predictors = []
def find_predictors(node, is_table, **kwargs):
if is_table and isinstance(node, ast.Identifier):
if is_predictor(node):
query_predictors.append(node)
utils.query_traversal(ast_query, find_predictors)
渲染器用于将 AST 查询转换为不同 SQL 方言的字符串。
from mindsdb_sql.render.sqlalchemy_render import SqlalchemyRender
renderer = SqlalchemyRender('mysql') # 选择方言
sql = renderer.get_string(ast_query, with_failback=True)
如果 with_failback==True:当 sqlalchemy 无法渲染查询时,将返回 AST 树的 SQL 字符串表示形式(使用 to_string 方法)
目前只有一个可用的渲染器:SqlalchemyRender。
- 它将 AST 查询转换为 sqlalchemy 查询。
为此使用了命令式映射
- 然后使用所选方言在 sqlalchemy 内部编译生成的 sqlalchemy 对象
目前支持的方言:mysql, postgresql, sqlite, mssql, oracle
注意:
- 表名最多支持两部分
- 可以是 (integration.table) 或 (schema.table)
- 但不能是 (integration.schema.table)
- 渲染后的 SQL 中的条件有时可能略有变化,例如 'not a=b' 变为 'a!='
运行所有组件的测试
env PYTHONPATH=./ pytest