nsj-rest-lib2 0.0.30__py3-none-any.whl → 0.0.32__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.
@@ -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