nsj-rest-lib2 0.0.29__py3-none-any.whl → 0.0.31__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nsj_rest_lib2/compiler/compiler.py +16 -7
- nsj_rest_lib2/compiler/compiler_structures.py +1 -0
- nsj_rest_lib2/compiler/dto_compiler.py +9 -3
- nsj_rest_lib2/compiler/edl_model/primitives.py +13 -2
- nsj_rest_lib2/compiler/entity_compiler.py +4 -0
- nsj_rest_lib2/compiler/migration_compiler.py +674 -0
- nsj_rest_lib2/compiler/migration_compiler_alter_table.py +201 -0
- nsj_rest_lib2/compiler/migration_compiler_create_table.py +75 -0
- nsj_rest_lib2/compiler/migration_compiler_util.py +144 -0
- nsj_rest_lib2/compiler/property_compiler.py +64 -66
- nsj_rest_lib2/compiler/util/relation_ref.py +118 -0
- {nsj_rest_lib2-0.0.29.dist-info → nsj_rest_lib2-0.0.31.dist-info}/METADATA +1 -1
- {nsj_rest_lib2-0.0.29.dist-info → nsj_rest_lib2-0.0.31.dist-info}/RECORD +15 -11
- nsj_rest_lib2/compiler/ai_compiler.py +0 -285
- {nsj_rest_lib2-0.0.29.dist-info → nsj_rest_lib2-0.0.31.dist-info}/WHEEL +0 -0
- {nsj_rest_lib2-0.0.29.dist-info → nsj_rest_lib2-0.0.31.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from nsj_rest_lib2.compiler.compiler import EDLCompiler
|
|
5
|
+
from nsj_rest_lib2.compiler.compiler_structures import PropertiesCompilerStructure
|
|
6
|
+
from nsj_rest_lib2.compiler.edl_model.entity_model import EntityModel
|
|
7
|
+
from nsj_rest_lib2.compiler.edl_model.entity_model_base import EntityModelBase
|
|
8
|
+
from nsj_rest_lib2.compiler.edl_model.primitives import PrimitiveTypes, PropertyType
|
|
9
|
+
from nsj_rest_lib2.compiler.migration_compiler_util import MigrationCompilerUtil
|
|
10
|
+
from nsj_rest_lib2.compiler.migration_compiler_create_table import (
|
|
11
|
+
MigrationCompilerCreateTable,
|
|
12
|
+
)
|
|
13
|
+
from nsj_rest_lib2.compiler.migration_compiler_alter_table import (
|
|
14
|
+
MigrationCompilerAlterTable,
|
|
15
|
+
)
|
|
16
|
+
from nsj_rest_lib2.compiler.migration_compiler_create_table import (
|
|
17
|
+
MigrationCompilerCreateTable,
|
|
18
|
+
)
|
|
19
|
+
from nsj_rest_lib2.compiler.migration_compiler_alter_table import (
|
|
20
|
+
MigrationCompilerAlterTable,
|
|
21
|
+
)
|
|
22
|
+
from nsj_rest_lib2.compiler.util.relation_ref import RelationRef, RelationRefParser
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class DiffAction:
|
|
26
|
+
ADD_COLUMN = "add_column"
|
|
27
|
+
ALTER_COLUMN = "alter_column"
|
|
28
|
+
DROP_COLUMN = "drop_column"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# TODO Tratar criação e remoção de FKs
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class DiffProperty:
|
|
36
|
+
action: str
|
|
37
|
+
property_name: str
|
|
38
|
+
entity_name_old: Optional[str] = None
|
|
39
|
+
entity_name: Optional[str] = None
|
|
40
|
+
old_datatype: Optional[PropertyType] = None
|
|
41
|
+
new_datatype: Optional[PropertyType] = None
|
|
42
|
+
old_required: Optional[bool] = None
|
|
43
|
+
new_required: Optional[bool] = None
|
|
44
|
+
new_description: Optional[str] = None
|
|
45
|
+
new_default: Any = None
|
|
46
|
+
old_pk: Optional[bool] = None
|
|
47
|
+
new_pk: Optional[bool] = None
|
|
48
|
+
new_max_length: Optional[int] = None
|
|
49
|
+
new_check: list[Any] = field(default_factory=list)
|
|
50
|
+
renamed: bool = False
|
|
51
|
+
datatype_changed: bool = False
|
|
52
|
+
required_changed: bool = False
|
|
53
|
+
default_changed: bool = False
|
|
54
|
+
pk_changed: bool = False
|
|
55
|
+
max_length_changed: bool = False
|
|
56
|
+
check_changed: bool = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MigrationCompiler:
|
|
60
|
+
def __init__(self, compiler: EDLCompiler):
|
|
61
|
+
self._compiler = compiler
|
|
62
|
+
self._create_table_compiler = MigrationCompilerCreateTable()
|
|
63
|
+
self._alter_table_compiler = MigrationCompilerAlterTable()
|
|
64
|
+
|
|
65
|
+
def compile(
|
|
66
|
+
self,
|
|
67
|
+
entity_model: EntityModelBase,
|
|
68
|
+
entity_models: dict[str, EntityModel],
|
|
69
|
+
entity_model_old: EntityModelBase | None = None,
|
|
70
|
+
) -> str:
|
|
71
|
+
# Resolvendo a estrutura de propriedades da entidade nova
|
|
72
|
+
properties_structure = PropertiesCompilerStructure()
|
|
73
|
+
self._compiler._make_properties_structures(
|
|
74
|
+
properties_structure, entity_model, entity_models
|
|
75
|
+
)
|
|
76
|
+
self._filter_properties_for_migration(properties_structure)
|
|
77
|
+
|
|
78
|
+
# Resolvendo a estrutura de propriedades da versão antiga da mesma entidade (para detecção de rename)
|
|
79
|
+
properties_structure_old = None
|
|
80
|
+
if entity_model_old:
|
|
81
|
+
properties_structure_old = PropertiesCompilerStructure()
|
|
82
|
+
self._compiler._make_properties_structures(
|
|
83
|
+
properties_structure_old, entity_model_old, entity_models
|
|
84
|
+
)
|
|
85
|
+
self._filter_properties_for_migration(properties_structure_old)
|
|
86
|
+
|
|
87
|
+
table_name = entity_model.repository.map
|
|
88
|
+
column_specs, fk_specs = self._build_column_and_fk_specs(
|
|
89
|
+
entity_model, properties_structure, entity_models
|
|
90
|
+
)
|
|
91
|
+
rename_operations = (
|
|
92
|
+
self._detect_renamed_columns(properties_structure, properties_structure_old)
|
|
93
|
+
if properties_structure_old
|
|
94
|
+
else []
|
|
95
|
+
)
|
|
96
|
+
pk_columns = [spec["column_name"] for spec in column_specs if spec["is_pk"]]
|
|
97
|
+
|
|
98
|
+
create_table_sql = self._create_table_compiler.compile(
|
|
99
|
+
table_name, column_specs, pk_columns, fk_specs
|
|
100
|
+
)
|
|
101
|
+
alter_block_sql = self._alter_table_compiler.compile(
|
|
102
|
+
table_name, column_specs, pk_columns, rename_operations, fk_specs
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
block_lines = [
|
|
106
|
+
"DO $MIGRATION$",
|
|
107
|
+
"BEGIN",
|
|
108
|
+
f" IF exists_table('{table_name}') THEN",
|
|
109
|
+
]
|
|
110
|
+
block_lines.extend(alter_block_sql)
|
|
111
|
+
block_lines.append(" ELSE")
|
|
112
|
+
block_lines.extend(MigrationCompilerUtil.indent_sql(create_table_sql, 8))
|
|
113
|
+
block_lines.append(" END IF;")
|
|
114
|
+
block_lines.append("END$MIGRATION$;")
|
|
115
|
+
|
|
116
|
+
return "\n".join(block_lines)
|
|
117
|
+
|
|
118
|
+
def _filter_properties_for_migration(
|
|
119
|
+
self, properties_structure: PropertiesCompilerStructure
|
|
120
|
+
) -> None:
|
|
121
|
+
"""
|
|
122
|
+
Remove propriedades herdadas via trait_from ou extends, pois as migrações
|
|
123
|
+
dessas entidades devem ser conduzidas nos EDLs originais.
|
|
124
|
+
"""
|
|
125
|
+
allowed_properties: dict[str, Any] = {}
|
|
126
|
+
origins = getattr(properties_structure, "property_origins", {}) or {}
|
|
127
|
+
for name, prop in properties_structure.properties.items():
|
|
128
|
+
origin = origins.get(name, "self")
|
|
129
|
+
if origin in {"trait", "extends"}:
|
|
130
|
+
continue
|
|
131
|
+
allowed_properties[name] = prop
|
|
132
|
+
|
|
133
|
+
# Atualiza propriedades e coleções dependentes
|
|
134
|
+
properties_structure.properties = allowed_properties
|
|
135
|
+
allowed_names = set(allowed_properties.keys())
|
|
136
|
+
properties_structure.required = [
|
|
137
|
+
r for r in properties_structure.required if r in allowed_names
|
|
138
|
+
]
|
|
139
|
+
properties_structure.partition_data = [
|
|
140
|
+
p for p in properties_structure.partition_data if p in allowed_names
|
|
141
|
+
]
|
|
142
|
+
properties_structure.search_properties = [
|
|
143
|
+
s for s in properties_structure.search_properties if s in allowed_names
|
|
144
|
+
]
|
|
145
|
+
properties_structure.metric_label = [
|
|
146
|
+
m for m in properties_structure.metric_label if m in allowed_names
|
|
147
|
+
]
|
|
148
|
+
properties_structure.entity_properties = {
|
|
149
|
+
k: v
|
|
150
|
+
for k, v in properties_structure.entity_properties.items()
|
|
151
|
+
if k in allowed_names
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# FIXME Comentado por hora (tentando confiar mais no BD do que nos EDLs)
|
|
155
|
+
# def _compare_properties_structures(
|
|
156
|
+
# self,
|
|
157
|
+
# properties_structure: PropertiesCompilerStructure,
|
|
158
|
+
# properties_structure_old: PropertiesCompilerStructure,
|
|
159
|
+
# ) -> tuple[list[DiffProperty], list[DiffProperty], list[DiffProperty]]:
|
|
160
|
+
# alter_properties: list[DiffProperty] = []
|
|
161
|
+
# add_properties: list[DiffProperty] = []
|
|
162
|
+
# drop_properties: list[DiffProperty] = []
|
|
163
|
+
|
|
164
|
+
# mapa_equivalencias_old_new: dict[str, str] = {}
|
|
165
|
+
|
|
166
|
+
# # Identificando as propriedades a serem alteradas
|
|
167
|
+
# for property_name_new in properties_structure.properties:
|
|
168
|
+
# # Guardando as propriedades da nova coluna
|
|
169
|
+
# property_new = properties_structure.properties[property_name_new]
|
|
170
|
+
# entity_mapping_new = properties_structure_old.entity_properties.get(
|
|
171
|
+
# property_name_new
|
|
172
|
+
# )
|
|
173
|
+
# entity_name_new = property_name_new
|
|
174
|
+
# if entity_mapping_new and entity_mapping_new.column:
|
|
175
|
+
# entity_name_new = entity_mapping_new.column
|
|
176
|
+
|
|
177
|
+
# # Tentando encontrar uma propriedade equivalente na versão antiga
|
|
178
|
+
# # É equivalente se tiver o mesmo nome de propriedade, ou se tiver o mesmo nome de coluna
|
|
179
|
+
|
|
180
|
+
# # Recuperando a coluna nova, se houver
|
|
181
|
+
# achou_property_equivalente = False
|
|
182
|
+
# property_name_old = None
|
|
183
|
+
# property_old = None
|
|
184
|
+
# entity_name_old = None
|
|
185
|
+
|
|
186
|
+
# if (
|
|
187
|
+
# properties_structure_old
|
|
188
|
+
# and property_name_new in properties_structure_old.properties
|
|
189
|
+
# ):
|
|
190
|
+
# achou_property_equivalente = True
|
|
191
|
+
|
|
192
|
+
# # Pelo nome da propriedade
|
|
193
|
+
# property_name_old = property_name_new
|
|
194
|
+
# property_old = properties_structure_old.properties[property_name_old]
|
|
195
|
+
# entity_mapping_old = properties_structure_old.entity_properties.get(
|
|
196
|
+
# property_name_old
|
|
197
|
+
# )
|
|
198
|
+
# entity_name_old = property_name_old
|
|
199
|
+
# if entity_mapping_old and entity_mapping_old.column:
|
|
200
|
+
# entity_name_old = entity_mapping_old.column
|
|
201
|
+
|
|
202
|
+
# # Guardando a equivalência para ajudar no drop
|
|
203
|
+
# mapa_equivalencias_old_new[property_name_old] = property_name_new
|
|
204
|
+
|
|
205
|
+
# elif properties_structure_old:
|
|
206
|
+
# # Buscando pelo nome da coluna
|
|
207
|
+
# for property_name_old in properties_structure_old.properties:
|
|
208
|
+
# property_old = properties_structure_old.properties[
|
|
209
|
+
# property_name_old
|
|
210
|
+
# ]
|
|
211
|
+
# entity_mapping_old = properties_structure_old.entity_properties.get(
|
|
212
|
+
# property_name_old
|
|
213
|
+
# )
|
|
214
|
+
# entity_name_old = property_name_old
|
|
215
|
+
# if entity_mapping_old and entity_mapping_old.column:
|
|
216
|
+
# entity_name_old = entity_mapping_old.column
|
|
217
|
+
|
|
218
|
+
# if entity_name_new == entity_name_old:
|
|
219
|
+
# achou_property_equivalente = True
|
|
220
|
+
|
|
221
|
+
# # Guardando a equivalência para ajudar no drop
|
|
222
|
+
# mapa_equivalencias_old_new[property_name_old] = (
|
|
223
|
+
# property_name_new
|
|
224
|
+
# )
|
|
225
|
+
|
|
226
|
+
# break
|
|
227
|
+
|
|
228
|
+
# if not achou_property_equivalente:
|
|
229
|
+
# # Construindo um objeto de adição de coluna
|
|
230
|
+
# mapped_values_new = []
|
|
231
|
+
# if property_new.domain_config:
|
|
232
|
+
# mapped_values_new = [
|
|
233
|
+
# v.mapped_value for v in property_new.domain_config
|
|
234
|
+
# ]
|
|
235
|
+
# mapped_values_new.sort(key=lambda x: x)
|
|
236
|
+
|
|
237
|
+
# add_properties.append(
|
|
238
|
+
# DiffProperty(
|
|
239
|
+
# action=DiffAction.ADD_COLUMN,
|
|
240
|
+
# property_name=property_name_new,
|
|
241
|
+
# entity_name=entity_name_new,
|
|
242
|
+
# new_datatype=property_new.type,
|
|
243
|
+
# new_required=property_name_new in properties_structure.required,
|
|
244
|
+
# new_description=property_new.description,
|
|
245
|
+
# new_default=property_new.default,
|
|
246
|
+
# new_pk=property_new.pk,
|
|
247
|
+
# new_max_length=property_new.max_length,
|
|
248
|
+
# new_check=mapped_values_new,
|
|
249
|
+
# datatype_changed=True,
|
|
250
|
+
# required_changed=property_name_new
|
|
251
|
+
# in properties_structure.required,
|
|
252
|
+
# default_changed=property_new.default is not None,
|
|
253
|
+
# pk_changed=bool(property_new.pk),
|
|
254
|
+
# max_length_changed=property_new.max_length is not None,
|
|
255
|
+
# check_changed=bool(mapped_values_new),
|
|
256
|
+
# )
|
|
257
|
+
# )
|
|
258
|
+
|
|
259
|
+
# else:
|
|
260
|
+
|
|
261
|
+
# # Construindo um objeto de alteração (para ser usado, se necessário)
|
|
262
|
+
# persist_alter = False
|
|
263
|
+
# diff_property = DiffProperty(
|
|
264
|
+
# action=DiffAction.ALTER_COLUMN,
|
|
265
|
+
# property_name=property_name_new,
|
|
266
|
+
# entity_name_old=entity_name_old,
|
|
267
|
+
# entity_name=entity_name_new,
|
|
268
|
+
# )
|
|
269
|
+
|
|
270
|
+
# # Verificando se é alteração do nome da coluna
|
|
271
|
+
# if entity_name_old != entity_name_new:
|
|
272
|
+
# persist_alter = True
|
|
273
|
+
# diff_property.entity_name_old = entity_name_old
|
|
274
|
+
# diff_property.renamed = True
|
|
275
|
+
|
|
276
|
+
# # Verificando se é alteração de tipo
|
|
277
|
+
# if property_old.type != property_new.type:
|
|
278
|
+
# persist_alter = True
|
|
279
|
+
# diff_property.old_datatype = property_old.type
|
|
280
|
+
# diff_property.new_datatype = property_new.type
|
|
281
|
+
# diff_property.datatype_changed = True
|
|
282
|
+
|
|
283
|
+
# # Verificando se houve alteração de nulidade
|
|
284
|
+
# required_new = property_name_new in properties_structure.required
|
|
285
|
+
# required_old = property_name_old in properties_structure_old.required
|
|
286
|
+
# if required_new != required_old:
|
|
287
|
+
# persist_alter = True
|
|
288
|
+
# diff_property.old_required = required_old
|
|
289
|
+
# diff_property.new_required = required_new
|
|
290
|
+
# diff_property.required_changed = True
|
|
291
|
+
|
|
292
|
+
# # Verificando se houve alteração de descrição
|
|
293
|
+
# if property_old.description != property_new.description:
|
|
294
|
+
# persist_alter = True
|
|
295
|
+
# diff_property.new_description = property_new.description
|
|
296
|
+
|
|
297
|
+
# # Verificando se houve alteração de valor default
|
|
298
|
+
# if property_old.default != property_new.default:
|
|
299
|
+
# persist_alter = True
|
|
300
|
+
# diff_property.new_default = property_new.default
|
|
301
|
+
# diff_property.default_changed = True
|
|
302
|
+
|
|
303
|
+
# # Verificando se houve alteração de PK
|
|
304
|
+
# if property_old.pk != property_new.pk:
|
|
305
|
+
# persist_alter = True
|
|
306
|
+
# diff_property.old_pk = property_old.pk
|
|
307
|
+
# diff_property.new_pk = property_new.pk
|
|
308
|
+
# diff_property.pk_changed = True
|
|
309
|
+
|
|
310
|
+
# # Verificando se houve alteração de precisão
|
|
311
|
+
# if property_old.max_length != property_new.max_length:
|
|
312
|
+
# persist_alter = True
|
|
313
|
+
# diff_property.new_max_length = property_new.max_length
|
|
314
|
+
# diff_property.max_length_changed = True
|
|
315
|
+
|
|
316
|
+
# # Constroi representação do check do enumerado old
|
|
317
|
+
# old_hash = None
|
|
318
|
+
# if property_old.domain_config:
|
|
319
|
+
# mapped_values_old = [
|
|
320
|
+
# v.mapped_value for v in property_old.domain_config
|
|
321
|
+
# ]
|
|
322
|
+
# mapped_values_old.sort(key=lambda x: x)
|
|
323
|
+
# # Fazendo um hash da lista
|
|
324
|
+
# old_hash = hash(tuple(mapped_values_old))
|
|
325
|
+
|
|
326
|
+
# # Constroi representação do check do enumerado new
|
|
327
|
+
# new_hash = None
|
|
328
|
+
# mapped_values_new = []
|
|
329
|
+
# if property_new.domain_config:
|
|
330
|
+
# mapped_values_new = [
|
|
331
|
+
# v.mapped_value for v in property_new.domain_config
|
|
332
|
+
# ]
|
|
333
|
+
# mapped_values_new.sort(key=lambda x: x)
|
|
334
|
+
# # Fazendo um hash da lista
|
|
335
|
+
# new_hash = hash(tuple(mapped_values_new))
|
|
336
|
+
|
|
337
|
+
# # Verificando se houve alteração de enumerado
|
|
338
|
+
# if old_hash != new_hash:
|
|
339
|
+
# persist_alter = True
|
|
340
|
+
# diff_property.new_check = mapped_values_new
|
|
341
|
+
# diff_property.check_changed = True
|
|
342
|
+
|
|
343
|
+
# # Guardando a alteração, caso necessário
|
|
344
|
+
# if persist_alter:
|
|
345
|
+
# alter_properties.append(diff_property)
|
|
346
|
+
|
|
347
|
+
# FIXME Comentado por hora (para não suportar ainda o DROP de colunas)
|
|
348
|
+
# Iterando as propriedades do objeto OLD, e verificando as que não existem no objeto NEW
|
|
349
|
+
# (considera-se aqui também a equilência por nome da propriedade ou da coluna)
|
|
350
|
+
# for property_name_old in properties_structure_old.properties:
|
|
351
|
+
# if property_name_old not in mapa_equivalencias_old_new:
|
|
352
|
+
# entity_mapping_old = properties_structure_old.entity_properties.get(
|
|
353
|
+
# property_name_old
|
|
354
|
+
# )
|
|
355
|
+
# entity_name_old = property_name_old
|
|
356
|
+
# if entity_mapping_old and entity_mapping_old.column:
|
|
357
|
+
# entity_name_old = entity_mapping_old.column
|
|
358
|
+
|
|
359
|
+
# property_old = properties_structure_old.properties[property_name_old]
|
|
360
|
+
# drop_properties.append(
|
|
361
|
+
# DiffProperty(
|
|
362
|
+
# action=DiffAction.DROP_COLUMN,
|
|
363
|
+
# property_name=property_name_old,
|
|
364
|
+
# entity_name=entity_name_old,
|
|
365
|
+
# old_datatype=property_old.type,
|
|
366
|
+
# old_pk=property_old.pk,
|
|
367
|
+
# )
|
|
368
|
+
# )
|
|
369
|
+
|
|
370
|
+
# return alter_properties, add_properties, drop_properties
|
|
371
|
+
|
|
372
|
+
def _build_column_and_fk_specs(
|
|
373
|
+
self,
|
|
374
|
+
entity_model: EntityModelBase,
|
|
375
|
+
properties_structure: PropertiesCompilerStructure,
|
|
376
|
+
entity_models: dict[str, EntityModel],
|
|
377
|
+
) -> tuple[list[dict[str, Any]], list[dict[str, str]]]:
|
|
378
|
+
column_specs: list[dict[str, Any]] = []
|
|
379
|
+
fk_specs: list[dict[str, str]] = []
|
|
380
|
+
existing_columns: set[str] = set()
|
|
381
|
+
|
|
382
|
+
for property_name, property_model in properties_structure.properties.items():
|
|
383
|
+
if not isinstance(property_model.type, PrimitiveTypes):
|
|
384
|
+
relation_ref = RelationRefParser.parse(property_model.type)
|
|
385
|
+
target_model = relation_ref.resolve_model(entity_models) if relation_ref else None
|
|
386
|
+
# Tenta identificar FK local a partir do relation_column
|
|
387
|
+
entity_mapping = properties_structure.entity_properties.get(
|
|
388
|
+
property_name
|
|
389
|
+
)
|
|
390
|
+
relation_column = (
|
|
391
|
+
entity_mapping.relation_column if entity_mapping else None
|
|
392
|
+
)
|
|
393
|
+
if relation_column:
|
|
394
|
+
relation_column_ref, column_name = self._extract_relation_column_target(
|
|
395
|
+
relation_column, entity_model
|
|
396
|
+
)
|
|
397
|
+
# len >= 3 => <escopo>/<entidade>(/ ...)/<coluna>
|
|
398
|
+
if relation_column_ref and column_name:
|
|
399
|
+
# Coluna está na própria entidade
|
|
400
|
+
if relation_column_ref.target_id == entity_model.id:
|
|
401
|
+
column_spec = self._build_column_spec_for_relation(
|
|
402
|
+
property_name,
|
|
403
|
+
property_model,
|
|
404
|
+
column_name,
|
|
405
|
+
properties_structure,
|
|
406
|
+
target_model,
|
|
407
|
+
)
|
|
408
|
+
if column_spec["column_name"] not in existing_columns:
|
|
409
|
+
column_specs.append(column_spec)
|
|
410
|
+
existing_columns.add(column_spec["column_name"])
|
|
411
|
+
fk_specs.append(
|
|
412
|
+
self._build_fk_spec(
|
|
413
|
+
entity_model,
|
|
414
|
+
column_name,
|
|
415
|
+
property_model,
|
|
416
|
+
entity_models,
|
|
417
|
+
target_model=target_model,
|
|
418
|
+
)
|
|
419
|
+
)
|
|
420
|
+
continue
|
|
421
|
+
entity_mapping = properties_structure.entity_properties.get(property_name)
|
|
422
|
+
column_name = (
|
|
423
|
+
entity_mapping.column
|
|
424
|
+
if entity_mapping and entity_mapping.column
|
|
425
|
+
else property_name
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
enum_values: list[Any] = []
|
|
429
|
+
if property_model.domain_config:
|
|
430
|
+
enum_values = [v.mapped_value for v in property_model.domain_config]
|
|
431
|
+
enum_values.sort(key=lambda x: x)
|
|
432
|
+
|
|
433
|
+
maximum_value = getattr(property_model, "maximum", None)
|
|
434
|
+
|
|
435
|
+
column_specs.append(
|
|
436
|
+
{
|
|
437
|
+
"property_name": property_name,
|
|
438
|
+
"column_name": column_name,
|
|
439
|
+
"datatype": property_model.type,
|
|
440
|
+
"sql_type": MigrationCompilerUtil.resolve_sql_type(
|
|
441
|
+
property_model.type, property_model.max_length
|
|
442
|
+
),
|
|
443
|
+
"not_null": property_name in properties_structure.required,
|
|
444
|
+
"default": property_model.default,
|
|
445
|
+
"description": property_model.description,
|
|
446
|
+
"is_pk": bool(property_model.pk),
|
|
447
|
+
"enum_values": enum_values,
|
|
448
|
+
"max_length": property_model.max_length,
|
|
449
|
+
"maximum": maximum_value,
|
|
450
|
+
"is_numeric": MigrationCompilerUtil.is_numeric(property_model.type),
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
# Verifica relacionamentos definidos em outras entidades que apontam colunas para a tabela atual
|
|
455
|
+
for other_id, other_entity in entity_models.items():
|
|
456
|
+
if other_id == f"{entity_model.escopo}/{entity_model.id}" or other_id == entity_model.id:
|
|
457
|
+
continue
|
|
458
|
+
for other_prop_name, other_prop_model in other_entity.properties.items():
|
|
459
|
+
if isinstance(other_prop_model.type, PrimitiveTypes):
|
|
460
|
+
continue
|
|
461
|
+
other_entity_mapping = other_entity.repository.properties.get(
|
|
462
|
+
other_prop_name
|
|
463
|
+
)
|
|
464
|
+
relation_column = (
|
|
465
|
+
other_entity_mapping.relation_column if other_entity_mapping else None
|
|
466
|
+
)
|
|
467
|
+
if not relation_column:
|
|
468
|
+
continue
|
|
469
|
+
relation_column_ref, column_name = self._extract_relation_column_target(
|
|
470
|
+
relation_column, other_entity
|
|
471
|
+
)
|
|
472
|
+
if not relation_column_ref or not column_name:
|
|
473
|
+
continue
|
|
474
|
+
if relation_column_ref.target_id != entity_model.id:
|
|
475
|
+
continue
|
|
476
|
+
|
|
477
|
+
target_model = relation_column_ref.resolve_model(
|
|
478
|
+
entity_models, base_model=other_entity
|
|
479
|
+
)
|
|
480
|
+
reference_model = (
|
|
481
|
+
target_model
|
|
482
|
+
if target_model and target_model != entity_model
|
|
483
|
+
else other_entity
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# A coluna mora na tabela atual, e referencia a entidade "dona" da propriedade (other_entity)
|
|
487
|
+
pk_type = self._resolve_pk_type(reference_model)
|
|
488
|
+
pk_column = self._resolve_pk_column(reference_model)
|
|
489
|
+
|
|
490
|
+
if column_name not in existing_columns:
|
|
491
|
+
column_specs.append(
|
|
492
|
+
self._build_external_column_spec(column_name, pk_type)
|
|
493
|
+
)
|
|
494
|
+
existing_columns.add(column_name)
|
|
495
|
+
fk_specs.append(
|
|
496
|
+
{
|
|
497
|
+
"column_name": column_name,
|
|
498
|
+
"ref_table": reference_model.repository.map,
|
|
499
|
+
"ref_column": pk_column,
|
|
500
|
+
}
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
return column_specs, fk_specs
|
|
504
|
+
|
|
505
|
+
@staticmethod
|
|
506
|
+
def _extract_relation_column_target(
|
|
507
|
+
relation_column: str,
|
|
508
|
+
base_model: EntityModelBase | None = None,
|
|
509
|
+
) -> tuple[RelationRef | None, str | None]:
|
|
510
|
+
parts = [part for part in relation_column.split("/") if part]
|
|
511
|
+
if len(parts) < 2:
|
|
512
|
+
return None, None
|
|
513
|
+
|
|
514
|
+
column_name = parts[-1]
|
|
515
|
+
relation_path = "/".join(parts[:-1])
|
|
516
|
+
|
|
517
|
+
relation_ref = RelationRefParser.parse(relation_path)
|
|
518
|
+
if relation_ref:
|
|
519
|
+
if not relation_ref.scope and base_model and getattr(base_model, "escopo", None):
|
|
520
|
+
relation_ref.scope = base_model.escopo # type: ignore
|
|
521
|
+
return relation_ref, column_name
|
|
522
|
+
|
|
523
|
+
# Fallback para o formato antigo <escopo>/<entidade>/<coluna>
|
|
524
|
+
if len(parts) >= 3:
|
|
525
|
+
return RelationRefParser.parse("/".join(parts[:-1])), column_name
|
|
526
|
+
|
|
527
|
+
return None, column_name
|
|
528
|
+
|
|
529
|
+
def _build_column_spec_for_relation(
|
|
530
|
+
self,
|
|
531
|
+
property_name: str,
|
|
532
|
+
property_model: Any,
|
|
533
|
+
local_column: str,
|
|
534
|
+
properties_structure: PropertiesCompilerStructure,
|
|
535
|
+
target_model: EntityModelBase | None,
|
|
536
|
+
) -> dict[str, Any]:
|
|
537
|
+
# Descobre o tipo da PK da entidade referenciada
|
|
538
|
+
pk_type = PrimitiveTypes.UUID
|
|
539
|
+
if target_model:
|
|
540
|
+
pk_prop = next(
|
|
541
|
+
(p for p in target_model.properties if target_model.properties[p].pk),
|
|
542
|
+
None,
|
|
543
|
+
)
|
|
544
|
+
if pk_prop:
|
|
545
|
+
pk_type = target_model.properties[pk_prop].type
|
|
546
|
+
|
|
547
|
+
enum_values: list[Any] = []
|
|
548
|
+
maximum_value = None
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
"property_name": property_name,
|
|
552
|
+
"column_name": local_column,
|
|
553
|
+
"datatype": pk_type,
|
|
554
|
+
"sql_type": MigrationCompilerUtil.resolve_sql_type(pk_type, None),
|
|
555
|
+
"not_null": property_name in properties_structure.required,
|
|
556
|
+
"default": None,
|
|
557
|
+
"description": None,
|
|
558
|
+
"is_pk": False,
|
|
559
|
+
"enum_values": enum_values,
|
|
560
|
+
"max_length": None,
|
|
561
|
+
"maximum": maximum_value,
|
|
562
|
+
"is_numeric": MigrationCompilerUtil.is_numeric(pk_type),
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
def _build_fk_spec(
|
|
566
|
+
self,
|
|
567
|
+
entity_model: EntityModelBase,
|
|
568
|
+
local_column: str,
|
|
569
|
+
property_model: Any,
|
|
570
|
+
entity_models: dict[str, EntityModel],
|
|
571
|
+
target_model: EntityModelBase | None = None,
|
|
572
|
+
) -> dict[str, str]:
|
|
573
|
+
ref_entity = target_model
|
|
574
|
+
if ref_entity is None:
|
|
575
|
+
ref_entity_id = property_model.type
|
|
576
|
+
ref_entity = entity_models.get(ref_entity_id)
|
|
577
|
+
ref_table = None
|
|
578
|
+
ref_pk_column = None
|
|
579
|
+
if ref_entity:
|
|
580
|
+
ref_table = ref_entity.repository.map
|
|
581
|
+
ref_pk_prop = next(
|
|
582
|
+
(p for p in ref_entity.properties if ref_entity.properties[p].pk),
|
|
583
|
+
None,
|
|
584
|
+
)
|
|
585
|
+
if ref_pk_prop:
|
|
586
|
+
ref_pk_column = ref_pk_prop
|
|
587
|
+
entity_mapping_ref = ref_entity.repository.properties.get(ref_pk_prop)
|
|
588
|
+
if entity_mapping_ref and entity_mapping_ref.column:
|
|
589
|
+
ref_pk_column = entity_mapping_ref.column
|
|
590
|
+
|
|
591
|
+
return {
|
|
592
|
+
"column_name": local_column,
|
|
593
|
+
"ref_table": ref_table or "",
|
|
594
|
+
"ref_column": ref_pk_column or "id",
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
def _resolve_pk_type(self, entity_model: EntityModelBase) -> PrimitiveTypes:
|
|
598
|
+
pk_prop = next(
|
|
599
|
+
(p for p in entity_model.properties if entity_model.properties[p].pk), None
|
|
600
|
+
)
|
|
601
|
+
if not pk_prop:
|
|
602
|
+
return PrimitiveTypes.UUID
|
|
603
|
+
return entity_model.properties[pk_prop].type
|
|
604
|
+
|
|
605
|
+
def _resolve_pk_column(self, entity_model: EntityModelBase) -> str:
|
|
606
|
+
pk_prop = next(
|
|
607
|
+
(p for p in entity_model.properties if entity_model.properties[p].pk), None
|
|
608
|
+
)
|
|
609
|
+
if not pk_prop:
|
|
610
|
+
return "id"
|
|
611
|
+
entity_mapping = entity_model.repository.properties.get(pk_prop)
|
|
612
|
+
if entity_mapping and entity_mapping.column:
|
|
613
|
+
return entity_mapping.column
|
|
614
|
+
return pk_prop
|
|
615
|
+
|
|
616
|
+
def _build_external_column_spec(
|
|
617
|
+
self, column_name: str, pk_type: PrimitiveTypes
|
|
618
|
+
) -> dict[str, Any]:
|
|
619
|
+
"""
|
|
620
|
+
Constrói especificação de coluna para FK definida em outra entidade,
|
|
621
|
+
mas cuja coluna está na tabela atual.
|
|
622
|
+
"""
|
|
623
|
+
return {
|
|
624
|
+
"property_name": column_name,
|
|
625
|
+
"column_name": column_name,
|
|
626
|
+
"datatype": pk_type,
|
|
627
|
+
"sql_type": MigrationCompilerUtil.resolve_sql_type(pk_type, None),
|
|
628
|
+
"not_null": False,
|
|
629
|
+
"default": None,
|
|
630
|
+
"description": None,
|
|
631
|
+
"is_pk": False,
|
|
632
|
+
"enum_values": [],
|
|
633
|
+
"max_length": None,
|
|
634
|
+
"maximum": None,
|
|
635
|
+
"is_numeric": MigrationCompilerUtil.is_numeric(pk_type),
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
def _detect_renamed_columns(
|
|
639
|
+
self,
|
|
640
|
+
properties_structure: PropertiesCompilerStructure,
|
|
641
|
+
properties_structure_old: PropertiesCompilerStructure | None,
|
|
642
|
+
) -> list[tuple[str, str]]:
|
|
643
|
+
"""
|
|
644
|
+
Retorna lista de tuplas (old_column, new_column) quando o mapeamento
|
|
645
|
+
de uma propriedade mudou de nome de coluna.
|
|
646
|
+
"""
|
|
647
|
+
if not properties_structure_old:
|
|
648
|
+
return []
|
|
649
|
+
|
|
650
|
+
rename_ops: list[tuple[str, str]] = []
|
|
651
|
+
for prop_name, _ in properties_structure.properties.items():
|
|
652
|
+
if prop_name not in properties_structure_old.properties:
|
|
653
|
+
continue
|
|
654
|
+
|
|
655
|
+
entity_mapping_new = properties_structure.entity_properties.get(prop_name)
|
|
656
|
+
new_column = (
|
|
657
|
+
entity_mapping_new.column
|
|
658
|
+
if entity_mapping_new and entity_mapping_new.column
|
|
659
|
+
else prop_name
|
|
660
|
+
)
|
|
661
|
+
|
|
662
|
+
entity_mapping_old = properties_structure_old.entity_properties.get(
|
|
663
|
+
prop_name
|
|
664
|
+
)
|
|
665
|
+
old_column = (
|
|
666
|
+
entity_mapping_old.column
|
|
667
|
+
if entity_mapping_old and entity_mapping_old.column
|
|
668
|
+
else prop_name
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
if old_column != new_column:
|
|
672
|
+
rename_ops.append((old_column, new_column))
|
|
673
|
+
|
|
674
|
+
return rename_ops
|