ormlambda 3.35.3__py3-none-any.whl → 4.0.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 +79 -51
- ormlambda/caster/caster.py +6 -1
- ormlambda/common/abstract_classes/__init__.py +0 -2
- ormlambda/common/enums/__init__.py +1 -0
- ormlambda/common/enums/order_type.py +9 -0
- ormlambda/common/errors/__init__.py +13 -3
- ormlambda/common/global_checker.py +86 -8
- ormlambda/common/interfaces/IQueryCommand.py +2 -2
- ormlambda/common/interfaces/__init__.py +0 -2
- ormlambda/dialects/__init__.py +75 -3
- ormlambda/dialects/default/base.py +1 -1
- ormlambda/dialects/mysql/__init__.py +35 -78
- ormlambda/dialects/mysql/base.py +226 -40
- ormlambda/dialects/mysql/clauses/ST_AsText.py +26 -0
- ormlambda/dialects/mysql/clauses/ST_Contains.py +30 -0
- ormlambda/dialects/mysql/clauses/__init__.py +1 -0
- ormlambda/dialects/mysql/repository/__init__.py +1 -0
- ormlambda/{databases/my_sql → dialects/mysql/repository}/repository.py +0 -5
- ormlambda/dialects/mysql/types.py +6 -0
- ormlambda/engine/base.py +26 -4
- ormlambda/errors.py +9 -0
- ormlambda/model/base_model.py +3 -10
- ormlambda/repository/base_repository.py +1 -1
- ormlambda/repository/interfaces/IRepositoryBase.py +0 -7
- ormlambda/repository/response.py +12 -7
- ormlambda/sql/__init__.py +12 -3
- ormlambda/sql/clause_info/__init__.py +0 -2
- ormlambda/sql/clause_info/clause_info.py +94 -76
- ormlambda/sql/clause_info/interface/IAggregate.py +14 -4
- ormlambda/sql/clause_info/interface/IClauseInfo.py +6 -11
- ormlambda/sql/clauses/alias.py +6 -37
- ormlambda/sql/clauses/count.py +21 -36
- ormlambda/sql/clauses/group_by.py +13 -19
- ormlambda/sql/clauses/having.py +2 -6
- ormlambda/sql/clauses/insert.py +3 -3
- ormlambda/sql/clauses/interfaces/__init__.py +0 -1
- ormlambda/sql/clauses/join/join_context.py +5 -12
- ormlambda/sql/clauses/joins.py +34 -52
- ormlambda/sql/clauses/limit.py +1 -2
- ormlambda/sql/clauses/offset.py +1 -2
- ormlambda/sql/clauses/order.py +17 -21
- ormlambda/sql/clauses/select.py +56 -28
- ormlambda/sql/clauses/update.py +13 -10
- ormlambda/sql/clauses/where.py +20 -39
- ormlambda/sql/column/__init__.py +1 -0
- ormlambda/sql/column/column.py +19 -12
- ormlambda/sql/column/column_proxy.py +117 -0
- ormlambda/sql/column_table_proxy.py +23 -0
- ormlambda/sql/comparer.py +31 -65
- ormlambda/sql/compiler.py +248 -58
- ormlambda/sql/context/__init__.py +304 -0
- ormlambda/sql/ddl.py +19 -5
- ormlambda/sql/elements.py +3 -0
- ormlambda/sql/foreign_key.py +42 -64
- ormlambda/sql/functions/__init__.py +0 -1
- ormlambda/sql/functions/concat.py +35 -38
- ormlambda/sql/functions/max.py +12 -36
- ormlambda/sql/functions/min.py +13 -28
- ormlambda/sql/functions/sum.py +17 -33
- ormlambda/sql/sqltypes.py +2 -0
- ormlambda/sql/table/__init__.py +1 -0
- ormlambda/sql/table/table.py +31 -45
- ormlambda/sql/table/table_proxy.py +88 -0
- ormlambda/sql/type_api.py +4 -1
- ormlambda/sql/types.py +15 -12
- ormlambda/statements/__init__.py +0 -2
- ormlambda/statements/base_statement.py +51 -84
- ormlambda/statements/interfaces/IStatements.py +77 -123
- ormlambda/statements/interfaces/__init__.py +1 -1
- ormlambda/statements/query_builder.py +296 -128
- ormlambda/statements/statements.py +120 -110
- ormlambda/statements/types.py +5 -25
- ormlambda/util/__init__.py +7 -100
- ormlambda/util/langhelpers.py +102 -0
- ormlambda/util/module_tree/dynamic_module.py +1 -1
- ormlambda/util/preloaded.py +80 -0
- ormlambda/util/typing.py +12 -3
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/METADATA +29 -31
- ormlambda-4.0.0.dist-info/RECORD +139 -0
- ormlambda/common/abstract_classes/clause_info_converter.py +0 -65
- ormlambda/common/abstract_classes/decomposition_query.py +0 -141
- ormlambda/common/abstract_classes/query_base.py +0 -15
- ormlambda/common/interfaces/ICustomAlias.py +0 -7
- ormlambda/common/interfaces/IDecompositionQuery.py +0 -33
- ormlambda/databases/__init__.py +0 -4
- ormlambda/databases/my_sql/__init__.py +0 -3
- ormlambda/databases/my_sql/clauses/ST_AsText.py +0 -37
- ormlambda/databases/my_sql/clauses/ST_Contains.py +0 -36
- ormlambda/databases/my_sql/clauses/__init__.py +0 -14
- ormlambda/databases/my_sql/clauses/count.py +0 -33
- ormlambda/databases/my_sql/clauses/delete.py +0 -9
- ormlambda/databases/my_sql/clauses/drop_table.py +0 -26
- ormlambda/databases/my_sql/clauses/group_by.py +0 -17
- ormlambda/databases/my_sql/clauses/having.py +0 -12
- ormlambda/databases/my_sql/clauses/insert.py +0 -9
- ormlambda/databases/my_sql/clauses/joins.py +0 -14
- ormlambda/databases/my_sql/clauses/limit.py +0 -6
- ormlambda/databases/my_sql/clauses/offset.py +0 -6
- ormlambda/databases/my_sql/clauses/order.py +0 -8
- ormlambda/databases/my_sql/clauses/update.py +0 -8
- ormlambda/databases/my_sql/clauses/upsert.py +0 -9
- ormlambda/databases/my_sql/clauses/where.py +0 -7
- ormlambda/dialects/interface/__init__.py +0 -1
- ormlambda/dialects/interface/dialect.py +0 -78
- ormlambda/sql/clause_info/aggregate_function_base.py +0 -96
- ormlambda/sql/clause_info/clause_info_context.py +0 -87
- ormlambda/sql/clauses/interfaces/ISelect.py +0 -17
- ormlambda/sql/clauses/new_join.py +0 -119
- ormlambda/util/load_module.py +0 -21
- ormlambda/util/plugin_loader.py +0 -32
- ormlambda-3.35.3.dist-info/RECORD +0 -159
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/__init__.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/caster.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/__init__.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/boolean.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/bytes.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/date.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/datetime.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/decimal.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/float.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/int.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/iterable.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/json.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/none.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/point.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql}/caster/types/string.py +0 -0
- /ormlambda/{databases/my_sql → dialects/mysql/repository}/pool_types.py +0 -0
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/AUTHORS +0 -0
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/LICENSE +0 -0
- {ormlambda-3.35.3.dist-info → ormlambda-4.0.0.dist-info}/WHEEL +0 -0
@@ -6,10 +6,9 @@ from ormlambda.sql.comparer import Comparer
|
|
6
6
|
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
|
-
from ormlambda.statements.interfaces.IStatements import
|
9
|
+
from ormlambda.statements.interfaces.IStatements import IStatements
|
10
10
|
from ormlambda.sql.clause_info import ClauseInfo
|
11
11
|
from ormlambda import Table
|
12
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
13
12
|
from ormlambda.common.enums.join_type import JoinType
|
14
13
|
from ormlambda.dialects import Dialect
|
15
14
|
|
@@ -17,34 +16,31 @@ if TYPE_CHECKING:
|
|
17
16
|
type TupleJoinType[LTable: Table, LProp, RTable: Table, RProp] = tuple[Comparer]
|
18
17
|
|
19
18
|
|
20
|
-
class JoinContext[TParent: Table
|
19
|
+
class JoinContext[TParent: Table]:
|
21
20
|
def __init__(
|
22
21
|
self,
|
23
|
-
statements:
|
22
|
+
statements: IStatements[TParent],
|
24
23
|
joins: tuple,
|
25
|
-
context: ClauseContextType,
|
26
24
|
dialect: Dialect,
|
27
25
|
) -> None:
|
28
26
|
self._statements = statements
|
29
27
|
self._parent: TParent = statements.model
|
30
28
|
self._joins: Iterable[tuple[Comparer, JoinType]] = joins
|
31
|
-
self._context: ClauseContextType = context
|
32
29
|
self._dialect: Dialect = dialect
|
33
30
|
|
34
|
-
def __enter__(self) ->
|
31
|
+
def __enter__(self) -> IStatements[TParent]:
|
35
32
|
for comparer, by in self._joins:
|
36
33
|
fk_clause, alias = self.get_fk_clause(comparer)
|
37
34
|
|
38
35
|
foreign_key: ForeignKey = ForeignKey(comparer=comparer, clause_name=alias, keep_alive=True, dialect=self._dialect)
|
39
36
|
fk_clause.alias_table = foreign_key.get_alias(self._dialect)
|
40
|
-
self._context.add_clause_to_context(fk_clause)
|
41
37
|
setattr(self._parent, alias, foreign_key)
|
42
38
|
|
43
39
|
# TODOH [x]: We need to preserve the 'foreign_key' variable while inside the 'with' clause.
|
44
40
|
# Keep in mind that 'ForeignKey.stored_calls' is cleared every time we call methods like
|
45
41
|
# .select(), .select_one(), .insert(), .update(), or .count(). This means we only retain
|
46
42
|
# the context from the first call of any of these methods.
|
47
|
-
|
43
|
+
# FIXME [ ]: See how to deal with context when using JoinContext class and PATH_CONTEXT
|
48
44
|
|
49
45
|
return self
|
50
46
|
|
@@ -54,10 +50,7 @@ class JoinContext[TParent: Table, TRepo]:
|
|
54
50
|
|
55
51
|
for comparer, _ in self._joins:
|
56
52
|
_, attribute = self.get_fk_clause(comparer)
|
57
|
-
fk: ForeignKey = getattr(self._parent, attribute)
|
58
53
|
delattr(self._parent, attribute)
|
59
|
-
del self._context._table_context[fk.tright]
|
60
|
-
ForeignKey.stored_calls.remove(fk)
|
61
54
|
return None
|
62
55
|
|
63
56
|
def __getattr__(self, name: str) -> TParent:
|
ormlambda/sql/clauses/joins.py
CHANGED
@@ -1,15 +1,13 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from collections import defaultdict
|
3
|
-
from typing import override, Optional, TYPE_CHECKING
|
3
|
+
from typing import override, Optional, TYPE_CHECKING
|
4
4
|
|
5
5
|
|
6
6
|
from ormlambda.util.module_tree.dfs_traversal import DFSTraversal
|
7
7
|
from ormlambda.common.interfaces.IJoinSelector import IJoinSelector
|
8
8
|
from ormlambda.common.interfaces.IQueryCommand import IQuery
|
9
|
-
from ormlambda import JoinType
|
10
|
-
from ormlambda.sql.clause_info import ClauseInfo
|
9
|
+
from ormlambda import ColumnProxy, JoinType
|
11
10
|
from ormlambda.sql.comparer import Comparer
|
12
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
13
11
|
from ormlambda.sql.elements import ClauseElement
|
14
12
|
|
15
13
|
|
@@ -34,78 +32,41 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], Cl
|
|
34
32
|
|
35
33
|
@override
|
36
34
|
def __repr__(self) -> str:
|
37
|
-
|
38
|
-
table_col_right: str = f"{self.right_table.table_alias()}.{self._right_col}"
|
39
|
-
|
40
|
-
return f"{IQuery.__name__}: {self.__class__.__name__} ({table_col_left} == {table_col_right})"
|
35
|
+
return f"{IQuery.__name__}: {self.__class__.__name__} ({self.alias})"
|
41
36
|
|
42
37
|
def __init__[LProp, RProp](
|
43
38
|
self,
|
44
39
|
where: Comparer,
|
45
40
|
by: JoinType,
|
46
41
|
alias: Optional[str] = "{table}",
|
47
|
-
context: ClauseContextType = None,
|
48
42
|
*,
|
49
43
|
dialect: Dialect,
|
50
44
|
**kw,
|
51
45
|
) -> None:
|
52
|
-
lcon = where.left_condition
|
53
|
-
rcon = where.right_condition
|
46
|
+
self.lcon: ColumnProxy = where.left_condition
|
47
|
+
self.rcon: ColumnProxy = where.right_condition
|
54
48
|
self._comparer: Comparer = where
|
55
|
-
self._orig_table: TLeft = lcon.table
|
56
|
-
self._right_table: TRight = rcon.table
|
49
|
+
self._orig_table: TLeft = self.lcon.table
|
50
|
+
self._right_table: TRight = self.rcon.table
|
57
51
|
self._by: JoinType = by
|
58
|
-
self._left_col: str = lcon._column.column_name
|
59
|
-
self._right_col: str = rcon._column.column_name
|
52
|
+
self._left_col: str = self.lcon._column.column_name
|
53
|
+
self._right_col: str = self.rcon._column.column_name
|
60
54
|
self._compareop = where._compare
|
61
|
-
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
62
55
|
|
63
56
|
# COMMENT: When multiple columns reference the same table, we need to create an alias to maintain clear references.
|
64
57
|
self._alias: Optional[str] = alias
|
65
58
|
|
66
|
-
self._from_clause = ClauseInfo(self.right_table, alias_table=alias, context=self._context, dialect=dialect, **kw)
|
67
|
-
self._left_table_clause = ClauseInfo(self.left_table, column=self.left_col, alias_clause=None, context=self._create_partial_context(), dialect=dialect, **kw)
|
68
|
-
self._right_table_clause = ClauseInfo(self.right_table, column=self.right_col, alias_clause=None, context=self._create_partial_context(), dialect=dialect, **kw)
|
69
|
-
|
70
59
|
def __eq__(self, __value: JoinSelector) -> bool:
|
71
60
|
return isinstance(__value, JoinSelector) and self.__hash__() == __value.__hash__()
|
72
61
|
|
73
62
|
def __hash__(self) -> int:
|
74
|
-
|
75
|
-
|
76
|
-
self.left_table,
|
77
|
-
self.right_table,
|
78
|
-
self._by,
|
79
|
-
self._left_col,
|
80
|
-
self._right_col,
|
81
|
-
self._compareop,
|
82
|
-
)
|
83
|
-
)
|
84
|
-
|
85
|
-
def _create_partial_context(self) -> ClauseInfoContext:
|
86
|
-
"""
|
87
|
-
Only use table_context from global context
|
88
|
-
"""
|
89
|
-
if not self._context:
|
90
|
-
return ClauseInfoContext()
|
91
|
-
return ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
63
|
+
# Only can add the first instance of a JoinsSelector which has the same alias
|
64
|
+
return hash(self.alias)
|
92
65
|
|
93
66
|
@classmethod
|
94
67
|
def join_selectors(cls, dialect: Dialect, *args: JoinSelector) -> str:
|
95
68
|
return "\n".join([x.query(dialect) for x in args])
|
96
69
|
|
97
|
-
def query(self, dialect: Dialect, **kwargs) -> str:
|
98
|
-
self._context = ClauseInfoContext(clause_context=None, table_context=self._context._table_context)
|
99
|
-
list_ = [
|
100
|
-
self._by.value, # inner join
|
101
|
-
self._from_clause.query(dialect, **kwargs),
|
102
|
-
"ON",
|
103
|
-
self._left_table_clause.query(dialect, **kwargs),
|
104
|
-
self._compareop, # =
|
105
|
-
self._right_table_clause.query(dialect, **kwargs),
|
106
|
-
]
|
107
|
-
return " ".join([x for x in list_ if x is not None])
|
108
|
-
|
109
70
|
@property
|
110
71
|
def left_table(self) -> TLeft:
|
111
72
|
return self._orig_table
|
@@ -137,9 +98,9 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], Cl
|
|
137
98
|
for obj in joins:
|
138
99
|
join_object_map[obj.left_table].append(obj)
|
139
100
|
|
140
|
-
graph: dict[
|
101
|
+
graph: dict[str, list[str]] = defaultdict(list)
|
141
102
|
for join in joins:
|
142
|
-
graph[join.
|
103
|
+
graph[join.alias].append(join.right_table.__table_name__)
|
143
104
|
|
144
105
|
sorted_graph = DFSTraversal.sort(graph)[::-1]
|
145
106
|
|
@@ -155,5 +116,26 @@ class JoinSelector[TLeft: Table, TRight: Table](IJoinSelector[TLeft, TRight], Cl
|
|
155
116
|
res.extend(tables)
|
156
117
|
return res
|
157
118
|
|
119
|
+
@classmethod
|
120
|
+
def sort_joins_by_alias(cls, joins: set[JoinSelector]) -> tuple[JoinSelector]:
|
121
|
+
# FIXME [x]: How to sort when needed because it's not necessary at this point. It is for testing purpouse
|
122
|
+
if len(joins) == 1:
|
123
|
+
return tuple(joins)
|
124
|
+
|
125
|
+
join_object_map: dict[str, JoinSelector] = {}
|
126
|
+
|
127
|
+
for obj in joins:
|
128
|
+
join_object_map[obj.alias] = obj
|
129
|
+
|
130
|
+
sorted_graph = []
|
131
|
+
|
132
|
+
for alias in sorted([x.alias for x in joins]):
|
133
|
+
sorted_graph.append(join_object_map[alias])
|
134
|
+
|
135
|
+
if not sorted_graph:
|
136
|
+
return tuple(joins)
|
137
|
+
|
138
|
+
return tuple(sorted_graph)
|
139
|
+
|
158
140
|
|
159
141
|
__all__ = ["JoinSelector"]
|
ormlambda/sql/clauses/limit.py
CHANGED
@@ -4,9 +4,8 @@ from ormlambda.sql.elements import ClauseElement
|
|
4
4
|
|
5
5
|
class Limit(ClauseElement):
|
6
6
|
__visit_name__ = "limit"
|
7
|
-
LIMIT = "LIMIT"
|
8
7
|
|
9
|
-
def __init__(self, number: int
|
8
|
+
def __init__(self, number: int) -> None:
|
10
9
|
if not isinstance(number, int):
|
11
10
|
raise ValueError
|
12
11
|
self._number: int = number
|
ormlambda/sql/clauses/offset.py
CHANGED
@@ -4,9 +4,8 @@ from ormlambda.sql.elements import ClauseElement
|
|
4
4
|
|
5
5
|
class Offset(ClauseElement):
|
6
6
|
__visit_name__ = "offset"
|
7
|
-
OFFSET = "OFFSET"
|
8
7
|
|
9
|
-
def __init__(self, number: int
|
8
|
+
def __init__(self, number: int) -> None:
|
10
9
|
if not isinstance(number, int):
|
11
10
|
raise ValueError
|
12
11
|
self._number: int = number
|
ormlambda/sql/clauses/order.py
CHANGED
@@ -1,44 +1,33 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import typing as tp
|
3
3
|
|
4
|
-
from ormlambda.sql.clause_info
|
4
|
+
from ormlambda.sql.clause_info import IAggregate
|
5
5
|
from ormlambda.sql.types import ColumnType
|
6
|
-
from ormlambda.sql.clause_info import AggregateFunctionBase
|
7
6
|
|
8
|
-
from ormlambda.
|
7
|
+
from ormlambda.common.enums import OrderType
|
9
8
|
from ormlambda.sql.elements import ClauseElement
|
10
9
|
|
11
10
|
if tp.TYPE_CHECKING:
|
12
|
-
from ormlambda
|
11
|
+
from ormlambda import ColumnProxy
|
13
12
|
|
14
13
|
|
15
|
-
class Order(
|
14
|
+
class Order[TProp](ClauseElement, IAggregate):
|
16
15
|
__visit_name__ = "order"
|
17
16
|
|
18
|
-
|
19
|
-
def FUNCTION_NAME() -> str:
|
20
|
-
return "ORDER BY"
|
17
|
+
columns: tuple[ColumnType[TProp], ...]
|
21
18
|
|
22
|
-
def __init__
|
19
|
+
def __init__(
|
23
20
|
self,
|
24
|
-
|
21
|
+
*columns: ColumnProxy[TProp],
|
25
22
|
order_type: tp.Iterable[OrderType],
|
26
|
-
context: ClauseContextType = None,
|
27
|
-
*,
|
28
|
-
dialect: Dialect,
|
29
|
-
**kw,
|
30
23
|
):
|
31
|
-
|
32
|
-
|
33
|
-
column=column,
|
34
|
-
context=context,
|
35
|
-
dialect=dialect,
|
36
|
-
**kw,
|
37
|
-
)
|
24
|
+
if isinstance(columns, str) or not isinstance(columns, tp.Iterable):
|
25
|
+
columns = (columns,)
|
38
26
|
|
39
27
|
if isinstance(order_type, str) or not isinstance(order_type, tp.Iterable):
|
40
28
|
order_type = (order_type,)
|
41
29
|
|
30
|
+
self.columns = columns
|
42
31
|
self._order_type: list[OrderType] = [self.__cast_to_OrderType(x) for x in order_type]
|
43
32
|
|
44
33
|
def __cast_to_OrderType(self, _value: tp.Any) -> tp.Iterable[OrderType]:
|
@@ -52,4 +41,11 @@ class Order(AggregateFunctionBase, ClauseElement):
|
|
52
41
|
pass
|
53
42
|
raise Exception(f"order_type param only can be 'ASC' or 'DESC' string or '{OrderType.__name__}' enum")
|
54
43
|
|
44
|
+
def used_columns(self):
|
45
|
+
return self.columns
|
46
|
+
|
47
|
+
@property
|
48
|
+
def dtype(self) -> tp.Any: ...
|
49
|
+
|
50
|
+
|
55
51
|
__all__ = ["Order"]
|
ormlambda/sql/clauses/select.py
CHANGED
@@ -1,51 +1,79 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
+
from ormlambda import ColumnProxy
|
2
3
|
from ormlambda.sql.elements import ClauseElement
|
3
|
-
from typing import Optional, Type,
|
4
|
+
from typing import Any, Iterable, Optional, Type, TYPE_CHECKING
|
4
5
|
|
5
|
-
from ormlambda.sql.clause_info import ClauseInfo
|
6
|
-
from ormlambda.sql.
|
7
|
-
from ormlambda.sql.
|
8
|
-
from ormlambda.common.abstract_classes.decomposition_query import DecompositionQueryBase
|
6
|
+
from ormlambda.sql.clause_info import ClauseInfo, IAggregate
|
7
|
+
from ormlambda.sql.types import SelectCol
|
8
|
+
from ormlambda.sql.table import TableProxy
|
9
9
|
|
10
10
|
if TYPE_CHECKING:
|
11
|
+
from ormlambda.sql.types import AliasType, ColumnType
|
11
12
|
from ormlambda import Table
|
12
|
-
from ormlambda.dialects import Dialect
|
13
13
|
|
14
|
+
type Selectable = ColumnType | TableProxy
|
14
15
|
|
15
|
-
class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ClauseElement):
|
16
|
-
__visit_name__ = "select"
|
17
16
|
|
18
|
-
|
17
|
+
class Select[T: Type[Table]](ClauseElement, IAggregate):
|
18
|
+
__visit_name__ = "select"
|
19
19
|
|
20
20
|
def __init__(
|
21
21
|
self,
|
22
|
-
|
23
|
-
columns:
|
22
|
+
table: T,
|
23
|
+
columns: SelectCol,
|
24
24
|
*,
|
25
25
|
alias_table: AliasType[ClauseInfo] = "{table}",
|
26
|
-
|
27
|
-
|
28
|
-
**kwargs,
|
26
|
+
alias: Optional[AliasType[T]] = None,
|
27
|
+
avoid_duplicates: bool = False,
|
29
28
|
) -> None:
|
30
|
-
|
31
|
-
|
32
|
-
columns,
|
33
|
-
context=context,
|
34
|
-
dialect=dialect,
|
35
|
-
**kwargs,
|
36
|
-
)
|
29
|
+
self._table = table
|
30
|
+
self._columns = columns
|
37
31
|
self._alias_table = alias_table
|
38
|
-
|
39
|
-
self.
|
32
|
+
self._alias = alias
|
33
|
+
self._avoid_duplicates = avoid_duplicates
|
34
|
+
|
35
|
+
@property
|
36
|
+
def columns(self) -> tuple[SelectCol, ...]:
|
37
|
+
if not isinstance(self._columns, Iterable):
|
38
|
+
return [self._columns]
|
39
|
+
return self._columns
|
40
40
|
|
41
41
|
@property
|
42
|
-
def
|
43
|
-
return
|
42
|
+
def table(self) -> TableProxy[T]:
|
43
|
+
return TableProxy(self._table)
|
44
|
+
|
45
|
+
@property
|
46
|
+
def alias(self) -> str:
|
47
|
+
return self._alias
|
48
|
+
|
49
|
+
@alias.setter
|
50
|
+
def alias(self, value) -> None:
|
51
|
+
self._alias = value
|
44
52
|
|
45
53
|
@property
|
46
|
-
def
|
47
|
-
|
48
|
-
|
54
|
+
def avoid_duplicates(self) -> bool:
|
55
|
+
return self._avoid_duplicates
|
56
|
+
|
57
|
+
def used_columns(self):
|
58
|
+
res = []
|
59
|
+
|
60
|
+
for col in self._columns:
|
61
|
+
if isinstance(col, ColumnProxy):
|
62
|
+
res.append(col)
|
63
|
+
|
64
|
+
elif isinstance(col, IAggregate):
|
65
|
+
res.extend(col.used_columns())
|
66
|
+
return res
|
67
|
+
|
68
|
+
@property
|
69
|
+
def dtype(self) -> Any: ...
|
70
|
+
|
71
|
+
def __getitem__(self, key: str) -> SelectCol:
|
72
|
+
for clause in self.columns:
|
73
|
+
if isinstance(clause, ColumnProxy) and key in (clause.column_name, clause.alias, clause.get_full_chain("_")):
|
74
|
+
return clause
|
75
|
+
if isinstance(clause, IAggregate) and key == clause.alias:
|
76
|
+
return clause
|
49
77
|
|
50
78
|
|
51
79
|
__all__ = ["Select"]
|
ormlambda/sql/clauses/update.py
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from typing import Type, override, Any, TYPE_CHECKING, Optional
|
3
3
|
|
4
|
-
from ormlambda import Table, Column
|
5
|
-
from ormlambda.caster.caster import Caster
|
4
|
+
from ormlambda import Table, Column, ColumnProxy
|
6
5
|
from .where import Where
|
7
6
|
from ormlambda.common.abstract_classes import NonQueryBase
|
8
7
|
from .interfaces import IUpdate
|
@@ -30,9 +29,15 @@ class UpdateKeyError(KeyError):
|
|
30
29
|
class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseElement):
|
31
30
|
__visit_name__ = "update"
|
32
31
|
|
33
|
-
def __init__(
|
32
|
+
def __init__(
|
33
|
+
self,
|
34
|
+
model: T,
|
35
|
+
repository: Any,
|
36
|
+
where: list[Where],
|
37
|
+
engine: Engine,
|
38
|
+
) -> None:
|
34
39
|
super().__init__(model, repository, engine=engine)
|
35
|
-
self._where: Optional[
|
40
|
+
self._where: Optional[Where] = where
|
36
41
|
|
37
42
|
@override
|
38
43
|
@property
|
@@ -42,11 +47,9 @@ class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseEleme
|
|
42
47
|
@override
|
43
48
|
def execute(self) -> None:
|
44
49
|
if self._where:
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
# TODOH []: Refactor this part. We need to get only the columns withouth __table_name__ preffix
|
49
|
-
self._query += " " + query_with_table.replace(x.left_condition(self._dialect).table.__table_name__ + ".", "")
|
50
|
+
where_string = self._where.compile(self._engine.dialect).string
|
51
|
+
|
52
|
+
self._query += " " + where_string
|
50
53
|
return self._engine.repository.execute_with_values(self._query, self._values)
|
51
54
|
|
52
55
|
@override
|
@@ -61,7 +64,7 @@ class Update[T: Type[Table], TRepo](NonQueryBase[T, TRepo], IUpdate, ClauseEleme
|
|
61
64
|
if not hasattr(self._model, col):
|
62
65
|
raise UpdateKeyError(self._model, col)
|
63
66
|
col = getattr(self._model, col)
|
64
|
-
if not isinstance(col, Column):
|
67
|
+
if not isinstance(col, Column | ColumnProxy):
|
65
68
|
raise ValueError
|
66
69
|
|
67
70
|
if self.__is_valid__(col):
|
ormlambda/sql/clauses/where.py
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import typing as tp
|
3
|
+
from ormlambda import ColumnProxy
|
3
4
|
from ormlambda.sql.comparer import Comparer
|
4
|
-
from ormlambda.sql.clause_info import AggregateFunctionBase
|
5
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, ClauseContextType
|
6
5
|
from ormlambda.sql.elements import ClauseElement
|
7
6
|
|
8
7
|
if tp.TYPE_CHECKING:
|
9
|
-
from ormlambda.
|
8
|
+
from ormlambda.statements.types import WhereTypes
|
10
9
|
|
11
10
|
|
12
|
-
class Where(
|
11
|
+
class Where[T](ClauseElement):
|
13
12
|
"""
|
14
13
|
The purpose of this class is to create 'WHERE' condition queries properly.
|
15
14
|
"""
|
@@ -18,48 +17,30 @@ class Where(AggregateFunctionBase, ClauseElement):
|
|
18
17
|
|
19
18
|
def __init__(
|
20
19
|
self,
|
21
|
-
*comparer:
|
20
|
+
*comparer: WhereTypes,
|
22
21
|
restrictive: bool = True,
|
23
|
-
context: ClauseContextType = None,
|
24
22
|
) -> None:
|
25
|
-
self.
|
26
|
-
self.
|
27
|
-
self._context: ClauseContextType = context if context else ClauseInfoContext()
|
23
|
+
self.comparer: set[Comparer] = set(comparer)
|
24
|
+
self.restrictive: bool = restrictive
|
28
25
|
|
29
|
-
|
30
|
-
|
31
|
-
return "WHERE"
|
26
|
+
def used_columns(self) -> tp.Iterable[ColumnProxy]:
|
27
|
+
res = []
|
32
28
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
comparer = Comparer.join_comparers(
|
37
|
-
self._comparer,
|
38
|
-
restrictive=self._restrictive,
|
39
|
-
context=context,
|
40
|
-
dialect=dialect,
|
41
|
-
**kwargs,
|
42
|
-
)
|
43
|
-
else:
|
44
|
-
comparer = self._comparer
|
45
|
-
return f"{self.FUNCTION_NAME()} {comparer}"
|
29
|
+
for comparer in self.comparer:
|
30
|
+
if isinstance(comparer.left_condition, ColumnProxy):
|
31
|
+
res.append(comparer.left_condition)
|
46
32
|
|
47
|
-
|
48
|
-
|
49
|
-
return None
|
33
|
+
if isinstance(comparer.right_condition, ColumnProxy):
|
34
|
+
res.append(comparer.right_condition)
|
50
35
|
|
51
|
-
|
52
|
-
def join_condition(cls, wheres: tp.Iterable[Where], restrictive: bool, context: ClauseInfoContext, dialect: Dialect = None) -> str:
|
53
|
-
if not isinstance(wheres, tp.Iterable):
|
54
|
-
wheres = (wheres,)
|
36
|
+
return res
|
55
37
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
return cls(*comparers, restrictive=restrictive, context=context).query(dialect=dialect)
|
38
|
+
def add_comparers(self, comparers: tp.Iterable[Comparer]) -> None:
|
39
|
+
if not isinstance(comparers, tp.Iterable):
|
40
|
+
comparers = [comparers]
|
41
|
+
|
42
|
+
for comparer in comparers:
|
43
|
+
self.comparer.add(comparer)
|
63
44
|
|
64
45
|
|
65
46
|
__all__ = ["Where"]
|
ormlambda/sql/column/__init__.py
CHANGED
ormlambda/sql/column/column.py
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
|
-
import
|
3
|
-
from
|
4
|
-
from ormlambda.sql.types import TableType, ComparerType, ColumnType
|
2
|
+
from typing import Annotated, Any, Iterable, Type, Optional, TYPE_CHECKING, get_type_hints, overload, get_origin, get_args, ClassVar
|
3
|
+
from ormlambda.sql.elements import ClauseElement
|
4
|
+
from ormlambda.sql.types import TableType, ComparerType, ColumnType, TypeEngine
|
5
5
|
from ormlambda import ConditionType
|
6
|
-
from ormlambda
|
6
|
+
from ormlambda import util
|
7
7
|
|
8
8
|
if TYPE_CHECKING:
|
9
9
|
import re
|
@@ -23,7 +23,8 @@ from ormlambda.types import (
|
|
23
23
|
)
|
24
24
|
|
25
25
|
|
26
|
-
class Column[TProp]:
|
26
|
+
class Column[TProp](ClauseElement):
|
27
|
+
__visit_name__ = "column"
|
27
28
|
PRIVATE_CHAR: ClassVar[str] = "_"
|
28
29
|
|
29
30
|
_dbtype: Optional[TypeEngine]
|
@@ -44,6 +45,8 @@ class Column[TProp]:
|
|
44
45
|
"_check",
|
45
46
|
)
|
46
47
|
|
48
|
+
table: Type[Table]
|
49
|
+
|
47
50
|
@overload
|
48
51
|
def __init__(self, *, column_name: str): ...
|
49
52
|
|
@@ -100,8 +103,8 @@ class Column[TProp]:
|
|
100
103
|
def __str__(self) -> str:
|
101
104
|
return self.table.__table_name__ + "." + self.column_name
|
102
105
|
|
103
|
-
def __set_name__
|
104
|
-
self.table: TableType[
|
106
|
+
def __set_name__(self, owner: TableType[Table], name: str) -> None:
|
107
|
+
self.table: TableType[Table] = owner
|
105
108
|
self.column_name = name
|
106
109
|
self.__private_name = self.PRIVATE_CHAR + name
|
107
110
|
|
@@ -133,9 +136,12 @@ class Column[TProp]:
|
|
133
136
|
)
|
134
137
|
)
|
135
138
|
|
139
|
+
@util.preload_module("ormlambda.sql.type_api")
|
136
140
|
def _fill_from_annotations[T: Table](self, obj: Type[T], name: str) -> None:
|
137
141
|
"""Read the metada when using Annotated typing class, and set the attributes accordingly"""
|
138
142
|
|
143
|
+
TypeEngine = util.preloaded.sql_type_api.TypeEngine
|
144
|
+
|
139
145
|
annotations = get_type_hints(obj, include_extras=True)
|
140
146
|
if name in annotations:
|
141
147
|
annotation = annotations[name]
|
@@ -182,9 +188,9 @@ class Column[TProp]:
|
|
182
188
|
def dbtype(self) -> TypeEngine[TProp]:
|
183
189
|
return self._dbtype if self._dbtype else self.dtype
|
184
190
|
|
185
|
-
@
|
191
|
+
@util.preload_module("ormlambda.sql.comparer")
|
186
192
|
def __comparer_creator(self, other: ColumnType, compare: ComparerType) -> Comparer:
|
187
|
-
|
193
|
+
Comparer = util.preloaded.sql_comparer.Comparer
|
188
194
|
|
189
195
|
return Comparer(self, other, compare)
|
190
196
|
|
@@ -215,20 +221,21 @@ class Column[TProp]:
|
|
215
221
|
def not_contains(self, other: ColumnType) -> Comparer:
|
216
222
|
return self.__comparer_creator(other, ConditionType.NOT_IN.value)
|
217
223
|
|
224
|
+
@util.preload_module("ormlambda.sql.comparer")
|
218
225
|
def regex(self, pattern: str, flags: Optional[re.RegexFlag | Iterable[re.RegexFlag]] = None) -> Regex:
|
219
|
-
|
226
|
+
Regex = util.preloaded.sql_comparer.Regex
|
220
227
|
|
221
228
|
if not isinstance(flags, Iterable):
|
222
229
|
flags = (flags,)
|
223
230
|
return Regex(
|
224
231
|
left_condition=self,
|
225
232
|
right_condition=pattern,
|
226
|
-
context=None,
|
227
233
|
flags=flags,
|
228
234
|
)
|
229
235
|
|
236
|
+
@util.preload_module("ormlambda.sql.comparer")
|
230
237
|
def like(self, pattern: str) -> Like:
|
231
|
-
|
238
|
+
Like = util.preloaded.sql_comparer.Like
|
232
239
|
|
233
240
|
return Like(self, pattern)
|
234
241
|
|