11from collections import defaultdict
2+ from itertools import islice
23import random
34
45import psycopg2
1718 'sale_order' ,
1819]
1920
21+ rng = random .Random (1 )
22+
2023def drop_tables (cr ):
2124 for t in tables :
2225 cr .execute (f"DROP TABLE IF EXISTS { t } " )
@@ -30,17 +33,17 @@ def create_tables(cr):
3033 cr .execute ("""
3134 CREATE TABLE sale_order (
3235 id SERIAL PRIMARY KEY,
33- state VARCHAR NOT NULL, -- Can be 'draft', 'to confirm', 'confirmed', 'cancel' or 'done'
3436 partner VARCHAR NOT NULL
37+ uniform_100 INT,
38+ float_uniform_100 FLOAT,
3539 )
3640 """ )
3741 cr .execute ("""
3842 CREATE TABLE sale_order_line (
3943 id SERIAL PRIMARY KEY,
4044 sale_id INT NOT NULL,
41- delivery_date DATE,
42- qty FLOAT,
43- amount FLOAT,
45+ uniform_100 INT,
46+ float_uniform_100 FLOAT,
4447 FOREIGN KEY (sale_id) REFERENCES sale_order (id)
4548 )
4649 """ )
@@ -63,19 +66,11 @@ def create_tables(cr):
6366
6467data_cases = {
6568 'small' : {
66- 'nb_sale_order' : 50000 ,
67- 'sale_order_state_proportion' : {
68- 'draft' : 0.2 ,
69- 'to confirm' : 0.2 ,
70- 'confirmed' : 0.2 ,
71- 'cancel' : 0.2 ,
72- 'done' : 0.2 ,
73- },
74-
75- 'nb_sale_order_line' : 500000 ,
76- 'qty_sale_order_line' : [0 , 100000 ],
69+ 'nb_sale_order' : 50_000 ,
70+ 'nb_sale_order_line' : 500_000 ,
7771
7872 'sale_order_tags' : ['urgent' , 'safe' , 'boring client' , 'water' , 'other' ],
73+ 'sale_order_tags_weight' : [0.1 , 0.1 , 0.1 , 0.1 , 0.1 ],
7974 'nb_sale_order_tag_line_rel' : 10000 ,
8075 },
8176}
@@ -85,33 +80,53 @@ def create_indexes(cr):
8580
8681 """ )
8782
88- def fill_database (data_case ):
83+ def get_values_generator_so (** kwargs ):
84+ states = list (kwargs ['sale_order_state_proportion' ].keys ())
85+ states_w = list (kwargs ['sale_order_state_proportion' ].values ())
86+ for i in range (kwargs ['nb_sale_order' ]):
87+ yield (
88+ rng .choices (states , states_w , k = 1 )[0 ],
89+ f'partern_{ i } ' ,
90+ )
8991
90- with psycopg2 .connect (CONNECTION_PARAMS ) as conn , conn .cursor () as cr :
91- states = []
92- for state , proportion in data_case ['sale_order_state_proportion' ].items ():
93- states += [state ] * int (proportion * data_case ['nb_sale_order' ])
94- values_sale_order = tuple (
95- (state , f'partern_{ i } ' )
96- for i , state in zip (range (data_case ['nb_sale_order' ]), random .sample (states , k = len (states )))
92+ def get_values_generator_sol (** kwargs ):
93+ for i in range (kwargs ['nb_sale_order_line' ]):
94+ yield (
95+ rng .randint (1 , kwargs ['nb_sale_order' ]),
96+ rng .randint (0 , 100 ),
97+ rng .random () * 100 ,
9798 )
9899
99- cr .execute (f"""
100- INSERT INTO sale_order (state, partner) VALUES { ',' .join (['%s' ] * len (values_sale_order ))}
101- """ , values_sale_order )
100+ def split_every (n , iterator , piece_maker = tuple ):
101+ """Splits an iterable into length-n pieces. The last piece will be shorter
102+ if ``n`` does not evenly divide the iterable length.
103+
104+ :param int n: maximum size of each generated chunk
105+ :param Iterable iterable: iterable to chunk into pieces
106+ :param piece_maker: callable taking an iterable and collecting each
107+ chunk from its slice, *must consume the entire slice*.
108+ """
109+ piece = piece_maker (islice (iterator , n ))
110+ while piece :
111+ yield piece
112+ piece = piece_maker (islice (iterator , n ))
113+
114+ def fill_database (data_case ):
115+ BATCH_SIZE_CREATION = 100_000
102116
103117 with psycopg2 .connect (CONNECTION_PARAMS ) as conn , conn .cursor () as cr :
104- values_sale_order_line = tuple (
105- (
106- random .randint (1 , data_case ['nb_sale_order' ]),
107- random .randint (data_case ['qty_sale_order_line' ][0 ], data_case ['qty_sale_order_line' ][1 ]),
108- )
109- for i in range (data_case ['nb_sale_order_line' ])
110- )
118+ for i , pieces in enumerate (split_every (BATCH_SIZE_CREATION , get_values_generator_so (** data_case ))):
119+ print (f"Insert { i * BATCH_SIZE_CREATION } /{ data_case ['nb_sale_order' ]} SO" )
120+ cr .execute (f"""
121+ INSERT INTO sale_order (state, partner, uniform_100, float_uniform_100) VALUES { ',' .join (['%s' ] * len (pieces ))}
122+ """ , pieces )
111123
112- cr .execute (f"""
113- INSERT INTO sale_order_line (sale_id, qty) VALUES { ',' .join (['%s' ] * len (values_sale_order_line ))}
114- """ , values_sale_order_line )
124+ with psycopg2 .connect (CONNECTION_PARAMS ) as conn , conn .cursor () as cr :
125+ for pieces in split_every (BATCH_SIZE_CREATION , get_values_generator_sol (** data_case )):
126+ print (f"Insert { i * BATCH_SIZE_CREATION } /{ data_case ['nb_sale_order_line' ]} SOL " )
127+ cr .execute (f"""
128+ INSERT INTO sale_order_line (sale_id, qty, price) VALUES { ',' .join (['%s' ] * len (pieces ))}
129+ """ , pieces )
115130
116131
117132SQL_alternative = {
@@ -139,7 +154,7 @@ def fill_database(data_case):
139154 ORDER BY "sale_order_line"."id"
140155 """ ,
141156
142- 'join' :
157+ 'left join' :
143158 """
144159 SELECT "sale_order_line"."id"
145160 FROM "sale_order_line"
@@ -197,12 +212,12 @@ def fill_database(data_case):
197212 ORDER BY "sale_order_line"."id"
198213 """ ,
199214
200- 'join' :
215+ 'left join' :
201216 """
202217 SELECT "sale_order_line"."id"
203218 FROM "sale_order_line"
204219 LEFT JOIN "sale_order" ON "sale_order"."id" = "sale_order_line"."sale_id"
205- WHERE "sale_order"."state" = 'draft' OR "sale_order_line"."qty" > 50000
220+ WHERE ( "sale_order"."state" = 'draft') OR "sale_order_line"."qty" > 50000
206221 ORDER BY "sale_order_line"."id"
207222 """ ,
208223
@@ -216,18 +231,6 @@ def fill_database(data_case):
216231 WHERE "sale_order_line"."sale_id" IN (SELECT "id" FROM "subquery") OR "sale_order_line"."qty" > 50000
217232 ORDER BY "sale_order_line"."id"
218233 """ ,
219-
220- # 'CTE, join':
221- # """
222- # WITH subquery AS (
223- # SELECT * FROM "sale_order" WHERE "sale_order"."state" = 'draft'
224- # )
225- # SELECT "sale_order_line"."id"
226- # FROM "sale_order_line"
227- # LEFT JOIN "subquery" ON "subquery"."id" = "sale_order_line"."sale_id"
228- # WHERE "subquery"."id" IS NOT NULL OR "sale_order_line"."qty" > 50000
229- # ORDER BY "sale_order_line"."id"
230- # """,
231234 },
232235 "sale.order.line - search - [order_id.state = 'draft' AND qty > 50000]" : {
233236 'in' : """
@@ -255,7 +258,7 @@ def fill_database(data_case):
255258 ORDER BY "sale_order_line"."id"
256259 """ ,
257260
258- 'join' :
261+ 'left join' :
259262 """
260263 SELECT "sale_order_line"."id"
261264 FROM "sale_order_line"
@@ -274,23 +277,10 @@ def fill_database(data_case):
274277 WHERE "sale_order_line"."sale_id" IN (SELECT "id" FROM "subquery") AND "sale_order_line"."qty" > 50000
275278 ORDER BY "sale_order_line"."id"
276279 """ ,
277-
278- # 'CTE, join':
279- # """
280- # WITH subquery AS (
281- # SELECT * FROM "sale_order" WHERE "sale_order"."state" = 'draft'
282- # )
283- # SELECT "sale_order_line"."id"
284- # FROM "sale_order_line"
285- # LEFT JOIN "subquery" ON "subquery"."id" = "sale_order_line"."sale_id"
286- # WHERE "subquery"."id" IS NOT NULL AND "sale_order_line"."qty" > 50000
287- # ORDER BY "sale_order_line"."id"
288- # """,
289280 },
290281}
291282
292283def one_data_case_test ():
293-
294284 result_explain = defaultdict (dict )
295285 for alternative , sql_requests in SQL_alternative .items ():
296286 with psycopg2 .connect (CONNECTION_PARAMS ) as conn , conn .cursor () as cr :
@@ -300,18 +290,18 @@ def one_data_case_test():
300290 current_res = tuple (cr .fetchall ())
301291 if res and current_res != res :
302292 print (f"{ res } != { current_res } " )
303- raise ValueError (f"{ case_str } doens't have the same result than the previous one for { alternative } " )
293+ raise ValueError (f"ERROR: { case_str } doens't have the same result than the previous one for { alternative } " )
304294 res = current_res
305295
306- for case_str , sql_request in sql_requests . items () :
307- with psycopg2 . connect ( CONNECTION_PARAMS ) as conn , conn . cursor () as cr :
296+ with psycopg2 . connect ( CONNECTION_PARAMS ) as conn , conn . cursor () as cr :
297+ for case_str , sql_request in sql_requests . items () :
308298 plan = psql_explain (cr , sql_request )
309299 result_explain [alternative ][case_str ] = plan
310300
311301 for alternative , case_plans in result_explain .items ():
312302 list_plan = list (case_plans .values ())
313303 if len (set (list_plan )) != 1 :
314- print (f"\n - > Some queries has a different plan for { alternative } ({ len (set (list_plan ))} ):" )
304+ print (f"\n = > Some queries has a different plan for { alternative } ({ len (set (list_plan ))} ):" )
315305 case_by_plan = defaultdict (list )
316306 for case , plan in case_plans .items ():
317307 case_by_plan [plan ].append (case )
@@ -320,7 +310,7 @@ def one_data_case_test():
320310 print (f"These cases { cases } has this plan:\n { plan } " )
321311 print (" ------------------------" )
322312 else :
323- print (f"\n - > { alternative } all plan is equals" )
313+ print (f"\n = > { alternative } all plan is equals" )
324314
325315
326316def main ():
@@ -352,8 +342,7 @@ def main():
352342# https://github.com/postgres/postgres/commit/41efb8340877e8ffd0023bb6b2ef22ffd1ca014d
353343
354344
355-
356-
345+ # TODO Convert this stuff in a Odoo module to test it with expression.py
357346
358347if __name__ == '__main__' :
359348 main ()
0 commit comments