dapper-sqls 1.1.3__py3-none-any.whl → 1.2.1__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.
@@ -1,24 +1,9 @@
1
1
  # coding: utf-8
2
2
  import re
3
- from pydantic import Field, BaseModel
3
+ from pydantic import Field, BaseModel, model_validator
4
+ from typing import Optional, List, Set, Any
5
+ import json
4
6
 
5
- class InformationSchemaTables(BaseModel):
6
- TABLE_CATALOG : str = Field(None, description="")
7
- TABLE_SCHEMA : str = Field(None, description="")
8
- TABLE_NAME : str = Field(None, description="")
9
- COLUMN_NAME : str = Field(None, description="")
10
- DATA_TYPE : str = Field(None, description="")
11
- IS_NULLABLE : str = Field(None, description="")
12
-
13
- class InformationSchemaRoutines(BaseModel):
14
- SPECIFIC_CATALOG : str = Field(None, description="")
15
- SPECIFIC_SCHEMA : str = Field(None, description="")
16
- SPECIFIC_NAME : str = Field(None, description="")
17
- ORDINAL_POSITION : int = Field(None, description="")
18
- PARAMETER_NAME : str = Field(None, description="")
19
- DATA_TYPE : str = Field(None, description="")
20
- PROCEDURE_DEFINITION : str = Field(None, description="")
21
-
22
7
  type_mapping = {
23
8
  'int': 'int',
24
9
  'bigint': 'int',
@@ -44,22 +29,318 @@ type_mapping = {
44
29
  'rowversion': 'bytes',
45
30
  }
46
31
 
47
- def create_field(data : InformationSchemaTables, field = 'Field(None, description="")'):
32
+ QUERY_FIELD_TYPES = {
33
+ 'str': 'StringQueryField',
34
+ 'int': 'NumericQueryField',
35
+ 'float': 'NumericQueryField',
36
+ 'bool': 'BoolQueryField',
37
+ 'datetime': 'DateQueryField',
38
+ 'datetime.time': 'DateQueryField',
39
+ 'bytes': 'BytesQueryField',
40
+ }
41
+
42
+ JOIN_CONDITIONAL_FIELD_TYPES = {
43
+ 'str': 'JoinStringCondition',
44
+ 'int': 'JoinNumericCondition',
45
+ 'float': 'JoinNumericCondition',
46
+ 'bool': 'JoinBooleanCondition',
47
+ 'datetime': 'JoinDateCondition',
48
+ 'datetime.time': 'JoinDateCondition',
49
+ 'bytes': 'JoinBytesCondition',
50
+ }
51
+
52
+ class Relation(BaseModel):
53
+ original : bool = True
54
+ table : str
55
+ column : str
56
+
57
+ class SqlTableAuth(BaseModel):
58
+ table : str = ""
59
+ column : str = ""
60
+
61
+ class TableSettings(BaseModel):
62
+ insert : bool = False
63
+ update : bool = False
64
+ delete : bool = False
65
+ search : bool = True
66
+
67
+ class ForeignKey(BaseModel):
68
+ ref_table: str = ""
69
+ ref_column: str = ""
70
+ on_delete: str = ""
71
+ on_update: str = ""
72
+
73
+ class AutoColumnDescription(BaseModel):
74
+ nullable: Optional[bool] = None
75
+ type: Optional[str] = None
76
+ identity: Optional[bool] = None
77
+ default: Optional[Any] = None
78
+ primary_key: Optional[bool] = None
79
+ unique: Optional[bool] = None
80
+ foreign_key: Optional[ForeignKey] = None
81
+
82
+ class ColumnInformation(BaseModel):
83
+ TABLE_CATALOG : str = Field("", description="")
84
+ TABLE_SCHEMA : str = Field("", description="")
85
+ TABLE_NAME : str = Field("", description="")
86
+ COLUMN_NAME : str = Field("", description="")
87
+ DATA_TYPE : str = Field("", description="")
88
+ IS_NULLABLE : str = Field("", description="")
89
+ IS_IDENTITY : str = Field("", description="")
90
+ IS_PRIMARY_KEY : str = Field("", description="")
91
+ IS_UNIQUE : str = Field("", description="")
92
+ CHARACTER_MAXIMUM_LENGTH : Optional[int] = Field(None, description="")
93
+ COLUMN_DESCRIPTION : Optional[str] = Field("", description="")
94
+ AUTO_COLUMN_DESCRIPTION : Optional[AutoColumnDescription] = None
95
+
96
+ column_authentication : str = Field("", description="")
97
+ relation : Optional[Relation] = None
98
+ available : bool = True
99
+
100
+ @model_validator(mode="before")
101
+ @classmethod
102
+ def parse_json_fields(cls, data):
103
+ if isinstance(data, dict):
104
+ # Se o campo vier como string JSON, converte para dict e depois para AutoColumnDescription
105
+ if isinstance(data.get("AUTO_COLUMN_DESCRIPTION"), str):
106
+ try:
107
+ auto_col_dict = json.loads(data["AUTO_COLUMN_DESCRIPTION"])
108
+ if not auto_col_dict['foreign_key']:
109
+ auto_col_dict['foreign_key'] = None
110
+ data["AUTO_COLUMN_DESCRIPTION"] = auto_col_dict
111
+ description : AutoColumnDescription = AutoColumnDescription(**auto_col_dict)
112
+ if description.foreign_key:
113
+ data['relation'] = Relation(table=description.foreign_key.ref_table, column=description.foreign_key.ref_column)
114
+
115
+ except json.JSONDecodeError:
116
+ data["AUTO_COLUMN_DESCRIPTION"] = None
117
+
118
+ return data
119
+
120
+ class ForeignKey(BaseModel):
121
+ name: str
122
+ column: str
123
+ ref_table: str
124
+ ref_column: str
125
+ on_delete: str
126
+ on_update: str
127
+
128
+ class UniqueConstraint(BaseModel):
129
+ name: str
130
+ columns: List[str]
131
+
132
+ class PrimaryKey(BaseModel):
133
+ name: str
134
+ columns: List[str]
135
+
136
+ class AutoTableDescription(BaseModel):
137
+ columns: int
138
+ identity: List[str]
139
+ primary_keys: Optional[PrimaryKey]
140
+ unique_constraints: List[UniqueConstraint]
141
+ foreign_keys: List[ForeignKey]
142
+
143
+ class TableInformation(BaseModel):
144
+ TABLE_CATALOG : str = Field("", description="")
145
+ TABLE_SCHEMA : str = Field("", description="")
146
+ TABLE_NAME : str = Field("", description="")
147
+ AUTO_TABLE_DESCRIPTION : AutoTableDescription = Field(..., description="")
148
+ TABLE_DESCRIPTION : Optional[str] = Field("", description="")
149
+ settings : TableSettings = TableSettings()
150
+ available : bool = True
151
+
152
+ @model_validator(mode="before")
153
+ @classmethod
154
+ def parse_json_fields(cls, data):
155
+ if isinstance(data, dict):
156
+ if isinstance(data.get("AUTO_TABLE_DESCRIPTION"), str):
157
+ data["AUTO_TABLE_DESCRIPTION"] = json.loads(data["AUTO_TABLE_DESCRIPTION"])
158
+ return data
159
+
160
+ class SqlTable(TableInformation):
161
+ COLUMNS : list[ColumnInformation] = []
162
+
163
+ class InformationSchemaRoutines(BaseModel):
164
+ SPECIFIC_CATALOG : str = Field("", description="")
165
+ SPECIFIC_SCHEMA : str = Field("", description="")
166
+ SPECIFIC_NAME : str = Field("", description="")
167
+ ORDINAL_POSITION : int = Field(None, description="")
168
+ PARAMETER_NAME : str = Field("", description="")
169
+ DATA_TYPE : str = Field("", description="")
170
+ PROCEDURE_DEFINITION : str = Field("", description="")
48
171
 
49
- sql_data_type = data.DATA_TYPE.lower()
50
- python_type = type_mapping.get(sql_data_type)
51
- if python_type is None:
52
- python_type = 'str'
172
+ def create_database_description(tables: List[TableInformation]) -> str:
173
+ lines = []
174
+ for table in tables:
175
+ auto = table.AUTO_TABLE_DESCRIPTION
176
+ lines.append(f"\n[Tabela: {table.TABLE_NAME}]")
177
+
178
+ # Chave primária
179
+ if auto.primary_keys:
180
+ pk_cols = ", ".join(auto.primary_keys.columns or [])
181
+ lines.append(f"- PK: {pk_cols}")
182
+
183
+ # Restrições únicas
184
+ if auto.unique_constraints:
185
+ for uc in auto.unique_constraints:
186
+ cols = ", ".join(uc.columns or [])
187
+ lines.append(f"- UNIQUE ({uc.name}): {cols}")
188
+
189
+ # Chaves estrangeiras
190
+ if auto.foreign_keys:
191
+ for fk in auto.foreign_keys:
192
+ lines.append(
193
+ f"- FK: {fk.column} → {fk.ref_table}.{fk.ref_column} "
194
+ f"[ON DELETE {fk.on_delete}, ON UPDATE {fk.on_update}]"
195
+ )
196
+
197
+ # Colunas identidade
198
+ if auto.identity:
199
+ identity_cols = ", ".join(auto.identity)
200
+ lines.append(f"- Identity: {identity_cols}")
201
+
202
+ return "\n".join(lines)
203
+
204
+ def create_table_description(table_info: TableInformation) -> str:
205
+ parts = []
206
+
207
+ # Identity columns
208
+ identity = table_info.AUTO_TABLE_DESCRIPTION.identity or []
209
+ if identity:
210
+ identity_cols = ", ".join(identity)
211
+ parts.append(f"Identity columns: {identity_cols}")
212
+
213
+ # Primary key
214
+ pk = table_info.AUTO_TABLE_DESCRIPTION.primary_keys
215
+ if pk:
216
+ pk_cols = ", ".join(pk.columns or [])
217
+ parts.append(f"Primary key ({pk.name}: {pk_cols})" if pk_cols else f"Primary key ({pk.name})")
218
+
219
+ # Unique constraints
220
+ unique_list = table_info.AUTO_TABLE_DESCRIPTION.unique_constraints or []
221
+ for uc in unique_list:
222
+ cols = ", ".join(uc.columns or [])
223
+ parts.append(f"Unique constraint ({uc.name}: {cols})" if cols else f"Unique constraint ({uc.name})")
224
+
225
+ # Foreign keys
226
+ # for fk in table_info.AUTO_TABLE_DESCRIPTION.foreign_keys or []:
227
+ # ref = f"{fk.ref_table}.{fk.ref_column}"
228
+ # action = f"ON DELETE {fk.on_delete}, ON UPDATE {fk.on_update}"
229
+ # parts.append(f"Foreign key ({fk.column} → {ref}) [{action}]")
230
+
231
+ # Final assembly
232
+ auto_summary = "; ".join(parts)
233
+
234
+ if table_info.TABLE_DESCRIPTION and table_info.TABLE_DESCRIPTION.strip():
235
+ return f"{table_info.TABLE_DESCRIPTION.strip()} — [{auto_summary}]"
236
+ else:
237
+ return f"[{auto_summary}]"
238
+
239
+ def create_column_description(column_desc: Optional[str], auto_desc: Optional[AutoColumnDescription]) -> str:
240
+ if auto_desc is None:
241
+ return column_desc or ""
242
+
243
+ parts = []
244
+
245
+ # Nullable
246
+ if auto_desc.nullable is False:
247
+ parts.append("Required")
248
+ elif auto_desc.nullable is True:
249
+ parts.append("Optional")
250
+
251
+ # Type
252
+ if auto_desc.type:
253
+ parts.append(f"Type: {auto_desc.type}")
254
+
255
+ # Identity (Auto Increment)
256
+ if auto_desc.identity:
257
+ parts.append("Identity (auto increment)")
258
+
259
+ # Default value
260
+ if auto_desc.default is not None and str(auto_desc.default).lower() != 'null':
261
+ parts.append(f"Default: {auto_desc.default}")
262
+
263
+ # Primary key
264
+ if auto_desc.primary_key:
265
+ parts.append("Primary key")
266
+
267
+ # Unique
268
+ if auto_desc.unique:
269
+ parts.append("Unique")
270
+
271
+ # Foreign key
272
+ # if auto_desc.foreign_key:
273
+ # fk = auto_desc.foreign_key
274
+ # details = []
275
+ # if fk.ref_table and fk.ref_column:
276
+ # details.append(f"{fk.ref_table}.{fk.ref_column}")
277
+ # if fk.on_delete:
278
+ # details.append(f"ON DELETE {fk.on_delete}")
279
+ # if fk.on_update:
280
+ # details.append(f"ON UPDATE {fk.on_update}")
281
+ # fk_info = " | ".join(details) if details else "Foreign key"
282
+ # parts.append(f"Foreign key → {fk_info}")
283
+
284
+ auto_summary = "; ".join(parts)
285
+
286
+ if column_desc:
287
+ return f"{column_desc.strip()} — [{auto_summary}]"
288
+ else:
289
+ return f"[{auto_summary}]"
53
290
 
54
- if field == 'Field(None, description="")':
55
- if data.IS_NULLABLE:
56
- if python_type != 'str' and python_type != 'bool':
57
- return f'{data.COLUMN_NAME} : Union[{python_type}, str, None] = {field}'
58
- return f'{data.COLUMN_NAME} : Union[{python_type}, None] = {field}'
59
-
60
- if python_type != 'str' and python_type != 'bool':
61
- return f'{data.COLUMN_NAME} : Union[{python_type}, str] = {field}'
62
- return f'{data.COLUMN_NAME} : {python_type} = {field}'
291
+ def create_field(data: ColumnInformation, all_optional=True):
292
+ sql_data_type = data.DATA_TYPE.lower()
293
+ python_type = type_mapping.get(sql_data_type, 'str')
294
+
295
+ field_args = [
296
+ f'description="{create_column_description(data.COLUMN_DESCRIPTION, data.AUTO_COLUMN_DESCRIPTION)}"'
297
+ ]
298
+
299
+ # Define QueryField type adicional
300
+ query_field_type = QUERY_FIELD_TYPES.get(python_type)
301
+
302
+ join_conditional_field_type = JOIN_CONDITIONAL_FIELD_TYPES.get(python_type)
303
+
304
+ # Combina os tipos
305
+ if python_type in ['datetime', 'datetime.time']:
306
+ annotated_type = f'Union[{python_type}, str]'
307
+ else:
308
+ annotated_type = python_type
309
+
310
+ if query_field_type:
311
+ annotated_type = f'Union[{annotated_type}, {query_field_type}, {join_conditional_field_type}]'
312
+
313
+ if data.IS_NULLABLE == "YES" or all_optional:
314
+ annotated_type = f'Optional[{annotated_type}]'
315
+ field_def = f'Field(None, {", ".join(field_args)})' if field_args else 'Field(None)'
316
+ else:
317
+ field_def = f'Field(..., {", ".join(field_args)})' if field_args else 'Field(...)'
318
+
319
+ return f'{data.COLUMN_NAME}: {annotated_type} = {field_def}'
320
+
321
+ def create_arg(
322
+ data: ColumnInformation,
323
+ default_type: str = None,
324
+ default_value: str = "None",
325
+ with_query_field: bool = False
326
+ ):
327
+ sql_data_type = data.DATA_TYPE.lower()
328
+ python_type = default_type or type_mapping.get(sql_data_type, 'str')
329
+
330
+ # Define o tipo base
331
+ if python_type in ['datetime', 'datetime.time']:
332
+ annotated_type = f'Union[{python_type}, str]'
333
+ else:
334
+ annotated_type = python_type
335
+
336
+ # Adiciona o QueryField, se for solicitado
337
+ if with_query_field:
338
+ query_field_type = QUERY_FIELD_TYPES.get(python_type)
339
+ join_conditional_field_type = JOIN_CONDITIONAL_FIELD_TYPES.get(python_type)
340
+ if query_field_type:
341
+ annotated_type = f'Union[{annotated_type}, {query_field_type}, {join_conditional_field_type}]'
342
+
343
+ return f'{data.COLUMN_NAME}: {annotated_type} = {default_value}'
63
344
 
64
345
  def get_parameters_with_defaults(stored_procedure):
65
346
  # Regular expression to capture parameters and their default values
@@ -100,6 +381,17 @@ def get_parameters_with_defaults(stored_procedure):
100
381
 
101
382
  return params_with_defaults
102
383
 
384
+ def create_queue_update(fields_args_str : str):
385
+ return f'''def queue_update(self, *, {fields_args_str}):
386
+ super().queue_update(**locals())
387
+ '''
388
+
389
+ def create_set_ignored_fields(fields_args_str : str):
390
+ return f'''@classmethod
391
+ def set_ignored_fields(cls, *, {fields_args_str}):
392
+ super().set_ignored_fields(**locals())
393
+ '''
394
+
103
395
  def create_params_routine(data : InformationSchemaRoutines, defaults_values : dict[str, str | int | None]):
104
396
  sql_data_type = data.DATA_TYPE.lower()
105
397
  python_type = type_mapping.get(sql_data_type)
@@ -139,10 +431,13 @@ class BaseExecutorORM(BaseExecutor):
139
431
  def executor(self):
140
432
  return self._executor
141
433
 
142
- def fetchone(self, additional_sql : str = "", *, {fields_args_str}) -> {class_name}:
434
+ def count(self, additional_sql : str = "", *, {fields_args_str}):
435
+ return self.executor.count(self, {class_name}(**get_dict_args(locals(), ['additional_sql'])), additional_sql)
436
+
437
+ def fetchone(self, additional_sql : str = "", *, {fields_args_str}) -> Result.FetchoneModel[{class_name}]:
143
438
  return self.executor.fetchone(self, {class_name}(**get_dict_args(locals(), ['additional_sql'])), additional_sql)
144
439
 
145
- def fetchall(self, additional_sql : str = "", select_top : int = None, *, {fields_args_str}) -> list[{class_name}]:
440
+ def fetchall(self, additional_sql : str = "", select_top : int = None, *, {fields_args_str}) -> Result.FetchallModel[{class_name}]:
146
441
  return self.executor.fetchall(self, {class_name}(**get_dict_args(locals(), ['additional_sql', 'select_top'])), additional_sql, select_top)
147
442
 
148
443
  def delete(self, *, {fields_args_str}) -> Result.Send:
@@ -212,9 +507,6 @@ class {class_name}ORM(object):
212
507
  def __init__(self, dapper : Dapper):
213
508
  self._dapper = dapper
214
509
 
215
- class {class_name}({class_name}):
216
- ...
217
-
218
510
  @property
219
511
  def dapper(self):
220
512
  return self._dapper
@@ -289,10 +581,13 @@ class AsyncBaseExecutorORM(AsyncBaseExecutor):
289
581
  def executor(self):
290
582
  return self._executor
291
583
 
292
- async def fetchone(self, additional_sql : str = "", *, {fields_args_str}) -> {class_name}:
584
+ async def count(self, additional_sql : str = "", *, {fields_args_str}):
585
+ return await self.executor.count(self, {class_name}(**get_dict_args(locals(), ['additional_sql'])), additional_sql)
586
+
587
+ async def fetchone(self, additional_sql : str = "", *, {fields_args_str}) -> Result.FetchoneModel[{class_name}]:
293
588
  return await self.executor.fetchone(self, {class_name}(**get_dict_args(locals(), ['additional_sql'])), additional_sql)
294
589
 
295
- async def fetchall(self, additional_sql : str = "", select_top : int = None, *, {fields_args_str}) -> list[{class_name}]:
590
+ async def fetchall(self, additional_sql : str = "", select_top : int = None, *, {fields_args_str}) -> Result.FetchallModel[{class_name}]:
296
591
  return await self.executor.fetchall(self, {class_name}(**get_dict_args(locals(), ['additional_sql', 'select_top'])), additional_sql, select_top)
297
592
 
298
593
  async def delete(self, *, {fields_args_str}) -> Result.Send:
@@ -362,9 +657,6 @@ class Async{class_name}ORM(object):
362
657
  def __init__(self, async_dapper : AsyncDapper):
363
658
  self._async_dapper = async_dapper
364
659
 
365
- class {class_name}({class_name}):
366
- ...
367
-
368
660
  @property
369
661
  def async_dapper(self):
370
662
  return self._async_dapper
@@ -1,55 +1,120 @@
1
1
  # coding: utf-8
2
- from typing import Type, Union
3
- from pydantic import BaseModel
2
+ from typing import Union
4
3
  from datetime import datetime, date
5
- import json
6
-
7
- class Value:
8
- def __init__(self, value : Union[str, int, bytes, float, datetime, date, bool], prefix : str, suffix : str):
9
- self.value = value
10
- self.prefix = prefix
11
- self.suffix = suffix
4
+ from ..models import TableBaseModel, QueryFieldBase, BaseJoinConditionField, SearchTable, JoinSearchTable
12
5
 
13
6
  class QueryBuilder(object):
14
7
 
15
- def value(value : Union[str, int, bytes, float, datetime, date], prefix = "=", suffix = ""):
16
- json_value = json.dumps({'prefix': prefix, 'value': value, 'suffix': suffix})
17
- return f"#QueryBuilderValue#{json_value}#QueryBuilderValue#"
18
-
19
8
  @classmethod
20
- def _build_where_clause(cls, **kwargs):
21
- conditions = []
22
- for field, value in kwargs.items():
23
- if value is not None:
24
- if isinstance(value, str):
25
- if '#QueryBuilderValue#' in value:
26
- value = value.replace('#QueryBuilderValue#', '')
27
- value = Value(**json.loads(value.strip()))
28
- if isinstance(value.value, str):
29
- conditions.append(f"{field} {value.prefix} '{value.value}' {value.suffix}")
30
- else:
31
- conditions.append(f"{field} {value.prefix} {value.value} {value.suffix}")
32
- else:
33
- conditions.append(f"{field} = '{value}'")
9
+ def build_where_clause(cls, model: TableBaseModel, table_alias: str = "", base_table_alias : str = "") -> str:
10
+ clause_parts = []
11
+
12
+ for field_name, field_value in model:
13
+
14
+ if field_value is None:
15
+ continue
16
+
17
+ # Constrói o nome qualificado com alias, se houver
18
+ qualified_field = f"{table_alias}.{field_name}" if table_alias.strip() else field_name
19
+
20
+ if isinstance(field_value, BaseJoinConditionField):
21
+ sql = field_value.to_sql(base_table_alias, qualified_field)
22
+ if sql:
23
+ clause_parts.append(sql)
24
+
25
+ elif isinstance(field_value, QueryFieldBase):
26
+ sql = field_value.to_sql(qualified_field)
27
+ if sql:
28
+ clause_parts.append(sql)
29
+
30
+ else:
31
+ if isinstance(field_value, str):
32
+ escaped_value = field_value.replace("'", "''")
33
+ clause_parts.append(f"{qualified_field} = '{escaped_value}'")
34
+ elif isinstance(field_value, bool):
35
+ clause_parts.append(f"{qualified_field} = {'1' if field_value else '0'}")
36
+ elif isinstance(field_value, datetime):
37
+ clause_parts.append(f"{qualified_field} = '{field_value.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}'")
38
+ elif isinstance(field_value, date):
39
+ clause_parts.append(f"{qualified_field} = '{field_value.strftime('%Y-%m-%d')}'")
40
+ elif isinstance(field_value, bytes):
41
+ # Converte os bytes em uma string hexadecimal para SQL (ex: 0x4E6574)
42
+ hex_value = field_value.hex()
43
+ clause_parts.append(f"{qualified_field} = 0x{hex_value}")
34
44
  else:
35
- conditions.append(f"{field} = {value}")
36
- return " AND ".join(conditions)
45
+ clause_parts.append(f"{qualified_field} = {field_value}")
46
+
47
+ return " AND ".join(clause_parts)
48
+
49
+ @staticmethod
50
+ def format_sql_value(value):
51
+ if isinstance(value, str):
52
+ value = value.replace("'", "''")
53
+ return f"'{value}'"
54
+ elif isinstance(value, bool):
55
+ return '1' if value else '0'
56
+ elif isinstance(value, datetime):
57
+ return f"'{value.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}'"
58
+ elif isinstance(value, date):
59
+ return f"'{value.strftime('%Y-%m-%d')}'"
60
+ elif isinstance(value, bytes):
61
+ return f"0x{value.hex()}"
62
+ elif value is None:
63
+ return "NULL"
64
+ else:
65
+ return str(value)
66
+
67
+ @staticmethod
68
+ def build_select_fields(search_table: SearchTable, table_alias: str, rename_fields: bool = True) -> str:
69
+ model_fields = search_table.model.model_fields.keys()
70
+
71
+ if not rename_fields:
72
+ # Se include estiver presente, listar colunas com alias normal
73
+ if search_table.include:
74
+ fields = [f"{table_alias}.{f}" for f in search_table.include]
75
+ else:
76
+ # Nenhuma regra definida, usar '*'
77
+ return f"{table_alias}.*"
78
+ return ", ".join(fields)
79
+
80
+ # Caso rename_fields=True
81
+ if search_table.include:
82
+ fields = search_table.include
83
+ else:
84
+ fields = list(model_fields)
85
+
86
+ select_parts = [
87
+ f"{table_alias}.{field} AS {table_alias}{field}" for field in fields
88
+ ]
89
+ return ", ".join(select_parts)
37
90
 
38
91
  @classmethod
39
- def update(cls, model: Type[BaseModel], where : Union[str , Type[BaseModel]]):
40
- update_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(exclude_none=True).items()}
92
+ def update(cls, model: TableBaseModel, where : Union[str , TableBaseModel]):
93
+ model._reset_defaults()
94
+ update_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(mode="json", exclude_none=True).items()}
41
95
  if not isinstance(where, str):
42
- where_data = {k: int(v) if isinstance(v, bool) else v for k, v in where.model_dump(exclude_none=True).items()}
43
- where = cls._build_where_clause(**where_data)
96
+ where._reset_defaults()
97
+ for key in where.model_dump(mode="json", exclude_none=True):
98
+ if key in update_data:
99
+ update_data.pop(key, None)
100
+ where = cls.build_where_clause(where)
44
101
 
45
- set_clause = ", ".join([f"{key} = '{value}'" if isinstance(value, str) else f"{key} = {value}" for key, value in update_data.items()])
102
+ set_clause = ", ".join(
103
+ f"{key} = {cls.format_sql_value(value)}"
104
+ for key, value in update_data.items()
105
+ )
46
106
  sql_query = f"UPDATE {model.TABLE_NAME} SET {set_clause} WHERE {where}"
47
107
  return sql_query
48
108
 
49
- def insert(model: Type[BaseModel], name_column_id = 'Id'):
50
- insert_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(exclude_none=True).items()}
109
+ @classmethod
110
+ def insert(cls, model: TableBaseModel, name_column_id = 'Id'):
111
+ model._reset_defaults()
112
+ insert_data = {
113
+ k: int(v) if isinstance(v, bool) else v
114
+ for k, v in model.model_dump(mode="json", exclude_none=True).items()
115
+ }
51
116
  columns = ", ".join(insert_data.keys())
52
- values = ", ".join([f"'{value}'" if isinstance(value, str) else str(value) for value in insert_data.values()])
117
+ values = ", ".join(cls.format_sql_value(v) for v in insert_data.values())
53
118
  sql_query = f"""
54
119
  INSERT INTO {model.TABLE_NAME} ({columns})
55
120
  OUTPUT INSERTED.{name_column_id} AS Id
@@ -58,21 +123,77 @@ class QueryBuilder(object):
58
123
  return sql_query
59
124
 
60
125
  @classmethod
61
- def select(cls, model: Type[BaseModel], additional_sql : str = "" ,select_top : int= None):
126
+ def select(cls, model: TableBaseModel, additional_sql : str = "" ,select_top : int= None):
127
+ model._reset_defaults()
62
128
  top_clause = f"TOP ({select_top}) * " if select_top else "*"
63
- select_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(exclude_none=True).items()}
64
- where_clause = cls._build_where_clause(**select_data)
129
+ where_clause = cls.build_where_clause(model)
65
130
 
66
131
  sql_query = f"SELECT {top_clause} FROM {model.TABLE_NAME}"
67
132
  if where_clause:
68
133
  sql_query += f" WHERE {where_clause}"
69
134
  sql_query = f'{sql_query} {additional_sql}'
70
135
  return sql_query
136
+
137
+ @staticmethod
138
+ def build_on_clause(join_model: TableBaseModel, join_alias: str, base_alias: str) -> str:
139
+ clause_parts = []
140
+ for field_name, field_value in join_model:
141
+ if isinstance(field_value, BaseJoinConditionField):
142
+ sql = field_value.to_sql(base_alias, f"{join_alias}.{field_name}")
143
+ if sql:
144
+ clause_parts.append(sql)
145
+ return " AND ".join(clause_parts)
146
+
147
+ @classmethod
148
+ def select_with_joins(cls, main_search: SearchTable, joins: list[JoinSearchTable] = [], additional_sql: str = "", select_top: int = None) -> str:
149
+ main_model = main_search.model
150
+ main_model._reset_defaults()
151
+ main_table = main_model.TABLE_NAME
152
+ main_table_alias = main_model.TABLE_ALIAS
153
+ top_clause = f"TOP ({select_top})" if select_top else ""
154
+
155
+ # Campos SELECT da tabela principal
156
+ select_fields = cls.build_select_fields(main_search, main_table_alias, False)
157
+
158
+ # JOINs
159
+ join_clauses = []
160
+ for join_search in joins:
161
+ join_model = join_search.model
162
+ join_model._reset_defaults()
163
+ join_table = join_model.TABLE_NAME
164
+ join_table_alias = join_model.TABLE_ALIAS
165
+
166
+ join_type = join_search.join_type.upper()
167
+ on_conditions = cls.build_on_clause(join_model, join_table_alias, main_table_alias)
71
168
 
169
+ # Usa o alias no JOIN
170
+ join_clause = (
171
+ f"{join_type} JOIN {join_table} AS {join_table_alias} "
172
+ f"ON {on_conditions}"
173
+ )
174
+ join_clauses.append(join_clause)
175
+
176
+ # Campos SELECT do JOIN
177
+ select_fields += ", " + cls.build_select_fields(join_search, join_table_alias)
178
+
179
+ # WHERE principal
180
+ where_clause = cls.build_where_clause(main_model, main_table_alias)
181
+ where_part = f"WHERE {where_clause}" if where_clause else ""
182
+
183
+ # SQL final
184
+ sql_query = (
185
+ f"SELECT {top_clause} {select_fields} "
186
+ f"FROM {main_table} AS {main_table_alias} "
187
+ + " ".join(join_clauses)
188
+ + f" {where_part} {additional_sql}"
189
+ ).strip()
190
+
191
+ return sql_query
192
+
72
193
  @classmethod
73
- def delete(cls, model: Type[BaseModel]):
74
- delete_data = {k: int(v) if isinstance(v, bool) else v for k, v in model.model_dump(exclude_none=True).items()}
75
- where_clause = cls._build_where_clause(**delete_data)
194
+ def delete(cls, model: TableBaseModel):
195
+ model._reset_defaults()
196
+ where_clause = cls.build_where_clause(model)
76
197
  if not where_clause:
77
198
  raise ValueError("DELETE operation requires at least one condition.")
78
199
  sql_query = f"DELETE FROM {model.TABLE_NAME} WHERE {where_clause}"