2013-04-20 12 views
99

Pony ORM thực hiện thủ thuật tốt đẹp khi chuyển đổi biểu thức trình tạo thành SQL. Ví dụ:Làm thế nào Pony (ORM) thực hiện các thủ đoạn của nó?

>>> select(p for p in Person if p.name.startswith('Paul')) 
     .order_by(Person.name)[:2] 

SELECT "p"."id", "p"."name", "p"."age" 
FROM "Person" "p" 
WHERE "p"."name" LIKE "Paul%" 
ORDER BY "p"."name" 
LIMIT 2 

[Person[3], Person[1]] 
>>> 

Tôi biết Python có nội soi tuyệt vời và lập trình meta được lập trình, nhưng cách thư viện này có thể dịch biểu thức trình tạo mà không cần xử lý trước? Nó trông giống như ma thuật.

[cập nhật]

Blender đã viết:

Here is the file mà bạn đang theo đuổi. Nó dường như tái tạo lại máy phát bằng cách sử dụng một số thuật sĩ nội tâm. Tôi không chắc liệu nó có hỗ trợ 100% cú pháp Python hay không, nhưng điều này khá hay. - Máy xay sinh tố

Tôi đã suy nghĩ họ đã khám phá một số tính năng từ giao thức biểu hiện máy phát điện, nhưng nhìn tập tin này, và nhìn thấy những ast mô-đun liên quan đến ... Không, họ không được kiểm tra nguồn chương trình một cách nhanh chóng, là họ? Tâm-thổi ...

@BrenBarn: Nếu tôi cố gắng gọi cho các máy phát điện bên ngoài gọi select chức năng, kết quả là:

>>> x = (p for p in Person if p.age > 20) 
>>> x.next() 
Traceback (most recent call last): 
    File "<interactive input>", line 1, in <module> 
    File "<interactive input>", line 1, in <genexpr> 
    File "C:\Python27\lib\site-packages\pony\orm\core.py", line 1822, in next 
    % self.entity.__name__) 
    File "C:\Python27\lib\site-packages\pony\utils.py", line 92, in throw 
    raise exc 
TypeError: Use select(...) function or Person.select(...) method for iteration 
>>> 

Có vẻ như họ đang làm câu thần chú phức tạp hơn như kiểm tra việc select chức năng gọi và xử lý cây ngữ pháp cú pháp trừu tượng Python khi đang di chuyển.

Tôi vẫn muốn thấy một người nào đó giải thích nó, nguồn là cách vượt quá trình độ phù thủy của tôi.

+0

Có lẽ đối tượng 'p' là đối tượng của loại được Pony thực hiện, xem xét phương thức/thuộc tính nào đang được truy cập trên nó (ví dụ:' name', 'startswith') và chuyển đổi chúng thành SQL. – BrenBarn

+2

[Ở đây] (https://github.com/ponyorm/pony/blob/orm/pony/orm/decompiling.py#L52) là tệp mà bạn đang theo dõi. Nó dường như tái tạo lại máy phát bằng cách sử dụng một số thuật sĩ nội tâm. Tôi không chắc liệu nó có hỗ trợ 100% cú pháp Python hay không, nhưng điều này khá hay. – Blender

+1

@Blender: Tôi đã nhìn thấy loại lừa này trong LISP - kéo diễn viên đóng thế này bằng Python chỉ đơn giản là bị bệnh! –

Trả lời

183

Tác giả ORM Pony ở đây.

Pony dịch phát Python vào truy vấn SQL trong ba bước:

  1. Decompiling của bytecode máy phát điện và xây dựng lại máy phát điện AST (cây cú pháp trừu tượng)
  2. dịch của Python AST vào "SQL trừu tượng" - vũ trụ biểu diễn dựa trên danh sách truy vấn SQL
  3. Chuyển đổi biểu diễn SQL trừu tượng thành cụ thể phương ngữ SQL phụ thuộc vào cơ sở dữ liệu

Phần phức tạp nhất là bước thứ hai, trong đó Pony phải hiểu "ý nghĩa" của các biểu thức Python. Dường như bạn là hầu hết các quan tâm đến bước đầu tiên, vì vậy hãy để tôi giải thích cách giải mã hoạt động.

Hãy xem xét truy vấn này:

>>> from pony.orm.examples.estore import * 
>>> select(c for c in Customer if c.country == 'USA').show() 

nào sẽ được dịch sang SQL sau đây:

SELECT "c"."id", "c"."email", "c"."password", "c"."name", "c"."country", "c"."address" 
FROM "Customer" "c" 
WHERE "c"."country" = 'USA' 

Và dưới đây là kết quả của truy vấn này sẽ được in ra:

id|email    |password|name   |country|address 
--+-------------------+--------+--------------+-------+--------- 
1 |[email protected] |***  |John Smith |USA |address 1 
2 |[email protected]|***  |Matthew Reed |USA |address 2 
4 |[email protected]|***  |Rebecca Lawson|USA |address 4 

Chức năng select() chấp nhận trình tạo python làm đối số và t hen phân tích bytecode của nó. Chúng ta có thể nhận được hướng dẫn bytecode của máy phát điện này sử dụng python chuẩn dis mô-đun:

>>> gen = (c for c in Customer if c.country == 'USA') 
>>> import dis 
>>> dis.dis(gen.gi_frame.f_code) 
    1   0 LOAD_FAST    0 (.0) 
     >> 3 FOR_ITER    26 (to 32) 
       6 STORE_FAST    1 (c) 
       9 LOAD_FAST    1 (c) 
      12 LOAD_ATTR    0 (country) 
      15 LOAD_CONST    0 ('USA') 
      18 COMPARE_OP    2 (==) 
      21 POP_JUMP_IF_FALSE  3 
      24 LOAD_FAST    1 (c) 
      27 YIELD_VALUE   
      28 POP_TOP    
      29 JUMP_ABSOLUTE   3 
     >> 32 LOAD_CONST    1 (None) 
      35 RETURN_VALUE 

Pony ORM có chức năng decompile() trong mô-đun pony.orm.decompiling có thể khôi phục lại một AST từ bytecode:

>>> from pony.orm.decompiling import decompile 
>>> ast, external_names = decompile(gen) 

Ở đây, chúng ta có thể thấy biểu diễn văn bản của các nút AST:

>>> ast 
GenExpr(GenExprInner(Name('c'), [GenExprFor(AssName('c', 'OP_ASSIGN'), Name('.0'), 
[GenExprIf(Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]))])])) 

Bây giờ hãy xem cách hoạt động của hàm decompile().

Chức năng decompile() tạo đối tượng Decompiler, thực hiện mẫu Khách truy cập. Ví dụ về trình dịch ngược được hướng dẫn bytecode từng cái một. Đối với mỗi lệnh, đối tượng decompiler gọi phương thức riêng của nó. Tên của phương thức này bằng với tên của lệnh bytecode hiện tại.

Khi Python tính toán một biểu thức, nó sử dụng ngăn xếp, lưu trữ kết quả trung gian của phép tính. Đối tượng decompiler cũng có ngăn xếp riêng của nó, nhưng ngăn xếp này lưu trữ không phải là kết quả của phép tính biểu thức, nhưng nút AST cho biểu thức.

Khi phương pháp biên dịch ngược cho lệnh bytecode tiếp theo được gọi, cần có các nút AST từ ngăn xếp, kết hợp chúng vào nút AST mới, sau đó đặt nút này lên đầu ngăn xếp.

Ví dụ: hãy xem cách biểu diễn con số c.country == 'USA'. Các bytecode tương ứng đoạn là:

   9 LOAD_FAST    1 (c) 
      12 LOAD_ATTR    0 (country) 
      15 LOAD_CONST    0 ('USA') 
      18 COMPARE_OP    2 (==) 

Vì vậy, đối tượng decompiler nào sau đây:

  1. cuộc gọi decompiler.LOAD_FAST('c'). Phương pháp này đặt nút Name('c') trên đầu ngăn xếp bộ giải mã.
  2. Gọi decompiler.LOAD_ATTR('country'). Phương pháp này có nút Name('c') từ ngăn xếp, tạo nút Geattr(Name('c'), 'country') và đặt nó lên đầu ngăn xếp.
  3. Gọi decompiler.LOAD_CONST('USA'). Phương pháp này đặt nút Const('USA') lên trên chồng.
  4. Gọi decompiler.COMPARE_OP('=='). Phương pháp này có hai nút (Getattr và Const) từ ngăn xếp, và sau đó đặt Compare(Getattr(Name('c'), 'country'), [('==', Const('USA'))]) trên đầu ngăn xếp.

Sau khi tất cả hướng dẫn bytecode được xử lý, ngăn chứa bộ giải mã có chứa một nút AST tương ứng với biểu thức toàn bộ trình tạo.

Kể từ Pony ORM cần phải biên soạn lại phát và lambdas chỉ, đây không phải là phức tạp, bởi vì dòng hướng dẫn cho một máy phát điện là tương đối đơn giản - nó chỉ là một loạt các vòng lặp lồng nhau.

Hiện nay Pony ORM bao gồm các hướng dẫn toàn bộ máy phát điện thiết lập ngoại trừ hai điều:

  1. Inline nếu biểu thức: a if b else c
  2. so sánh Compound: a < b < c

Nếu Pony gặp biểu hiện như vậy nó làm tăng NotImplementedError ngoại lệ. Nhưng ngay cả trong trường hợp này bạn có thể làm cho nó hoạt động bằng cách chuyển biểu thức trình tạo dưới dạng chuỗi. Khi bạn vượt qua một máy phát điện như một chuỗi Pony không sử dụng mô-đun bộ giải mã. Thay vào đó, nó được AST sử dụng hàm Python chuẩn compiler.parse.

Hy vọng điều này sẽ trả lời câu hỏi của bạn.

+1

Giải thích tuyệt vời và một ví dụ thú vị về "DSL nội bộ" (ngôn ngữ cụ thể theo miền) được triển khai bằng Python. Xem http://stackoverflow.com/questions/tagged/dsl+python để biết thêm - gắn thẻ câu hỏi này là [dsl]. – RichVel

+0

Tuyệt vời! Cám ơn vì đã chia sẻ. – coleifer

+1

Trình diễn như thế nào? – ruipacheco

Các vấn đề liên quan