|
1 | 1 | import random |
2 | 2 | import uuid |
3 | | -from typing import Optional, Tuple |
| 3 | +from typing import Any, Optional, Tuple |
4 | 4 |
|
5 | 5 | from infrahub.core import registry |
| 6 | +from infrahub.core.branch import Branch |
| 7 | +from infrahub.core.manager import NodeManager |
6 | 8 | from infrahub.core.node import Node |
7 | 9 | from tests.helpers.query_benchmark.data_generator import DataGenerator |
8 | 10 | from tests.helpers.query_benchmark.db_query_profiler import InfrahubDatabaseProfiler |
9 | 11 |
|
10 | 12 |
|
11 | 13 | class CarGenerator(DataGenerator): |
12 | 14 | async def load_data(self, nb_elements: int) -> None: |
13 | | - await self.load_cars(nb_elements) |
| 15 | + default_branch = await registry.get_branch(db=self.db) |
| 16 | + await self.load_cars(default_branch, nb_elements) |
14 | 17 |
|
15 | | - async def load_cars(self, nb_cars: int, persons: Optional[dict[str, Node]] = None) -> dict[str, Node]: |
16 | | - """ |
17 | | - Load cars and return a mapping car_name -> car_node. |
18 | | - If 'persons' is specified, each car created is linked to a person. |
19 | | - """ |
| 18 | + async def load_car_random_name(self, branch: Branch, nbr_seats: int, **kwargs: Any) -> Node: |
| 19 | + car_schema = registry.schema.get_node_schema(name="TestCar", branch=branch) |
20 | 20 |
|
21 | | - default_branch = await registry.get_branch(db=self.db) |
22 | | - car_schema = registry.schema.get_node_schema(name="TestCar", branch=default_branch) |
| 21 | + short_id = str(uuid.uuid4())[:8] |
| 22 | + car_name = f"car-{short_id}" |
| 23 | + car_node = await Node.init(db=self.db, schema=car_schema, branch=branch) |
| 24 | + await car_node.new(db=self.db, name=car_name, nbr_seats=nbr_seats, **kwargs) |
| 25 | + |
| 26 | + return await car_node.save(db=self.db) |
23 | 27 |
|
| 28 | + async def load_cars(self, branch: Branch, nb_cars: int, **kwargs: Any) -> dict[str, Node]: |
24 | 29 | cars = {} |
25 | 30 | for _ in range(nb_cars): |
26 | | - short_id = str(uuid.uuid4())[:8] |
27 | | - car_name = f"car-{short_id}" |
28 | | - car_node = await Node.init(db=self.db, schema=car_schema, branch=default_branch) |
29 | | - if persons is not None: |
30 | | - random_person = random.choice([persons[person_name] for person_name in persons]) |
31 | | - await car_node.new(db=self.db, name=car_name, nbr_seats=4, owner=random_person) |
32 | | - else: |
33 | | - await car_node.new(db=self.db, name=car_name, nbr_seats=4) |
| 31 | + car_node = await self.load_car_random_name(nbr_seats=4, branch=branch, **kwargs) |
| 32 | + cars[car_node.name.value] = car_node # type: ignore[attr-defined] |
34 | 33 |
|
35 | | - async with self.db.start_session(): |
36 | | - await car_node.save(db=self.db) |
| 34 | + return cars |
37 | 35 |
|
38 | | - cars[car_name] = car_node |
| 36 | + |
| 37 | +class EngineGenerator(DataGenerator): |
| 38 | + async def load_data(self, nb_elements: int) -> None: |
| 39 | + default_branch = await registry.get_branch(db=self.db) |
| 40 | + await self.load_engines(default_branch, nb_elements) |
| 41 | + |
| 42 | + async def load_engines(self, branch: Branch, nb_cars: int, **kwargs: Any) -> dict[str, Node]: |
| 43 | + engines = {} |
| 44 | + for _ in range(nb_cars): |
| 45 | + engine_node = await self.load_engine_random_name(branch=branch, **kwargs) |
| 46 | + engines[engine_node.name.value] = engine_node # type: ignore[attr-defined] |
| 47 | + |
| 48 | + return engines |
| 49 | + |
| 50 | + async def load_engine_random_name(self, branch: Branch, **kwargs: Any) -> Node: |
| 51 | + engine_schema = registry.schema.get_node_schema(name="TestEngine", branch=branch) |
| 52 | + |
| 53 | + short_id = str(uuid.uuid4())[:8] |
| 54 | + engine_name = f"engine-{short_id}" |
| 55 | + engine_node = await Node.init(db=self.db, schema=engine_schema, branch=branch) |
| 56 | + await engine_node.new(db=self.db, name=engine_name, **kwargs) |
| 57 | + |
| 58 | + return await engine_node.save(db=self.db) |
| 59 | + |
| 60 | + |
| 61 | +class CarWithDiffInSecondBranchGenerator(CarGenerator): |
| 62 | + persons: Optional[dict[str, Node]] # mapping of existing cars names -> node |
| 63 | + nb_persons: int |
| 64 | + diff_ratio: float # 0.1 means 10% of added nodes, 10% of deleted nodes, 10% of modified nodes |
| 65 | + main_branch: Branch |
| 66 | + diff_branch: Branch |
| 67 | + |
| 68 | + def __init__( |
| 69 | + self, db: InfrahubDatabaseProfiler, nb_persons: int, diff_ratio: float, main_branch: Branch, diff_branch: Branch |
| 70 | + ) -> None: |
| 71 | + super().__init__(db) |
| 72 | + self.persons = None |
| 73 | + self.nb_persons = nb_persons |
| 74 | + self.diff_ratio = diff_ratio |
| 75 | + self.main_branch = main_branch |
| 76 | + self.diff_branch = diff_branch |
| 77 | + |
| 78 | + async def init(self) -> None: |
| 79 | + """Load persons, that will be later connected to generated cars.""" |
| 80 | + self.persons = await PersonGenerator(self.db).load_persons(nb_persons=self.nb_persons) |
| 81 | + |
| 82 | + async def load_cars_with_multiple_rels(self, branch: Branch, nb_cars: int) -> dict[str, Node]: |
| 83 | + assert self.persons is not None |
| 84 | + engine_generator = EngineGenerator(db=self.db) |
| 85 | + |
| 86 | + cars = {} |
| 87 | + for _ in range(nb_cars): |
| 88 | + owner = random.choice([self.persons[person_name] for person_name in self.persons]) |
| 89 | + drivers = random.choices([self.persons[person_name] for person_name in self.persons], k=nb_cars) |
| 90 | + engine = await engine_generator.load_engine_random_name(branch=branch) |
| 91 | + car = await self.load_car_random_name( |
| 92 | + branch=branch, nbr_seats=4, owner=owner, drivers=drivers, engine=engine |
| 93 | + ) |
| 94 | + cars[car.name.value] = car # type: ignore[attr-defined] |
39 | 95 |
|
40 | 96 | return cars |
41 | 97 |
|
| 98 | + async def load_data(self, nb_elements: int) -> None: |
| 99 | + """ |
| 100 | + Load cars in main branch, rebase diff branch on main branch, then load changes |
| 101 | + within diff branch according to a given ratio. |
| 102 | + Differences are: |
| 103 | + - Updates some cars attributes as well as 1:1, 1:N, N:N relationships. |
| 104 | + - Add new cars. |
| 105 | + Note that we do not delete cars within diff branch as it seems to take too long. |
| 106 | + """ |
| 107 | + |
| 108 | + assert self.persons is not None, "'init' method should be called before 'load_data'" |
| 109 | + |
| 110 | + if nb_elements == 0: |
| 111 | + return |
| 112 | + |
| 113 | + # Load cars in main branch |
| 114 | + new_cars = await self.load_cars_with_multiple_rels(nb_cars=nb_elements, branch=self.main_branch) |
| 115 | + |
| 116 | + # Integrate these new cars in diff branch |
| 117 | + await self.diff_branch.rebase(self.db) |
| 118 | + |
| 119 | + # Retrieve car nodes from diff branch, including the ones not present in main branch |
| 120 | + # that were created by prior calls to `load_data` |
| 121 | + car_schema = registry.schema.get_node_schema(name="TestCar", branch=self.diff_branch) |
| 122 | + car_nodes = await NodeManager.query(db=self.db, schema=car_schema, branch=self.diff_branch) |
| 123 | + new_car_nodes = [car_node for car_node in car_nodes if car_node.name.value in new_cars] |
| 124 | + |
| 125 | + nb_diff = max(int(nb_elements * self.diff_ratio), 1) |
| 126 | + |
| 127 | + # Update cars in diff branch |
| 128 | + car_nodes_updatable = new_car_nodes |
| 129 | + car_nodes_to_update = random.choices(car_nodes_updatable, k=nb_diff) |
| 130 | + for i, car_node in enumerate(car_nodes_to_update): |
| 131 | + car_node.name.value = f"updated-car-{str(uuid.uuid4())[:8]}" |
| 132 | + |
| 133 | + # Permute engines among car nodes to update, so it keeps one-to-one relationship between cars-engines |
| 134 | + new_engine = car_nodes_to_update[(i + 1) % len(car_nodes_to_update)].engine |
| 135 | + car_node.engine.update(db=self.db, data=new_engine) |
| 136 | + |
| 137 | + # Update one-to-many relationship |
| 138 | + new_owner = random.choice([self.persons[person_name] for person_name in self.persons]) |
| 139 | + car_node.owner.update(db=self.db, data=new_owner) |
| 140 | + |
| 141 | + # Update many-to-many relationship |
| 142 | + new_drivers = random.choices([self.persons[person_name] for person_name in self.persons]) |
| 143 | + car_node.drivers.update(db=self.db, data=new_drivers) |
| 144 | + |
| 145 | + await car_node.save(db=self.db) |
| 146 | + |
| 147 | + # Add a few cars in diff branch |
| 148 | + added_cars = await self.load_cars_with_multiple_rels(nb_cars=nb_diff, branch=self.diff_branch) |
| 149 | + |
| 150 | + assert len(added_cars) == len(car_nodes_to_update) == nb_diff |
| 151 | + |
42 | 152 |
|
43 | 153 | class PersonGenerator(DataGenerator): |
44 | 154 | async def load_data(self, nb_elements: int) -> None: |
@@ -77,42 +187,6 @@ async def load_persons( |
77 | 187 | return persons_names_to_nodes |
78 | 188 |
|
79 | 189 |
|
80 | | -class PersonFromExistingCarGenerator(PersonGenerator): |
81 | | - cars: Optional[dict[str, Node]] # mapping of existing cars names -> node |
82 | | - nb_cars: int |
83 | | - |
84 | | - def __init__(self, db: InfrahubDatabaseProfiler, nb_cars: int) -> None: |
85 | | - super().__init__(db) |
86 | | - self.nb_cars = nb_cars |
87 | | - self.cars = None |
88 | | - |
89 | | - async def init(self) -> None: |
90 | | - """Load cars, that will be later connected to generated persons.""" |
91 | | - self.cars = await CarGenerator(self.db).load_cars(nb_cars=self.nb_cars) |
92 | | - |
93 | | - async def load_data(self, nb_elements: int) -> None: |
94 | | - assert self.cars is not None, "'init' method should be called before 'load_data'" |
95 | | - await self.load_persons(nb_persons=nb_elements, cars=self.cars) |
96 | | - |
97 | | - |
98 | | -class CarFromExistingPersonGenerator(CarGenerator): |
99 | | - persons: Optional[dict[str, Node]] # mapping of existing cars names -> node |
100 | | - nb_persons: int |
101 | | - |
102 | | - def __init__(self, db: InfrahubDatabaseProfiler, nb_persons: int) -> None: |
103 | | - super().__init__(db) |
104 | | - self.nb_persons = nb_persons |
105 | | - self.persons = None |
106 | | - |
107 | | - async def init(self) -> None: |
108 | | - """Load persons, that will be later connected to generated cars.""" |
109 | | - self.persons = await PersonGenerator(self.db).load_persons(nb_persons=self.nb_persons) |
110 | | - |
111 | | - async def load_data(self, nb_elements: int) -> None: |
112 | | - assert self.persons is not None, "'init' method should be called before 'load_data'" |
113 | | - await self.load_cars(nb_cars=nb_elements, persons=self.persons) |
114 | | - |
115 | | - |
116 | 190 | class CarGeneratorWithOwnerHavingUniqueCar(CarGenerator): |
117 | 191 | persons: list[Tuple[str, Node]] # mapping of existing cars names -> node |
118 | 192 | nb_persons: int |
@@ -154,33 +228,3 @@ async def load_data(self, nb_elements: int) -> None: |
154 | 228 | await car_node.save(db=self.db) |
155 | 229 |
|
156 | 230 | self.nb_cars_loaded += nb_elements |
157 | | - |
158 | | - |
159 | | -class CarAndPersonIsolatedGenerator(DataGenerator): |
160 | | - def __init__(self, db: InfrahubDatabaseProfiler) -> None: |
161 | | - super().__init__(db) |
162 | | - self.car_generator: CarGenerator = CarGenerator(db) |
163 | | - self.person_generator: PersonGenerator = PersonGenerator(db) |
164 | | - |
165 | | - async def load_data(self, nb_elements: int) -> None: |
166 | | - """ |
167 | | - Load not connected cars and persons. Note that 'nb_elements' cars plus 'nb_elements' persons are loaded. |
168 | | - """ |
169 | | - |
170 | | - await self.car_generator.load_cars(nb_cars=nb_elements) |
171 | | - await self.person_generator.load_persons(nb_persons=nb_elements) |
172 | | - |
173 | | - |
174 | | -class CarAndPersonConnectedGenerator(DataGenerator): |
175 | | - def __init__(self, db: InfrahubDatabaseProfiler) -> None: |
176 | | - super().__init__(db) |
177 | | - self.car_generator: CarGenerator = CarGenerator(db) |
178 | | - self.person_generator: PersonGenerator = PersonGenerator(db) |
179 | | - |
180 | | - async def load_data(self, nb_elements: int) -> None: |
181 | | - """ |
182 | | - Load connected cars and persons. Note that 'nb_elements' cars plus 'nb_elements' persons are loaded. |
183 | | - """ |
184 | | - |
185 | | - persons = await self.person_generator.load_persons(nb_persons=nb_elements) |
186 | | - await self.car_generator.load_cars(nb_cars=nb_elements, persons=persons) |
|
0 commit comments