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.
- dapper_sqls/__init__.py +3 -1
- dapper_sqls/_types.py +25 -2
- dapper_sqls/async_dapper/async_executors.py +127 -52
- dapper_sqls/builders/model/model.py +418 -33
- dapper_sqls/builders/model/utils.py +334 -42
- dapper_sqls/builders/query.py +164 -43
- dapper_sqls/builders/stored.py +15 -5
- dapper_sqls/builders/stp.py +6 -2
- dapper_sqls/config.py +40 -31
- dapper_sqls/dapper/executors.py +130 -55
- dapper_sqls/http/__init__.py +4 -0
- dapper_sqls/http/aiohttp.py +155 -0
- dapper_sqls/http/decorators.py +123 -0
- dapper_sqls/http/models.py +58 -0
- dapper_sqls/http/request.py +140 -0
- dapper_sqls/models/__init__.py +3 -5
- dapper_sqls/models/base.py +246 -20
- dapper_sqls/models/connection.py +1 -1
- dapper_sqls/models/query_field.py +214 -0
- dapper_sqls/models/result.py +314 -44
- dapper_sqls/sqlite/__init__.py +1 -0
- dapper_sqls/sqlite/async_local_database.py +69 -5
- dapper_sqls/sqlite/decorators.py +69 -0
- dapper_sqls/sqlite/installer.py +8 -4
- dapper_sqls/sqlite/local_database.py +39 -5
- dapper_sqls/sqlite/models.py +25 -1
- dapper_sqls/sqlite/utils.py +2 -1
- dapper_sqls/utils.py +16 -4
- dapper_sqls-1.2.1.dist-info/METADATA +41 -0
- dapper_sqls-1.2.1.dist-info/RECORD +40 -0
- {dapper_sqls-1.1.3.dist-info → dapper_sqls-1.2.1.dist-info}/WHEEL +1 -1
- dapper_sqls-1.1.3.dist-info/METADATA +0 -10
- dapper_sqls-1.1.3.dist-info/RECORD +0 -33
- {dapper_sqls-1.1.3.dist-info → dapper_sqls-1.2.1.dist-info}/top_level.txt +0 -0
dapper_sqls/models/base.py
CHANGED
@@ -1,35 +1,261 @@
|
|
1
|
-
from pydantic import BaseModel,
|
2
|
-
from abc import ABC
|
1
|
+
from pydantic import BaseModel, ConfigDict, PrivateAttr, Field, create_model
|
3
2
|
from abc import ABC, abstractmethod
|
4
|
-
from
|
3
|
+
from typing import Set, Any, ClassVar, get_origin, get_args, Union, Optional, Literal, get_type_hints, List
|
4
|
+
from ..utils import get_dict_args
|
5
|
+
from dataclasses import asdict
|
6
|
+
import copy
|
7
|
+
import datetime
|
8
|
+
|
9
|
+
QUERY_FIELD_TYPES = {
|
10
|
+
'StringQueryField',
|
11
|
+
'NumericQueryField',
|
12
|
+
'BoolQueryField',
|
13
|
+
'DateQueryField',
|
14
|
+
'BytesQueryField',
|
15
|
+
'JoinStringCondition',
|
16
|
+
'JoinNumericCondition',
|
17
|
+
'JoinBooleanCondition',
|
18
|
+
'JoinDateCondition',
|
19
|
+
'JoinBytesCondition',
|
20
|
+
}
|
21
|
+
|
22
|
+
def convert_datetime_date_to_str(annotation):
|
23
|
+
"""Convert datetime/date or their unions with str to just str."""
|
24
|
+
if annotation in (datetime.datetime, datetime.date):
|
25
|
+
return str
|
26
|
+
origin_inner = get_origin(annotation)
|
27
|
+
args_inner = get_args(annotation)
|
28
|
+
if origin_inner is Union:
|
29
|
+
set_args = set(args_inner)
|
30
|
+
if str in set_args and (datetime.datetime in set_args or datetime.date in set_args):
|
31
|
+
return str
|
32
|
+
return annotation
|
33
|
+
|
34
|
+
def remove_query_field_types(annotation):
|
35
|
+
"""
|
36
|
+
Remove os tipos de QueryField (como StringQueryField) de uma Union ou substitui diretamente
|
37
|
+
"""
|
38
|
+
origin = get_origin(annotation)
|
39
|
+
args = get_args(annotation)
|
40
|
+
|
41
|
+
def is_query_field(arg):
|
42
|
+
return getattr(arg, '__name__', '') in QUERY_FIELD_TYPES
|
43
|
+
|
44
|
+
if origin is Union:
|
45
|
+
new_args = tuple(arg for arg in args if not is_query_field(arg))
|
46
|
+
if len(new_args) == 1:
|
47
|
+
return new_args[0]
|
48
|
+
return Union[new_args]
|
49
|
+
elif is_query_field(annotation):
|
50
|
+
return str # fallback para str caso algum passe isolado
|
51
|
+
return annotation
|
52
|
+
|
53
|
+
def is_optional(annotation):
|
54
|
+
"""Check if an annotation is Optional[...] or Union[..., None]."""
|
55
|
+
origin = get_origin(annotation)
|
56
|
+
args = get_args(annotation)
|
57
|
+
return origin is Union and type(None) in args
|
58
|
+
|
59
|
+
def remove_optional(annotation):
|
60
|
+
"""Remove NoneType from Union[...]"""
|
61
|
+
args = tuple(arg for arg in get_args(annotation) if arg is not type(None))
|
62
|
+
if len(args) == 1:
|
63
|
+
return args[0]
|
64
|
+
return Union[args]
|
65
|
+
|
66
|
+
def make_optional(annotation):
|
67
|
+
"""Make an annotation optional if not already."""
|
68
|
+
if is_optional(annotation):
|
69
|
+
return annotation
|
70
|
+
return Optional[annotation]
|
71
|
+
|
72
|
+
class SensitiveFields(object):
|
73
|
+
|
74
|
+
_sensitive_fields : Set[str] = set()
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
def set(cls, new_sensitive_filds : Set[str]):
|
78
|
+
cls._sensitive_fields = new_sensitive_filds
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def get(cls):
|
82
|
+
return cls._sensitive_fields
|
83
|
+
|
5
84
|
|
6
85
|
class TableBaseModel(BaseModel, ABC):
|
7
|
-
class Config(
|
86
|
+
class Config(ConfigDict):
|
8
87
|
from_attributes = True
|
9
88
|
|
10
|
-
|
89
|
+
TABLE_NAME: ClassVar[str]
|
11
90
|
|
12
|
-
|
13
|
-
|
14
|
-
|
91
|
+
TABLE_ALIAS: ClassVar[str]
|
92
|
+
|
93
|
+
DESCRIPTION : ClassVar[str]
|
94
|
+
|
95
|
+
IDENTITIES : ClassVar[Set[str]]
|
96
|
+
|
97
|
+
PRIMARY_KEYs : ClassVar[Set[str]]
|
98
|
+
|
99
|
+
OPTIONAL_FIELDS : ClassVar[Set[str]]
|
100
|
+
|
101
|
+
MAX_LENGTH_FIELDS: ClassVar[dict[str, int]] = {}
|
102
|
+
|
103
|
+
_explicit_fields: Set[str] = PrivateAttr(default_factory=set)
|
104
|
+
_pending_updates: dict[str, Any] = PrivateAttr(default_factory=dict)
|
105
|
+
_initial_values: dict[str, Any] = PrivateAttr(default_factory=dict)
|
106
|
+
|
107
|
+
|
108
|
+
def __init__(self, **data):
|
109
|
+
sensitive = SensitiveFields.get()
|
110
|
+
filtered_data = {k: v for k, v in data.items() if k not in sensitive}
|
111
|
+
|
112
|
+
super().__init__(**filtered_data)
|
113
|
+
self._explicit_fields = set(filtered_data.keys())
|
114
|
+
self._initial_values = copy.deepcopy(self.model_dump())
|
115
|
+
|
116
|
+
def _reset_defaults(self):
|
117
|
+
for field_name, model_field in self.model_fields.items():
|
118
|
+
if field_name not in self._explicit_fields:
|
119
|
+
setattr(self, field_name, None)
|
120
|
+
|
121
|
+
def reset_to_initial_values(self):
|
122
|
+
for key, value in self._initial_values.items():
|
123
|
+
setattr(self, key, copy.deepcopy(value))
|
124
|
+
self.clear_updates()
|
125
|
+
|
126
|
+
def equals(self, other: "TableBaseModel") -> bool:
|
127
|
+
return self.model_dump() == other.model_dump()
|
128
|
+
|
129
|
+
def clear_updates(self):
|
130
|
+
self._pending_updates.clear()
|
15
131
|
|
132
|
+
def has_updates(self) -> bool:
|
133
|
+
for key, new_value in self._pending_updates.items():
|
134
|
+
if key in self.model_fields:
|
135
|
+
current_value = getattr(self, key, None)
|
136
|
+
|
137
|
+
if isinstance(current_value, BaseModel) and isinstance(new_value, BaseModel):
|
138
|
+
if current_value.model_dump() != new_value.model_dump():
|
139
|
+
return True
|
140
|
+
|
141
|
+
elif hasattr(current_value, "__dataclass_fields__") and hasattr(new_value, "__dataclass_fields__"):
|
142
|
+
if asdict(current_value) != asdict(new_value):
|
143
|
+
return True
|
144
|
+
|
145
|
+
elif hasattr(current_value, "__dict__") and hasattr(new_value, "__dict__"):
|
146
|
+
if vars(current_value) != vars(new_value):
|
147
|
+
return True
|
148
|
+
|
149
|
+
elif new_value != current_value:
|
150
|
+
return True
|
151
|
+
return False
|
152
|
+
|
153
|
+
@staticmethod
|
154
|
+
def queue_update(self : 'TableBaseModel', **fields):
|
155
|
+
fields = get_dict_args(fields)
|
156
|
+
for key, value in fields.items():
|
157
|
+
if value != None and key in self.model_fields:
|
158
|
+
self._pending_updates[key] = value
|
159
|
+
|
160
|
+
def apply_updates(self):
|
161
|
+
for key, value in self._pending_updates.items():
|
162
|
+
if key in self.model_fields:
|
163
|
+
setattr(self, key, value)
|
164
|
+
self.clear_updates()
|
165
|
+
|
166
|
+
def alter_model_class(self, remove_fields: tuple[str] = (), mode: Literal['all_optional', 'all_required', 'original'] = 'all_optional', query_field = False):
|
167
|
+
fields = {}
|
168
|
+
|
169
|
+
for field_name, field in self.model_fields.items():
|
170
|
+
if field_name in remove_fields:
|
171
|
+
continue
|
172
|
+
|
173
|
+
ann = convert_datetime_date_to_str(field.annotation)
|
174
|
+
if not query_field:
|
175
|
+
ann = remove_query_field_types(ann)
|
176
|
+
|
177
|
+
max_length = None
|
178
|
+
if mode in ('all_required', 'original'):
|
179
|
+
max_length = self.MAX_LENGTH_FIELDS.get(field_name)
|
180
|
+
if isinstance(max_length, int) and max_length < 1:
|
181
|
+
max_length = None
|
182
|
+
|
183
|
+
default = field.default
|
184
|
+
|
185
|
+
if mode == 'all_optional':
|
186
|
+
ann = make_optional(ann)
|
187
|
+
default = None
|
188
|
+
|
189
|
+
elif mode == 'all_required':
|
190
|
+
if is_optional(ann):
|
191
|
+
ann = remove_optional(ann)
|
192
|
+
default = ...
|
193
|
+
|
194
|
+
elif mode == 'original':
|
195
|
+
if field_name in self.OPTIONAL_FIELDS:
|
196
|
+
ann = make_optional(ann)
|
197
|
+
default = None
|
198
|
+
else:
|
199
|
+
if is_optional(ann):
|
200
|
+
ann = remove_optional(ann)
|
201
|
+
default = ...
|
202
|
+
|
203
|
+
fields[field_name] = (ann, Field(default=default, description=field.description, max_length=max_length))
|
204
|
+
|
205
|
+
new_model_class = create_model(
|
206
|
+
self.__name__,
|
207
|
+
__config__=ConfigDict(extra='forbid'),
|
208
|
+
**fields
|
209
|
+
)
|
210
|
+
return new_model_class
|
211
|
+
|
212
|
+
@classmethod
|
213
|
+
def get_field_type_names(cls) -> dict[str, set[str]]:
|
214
|
+
result = {}
|
215
|
+
type_hints = get_type_hints(cls, include_extras=True)
|
216
|
+
|
217
|
+
for field_name, hint in type_hints.items():
|
218
|
+
if field_name.startswith('_') or get_origin(hint) is ClassVar:
|
219
|
+
continue
|
220
|
+
|
221
|
+
args = get_args(hint)
|
222
|
+
if not args:
|
223
|
+
args = (hint,)
|
224
|
+
|
225
|
+
types = {
|
226
|
+
t.__name__ if hasattr(t, '__name__') else t._name if hasattr(t, '_name') else str(t)
|
227
|
+
for t in args
|
228
|
+
if t is not type(None)
|
229
|
+
}
|
230
|
+
|
231
|
+
result[field_name] = types
|
232
|
+
|
233
|
+
return result
|
234
|
+
|
235
|
+
class SearchTable(BaseModel):
|
236
|
+
model: TableBaseModel
|
237
|
+
include: Optional[List[str]] = Field(default_factory=list)
|
238
|
+
|
239
|
+
class JoinSearchTable(SearchTable):
|
240
|
+
join_type: Literal["INNER", "LEFT", "RIGHT", "FULL"] = "LEFT"
|
241
|
+
|
16
242
|
class BaseUpdate(ABC):
|
17
243
|
|
18
|
-
|
19
|
-
|
20
|
-
|
244
|
+
def __init__(self, executor , model):
|
245
|
+
self._set_data = model
|
246
|
+
self._executor = executor
|
21
247
|
|
22
|
-
|
23
|
-
|
24
|
-
|
248
|
+
@property
|
249
|
+
def set_data(self):
|
250
|
+
return self._set_data
|
25
251
|
|
26
|
-
|
27
|
-
|
28
|
-
|
252
|
+
@property
|
253
|
+
def executor(self):
|
254
|
+
return self._executor
|
29
255
|
|
30
|
-
|
31
|
-
|
32
|
-
|
256
|
+
@abstractmethod
|
257
|
+
def where(self, *args):
|
258
|
+
pass
|
33
259
|
|
34
260
|
|
35
261
|
|
dapper_sqls/models/connection.py
CHANGED
@@ -34,7 +34,7 @@ class ConnectionStringData(object):
|
|
34
34
|
@username.setter
|
35
35
|
def username(self, value: str):
|
36
36
|
if not isinstance(value, str):
|
37
|
-
raise ValueError("O nome de
|
37
|
+
raise ValueError("O nome de usuário deve ser uma string.")
|
38
38
|
self._username = value
|
39
39
|
|
40
40
|
@property
|
@@ -0,0 +1,214 @@
|
|
1
|
+
from typing import Union, List, Literal, Any, Optional
|
2
|
+
from pydantic import BaseModel, Field, create_model
|
3
|
+
from datetime import datetime, date
|
4
|
+
from abc import ABC, abstractmethod
|
5
|
+
|
6
|
+
class QueryFieldBase(BaseModel, ABC):
|
7
|
+
|
8
|
+
class Config:
|
9
|
+
extra = "forbid"
|
10
|
+
|
11
|
+
prefix: Optional[str] = Field(
|
12
|
+
default=...,
|
13
|
+
description="Optional prefix to be prepended to the SQL condition (e.g., for parentheses or NOT)"
|
14
|
+
)
|
15
|
+
suffix: Optional[str] = Field(
|
16
|
+
default=...,
|
17
|
+
description="Optional suffix to be appended to the SQL condition (e.g., for closing parentheses)"
|
18
|
+
)
|
19
|
+
|
20
|
+
def quote(self, val):
|
21
|
+
if isinstance(val, str):
|
22
|
+
val = val.replace("'", "''")
|
23
|
+
return f"'{val}'"
|
24
|
+
elif isinstance(val, bool):
|
25
|
+
return '1' if val else '0'
|
26
|
+
elif isinstance(val, datetime):
|
27
|
+
return f"'{val.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]}'"
|
28
|
+
elif isinstance(val, date):
|
29
|
+
return f"'{val.strftime('%Y-%m-%d')}'"
|
30
|
+
return str(val)
|
31
|
+
|
32
|
+
def format_sql(self, field_name: str, value_expr: str, operator: str) -> str:
|
33
|
+
prefix = self.prefix if isinstance(self.prefix, str) else ""
|
34
|
+
suffix = self.suffix if isinstance(self.suffix, str) else ""
|
35
|
+
return f"{prefix}{field_name} {operator} {value_expr}{suffix}"
|
36
|
+
|
37
|
+
@abstractmethod
|
38
|
+
def to_sql(self, field_name: str):
|
39
|
+
...
|
40
|
+
|
41
|
+
|
42
|
+
class StringQueryField(QueryFieldBase):
|
43
|
+
value: Union[str, List[str]] = Field(
|
44
|
+
default=...,
|
45
|
+
description="The value or list of values to compare against the string column"
|
46
|
+
)
|
47
|
+
operator: Literal['=', '!=', 'LIKE', 'IN', 'NOT IN'] = Field(
|
48
|
+
default=...,
|
49
|
+
description="SQL operator used for comparison"
|
50
|
+
)
|
51
|
+
|
52
|
+
case_insensitive: bool = Field(
|
53
|
+
default=...,
|
54
|
+
description="Whether to apply case-insensitive matching (uses UPPER() on field and value)"
|
55
|
+
)
|
56
|
+
|
57
|
+
def apply_like_pattern(self, v: str) -> str:
|
58
|
+
if self.operator == 'LIKE':
|
59
|
+
return f"%{v}%"
|
60
|
+
return v
|
61
|
+
|
62
|
+
def to_sql(self, field_name: str) -> str:
|
63
|
+
field_expr = f"UPPER({field_name})" if self.case_insensitive else field_name
|
64
|
+
|
65
|
+
if isinstance(self.value, list):
|
66
|
+
values = [self.apply_like_pattern(v) for v in self.value]
|
67
|
+
values = [v.upper() if self.case_insensitive else v for v in values]
|
68
|
+
value_expr = "(" + ", ".join(self.quote(v) for v in values) + ")"
|
69
|
+
else:
|
70
|
+
val = self.apply_like_pattern(self.value)
|
71
|
+
val = val.upper() if self.case_insensitive else val
|
72
|
+
value_expr = self.quote(val)
|
73
|
+
|
74
|
+
return self.format_sql(field_expr, value_expr, self.operator)
|
75
|
+
|
76
|
+
class NumericQueryField(QueryFieldBase):
|
77
|
+
value: Union[int, float, List[Union[int, float]]] = Field(
|
78
|
+
default=...,
|
79
|
+
description="The numeric value or list of values to compare against the column"
|
80
|
+
)
|
81
|
+
operator: Literal['=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN'] = Field(
|
82
|
+
default=...,
|
83
|
+
description="SQL operator used for numeric comparison"
|
84
|
+
)
|
85
|
+
|
86
|
+
def to_sql(self, field_name: str) -> str:
|
87
|
+
if isinstance(self.value, list):
|
88
|
+
value_expr = "(" + ", ".join(str(v) for v in self.value) + ")"
|
89
|
+
else:
|
90
|
+
value_expr = str(self.value)
|
91
|
+
|
92
|
+
return self.format_sql(field_name, value_expr, self.operator)
|
93
|
+
|
94
|
+
class BoolQueryField(QueryFieldBase):
|
95
|
+
value: bool = Field(
|
96
|
+
default=...,
|
97
|
+
description="Boolean value to compare against the column"
|
98
|
+
)
|
99
|
+
operator: Literal['=', '!='] = Field(
|
100
|
+
default=...,
|
101
|
+
description="SQL operator used for boolean comparison"
|
102
|
+
)
|
103
|
+
|
104
|
+
def to_sql(self, field_name: str) -> str:
|
105
|
+
value_expr = '1' if self.value else '0'
|
106
|
+
return self.format_sql(field_name, value_expr, self.operator)
|
107
|
+
|
108
|
+
|
109
|
+
class DateQueryField(QueryFieldBase):
|
110
|
+
value:Union[str, datetime, date] = Field(
|
111
|
+
default=...,
|
112
|
+
description="Date or datetime value to compare (can also be a string in ISO format)"
|
113
|
+
)
|
114
|
+
operator: Literal['=', '!=', '>', '<', '>=', '<='] = Field(
|
115
|
+
default=...,
|
116
|
+
description="SQL operator used for date/time comparison"
|
117
|
+
)
|
118
|
+
|
119
|
+
def to_sql(self, field_name: str) -> str:
|
120
|
+
if isinstance(self.value, str):
|
121
|
+
value_expr = f"'{self.value}'"
|
122
|
+
else:
|
123
|
+
value_expr = self.quote(self.value)
|
124
|
+
return self.format_sql(field_name, value_expr, self.operator)
|
125
|
+
|
126
|
+
|
127
|
+
class BytesQueryField(QueryFieldBase):
|
128
|
+
value: Union[bytes, List[bytes]] = Field(
|
129
|
+
default=...,
|
130
|
+
description="The bytes value or list of byte values to compare against the column"
|
131
|
+
)
|
132
|
+
operator: Literal['=', '!=', 'IN', 'NOT IN'] = Field(
|
133
|
+
default=...,
|
134
|
+
description="SQL operator used for byte comparison"
|
135
|
+
)
|
136
|
+
|
137
|
+
def to_sql(self, field_name: str) -> str:
|
138
|
+
def format_byte(b: bytes) -> str:
|
139
|
+
return "0x" + b.hex() # SQL Server format
|
140
|
+
|
141
|
+
if isinstance(self.value, list):
|
142
|
+
value_expr = "(" + ", ".join(format_byte(v) for v in self.value) + ")"
|
143
|
+
else:
|
144
|
+
value_expr = format_byte(self.value)
|
145
|
+
|
146
|
+
return self.format_sql(field_name, value_expr, self.operator)
|
147
|
+
|
148
|
+
class BaseJoinConditionField(BaseModel):
|
149
|
+
class Config:
|
150
|
+
extra = "forbid"
|
151
|
+
|
152
|
+
join_table_column: str = Field(
|
153
|
+
...,
|
154
|
+
description="Join table column"
|
155
|
+
)
|
156
|
+
operator: Literal['=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN', 'LIKE'] = Field(
|
157
|
+
default=...,
|
158
|
+
description="SQL operator used for join condition"
|
159
|
+
)
|
160
|
+
|
161
|
+
def to_sql(self, alias_table : str, field_name: str) -> str:
|
162
|
+
right = f"{alias_table}.{self.join_table_column}"
|
163
|
+
return f"{field_name} {self.operator} {right}"
|
164
|
+
|
165
|
+
@classmethod
|
166
|
+
def with_join_table_column_type(cls, join_table_column_type: Any):
|
167
|
+
new_model = create_model(
|
168
|
+
cls.__name__,
|
169
|
+
__base__=cls,
|
170
|
+
join_table_column=(
|
171
|
+
join_table_column_type,
|
172
|
+
Field(
|
173
|
+
...,
|
174
|
+
description=cls.model_fields['join_table_column'].description
|
175
|
+
)
|
176
|
+
),
|
177
|
+
)
|
178
|
+
return new_model
|
179
|
+
|
180
|
+
class JoinNumericCondition(BaseJoinConditionField):
|
181
|
+
operator: Literal['=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN'] = Field(
|
182
|
+
default=...,
|
183
|
+
description="SQL operator used for numeric comparison"
|
184
|
+
)
|
185
|
+
|
186
|
+
class JoinNumericCondition(BaseJoinConditionField):
|
187
|
+
operator: Literal['=', '!=', '>', '<', '>=', '<=', 'IN', 'NOT IN'] = Field(
|
188
|
+
default=...,
|
189
|
+
description="SQL operator used for numeric comparison in a join condition"
|
190
|
+
)
|
191
|
+
|
192
|
+
class JoinStringCondition(BaseJoinConditionField):
|
193
|
+
operator: Literal['=', '!=', 'LIKE', 'IN', 'NOT IN'] = Field(
|
194
|
+
default=...,
|
195
|
+
description="SQL operator used for string comparison in a join condition"
|
196
|
+
)
|
197
|
+
|
198
|
+
class JoinBooleanCondition(BaseJoinConditionField):
|
199
|
+
operator: Literal['=', '!='] = Field(
|
200
|
+
default=...,
|
201
|
+
description="SQL operator used for boolean comparison in a join condition"
|
202
|
+
)
|
203
|
+
|
204
|
+
class JoinDateCondition(BaseJoinConditionField):
|
205
|
+
operator: Literal['=', '!=', '>', '<', '>=', '<='] = Field(
|
206
|
+
default=...,
|
207
|
+
description="SQL operator used for date/time comparison in a join condition"
|
208
|
+
)
|
209
|
+
|
210
|
+
class JoinBytesCondition(BaseJoinConditionField):
|
211
|
+
operator: Literal['=', '!=', 'IN', 'NOT IN'] = Field(
|
212
|
+
default=...,
|
213
|
+
description="SQL operator used for byte comparison in a join condition"
|
214
|
+
)
|