无需预定义模型,如何实现类OData $filter的URI查询参数分词?
类OData $filter无实体依赖的简易分词方案指导
一、轻量解析方案选型
- ANTLR4自定义语法:用ANTLR4定义简化版OData $filter语法(仅保留核心运算符:
eq/ne/gt/lt/ge/le、and/or、括号、基础数据类型),生成词法/语法解析器,直接输出可用于构建SQL的分词节点(例如把Name eq 'Alice'解析为{type: 'comparison', left: 'Name', op: '=', right: "'Alice'"}),完全无需实体元数据,只做语法结构解析。 - 手写递归下降解析器:针对简易需求,手写解析成本极低,核心分两步:
- 词法拆分:将$filter字符串拆分为tokens(字段名、运算符、值、逻辑符、括号),比如
Age gt 18 and (Status eq 'Active' or Status eq 'Pending')会被拆成['Age', 'gt', '18', 'and', '(', 'Status', 'eq', "'Active'", 'or', 'Status', 'eq', "'Pending'", ')'] - 语法解析:按优先级(括号>比较运算>逻辑运算)递归构建抽象语法树(AST),最后遍历AST生成SQL条件片段。
- 词法拆分:将$filter字符串拆分为tokens(字段名、运算符、值、逻辑符、括号),比如
二、简化版$filter语法约束(降低解析复杂度)
先限定支持的语法子集,减少开发量:
- 仅支持字段名(字母、数字、下划线)、基础数据类型(字符串用单引号包裹、数字、布尔值
true/false) - 核心运算符映射:比较运算符
eq/ne/gt/lt/ge/le对应SQL的=/>/</>=/<=/!=;逻辑运算符and/or直接对应SQL的AND/OR;支持括号改变优先级 - 暂不支持函数(如
contains、startswith)、导航属性,后续按需扩展
三、示例实现思路(Python)
1. 词法拆分函数
def tokenize_filter(filter_str): # 给括号、运算符添加空格分隔,方便拆分 processed = filter_str.replace('(', ' ( ').replace(')', ' ) ') processed = processed.replace('eq', ' eq ').replace('ne', ' ne ') processed = processed.replace('gt', ' gt ').replace('lt', ' lt ') processed = processed.replace('ge', ' ge ').replace('le', ' le ') processed = processed.replace('and', ' and ').replace('or', ' or ') # 拆分并过滤空字符串 tokens = [t.strip() for t in processed.split() if t.strip()] return tokens
调用后,Age gt 18 and Status eq 'Active'会得到['Age', 'gt', '18', 'and', 'Status', 'eq', "'Active'"]
2. 语法解析与SQL生成
基于tokens递归解析,先处理括号内的子表达式,再处理比较运算,最后处理逻辑运算,直接生成SQL条件:
def parse_tokens(tokens): if not tokens: return "" # 处理括号包裹的子表达式 if tokens[0] == '(': close_idx = tokens.index(')') inner_sql = parse_tokens(tokens[1:close_idx]) remaining = tokens[close_idx+1:] if remaining: return f"({inner_sql}) {parse_tokens(remaining)}" return f"({inner_sql})" # 处理比较运算 if len(tokens) >=3 and tokens[1] in ['eq','ne','gt','lt','ge','le']: field = tokens[0] op_map = {'eq':'=', 'ne':'!=', 'gt':'>', 'lt':'<', 'ge':'>=', 'le':'<='} op = op_map[tokens[1]] value = tokens[2] remaining = tokens[3:] if remaining: return f"{field} {op} {value} {parse_tokens(remaining)}" return f"{field} {op} {value}" # 处理逻辑运算符 if tokens[0] in ['and','or']: return f" {tokens[0].upper()} {parse_tokens(tokens[1:])}" return ""
调用示例:
filter_str = "Age gt 18 and (Status eq 'Active' or Status eq 'Pending')" tokens = tokenize_filter(filter_str) sql_where = parse_tokens(tokens) # 输出结果:Age > 18 AND (Status = 'Active' OR Status = 'Pending')
四、安全注意事项
- 字段名白名单验证:解析出的字段名必须与目标表的实际字段匹配,防止恶意注入(比如用户输入
1=1作为字段名) - 值的转义处理:字符串值要做SQL转义(比如把单引号
'转成''),避免注入风险
内容的提问来源于stack exchange,提问作者RTD
相关产品推荐
相关产品推荐

