ormlambda 2.11.1__py3-none-any.whl → 3.7.0__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.
- ormlambda/__init__.py +11 -9
- ormlambda/caster/__init__.py +3 -0
- ormlambda/caster/base_caster.py +69 -0
- ormlambda/caster/caster.py +48 -0
- ormlambda/caster/interfaces/ICaster.py +26 -0
- ormlambda/caster/interfaces/__init__.py +1 -0
- ormlambda/common/__init__.py +1 -1
- ormlambda/common/abstract_classes/__init__.py +3 -3
- ormlambda/common/abstract_classes/decomposition_query.py +117 -315
- ormlambda/common/abstract_classes/non_query_base.py +1 -1
- ormlambda/common/enums/condition_types.py +2 -1
- ormlambda/common/enums/join_type.py +4 -1
- ormlambda/common/errors/__init__.py +15 -2
- ormlambda/common/global_checker.py +28 -0
- ormlambda/common/interfaces/ICustomAlias.py +4 -1
- ormlambda/common/interfaces/IDecompositionQuery.py +9 -34
- ormlambda/common/interfaces/IJoinSelector.py +21 -0
- ormlambda/common/interfaces/__init__.py +4 -6
- ormlambda/components/__init__.py +4 -0
- ormlambda/components/insert/abstract_insert.py +1 -1
- ormlambda/components/select/ISelect.py +17 -0
- ormlambda/components/select/__init__.py +1 -0
- ormlambda/components/update/abstract_update.py +4 -4
- ormlambda/components/upsert/abstract_upsert.py +1 -1
- ormlambda/databases/__init__.py +5 -0
- ormlambda/databases/my_sql/__init__.py +3 -1
- ormlambda/databases/my_sql/caster/__init__.py +1 -0
- ormlambda/databases/my_sql/caster/caster.py +38 -0
- ormlambda/databases/my_sql/caster/read.py +39 -0
- ormlambda/databases/my_sql/caster/types/__init__.py +8 -0
- ormlambda/databases/my_sql/caster/types/bytes.py +31 -0
- ormlambda/databases/my_sql/caster/types/datetime.py +34 -0
- ormlambda/databases/my_sql/caster/types/float.py +31 -0
- ormlambda/databases/my_sql/caster/types/int.py +31 -0
- ormlambda/databases/my_sql/caster/types/iterable.py +31 -0
- ormlambda/databases/my_sql/caster/types/none.py +30 -0
- ormlambda/databases/my_sql/caster/types/point.py +43 -0
- ormlambda/databases/my_sql/caster/types/string.py +31 -0
- ormlambda/databases/my_sql/caster/write.py +37 -0
- ormlambda/databases/my_sql/clauses/ST_AsText.py +36 -0
- ormlambda/databases/my_sql/clauses/ST_Contains.py +31 -0
- ormlambda/databases/my_sql/clauses/__init__.py +6 -4
- ormlambda/databases/my_sql/clauses/alias.py +24 -21
- ormlambda/databases/my_sql/clauses/count.py +32 -28
- ormlambda/databases/my_sql/clauses/create_database.py +3 -4
- ormlambda/databases/my_sql/clauses/delete.py +10 -10
- ormlambda/databases/my_sql/clauses/drop_database.py +3 -5
- ormlambda/databases/my_sql/clauses/drop_table.py +3 -3
- ormlambda/databases/my_sql/clauses/group_by.py +4 -7
- ormlambda/databases/my_sql/clauses/insert.py +33 -19
- ormlambda/databases/my_sql/clauses/joins.py +66 -59
- ormlambda/databases/my_sql/clauses/limit.py +1 -1
- ormlambda/databases/my_sql/clauses/offset.py +1 -1
- ormlambda/databases/my_sql/clauses/order.py +36 -23
- ormlambda/databases/my_sql/clauses/select.py +25 -36
- ormlambda/databases/my_sql/clauses/update.py +38 -13
- ormlambda/databases/my_sql/clauses/upsert.py +2 -2
- ormlambda/databases/my_sql/clauses/where.py +45 -0
- ormlambda/databases/my_sql/functions/concat.py +24 -27
- ormlambda/databases/my_sql/functions/max.py +32 -28
- ormlambda/databases/my_sql/functions/min.py +32 -28
- ormlambda/databases/my_sql/functions/sum.py +32 -28
- ormlambda/databases/my_sql/join_context.py +75 -0
- ormlambda/databases/my_sql/repository/__init__.py +1 -0
- ormlambda/databases/my_sql/{repository.py → repository/repository.py} +104 -73
- ormlambda/databases/my_sql/statements.py +231 -153
- ormlambda/engine/__init__.py +0 -0
- ormlambda/engine/template.py +47 -0
- ormlambda/model/__init__.py +0 -0
- ormlambda/model/base_model.py +37 -0
- ormlambda/repository/__init__.py +2 -0
- ormlambda/repository/base_repository.py +14 -0
- ormlambda/repository/interfaces/IDatabaseConnection.py +12 -0
- ormlambda/{common → repository}/interfaces/IRepositoryBase.py +6 -5
- ormlambda/repository/interfaces/__init__.py +2 -0
- ormlambda/sql/__init__.py +3 -0
- ormlambda/sql/clause_info/__init__.py +3 -0
- ormlambda/sql/clause_info/clause_info.py +434 -0
- ormlambda/sql/clause_info/clause_info_context.py +87 -0
- ormlambda/sql/clause_info/interface/IAggregate.py +10 -0
- ormlambda/sql/clause_info/interface/__init__.py +1 -0
- ormlambda/sql/column.py +126 -0
- ormlambda/sql/comparer.py +156 -0
- ormlambda/sql/foreign_key.py +115 -0
- ormlambda/sql/interfaces/__init__.py +0 -0
- ormlambda/sql/table/__init__.py +1 -0
- ormlambda/{utils → sql/table}/fields.py +6 -5
- ormlambda/{utils → sql/table}/table_constructor.py +43 -91
- ormlambda/sql/types.py +25 -0
- ormlambda/statements/__init__.py +2 -0
- ormlambda/statements/base_statement.py +129 -0
- ormlambda/statements/interfaces/IStatements.py +309 -0
- ormlambda/statements/interfaces/__init__.py +1 -0
- ormlambda/statements/types.py +51 -0
- ormlambda/utils/__init__.py +1 -3
- ormlambda/utils/module_tree/__init__.py +1 -0
- ormlambda/utils/module_tree/dynamic_module.py +20 -14
- {ormlambda-2.11.1.dist-info → ormlambda-3.7.0.dist-info}/METADATA +132 -68
- ormlambda-3.7.0.dist-info/RECORD +117 -0
- ormlambda/common/abstract_classes/abstract_model.py +0 -115
- ormlambda/common/interfaces/IAggregate.py +0 -10
- ormlambda/common/interfaces/IStatements.py +0 -348
- ormlambda/components/where/__init__.py +0 -1
- ormlambda/components/where/abstract_where.py +0 -15
- ormlambda/databases/my_sql/clauses/where_condition.py +0 -222
- ormlambda/model_base.py +0 -36
- ormlambda/utils/column.py +0 -105
- ormlambda/utils/foreign_key.py +0 -81
- ormlambda/utils/lambda_disassembler/__init__.py +0 -4
- ormlambda/utils/lambda_disassembler/dis_types.py +0 -133
- ormlambda/utils/lambda_disassembler/disassembler.py +0 -69
- ormlambda/utils/lambda_disassembler/dtypes.py +0 -103
- ormlambda/utils/lambda_disassembler/name_of.py +0 -41
- ormlambda/utils/lambda_disassembler/nested_element.py +0 -44
- ormlambda/utils/lambda_disassembler/tree_instruction.py +0 -145
- ormlambda-2.11.1.dist-info/RECORD +0 -81
- /ormlambda/{utils → sql}/dtypes.py +0 -0
- {ormlambda-2.11.1.dist-info → ormlambda-3.7.0.dist-info}/LICENSE +0 -0
- {ormlambda-2.11.1.dist-info → ormlambda-3.7.0.dist-info}/WHEEL +0 -0
@@ -1,39 +1,43 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from ormlambda.sql.clause_info import AggregateFunctionBase
|
3
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
4
|
+
|
5
|
+
from ormlambda.sql.types import AliasType, ColumnType
|
6
|
+
|
7
|
+
from ormlambda import Table
|
8
|
+
|
1
9
|
import typing as tp
|
2
10
|
|
11
|
+
from ormlambda.sql.types import ASTERISK
|
12
|
+
|
3
13
|
if tp.TYPE_CHECKING:
|
4
14
|
from ormlambda import Table
|
5
|
-
from ormlambda.
|
6
|
-
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase, ClauseInfo
|
7
|
-
from ormlambda import JoinType
|
15
|
+
from ormlambda.sql.types import ColumnType, AliasType, TableType
|
8
16
|
|
9
17
|
|
10
|
-
class Count[T:
|
11
|
-
|
18
|
+
class Count[T: Table](AggregateFunctionBase[T]):
|
19
|
+
@staticmethod
|
20
|
+
def FUNCTION_NAME() -> str:
|
21
|
+
return "COUNT"
|
12
22
|
|
13
|
-
def __init__(
|
23
|
+
def __init__[TProp: Table](
|
14
24
|
self,
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
25
|
+
element: ColumnType[T] | TableType[TProp],
|
26
|
+
alias_table: AliasType[ColumnType[TProp]] = None,
|
27
|
+
alias_clause: AliasType[ColumnType[TProp]] = "count",
|
28
|
+
context: ClauseContextType = None,
|
29
|
+
keep_asterisk: bool = True,
|
30
|
+
preserve_context: bool = True,
|
21
31
|
) -> None:
|
32
|
+
table = self.extract_table(element)
|
33
|
+
column = element if self.is_column(element) else ASTERISK
|
34
|
+
|
22
35
|
super().__init__(
|
23
|
-
table,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
36
|
+
table=table if (alias_table or (context and table in context._table_context)) else None,
|
37
|
+
column=column,
|
38
|
+
alias_table=alias_table,
|
39
|
+
alias_clause=alias_clause,
|
40
|
+
context=context,
|
41
|
+
keep_asterisk=keep_asterisk,
|
42
|
+
preserve_context=preserve_context,
|
29
43
|
)
|
30
|
-
|
31
|
-
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
32
|
-
if isinstance(clause_info._row_column, IAggregate):
|
33
|
-
return clause_info._row_column.alias
|
34
|
-
return None
|
35
|
-
|
36
|
-
@property
|
37
|
-
def query(self) -> str:
|
38
|
-
col = ", ".join([x.query for x in self.all_clauses])
|
39
|
-
return f"{self.NAME}({col})"
|
@@ -1,15 +1,14 @@
|
|
1
1
|
from typing import Literal, override
|
2
2
|
from mysql.connector import errorcode, errors
|
3
|
-
from mysql.connector import MySQLConnection
|
4
3
|
|
5
|
-
from ormlambda import
|
4
|
+
from ormlambda.repository import BaseRepository
|
6
5
|
|
7
6
|
TypeExists = Literal["fail", "replace", "append"]
|
8
7
|
|
9
8
|
|
10
9
|
class CreateDatabase:
|
11
|
-
def __init__(self, repository:
|
12
|
-
self._repository:
|
10
|
+
def __init__(self, repository: BaseRepository) -> None:
|
11
|
+
self._repository: BaseRepository = repository
|
13
12
|
|
14
13
|
@override
|
15
14
|
@property
|
@@ -1,16 +1,16 @@
|
|
1
|
-
from typing import Any, override, Iterable, TYPE_CHECKING
|
1
|
+
from typing import Any, Optional, override, Iterable, TYPE_CHECKING
|
2
2
|
|
3
3
|
if TYPE_CHECKING:
|
4
4
|
from ormlambda import Column
|
5
5
|
|
6
6
|
from ormlambda import Table
|
7
|
-
from ormlambda import IRepositoryBase
|
7
|
+
from ormlambda.repository import IRepositoryBase
|
8
8
|
from ormlambda.components.delete import DeleteQueryBase
|
9
9
|
from mysql.connector import MySQLConnection
|
10
10
|
|
11
11
|
|
12
|
-
class DeleteQuery[T: Table](DeleteQueryBase[T, IRepositoryBase
|
13
|
-
def __init__(self, model: T, repository: IRepositoryBase
|
12
|
+
class DeleteQuery[T: Table](DeleteQueryBase[T, IRepositoryBase]):
|
13
|
+
def __init__(self, model: T, repository: IRepositoryBase) -> None:
|
14
14
|
super().__init__(model, repository)
|
15
15
|
|
16
16
|
@property
|
@@ -21,18 +21,18 @@ class DeleteQuery[T: Table](DeleteQueryBase[T, IRepositoryBase[MySQLConnection]]
|
|
21
21
|
def delete(self, instances: T | list[T]) -> None:
|
22
22
|
col: str = ""
|
23
23
|
if isinstance(instances, Table):
|
24
|
-
pk: Column = instances.get_pk()
|
25
|
-
if pk
|
24
|
+
pk: Optional[Column] = instances.get_pk()
|
25
|
+
if pk is None:
|
26
26
|
raise Exception(f"You cannot use 'DELETE' query without set primary key in '{instances.__table_name__}'")
|
27
27
|
col = pk.column_name
|
28
|
-
value = str(pk
|
28
|
+
value = str(instances[pk])
|
29
29
|
|
30
30
|
elif isinstance(instances, Iterable):
|
31
31
|
value: list[Any] = []
|
32
32
|
for ins in instances:
|
33
|
-
pk = ins.get_pk()
|
34
|
-
|
35
|
-
|
33
|
+
pk = type(ins).get_pk()
|
34
|
+
value.append(ins[pk])
|
35
|
+
col = pk.column_name
|
36
36
|
|
37
37
|
query: str = f"{self.CLAUSE} FROM {self._model.__table_name__} WHERE {col}"
|
38
38
|
if isinstance(value, str):
|
@@ -1,13 +1,11 @@
|
|
1
1
|
from typing import override
|
2
|
-
from mysql.connector import MySQLConnection
|
3
2
|
|
4
|
-
|
5
|
-
from ormlambda import IRepositoryBase
|
3
|
+
from ormlambda.repository import IRepositoryBase
|
6
4
|
|
7
5
|
|
8
6
|
class DropDatabase:
|
9
|
-
def __init__(self, repository: IRepositoryBase
|
10
|
-
self._repository: IRepositoryBase
|
7
|
+
def __init__(self, repository: IRepositoryBase) -> None:
|
8
|
+
self._repository: IRepositoryBase = repository
|
11
9
|
|
12
10
|
@override
|
13
11
|
def execute(self, name: str) -> None:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import Literal, override
|
2
2
|
|
3
|
-
from ormlambda import IRepositoryBase
|
3
|
+
from ormlambda.repository import IRepositoryBase
|
4
4
|
|
5
5
|
from mysql.connector import MySQLConnection
|
6
6
|
|
@@ -8,8 +8,8 @@ TypeExists = Literal["fail", "replace", "append"]
|
|
8
8
|
|
9
9
|
|
10
10
|
class DropTable:
|
11
|
-
def __init__(self, repository: IRepositoryBase
|
12
|
-
self._repository: IRepositoryBase
|
11
|
+
def __init__(self, repository: IRepositoryBase) -> None:
|
12
|
+
self._repository: IRepositoryBase = repository
|
13
13
|
|
14
14
|
@override
|
15
15
|
def execute(self, name: str = None) -> None:
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import typing as tp
|
2
2
|
from ormlambda.common.enums.join_type import JoinType
|
3
|
-
from ormlambda.
|
4
|
-
from ormlambda.common.interfaces.IAggregate import IAggregate
|
3
|
+
from ormlambda.sql.clause_info import IAggregate
|
5
4
|
from ormlambda import Table
|
5
|
+
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
|
6
6
|
|
7
7
|
|
8
|
-
class GroupBy[T: tp.Type[Table], *Ts, TProp](DecompositionQueryBase[T], IAggregate
|
8
|
+
class GroupBy[T: tp.Type[Table], *Ts, TProp](DecompositionQueryBase[T], IAggregate):
|
9
9
|
CLAUSE: str = "GROUP BY"
|
10
10
|
|
11
11
|
def __init__(
|
@@ -19,15 +19,12 @@ class GroupBy[T: tp.Type[Table], *Ts, TProp](DecompositionQueryBase[T], IAggrega
|
|
19
19
|
) -> None:
|
20
20
|
super().__init__(
|
21
21
|
table,
|
22
|
-
|
22
|
+
columns=column,
|
23
23
|
alias=alias,
|
24
24
|
alias_name=alias_name,
|
25
25
|
by=by,
|
26
26
|
)
|
27
27
|
|
28
|
-
def alias_children_resolver[Tclause: tp.Type[Table]](self, clause_info: ClauseInfo[Tclause]):
|
29
|
-
return None
|
30
|
-
|
31
28
|
@property
|
32
29
|
def query(self) -> str:
|
33
30
|
col: str = ", ".join([x.query for x in self.all_clauses])
|
@@ -1,14 +1,19 @@
|
|
1
|
-
from
|
2
|
-
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import override, Iterable, TYPE_CHECKING
|
3
4
|
|
4
5
|
from ormlambda import Table
|
5
6
|
from ormlambda import Column
|
6
7
|
from ormlambda.components.insert import InsertQueryBase
|
7
|
-
from ormlambda import IRepositoryBase
|
8
|
+
from ormlambda.repository import IRepositoryBase
|
9
|
+
from ormlambda.caster import Caster
|
10
|
+
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from ormlambda.databases.my_sql import MySQLRepository
|
8
13
|
|
9
14
|
|
10
|
-
class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase
|
11
|
-
def __init__(self, model: T, repository:
|
15
|
+
class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase]):
|
16
|
+
def __init__(self, model: T, repository: MySQLRepository) -> None:
|
12
17
|
super().__init__(model, repository)
|
13
18
|
|
14
19
|
@override
|
@@ -23,8 +28,10 @@ class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase[MySQLConnection]]
|
|
23
28
|
return self._repository.executemany_with_values(self.query, self._values)
|
24
29
|
|
25
30
|
@override
|
26
|
-
def insert(self, instances: T | list[T]) -> None:
|
27
|
-
|
31
|
+
def insert[TProp](self, instances: T | list[T]) -> None:
|
32
|
+
if not isinstance(instances, Iterable):
|
33
|
+
instances = (instances,)
|
34
|
+
valid_cols: list[list[Column[TProp]]] = []
|
28
35
|
self.__fill_dict_list(valid_cols, instances)
|
29
36
|
|
30
37
|
col_names: list[str] = []
|
@@ -32,11 +39,14 @@ class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase[MySQLConnection]]
|
|
32
39
|
col_values: list[list[str]] = []
|
33
40
|
for i, cols in enumerate(valid_cols):
|
34
41
|
col_values.append([])
|
42
|
+
CASTER = Caster(self._repository)
|
35
43
|
for col in cols:
|
44
|
+
clean_data = CASTER.for_column(col, instances[i]) # .resolve(instances[i][col])
|
36
45
|
if i == 0:
|
37
46
|
col_names.append(col.column_name)
|
38
|
-
wildcards.append(
|
39
|
-
|
47
|
+
wildcards.append(clean_data.wildcard_to_insert())
|
48
|
+
# COMMENT: avoid MySQLWriteCastBase.resolve when using PLACEHOLDERs
|
49
|
+
col_values[-1].append(clean_data.to_database)
|
40
50
|
|
41
51
|
join_cols = ", ".join(col_names)
|
42
52
|
unknown_rows = f'({", ".join(wildcards)})' # The number of "%s" must match the dict 'dicc_0' length
|
@@ -46,7 +56,7 @@ class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase[MySQLConnection]]
|
|
46
56
|
return None
|
47
57
|
|
48
58
|
@staticmethod
|
49
|
-
def __is_valid(column: Column) -> bool:
|
59
|
+
def __is_valid[TProp](column: Column[TProp], value: TProp) -> bool:
|
50
60
|
"""
|
51
61
|
We want to delete the column from table when it's specified with an 'AUTO_INCREMENT' or 'AUTO GENERATED ALWAYS AS (__) STORED' statement.
|
52
62
|
|
@@ -60,24 +70,28 @@ class InsertQuery[T: Table](InsertQueryBase[T, IRepositoryBase[MySQLConnection]]
|
|
60
70
|
- False -> Delete the column from dict query
|
61
71
|
"""
|
62
72
|
|
63
|
-
is_pk_none_and_auto_increment: bool = all([
|
73
|
+
is_pk_none_and_auto_increment: bool = all([value is None, column.is_primary_key, column.is_auto_increment])
|
64
74
|
|
65
75
|
if is_pk_none_and_auto_increment or column.is_auto_generated:
|
66
76
|
return False
|
67
77
|
return True
|
68
78
|
|
69
|
-
def __fill_dict_list[TProp](self, list_dict: list[str, TProp], values:
|
70
|
-
if
|
79
|
+
def __fill_dict_list[TProp](self, list_dict: list[str, TProp], values: list[T]) -> list[Column]:
|
80
|
+
if isinstance(values, Iterable):
|
81
|
+
for x in values:
|
82
|
+
self.__fill_dict_list(list_dict, x)
|
83
|
+
|
84
|
+
elif issubclass(values.__class__, Table):
|
71
85
|
new_list = []
|
72
|
-
for
|
73
|
-
if isinstance(
|
74
|
-
|
86
|
+
for prop in type(values).__dict__.values():
|
87
|
+
if not isinstance(prop, Column):
|
88
|
+
continue
|
75
89
|
|
90
|
+
value = getattr(values, prop.column_name)
|
91
|
+
if self.__is_valid(prop, value):
|
92
|
+
new_list.append(prop)
|
76
93
|
list_dict.append(new_list)
|
77
94
|
|
78
|
-
elif isinstance(values, Iterable):
|
79
|
-
for x in values:
|
80
|
-
self.__fill_dict_list(list_dict, x)
|
81
95
|
else:
|
82
96
|
raise Exception(f"Tipo de dato'{type(values)}' no esperado")
|
83
97
|
return None
|
@@ -1,88 +1,71 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from collections import defaultdict
|
3
|
-
from typing import override,
|
3
|
+
from typing import override, Optional, TYPE_CHECKING, Type
|
4
4
|
|
5
5
|
|
6
6
|
from ormlambda.utils.module_tree.dfs_traversal import DFSTraversal
|
7
|
+
from ormlambda.common.interfaces.IJoinSelector import IJoinSelector
|
7
8
|
from ormlambda.common.interfaces.IQueryCommand import IQuery
|
8
|
-
from ormlambda import Disassembler
|
9
9
|
from ormlambda import JoinType
|
10
|
+
from ormlambda.sql.clause_info import ClauseInfo
|
11
|
+
from ormlambda.sql.comparer import Comparer
|
12
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
10
13
|
|
11
14
|
# TODOL [x]: Try to import Table module without circular import Error
|
12
15
|
if TYPE_CHECKING:
|
13
16
|
from ormlambda import Table
|
14
17
|
|
15
18
|
|
16
|
-
class JoinSelector[TLeft, TRight](
|
19
|
+
class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight]):
|
17
20
|
__slots__: tuple = (
|
21
|
+
"_comparer",
|
18
22
|
"_orig_table",
|
19
|
-
"
|
23
|
+
"_right_table",
|
20
24
|
"_by",
|
21
25
|
"_left_col",
|
22
26
|
"_right_col",
|
23
27
|
"_compareop",
|
28
|
+
"_alias",
|
24
29
|
)
|
25
30
|
|
26
31
|
@override
|
27
32
|
def __repr__(self) -> str:
|
28
|
-
table_col_left: str = f"{self.
|
29
|
-
table_col_right: str = f"{self.
|
33
|
+
table_col_left: str = f"{self.left_table.table_alias()}.{self._left_col}"
|
34
|
+
table_col_right: str = f"{self.right_table.table_alias()}.{self._right_col}"
|
30
35
|
|
31
36
|
return f"{IQuery.__name__}: {self.__class__.__name__} ({table_col_left} == {table_col_right})"
|
32
37
|
|
33
|
-
|
34
|
-
def __init__(
|
38
|
+
def __init__[LProp, RProp](
|
35
39
|
self,
|
36
|
-
|
37
|
-
table_right: TRight,
|
38
|
-
col_left: str,
|
39
|
-
col_right: str,
|
40
|
+
where: Comparer[TLeft, LProp, TRight, RProp],
|
40
41
|
by: JoinType,
|
41
|
-
|
42
|
-
|
43
|
-
@overload
|
44
|
-
def __init__(
|
45
|
-
self,
|
46
|
-
table_left: TLeft,
|
47
|
-
table_right: TRight,
|
48
|
-
by: JoinType,
|
49
|
-
where: Callable[[TLeft, TRight], bool],
|
50
|
-
) -> None: ...
|
51
|
-
|
52
|
-
def __init__(
|
53
|
-
self,
|
54
|
-
table_left: Table,
|
55
|
-
table_right: Table,
|
56
|
-
by: JoinType,
|
57
|
-
col_left: Optional[str] = None,
|
58
|
-
col_right: Optional[str] = None,
|
59
|
-
where: Optional[Callable[[TLeft, TRight], bool]] = None,
|
42
|
+
alias: Optional[str] = "{table}",
|
43
|
+
context: ClauseContextType = None,
|
60
44
|
) -> None:
|
61
|
-
self.
|
62
|
-
self.
|
45
|
+
self._comparer: Comparer[TLeft, LProp, TRight, RProp] = where
|
46
|
+
self._orig_table: TLeft = where.left_condition.table
|
47
|
+
self._right_table: TRight = where.right_condition.table
|
63
48
|
self._by: JoinType = by
|
49
|
+
self._left_col: str = where.left_condition._column.column_name
|
50
|
+
self._right_col: str = where.right_condition._column.column_name
|
51
|
+
self._compareop = where._compare
|
52
|
+
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
64
53
|
|
65
|
-
|
66
|
-
|
54
|
+
# COMMENT: When multiple columns reference the same table, we need to create an alias to maintain clear references.
|
55
|
+
self._alias: Optional[str] = alias
|
67
56
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
self._compareop: str = "="
|
72
|
-
else:
|
73
|
-
_dis: Disassembler[TLeft, TRight] = Disassembler[TLeft, TRight](where)
|
74
|
-
self._left_col: str = _dis.cond_1.name
|
75
|
-
self._right_col: str = _dis.cond_2.name
|
76
|
-
self._compareop: str = _dis.compare_op
|
57
|
+
self._from_clause = ClauseInfo(self.right_table, alias_table=alias, context=self._context)
|
58
|
+
self._left_table_clause = ClauseInfo(self.left_table, column=self.left_col, alias_clause=None, context=self._create_partial_context())
|
59
|
+
self._right_table_clause = ClauseInfo(self.right_table, column=self.right_col, alias_clause=None, context=self._create_partial_context())
|
77
60
|
|
78
|
-
def __eq__(self, __value:
|
61
|
+
def __eq__(self, __value: JoinSelector) -> bool:
|
79
62
|
return isinstance(__value, JoinSelector) and self.__hash__() == __value.__hash__()
|
80
63
|
|
81
64
|
def __hash__(self) -> int:
|
82
65
|
return hash(
|
83
66
|
(
|
84
|
-
self.
|
85
|
-
self.
|
67
|
+
self.left_table,
|
68
|
+
self.right_table,
|
86
69
|
self._by,
|
87
70
|
self._left_col,
|
88
71
|
self._right_col,
|
@@ -90,27 +73,51 @@ class JoinSelector[TLeft, TRight](IQuery):
|
|
90
73
|
)
|
91
74
|
)
|
92
75
|
|
76
|
+
def _create_partial_context(self) -> ClauseInfoContext:
|
77
|
+
"""
|
78
|
+
Only use table_context from global context
|
79
|
+
"""
|
80
|
+
if not self._context:
|
81
|
+
return ClauseInfoContext()
|
82
|
+
return ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
83
|
+
|
93
84
|
@classmethod
|
94
|
-
def join_selectors(cls, *args:
|
85
|
+
def join_selectors(cls, *args: JoinSelector) -> str:
|
95
86
|
return "\n".join([x.query for x in args])
|
96
87
|
|
97
88
|
@property
|
98
89
|
@override
|
99
90
|
def query(self) -> str:
|
100
|
-
|
101
|
-
# table_name.first col = table_name.second_col
|
102
|
-
|
103
|
-
left_col = f"{self._orig_table.__table_name__}.{self._left_col}"
|
104
|
-
right_col = f"{self._table_right.__table_name__}.{self._right_col}"
|
91
|
+
self._context = ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
105
92
|
list_ = [
|
106
93
|
self._by.value, # inner join
|
107
|
-
self.
|
94
|
+
self._from_clause.query,
|
108
95
|
"ON",
|
109
|
-
|
96
|
+
self._left_table_clause.query,
|
110
97
|
self._compareop, # =
|
111
|
-
|
98
|
+
self._right_table_clause.query,
|
112
99
|
]
|
113
|
-
return " ".join(list_)
|
100
|
+
return " ".join([x for x in list_ if x is not None])
|
101
|
+
|
102
|
+
@property
|
103
|
+
def left_table(self) -> TLeft:
|
104
|
+
return self._orig_table
|
105
|
+
|
106
|
+
@property
|
107
|
+
def right_table(self) -> TRight:
|
108
|
+
return self._right_table
|
109
|
+
|
110
|
+
@property
|
111
|
+
def left_col(self) -> str:
|
112
|
+
return self._left_col
|
113
|
+
|
114
|
+
@property
|
115
|
+
def right_col(self) -> str:
|
116
|
+
return self._right_col
|
117
|
+
|
118
|
+
@property
|
119
|
+
def alias(self) -> str:
|
120
|
+
return self._alias
|
114
121
|
|
115
122
|
@classmethod
|
116
123
|
def sort_join_selectors(cls, joins: set[JoinSelector]) -> tuple[JoinSelector]:
|
@@ -121,11 +128,11 @@ class JoinSelector[TLeft, TRight](IQuery):
|
|
121
128
|
join_object_map: dict[str, list[JoinSelector]] = defaultdict(list)
|
122
129
|
|
123
130
|
for obj in joins:
|
124
|
-
join_object_map[obj.
|
131
|
+
join_object_map[obj.left_table].append(obj)
|
125
132
|
|
126
133
|
graph: dict[Type[Table], list[Type[Table]]] = defaultdict(list)
|
127
134
|
for join in joins:
|
128
|
-
graph[join.
|
135
|
+
graph[join.left_table].append(join.right_table)
|
129
136
|
|
130
137
|
sorted_graph = DFSTraversal.sort(graph)[::-1]
|
131
138
|
|
@@ -1,28 +1,36 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
|
2
|
+
import typing as tp
|
3
3
|
|
4
|
-
from ormlambda.
|
4
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
5
|
+
from ormlambda.sql.types import ColumnType
|
6
|
+
from ormlambda.sql.clause_info import AggregateFunctionBase
|
5
7
|
|
8
|
+
from ormlambda.statements import OrderType
|
6
9
|
|
7
|
-
if TYPE_CHECKING:
|
8
|
-
from ormlambda import Table
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
class Order(AggregateFunctionBase):
|
12
|
+
@staticmethod
|
13
|
+
def FUNCTION_NAME() -> str:
|
14
|
+
return "ORDER BY"
|
12
15
|
|
16
|
+
def __init__[TProp](
|
17
|
+
self,
|
18
|
+
column: tuple[ColumnType[TProp], ...] | ColumnType[TProp],
|
19
|
+
order_type: tp.Iterable[OrderType],
|
20
|
+
context: ClauseContextType = None,
|
21
|
+
):
|
22
|
+
super().__init__(
|
23
|
+
table=None,
|
24
|
+
column=column,
|
25
|
+
context=context,
|
26
|
+
)
|
13
27
|
|
14
|
-
|
15
|
-
ORDER = "ORDER BY"
|
16
|
-
|
17
|
-
def __init__[*Ts](self, instance: T, lambda_query: Callable[[Any], tuple[*Ts]], order_type: Iterable[OrderType]) -> None:
|
18
|
-
super().__init__(instance, lambda_query)
|
19
|
-
|
20
|
-
if isinstance(order_type, str) or not isinstance(order_type, Iterable):
|
28
|
+
if isinstance(order_type, str) or not isinstance(order_type, tp.Iterable):
|
21
29
|
order_type = (order_type,)
|
22
30
|
|
23
31
|
self._order_type: list[OrderType] = [self.__cast_to_OrderType(x) for x in order_type]
|
24
32
|
|
25
|
-
def __cast_to_OrderType(self, _value: Any) -> Iterable[OrderType]:
|
33
|
+
def __cast_to_OrderType(self, _value: tp.Any) -> tp.Iterable[OrderType]:
|
26
34
|
if isinstance(_value, OrderType):
|
27
35
|
return _value
|
28
36
|
|
@@ -33,15 +41,20 @@ class OrderQuery[T: Table](DecompositionQueryBase[T]):
|
|
33
41
|
pass
|
34
42
|
raise Exception(f"order_type param only can be 'ASC' or 'DESC' string or '{OrderType.__name__}' enum")
|
35
43
|
|
36
|
-
|
37
|
-
return None
|
38
|
-
|
39
|
-
@override
|
44
|
+
@tp.override
|
40
45
|
@property
|
41
46
|
def query(self) -> str:
|
42
|
-
|
47
|
+
string_columns: list[str] = []
|
48
|
+
columns = self.unresolved_column
|
49
|
+
|
50
|
+
# if this attr is not iterable means that we only pass one column without wrapped in a list or tuple
|
51
|
+
if not isinstance(columns, tp.Iterable):
|
52
|
+
columns = (columns,)
|
53
|
+
assert len(columns) == len(self._order_type)
|
54
|
+
|
55
|
+
context = ClauseInfoContext(table_context=self._context._table_context, clause_context=None) if self._context else None
|
56
|
+
for index, clause in enumerate(self._convert_into_clauseInfo(columns, context)):
|
57
|
+
clause.alias_clause = None
|
58
|
+
string_columns.append(f"{clause.query} {self._order_type[index].value}")
|
43
59
|
|
44
|
-
|
45
|
-
for index, x in enumerate(self.all_clauses):
|
46
|
-
query.append(f"{x.query} {self._order_type[index].value}")
|
47
|
-
return f"{self.ORDER} {", ".join(query)}"
|
60
|
+
return f"{self.FUNCTION_NAME()} {', '.join(string_columns)}"
|