[英]Guide on how to process AST formatted as JSON like structure
通過在Perl中use SQL::Abstract::Tree
,我可以通過以下方式為SQL生成AST:
my $sqlat = SQL::Abstract::Tree->new;
my $tree = $sqlat->parse($query_str);
其中$query_str
是一個SQL查詢。
例如,使用查詢字符串SELECT cust_id, a as A, z SUM(price) as q, from orders WHERE status > 55
,生成:
[
[
"SELECT",
[
[
"-LIST",
[
["-LITERAL", ["cust_id"]],
["AS", [["-LITERAL", ["a"]], ["-LITERAL", ["A"]]]],
[
"AS",
[
["-LITERAL", ["z"]],
["SUM", [["-PAREN", [["-LITERAL", ["price"]]]]]],
["-LITERAL", ["q"]],
],
],
[],
],
],
],
],
["FROM", [["-LITERAL", ["orders"]]]],
[
"WHERE",
[[">", [["-LITERAL", ["status"]], ["-LITERAL", [55]]]]],
],
]
我想了解一下AST並獲取有關它的某些信息。
我想知道是否有一種指南/教程/示例源代碼以這種格式傳遞AST。
我發現考慮步行AST的大多數文獻通常都假設我具有某種類層次結構,該類層次結構描述了步行AST的訪客模式的某種變化。
我的特定用例是將簡單SQL查詢轉換為聚合框架的Mongo查詢,並在此處給出一些示例。
到目前為止,這是我一直在做的事情:
我首先調用一個parse
函數,並在給定每個子樹的類型和類型(這是每個子樹中的第一個參數)的基礎上對樹進行調度,然后將其與樹的其余部分一起調用。 這是我的parse
函數:
sub parse {
my ($tree) = @_;
my %results = (ret => []);
for my $subtree (@$tree) {
my ($node_type, $node) = @$subtree;
my $result_dic = $dispatch{$node_type}->($node);
if ($result_dic->{type}) {
my $type = $result_dic->{type};
$results{$type} = [] unless $results{$type};
push $results{$type}, $result_dic->{ret};
%results = merge_except_for($result_dic, \%results, 'ret', $type);
}
else {
push @{$results{ret}}, @{$result_dic->{ret}};
}
}
return \%results;
}
它使用以下調度表:
my %dispatch = (
SELECT => sub {
my $node = shift;
my $result_dic = parse($node);
$result_dic->{type} = 'select';
if ($result_dic->{as}) {
push $result_dic->{ret}, $result_dic->{as}->[0][0];
}
return $result_dic;
},
'-LITERAL' => sub {
my $node = shift;
my $literal = $node;
return {ret => $node};
},
'-LIST' => sub {
my $node = shift;
my $result_dic = parse($node);
my $ret = flatten_ret($result_dic);
return flatten_ret($result_dic);
},
WHERE => sub {
my $tree = shift;
my @bin_ops = qw/= <= < >= >/;
my $op = $tree->[0];
if ($op ~~ @bin_ops) {
# Not yet implemented
}
return {ret => ''};
},
FROM => sub {
my $tree = shift;
my $parse_result = parse($tree);
return {ret => $parse_result->{ret},
type => 'database'};
},
AS => sub {
my $node = shift;
my $result_dic = parse($node);
$result_dic->{type} = 'as';
return $result_dic;
}
);
sub flatten_ret {
my $result_dic = shift;
return {ret => [
map {
ref($_) ? $_->[0] : $_
} @{$result_dic->{ret}}]};
}
但是我不確定某些事情,例如我是否應該檢查SELECT
子例程中的節點名稱是否為"AS"
,還是尋找一種遞歸填充數據的方法。
另外,每個調度調用應返回哪種類型的數據,最后如何合並?
另外,我是AST處理的新手,並且希望能掌握它,因此,對於如何改善問題的建議也將不勝感激。
您進行類型分派的想法大致正確。 通常,人們可能會在其上使用對象並使用分派方法。 但是,使用兩個元素的列表標記某種類型的數據也可以。 您錯誤的parse
函數將實現此分派,並以某種方式聚合輸出。 我不太確定您要達到的目標。
進行AST轉換時,記住要創建的確切輸出非常有用。 假設您要轉換
SELECT cust_id, a as A, SUM(price) as q from orders WHERE status > 55
進入數據結構
{
table => 'orders',
action => 'aggregate',
query => [
'$match' => { 'status' => { '$gt' => 55 } },
'$group' => {
'_id' => undef,
'cust_id' => '$cust_id',
'A' => '$a',
'q' => { '$sum' => '$price' },
},
],
}
為此我們需要做什么?
SELECT ... FROM ...
類型查詢。 aggregate
。 FROM
條目的表名 SELECT
項,獲取名稱以及產生該值的表達式。
WHERE
子句,則遞歸轉換每個條件。 如果遇到無法解析的語法,則會引發錯誤。
請注意,我的方法從頂部開始,並在需要時從AST的更深層提取信息。 這與您自下而上的方法相反,后者將所有數據匯總在一起,並希望最后有相關內容。 尤其是您的哈希合並看起來令人懷疑。
如何實現呢? 這是一個開始:
use Carp;
sub translate_select_statement {
my ($select, $from, @other_clauses) = @_;
$select->[0] eq 'SELECT'
or croak "First clause must be a SELECT clause, not $select->[0]";
$from->[0] eq 'FROM'
or croak "Second clause must be a FROM clause, not $from->[0]";
my $select_list = $select->[1];
my %groups = (
_id => undef,
translate_select_list(get_list_items($select_list)),
);
...
}
sub get_list_items {
my ($list) = @_;
if ($list->[0] eq '-LIST') {
return @{ $list->[1] };
}
else {
# so it's probably just a single item
return $list;
}
};
sub translate_select_list {
my %out;
for my $item (@_) {
my ($type, $data) = @$item;
if ($type eq '-LITERAL') {
my ($name) = @$data;
$out{$name} = '$' . $name;
}
elsif ($type eq '-AS') {
my ($expr, $name_literal) = @$data;
$name_literal->[0] eq '-LITERAL'
or croak "in 'x AS y' expression, y must be a literal, but it was $name_literal->[0]";
$out{$name_literal->[1][0]} = translate_expression($expr);
}
else {
croak "I select list, items must be literals or 'x AS y' expression. Found [$type, $data] instead.";
}
}
return %out;
}
sub translate_expression { ... }
我的構造方式更像是自上而下的解析器,但是例如對於算術表達式的翻譯,類型分配更為重要。 在上面的代碼中, if
/ else
情況更好,因為它們允許更多的驗證。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.