ormlambda 3.11.1__py3-none-any.whl → 3.12.2__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 +1 -1
- ormlambda/caster/caster.py +1 -1
- ormlambda/common/abstract_classes/clause_info_converter.py +73 -0
- ormlambda/common/abstract_classes/decomposition_query.py +12 -68
- ormlambda/common/abstract_classes/non_query_base.py +2 -2
- ormlambda/common/interfaces/ICustomAlias.py +1 -1
- ormlambda/components/delete/abstract_delete.py +2 -2
- ormlambda/components/join/__init__.py +1 -0
- ormlambda/databases/my_sql/clauses/delete.py +0 -1
- ormlambda/databases/my_sql/clauses/drop_table.py +8 -5
- ormlambda/databases/my_sql/clauses/group_by.py +1 -2
- ormlambda/databases/my_sql/clauses/select.py +2 -0
- ormlambda/databases/my_sql/clauses/upsert.py +8 -4
- ormlambda/databases/my_sql/query_builder.py +158 -0
- ormlambda/databases/my_sql/statements.py +19 -156
- ormlambda/engine/__init__.py +1 -1
- ormlambda/engine/url.py +4 -1
- ormlambda/sql/clause_info/__init__.py +2 -1
- ormlambda/sql/clause_info/aggregate_function_base.py +86 -0
- ormlambda/sql/clause_info/clause_info.py +1 -98
- ormlambda/sql/clause_info/interface/IClauseInfo.py +37 -0
- ormlambda/sql/clause_info/interface/__init__.py +1 -0
- ormlambda/sql/column.py +3 -0
- ormlambda/statements/base_statement.py +6 -2
- ormlambda/statements/interfaces/IStatements.py +25 -19
- ormlambda/utils/module_tree/dynamic_module.py +3 -2
- {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/METADATA +66 -16
- {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/RECORD +31 -28
- {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/WHEEL +1 -1
- ormlambda/databases/my_sql/caster/read.py +0 -39
- ormlambda/databases/my_sql/caster/write.py +0 -37
- /ormlambda/{databases/my_sql → components/join}/join_context.py +0 -0
- {ormlambda-3.11.1.dist-info → ormlambda-3.12.2.dist-info}/LICENSE +0 -0
ormlambda/__init__.py
CHANGED
@@ -21,4 +21,4 @@ from .model.base_model import (
|
|
21
21
|
ORM as ORM,
|
22
22
|
) # COMMENT: to avoid relative import we need to import BaseModel after import Table,Column, ForeignKey, IRepositoryBase and Disassembler
|
23
23
|
|
24
|
-
from .engine import create_engine, URL # noqa: F401
|
24
|
+
from .engine import create_engine, URL, make_url # noqa: F401
|
ormlambda/caster/caster.py
CHANGED
@@ -0,0 +1,73 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
import typing as tp
|
3
|
+
from ormlambda import Table
|
4
|
+
|
5
|
+
from ormlambda.sql.clause_info import ClauseInfo, AggregateFunctionBase
|
6
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseContextType
|
7
|
+
from ormlambda import ForeignKey
|
8
|
+
|
9
|
+
from ormlambda.sql.types import AliasType, TableType, ColumnType
|
10
|
+
|
11
|
+
|
12
|
+
class ClauseInfoConverter[T, TProp](tp.Protocol):
|
13
|
+
@classmethod
|
14
|
+
def convert(cls, data: T, alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[T]]: ...
|
15
|
+
|
16
|
+
|
17
|
+
class ConvertFromAnyType(ClauseInfoConverter[None, None]):
|
18
|
+
@classmethod
|
19
|
+
def convert(cls, data: tp.Any, alias_table: AliasType = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[None]]:
|
20
|
+
return [
|
21
|
+
ClauseInfo[None](
|
22
|
+
table=None,
|
23
|
+
column=data,
|
24
|
+
alias_table=alias_table,
|
25
|
+
alias_clause=kwargs.get("alias", None),
|
26
|
+
context=context,
|
27
|
+
)
|
28
|
+
]
|
29
|
+
|
30
|
+
|
31
|
+
class ConvertFromForeignKey[LT: Table, RT: Table](ClauseInfoConverter[RT, None]):
|
32
|
+
@classmethod
|
33
|
+
def convert(cls, data: ForeignKey[LT, RT], alias_table=None, context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[RT]]:
|
34
|
+
return ConvertFromTable[RT].convert(data.tright, data.alias, context, **kwargs)
|
35
|
+
|
36
|
+
|
37
|
+
class ConvertFromColumn[TProp](ClauseInfoConverter[None, TProp]):
|
38
|
+
@classmethod
|
39
|
+
def convert(cls, data: ColumnType[TProp], alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[None]]:
|
40
|
+
# COMMENT: if the property belongs to the main class, the columnn name in not prefixed. This only done if the property comes from any join.
|
41
|
+
attributes = {
|
42
|
+
"table": data.table,
|
43
|
+
"column": data,
|
44
|
+
"alias_table": alias_table,
|
45
|
+
"alias_clause": "{table}_{column}",
|
46
|
+
"context": context,
|
47
|
+
**kwargs,
|
48
|
+
}
|
49
|
+
clause_info = ClauseInfo[None](**attributes)
|
50
|
+
return [clause_info]
|
51
|
+
|
52
|
+
|
53
|
+
class ConvertFromIAggregate(ClauseInfoConverter[None, None]):
|
54
|
+
@classmethod
|
55
|
+
def convert(cls, data: AggregateFunctionBase, alias_table=None, context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[None]]:
|
56
|
+
return [data]
|
57
|
+
|
58
|
+
|
59
|
+
class ConvertFromTable[T: Table](ClauseInfoConverter[T, None]):
|
60
|
+
@classmethod
|
61
|
+
def convert(cls, data: T, alias_table: AliasType[ColumnType] = "{table}", context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[T]]:
|
62
|
+
"""
|
63
|
+
if the data is Table, means that we want to retrieve all columns
|
64
|
+
"""
|
65
|
+
return cls._extract_all_clauses(data, alias_table, context, **kwargs)
|
66
|
+
|
67
|
+
@staticmethod
|
68
|
+
def _extract_all_clauses(table: TableType[T], alias_table: AliasType[ColumnType], context: ClauseContextType = None, **kwargs) -> list[ClauseInfo[TableType[T]]]:
|
69
|
+
# all columns
|
70
|
+
column_clauses = []
|
71
|
+
for column in table.get_columns():
|
72
|
+
column_clauses.extend(ConvertFromColumn.convert(column, alias_table=alias_table, context=context, **kwargs))
|
73
|
+
return column_clauses
|
@@ -1,6 +1,5 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
import typing as tp
|
3
|
-
import abc
|
4
3
|
from ormlambda import Table, Column
|
5
4
|
|
6
5
|
from ormlambda.common.interfaces import IDecompositionQuery, ICustomAlias
|
@@ -10,8 +9,15 @@ from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext, Cla
|
|
10
9
|
from ormlambda import ForeignKey
|
11
10
|
from ormlambda.common.global_checker import GlobalChecker
|
12
11
|
|
13
|
-
from ormlambda.sql.types import
|
14
|
-
|
12
|
+
from ormlambda.sql.types import TableType, ColumnType
|
13
|
+
from .clause_info_converter import (
|
14
|
+
ClauseInfoConverter,
|
15
|
+
ConvertFromAnyType,
|
16
|
+
ConvertFromForeignKey,
|
17
|
+
ConvertFromColumn,
|
18
|
+
ConvertFromIAggregate,
|
19
|
+
ConvertFromTable,
|
20
|
+
)
|
15
21
|
|
16
22
|
type TableTupleType[T, *Ts] = tuple[T:TableType, *Ts]
|
17
23
|
type ValueType = tp.Union[
|
@@ -22,69 +28,6 @@ type ValueType = tp.Union[
|
|
22
28
|
]
|
23
29
|
|
24
30
|
|
25
|
-
class ClauseInfoConverter[T, TProp](abc.ABC):
|
26
|
-
@classmethod
|
27
|
-
@abc.abstractmethod
|
28
|
-
def convert(cls, data: T, alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[T]]: ...
|
29
|
-
|
30
|
-
|
31
|
-
class ConvertFromAnyType(ClauseInfoConverter[None, None]):
|
32
|
-
@classmethod
|
33
|
-
def convert(cls, data: tp.Any, alias_table: AliasType = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[None]]:
|
34
|
-
return [
|
35
|
-
ClauseInfo[None](
|
36
|
-
table=None,
|
37
|
-
column=data,
|
38
|
-
alias_table=alias_table,
|
39
|
-
alias_clause=None,
|
40
|
-
context=context,
|
41
|
-
)
|
42
|
-
]
|
43
|
-
|
44
|
-
|
45
|
-
class ConvertFromForeignKey[LT: Table, RT: Table](ClauseInfoConverter[RT, None]):
|
46
|
-
@classmethod
|
47
|
-
def convert(cls, data: ForeignKey[LT, RT], alias_table=None, context: ClauseContextType = None) -> list[ClauseInfo[RT]]:
|
48
|
-
return ConvertFromTable[RT].convert(data.tright, data.alias, context)
|
49
|
-
|
50
|
-
|
51
|
-
class ConvertFromColumn[TProp](ClauseInfoConverter[None, TProp]):
|
52
|
-
@classmethod
|
53
|
-
def convert(cls, data: ColumnType[TProp], alias_table: AliasType[ColumnType[TProp]] = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[None]]:
|
54
|
-
# COMMENT: if the property belongs to the main class, the columnn name in not prefixed. This only done if the property comes from any join.
|
55
|
-
clause_info = ClauseInfo[None](
|
56
|
-
table=data.table,
|
57
|
-
column=data,
|
58
|
-
alias_table=alias_table,
|
59
|
-
alias_clause="{table}_{column}",
|
60
|
-
context=context,
|
61
|
-
)
|
62
|
-
return [clause_info]
|
63
|
-
|
64
|
-
|
65
|
-
class ConvertFromIAggregate(ClauseInfoConverter[None, None]):
|
66
|
-
@classmethod
|
67
|
-
def convert(cls, data: AggregateFunctionBase, alias_table=None, context: ClauseContextType = None) -> list[ClauseInfo[None]]:
|
68
|
-
return [data]
|
69
|
-
|
70
|
-
|
71
|
-
class ConvertFromTable[T: Table](ClauseInfoConverter[T, None]):
|
72
|
-
@classmethod
|
73
|
-
def convert(cls, data: T, alias_table: AliasType[ColumnType] = "{table}", context: ClauseContextType = None) -> list[ClauseInfo[T]]:
|
74
|
-
"""
|
75
|
-
if the data is Table, means that we want to retrieve all columns
|
76
|
-
"""
|
77
|
-
return cls._extract_all_clauses(data, alias_table, context)
|
78
|
-
|
79
|
-
@staticmethod
|
80
|
-
def _extract_all_clauses(table: TableType[T], alias_table: AliasType[ColumnType], context: ClauseContextType = None) -> list[ClauseInfo[TableType[T]]]:
|
81
|
-
# all columns
|
82
|
-
column_clauses = []
|
83
|
-
for column in table.get_columns():
|
84
|
-
column_clauses.extend(ConvertFromColumn.convert(column, alias_table=alias_table, context=context))
|
85
|
-
return column_clauses
|
86
|
-
|
87
|
-
|
88
31
|
class DecompositionQueryBase[T: Table, *Ts](IDecompositionQuery[T, *Ts]):
|
89
32
|
@tp.overload
|
90
33
|
def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType]) -> None: ...
|
@@ -93,7 +36,8 @@ class DecompositionQueryBase[T: Table, *Ts](IDecompositionQuery[T, *Ts]):
|
|
93
36
|
@tp.overload
|
94
37
|
def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType], alias_table: str, context: ClauseContextType = ...) -> None: ...
|
95
38
|
|
96
|
-
def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType], alias_table: str = "{table}", *, context: ClauseContextType = ClauseInfoContext()) -> None:
|
39
|
+
def __init__(self, tables: tuple[TableType[T]], columns: tuple[ColumnType], alias_table: str = "{table}", *, context: ClauseContextType = ClauseInfoContext(), **kwargs) -> None:
|
40
|
+
self.kwargs = kwargs
|
97
41
|
self._tables: tuple[TableType[T]] = tables if isinstance(tables, tp.Iterable) else (tables,)
|
98
42
|
|
99
43
|
self._columns: tp.Callable[[T], tuple] = columns
|
@@ -145,7 +89,7 @@ class DecompositionQueryBase[T: Table, *Ts](IDecompositionQuery[T, *Ts]):
|
|
145
89
|
}
|
146
90
|
classConverter = next((converter for obj, converter in VALUE_TYPE_MAPPED.items() if validation(data, obj)), ConvertFromAnyType)
|
147
91
|
|
148
|
-
return classConverter.convert(data, alias_table=self._alias_table, context=self._context)
|
92
|
+
return classConverter.convert(data, alias_table=self._alias_table, context=self._context, **self.kwargs)
|
149
93
|
|
150
94
|
def __add_clause[TTable: TableType](self, clauses: list[ClauseInfo[TTable]] | ClauseInfo[TTable]) -> None:
|
151
95
|
if not isinstance(clauses, tp.Iterable):
|
@@ -5,11 +5,11 @@ from typing import Any, Optional, Type, override, TYPE_CHECKING
|
|
5
5
|
from ormlambda.common.interfaces.INonQueryCommand import INonQueryCommand
|
6
6
|
|
7
7
|
if TYPE_CHECKING:
|
8
|
-
from ormlambda.repository import
|
8
|
+
from ormlambda.repository import BaseRepository
|
9
9
|
from ormlambda import Table
|
10
10
|
|
11
11
|
|
12
|
-
class NonQueryBase[T: Type[Table], TRepo:
|
12
|
+
class NonQueryBase[T: Type[Table], TRepo: BaseRepository](INonQueryCommand):
|
13
13
|
__slots__: tuple[str, ...] = ("_model", "_repository", "_values", "_query")
|
14
14
|
|
15
15
|
def __init__(self, model: T, repository: TRepo) -> None:
|
@@ -3,13 +3,13 @@ from abc import abstractmethod
|
|
3
3
|
from typing import TYPE_CHECKING
|
4
4
|
|
5
5
|
if TYPE_CHECKING:
|
6
|
-
from ormlambda import Table,
|
6
|
+
from ormlambda import Table, BaseRepository
|
7
7
|
from ormlambda.common.abstract_classes import NonQueryBase
|
8
8
|
|
9
9
|
from .IDelete import IDelete
|
10
10
|
|
11
11
|
|
12
|
-
class DeleteQueryBase[T: Table, TRepo:
|
12
|
+
class DeleteQueryBase[T: Table, TRepo: BaseRepository](NonQueryBase[T, TRepo], IDelete[T]):
|
13
13
|
def __init__(self, model: T, repository: TRepo) -> None:
|
14
14
|
super().__init__(model, repository)
|
15
15
|
|
@@ -0,0 +1 @@
|
|
1
|
+
from .join_context import JoinContext, TupleJoinType # noqa: F401
|
@@ -6,7 +6,6 @@ if TYPE_CHECKING:
|
|
6
6
|
from ormlambda import Table
|
7
7
|
from ormlambda.repository import IRepositoryBase
|
8
8
|
from ormlambda.components.delete import DeleteQueryBase
|
9
|
-
from mysql.connector import MySQLConnection
|
10
9
|
|
11
10
|
|
12
11
|
class DeleteQuery[T: Table](DeleteQueryBase[T, IRepositoryBase]):
|
@@ -1,15 +1,18 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Literal, override, TYPE_CHECKING
|
2
3
|
|
3
|
-
|
4
|
+
if TYPE_CHECKING:
|
5
|
+
from mysql.connector import MySQLConnection
|
6
|
+
|
7
|
+
from ormlambda.repository import BaseRepository
|
4
8
|
|
5
|
-
from mysql.connector import MySQLConnection
|
6
9
|
|
7
10
|
TypeExists = Literal["fail", "replace", "append"]
|
8
11
|
|
9
12
|
|
10
13
|
class DropTable:
|
11
|
-
def __init__(self, repository:
|
12
|
-
self._repository:
|
14
|
+
def __init__(self, repository: BaseRepository[MySQLConnection]) -> None:
|
15
|
+
self._repository: BaseRepository[MySQLConnection] = repository
|
13
16
|
|
14
17
|
@override
|
15
18
|
def execute(self, name: str = None) -> None:
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import typing as tp
|
2
2
|
from ormlambda import Table
|
3
|
-
from ormlambda.sql.clause_info
|
4
|
-
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
3
|
+
from ormlambda.sql.clause_info import AggregateFunctionBase, ClauseInfoContext
|
5
4
|
from ormlambda.sql.types import ColumnType
|
6
5
|
|
7
6
|
|
@@ -21,11 +21,13 @@ class Select[T: Type[Table], *Ts](DecompositionQueryBase[T, *Ts], ISelect):
|
|
21
21
|
*,
|
22
22
|
alias_table: AliasType[ClauseInfo] = "{table}",
|
23
23
|
context: Optional[ClauseInfoContext] = None,
|
24
|
+
**kwargs,
|
24
25
|
) -> None:
|
25
26
|
super().__init__(
|
26
27
|
tables,
|
27
28
|
columns,
|
28
29
|
context=context,
|
30
|
+
**kwargs,
|
29
31
|
)
|
30
32
|
self._alias_table = alias_table
|
31
33
|
# We always need to add the self alias of the Select
|
@@ -1,15 +1,19 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import override, TYPE_CHECKING
|
2
3
|
|
3
4
|
from ormlambda import Table
|
4
5
|
from ormlambda.components.upsert import UpsertQueryBase
|
5
|
-
from ormlambda.repository import IRepositoryBase
|
6
|
-
|
6
|
+
from ormlambda.repository import IRepositoryBase, BaseRepository
|
7
|
+
|
7
8
|
|
8
9
|
from .insert import InsertQuery
|
9
10
|
|
11
|
+
if TYPE_CHECKING:
|
12
|
+
from mysql.connector import MySQLConnection
|
13
|
+
|
10
14
|
|
11
15
|
class UpsertQuery[T: Table](UpsertQueryBase[T, IRepositoryBase]):
|
12
|
-
def __init__(self, model: T, repository:
|
16
|
+
def __init__(self, model: T, repository: BaseRepository[MySQLConnection]) -> None:
|
13
17
|
super().__init__(model, repository)
|
14
18
|
|
15
19
|
@override
|
@@ -0,0 +1,158 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from typing import Iterable, TypedDict, Optional
|
3
|
+
|
4
|
+
from ormlambda.sql.clause_info.clause_info_context import ClauseInfoContext
|
5
|
+
from ormlambda.databases.my_sql.clauses.joins import JoinSelector
|
6
|
+
from ormlambda import ForeignKey
|
7
|
+
|
8
|
+
from ormlambda.common.interfaces import IQuery
|
9
|
+
|
10
|
+
|
11
|
+
from ormlambda.sql.clause_info import ClauseInfo
|
12
|
+
from .clauses import Limit
|
13
|
+
from .clauses import Offset
|
14
|
+
from .clauses import Order
|
15
|
+
from .clauses import Select
|
16
|
+
|
17
|
+
from .clauses import Where
|
18
|
+
from .clauses import Having
|
19
|
+
from .clauses import GroupBy
|
20
|
+
|
21
|
+
|
22
|
+
from ormlambda.common.enums import JoinType
|
23
|
+
|
24
|
+
|
25
|
+
class OrderType(TypedDict):
|
26
|
+
Select: Select
|
27
|
+
JoinSelector: JoinSelector
|
28
|
+
Where: Where
|
29
|
+
Order: Order
|
30
|
+
GroupBy: GroupBy
|
31
|
+
Having: Having
|
32
|
+
Limit: Limit
|
33
|
+
Offset: Offset
|
34
|
+
|
35
|
+
|
36
|
+
class QueryBuilder(IQuery):
|
37
|
+
__order__: tuple[str, ...] = ("Select", "JoinSelector", "Where", "GroupBy", "Having", "Order", "Limit", "Offset")
|
38
|
+
|
39
|
+
def __init__(self, by: JoinType = JoinType.INNER_JOIN):
|
40
|
+
self._context = ClauseInfoContext()
|
41
|
+
self._query_list: OrderType = {}
|
42
|
+
self._by = by
|
43
|
+
|
44
|
+
self._joins: Optional[IQuery] = None
|
45
|
+
self._select: Optional[IQuery] = None
|
46
|
+
self._where: Optional[IQuery] = None
|
47
|
+
self._order: Optional[IQuery] = None
|
48
|
+
self._group_by: Optional[IQuery] = None
|
49
|
+
self._limit: Optional[IQuery] = None
|
50
|
+
self._offset: Optional[IQuery] = None
|
51
|
+
|
52
|
+
def add_statement[T](self, clause: ClauseInfo[T]):
|
53
|
+
self.update_context(clause)
|
54
|
+
self._query_list[type(clause).__name__] = clause
|
55
|
+
|
56
|
+
@property
|
57
|
+
def by(self) -> JoinType:
|
58
|
+
return self._by
|
59
|
+
|
60
|
+
@by.setter
|
61
|
+
def by(self, value: JoinType) -> None:
|
62
|
+
self._by = value
|
63
|
+
|
64
|
+
@property
|
65
|
+
def JOINS(self) -> Optional[set[JoinSelector]]:
|
66
|
+
return self._joins
|
67
|
+
|
68
|
+
@property
|
69
|
+
def SELECT(self) -> IQuery:
|
70
|
+
return self._query_list.get("Select", None)
|
71
|
+
|
72
|
+
@property
|
73
|
+
def WHERE(self) -> IQuery:
|
74
|
+
where = self._query_list.get("Where", None)
|
75
|
+
if not isinstance(where, Iterable):
|
76
|
+
if not where:
|
77
|
+
return ()
|
78
|
+
return (where,)
|
79
|
+
return where
|
80
|
+
|
81
|
+
@property
|
82
|
+
def ORDER(self) -> IQuery:
|
83
|
+
return self._query_list.get("Order", None)
|
84
|
+
|
85
|
+
@property
|
86
|
+
def GROUP_BY(self) -> IQuery:
|
87
|
+
return self._query_list.get("GroupBy", None)
|
88
|
+
|
89
|
+
@property
|
90
|
+
def HAVING(self) -> IQuery:
|
91
|
+
where = self._query_list.get("Having", None)
|
92
|
+
if not isinstance(where, Iterable):
|
93
|
+
if not where:
|
94
|
+
return ()
|
95
|
+
return (where,)
|
96
|
+
return where
|
97
|
+
|
98
|
+
@property
|
99
|
+
def LIMIT(self) -> IQuery:
|
100
|
+
return self._query_list.get("Limit", None)
|
101
|
+
|
102
|
+
@property
|
103
|
+
def OFFSET(self) -> IQuery:
|
104
|
+
return self._query_list.get("Offset", None)
|
105
|
+
|
106
|
+
@property
|
107
|
+
def query(self) -> str:
|
108
|
+
# COMMENT: (select.query, query)We must first create an alias for 'FROM' and then define all the remaining clauses.
|
109
|
+
# This order is mandatory because it adds the clause name to the context when accessing the .query property of 'FROM'
|
110
|
+
|
111
|
+
extract_joins = self.pop_tables_and_create_joins_from_ForeignKey(self._by)
|
112
|
+
|
113
|
+
JOINS = self.stringify_foreign_key(extract_joins, " ")
|
114
|
+
query_list: tuple[str, ...] = (
|
115
|
+
self.SELECT.query,
|
116
|
+
JOINS,
|
117
|
+
Where.join_condition(self.WHERE, True, self._context) if self.WHERE else None,
|
118
|
+
self.GROUP_BY.query if self.GROUP_BY else None,
|
119
|
+
Having.join_condition(self.HAVING, True, self._context) if self.HAVING else None,
|
120
|
+
self.ORDER.query if self.ORDER else None,
|
121
|
+
self.LIMIT.query if self.LIMIT else None,
|
122
|
+
self.OFFSET.query if self.OFFSET else None,
|
123
|
+
)
|
124
|
+
return " ".join([x for x in query_list if x])
|
125
|
+
|
126
|
+
def stringify_foreign_key(self, joins: set[JoinSelector], sep: str = "\n") -> Optional[str]:
|
127
|
+
if not joins:
|
128
|
+
return None
|
129
|
+
sorted_joins = JoinSelector.sort_join_selectors(joins)
|
130
|
+
return f"{sep}".join([join.query for join in sorted_joins])
|
131
|
+
|
132
|
+
def pop_tables_and_create_joins_from_ForeignKey(self, by: JoinType = JoinType.INNER_JOIN) -> set[JoinSelector]:
|
133
|
+
# When we applied filters in any table that we wont select any column, we need to add manually all neccessary joins to achieve positive result.
|
134
|
+
if not ForeignKey.stored_calls:
|
135
|
+
return None
|
136
|
+
|
137
|
+
joins = set()
|
138
|
+
# Always it's gonna be a set of two
|
139
|
+
# FIXME [x]: Resolved when we get Compare object instead ClauseInfo. For instance, when we have multiples condition using '&' or '|'
|
140
|
+
for fk in ForeignKey.stored_calls.copy():
|
141
|
+
fk = ForeignKey.stored_calls.pop(fk)
|
142
|
+
self._context._add_table_alias(fk.tright, fk.alias)
|
143
|
+
join = JoinSelector(fk.resolved_function(self._context), by, context=self._context, alias=fk.alias)
|
144
|
+
joins.add(join)
|
145
|
+
|
146
|
+
return joins
|
147
|
+
|
148
|
+
def clear(self) -> None:
|
149
|
+
self.__init__()
|
150
|
+
return None
|
151
|
+
|
152
|
+
def update_context(self, clause: ClauseInfo) -> None:
|
153
|
+
if not hasattr(clause, "context"):
|
154
|
+
return None
|
155
|
+
|
156
|
+
if clause.context is not None:
|
157
|
+
self._context.update(clause.context)
|
158
|
+
clause.context = self._context
|