matrixone-python-sdk 0.1.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.
- matrixone/__init__.py +155 -0
- matrixone/account.py +723 -0
- matrixone/async_client.py +3913 -0
- matrixone/async_metadata_manager.py +311 -0
- matrixone/async_orm.py +123 -0
- matrixone/async_vector_index_manager.py +633 -0
- matrixone/base_client.py +208 -0
- matrixone/client.py +4672 -0
- matrixone/config.py +452 -0
- matrixone/connection_hooks.py +286 -0
- matrixone/exceptions.py +89 -0
- matrixone/logger.py +782 -0
- matrixone/metadata.py +820 -0
- matrixone/moctl.py +219 -0
- matrixone/orm.py +2277 -0
- matrixone/pitr.py +646 -0
- matrixone/pubsub.py +771 -0
- matrixone/restore.py +411 -0
- matrixone/search_vector_index.py +1176 -0
- matrixone/snapshot.py +550 -0
- matrixone/sql_builder.py +844 -0
- matrixone/sqlalchemy_ext/__init__.py +161 -0
- matrixone/sqlalchemy_ext/adapters.py +163 -0
- matrixone/sqlalchemy_ext/dialect.py +534 -0
- matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
- matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
- matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
- matrixone/sqlalchemy_ext/ivf_config.py +252 -0
- matrixone/sqlalchemy_ext/table_builder.py +351 -0
- matrixone/sqlalchemy_ext/vector_index.py +1721 -0
- matrixone/sqlalchemy_ext/vector_type.py +948 -0
- matrixone/version.py +580 -0
- matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
- matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
- matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
- matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
- matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +19 -0
- tests/offline/__init__.py +20 -0
- tests/offline/conftest.py +77 -0
- tests/offline/test_account.py +703 -0
- tests/offline/test_async_client_query_comprehensive.py +1218 -0
- tests/offline/test_basic.py +54 -0
- tests/offline/test_case_sensitivity.py +227 -0
- tests/offline/test_connection_hooks_offline.py +287 -0
- tests/offline/test_dialect_schema_handling.py +609 -0
- tests/offline/test_explain_methods.py +346 -0
- tests/offline/test_filter_logical_in.py +237 -0
- tests/offline/test_fulltext_search_comprehensive.py +795 -0
- tests/offline/test_ivf_config.py +249 -0
- tests/offline/test_join_methods.py +281 -0
- tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
- tests/offline/test_logical_in_method.py +237 -0
- tests/offline/test_matrixone_version_parsing.py +264 -0
- tests/offline/test_metadata_offline.py +557 -0
- tests/offline/test_moctl.py +300 -0
- tests/offline/test_moctl_simple.py +251 -0
- tests/offline/test_model_support_offline.py +359 -0
- tests/offline/test_model_support_simple.py +225 -0
- tests/offline/test_pinecone_filter_offline.py +377 -0
- tests/offline/test_pitr.py +585 -0
- tests/offline/test_pubsub.py +712 -0
- tests/offline/test_query_update.py +283 -0
- tests/offline/test_restore.py +445 -0
- tests/offline/test_snapshot_comprehensive.py +384 -0
- tests/offline/test_sql_escaping_edge_cases.py +551 -0
- tests/offline/test_sqlalchemy_integration.py +382 -0
- tests/offline/test_sqlalchemy_vector_integration.py +434 -0
- tests/offline/test_table_builder.py +198 -0
- tests/offline/test_unified_filter.py +398 -0
- tests/offline/test_unified_transaction.py +495 -0
- tests/offline/test_vector_index.py +238 -0
- tests/offline/test_vector_operations.py +688 -0
- tests/offline/test_vector_type.py +174 -0
- tests/offline/test_version_core.py +328 -0
- tests/offline/test_version_management.py +372 -0
- tests/offline/test_version_standalone.py +652 -0
- tests/online/__init__.py +20 -0
- tests/online/conftest.py +216 -0
- tests/online/test_account_management.py +194 -0
- tests/online/test_advanced_features.py +344 -0
- tests/online/test_async_client_interfaces.py +330 -0
- tests/online/test_async_client_online.py +285 -0
- tests/online/test_async_model_insert_online.py +293 -0
- tests/online/test_async_orm_online.py +300 -0
- tests/online/test_async_simple_query_online.py +802 -0
- tests/online/test_async_transaction_simple_query.py +300 -0
- tests/online/test_basic_connection.py +130 -0
- tests/online/test_client_online.py +238 -0
- tests/online/test_config.py +90 -0
- tests/online/test_config_validation.py +123 -0
- tests/online/test_connection_hooks_new_online.py +217 -0
- tests/online/test_dialect_schema_handling_online.py +331 -0
- tests/online/test_filter_logical_in_online.py +374 -0
- tests/online/test_fulltext_comprehensive.py +1773 -0
- tests/online/test_fulltext_label_online.py +433 -0
- tests/online/test_fulltext_search_online.py +842 -0
- tests/online/test_ivf_stats_online.py +506 -0
- tests/online/test_logger_integration.py +311 -0
- tests/online/test_matrixone_query_orm.py +540 -0
- tests/online/test_metadata_online.py +579 -0
- tests/online/test_model_insert_online.py +255 -0
- tests/online/test_mysql_driver_validation.py +213 -0
- tests/online/test_orm_advanced_features.py +2022 -0
- tests/online/test_orm_cte_integration.py +269 -0
- tests/online/test_orm_online.py +270 -0
- tests/online/test_pinecone_filter.py +708 -0
- tests/online/test_pubsub_operations.py +352 -0
- tests/online/test_query_methods.py +225 -0
- tests/online/test_query_update_online.py +433 -0
- tests/online/test_search_vector_index.py +557 -0
- tests/online/test_simple_fulltext_online.py +915 -0
- tests/online/test_snapshot_comprehensive.py +998 -0
- tests/online/test_sqlalchemy_engine_integration.py +336 -0
- tests/online/test_sqlalchemy_integration.py +425 -0
- tests/online/test_transaction_contexts.py +1219 -0
- tests/online/test_transaction_insert_methods.py +356 -0
- tests/online/test_transaction_query_methods.py +288 -0
- tests/online/test_unified_filter_online.py +529 -0
- tests/online/test_vector_comprehensive.py +706 -0
- tests/online/test_version_management.py +291 -0
matrixone/sql_builder.py
ADDED
@@ -0,0 +1,844 @@
|
|
1
|
+
# Copyright 2021 - 2022 Matrix Origin
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
"""
|
16
|
+
Unified SQL Builder for MatrixOne Client
|
17
|
+
|
18
|
+
This module provides a centralized SQL building system that eliminates
|
19
|
+
code duplication across different interfaces (ORM, Vector Search, Pinecone, etc.).
|
20
|
+
"""
|
21
|
+
|
22
|
+
from enum import Enum
|
23
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
24
|
+
|
25
|
+
|
26
|
+
class SQLClause(Enum):
|
27
|
+
"""SQL clause types"""
|
28
|
+
|
29
|
+
SELECT = "SELECT"
|
30
|
+
FROM = "FROM"
|
31
|
+
WHERE = "WHERE"
|
32
|
+
JOIN = "JOIN"
|
33
|
+
GROUP_BY = "GROUP BY"
|
34
|
+
HAVING = "HAVING"
|
35
|
+
ORDER_BY = "ORDER BY"
|
36
|
+
LIMIT = "LIMIT"
|
37
|
+
OFFSET = "OFFSET"
|
38
|
+
WITH = "WITH"
|
39
|
+
|
40
|
+
|
41
|
+
class DistanceFunction(Enum):
|
42
|
+
"""Vector distance functions"""
|
43
|
+
|
44
|
+
L2 = "l2_distance"
|
45
|
+
L2_SQ = "l2_distance_sq"
|
46
|
+
COSINE = "cosine_distance"
|
47
|
+
INNER_PRODUCT = "inner_product"
|
48
|
+
|
49
|
+
|
50
|
+
class MatrixOneSQLBuilder:
|
51
|
+
"""
|
52
|
+
Unified SQL Builder for MatrixOne operations.
|
53
|
+
|
54
|
+
This builder provides a consistent interface for constructing SQL queries
|
55
|
+
across all MatrixOne client interfaces, eliminating code duplication.
|
56
|
+
"""
|
57
|
+
|
58
|
+
def __init__(self):
|
59
|
+
self.reset()
|
60
|
+
|
61
|
+
def reset(self) -> "MatrixOneSQLBuilder":
|
62
|
+
"""Reset the builder to initial state"""
|
63
|
+
self._query_type = "SELECT" # SELECT, INSERT, UPDATE, DELETE
|
64
|
+
self._select_columns = []
|
65
|
+
self._from_table = None
|
66
|
+
self._from_snapshot = None
|
67
|
+
self._joins = []
|
68
|
+
self._where_conditions = []
|
69
|
+
self._where_params = []
|
70
|
+
self._group_by_columns = []
|
71
|
+
self._having_conditions = []
|
72
|
+
self._having_params = []
|
73
|
+
self._order_by_columns = []
|
74
|
+
self._limit_count = None
|
75
|
+
self._offset_count = None
|
76
|
+
self._ctes = []
|
77
|
+
self._vector_query = None
|
78
|
+
# INSERT specific
|
79
|
+
self._insert_columns = []
|
80
|
+
self._insert_values = []
|
81
|
+
# UPDATE specific
|
82
|
+
self._update_set_columns = []
|
83
|
+
self._update_set_values = []
|
84
|
+
return self
|
85
|
+
|
86
|
+
# SELECT clause methods
|
87
|
+
def select(self, *columns: Union[str, List[str]]) -> "MatrixOneSQLBuilder":
|
88
|
+
"""Add SELECT columns"""
|
89
|
+
for col in columns:
|
90
|
+
if isinstance(col, list):
|
91
|
+
self._select_columns.extend(col)
|
92
|
+
else:
|
93
|
+
self._select_columns.append(str(col))
|
94
|
+
return self
|
95
|
+
|
96
|
+
def select_all(self) -> "MatrixOneSQLBuilder":
|
97
|
+
"""Select all columns (*)"""
|
98
|
+
self._select_columns = ["*"]
|
99
|
+
return self
|
100
|
+
|
101
|
+
def add_vector_distance(
|
102
|
+
self,
|
103
|
+
vector_column: str,
|
104
|
+
query_vector: List[float],
|
105
|
+
distance_func: DistanceFunction = DistanceFunction.L2_SQ,
|
106
|
+
alias: str = "distance",
|
107
|
+
) -> "MatrixOneSQLBuilder":
|
108
|
+
"""Add vector distance calculation to SELECT"""
|
109
|
+
vector_str = "[" + ",".join(map(str, query_vector)) + "]"
|
110
|
+
distance_expr = f"{distance_func.value}({vector_column}, '{vector_str}') as {alias}"
|
111
|
+
self._select_columns.append(distance_expr)
|
112
|
+
return self
|
113
|
+
|
114
|
+
# INSERT methods
|
115
|
+
def insert_into(self, table_name: str) -> "MatrixOneSQLBuilder":
|
116
|
+
"""Start INSERT operation"""
|
117
|
+
self._query_type = "INSERT"
|
118
|
+
self._from_table = table_name
|
119
|
+
return self
|
120
|
+
|
121
|
+
def values(self, **kwargs) -> "MatrixOneSQLBuilder":
|
122
|
+
"""Add values for INSERT"""
|
123
|
+
if self._query_type != "INSERT":
|
124
|
+
raise ValueError("values() can only be used with INSERT operations")
|
125
|
+
|
126
|
+
if not self._insert_columns:
|
127
|
+
self._insert_columns = list(kwargs.keys())
|
128
|
+
|
129
|
+
values_row = []
|
130
|
+
for col in self._insert_columns:
|
131
|
+
if col in kwargs:
|
132
|
+
values_row.append(kwargs[col])
|
133
|
+
else:
|
134
|
+
values_row.append(None)
|
135
|
+
|
136
|
+
self._insert_values.append(values_row)
|
137
|
+
return self
|
138
|
+
|
139
|
+
def insert_values(self, columns: List[str], values: List[List[Any]]) -> "MatrixOneSQLBuilder":
|
140
|
+
"""Add multiple rows of values for INSERT"""
|
141
|
+
if self._query_type != "INSERT":
|
142
|
+
raise ValueError("insert_values() can only be used with INSERT operations")
|
143
|
+
|
144
|
+
self._insert_columns = columns
|
145
|
+
self._insert_values = values
|
146
|
+
return self
|
147
|
+
|
148
|
+
# UPDATE methods
|
149
|
+
def update(self, table_name: str) -> "MatrixOneSQLBuilder":
|
150
|
+
"""Start UPDATE operation"""
|
151
|
+
self._query_type = "UPDATE"
|
152
|
+
self._from_table = table_name
|
153
|
+
return self
|
154
|
+
|
155
|
+
def set(self, **kwargs) -> "MatrixOneSQLBuilder":
|
156
|
+
"""Add SET clauses for UPDATE"""
|
157
|
+
if self._query_type != "UPDATE":
|
158
|
+
raise ValueError("set() can only be used with UPDATE operations")
|
159
|
+
|
160
|
+
for key, value in kwargs.items():
|
161
|
+
self._update_set_columns.append(f"{key} = ?")
|
162
|
+
self._update_set_values.append(value)
|
163
|
+
return self
|
164
|
+
|
165
|
+
# DELETE methods
|
166
|
+
def delete_from(self, table_name: str) -> "MatrixOneSQLBuilder":
|
167
|
+
"""Start DELETE operation"""
|
168
|
+
self._query_type = "DELETE"
|
169
|
+
self._from_table = table_name
|
170
|
+
return self
|
171
|
+
|
172
|
+
# FROM clause methods
|
173
|
+
def from_table(self, table_name: str, snapshot: Optional[str] = None) -> "MatrixOneSQLBuilder":
|
174
|
+
"""Set FROM table with optional snapshot"""
|
175
|
+
self._from_table = table_name
|
176
|
+
self._from_snapshot = snapshot
|
177
|
+
return self
|
178
|
+
|
179
|
+
# JOIN clause methods
|
180
|
+
def join(self, table: str, on: str, join_type: str = "INNER") -> "MatrixOneSQLBuilder":
|
181
|
+
"""Add JOIN clause"""
|
182
|
+
join_clause = f"{join_type} JOIN {table} ON {on}"
|
183
|
+
self._joins.append(join_clause)
|
184
|
+
return self
|
185
|
+
|
186
|
+
def left_join(self, table: str, on: str) -> "MatrixOneSQLBuilder":
|
187
|
+
"""Add LEFT JOIN clause"""
|
188
|
+
return self.join(table, on, "LEFT")
|
189
|
+
|
190
|
+
def right_join(self, table: str, on: str) -> "MatrixOneSQLBuilder":
|
191
|
+
"""Add RIGHT JOIN clause"""
|
192
|
+
return self.join(table, on, "RIGHT")
|
193
|
+
|
194
|
+
# WHERE clause methods
|
195
|
+
def where(self, condition: str, *params: Any) -> "MatrixOneSQLBuilder":
|
196
|
+
"""Add WHERE condition"""
|
197
|
+
self._where_conditions.append(condition)
|
198
|
+
self._where_params.extend(params)
|
199
|
+
return self
|
200
|
+
|
201
|
+
def where_eq(self, column: str, value: Any) -> "MatrixOneSQLBuilder":
|
202
|
+
"""Add WHERE column = value"""
|
203
|
+
return self.where(f"{column} = ?", value)
|
204
|
+
|
205
|
+
def where_ne(self, column: str, value: Any) -> "MatrixOneSQLBuilder":
|
206
|
+
"""Add WHERE column != value"""
|
207
|
+
return self.where(f"{column} != ?", value)
|
208
|
+
|
209
|
+
def where_gt(self, column: str, value: Any) -> "MatrixOneSQLBuilder":
|
210
|
+
"""Add WHERE column > value"""
|
211
|
+
return self.where(f"{column} > ?", value)
|
212
|
+
|
213
|
+
def where_gte(self, column: str, value: Any) -> "MatrixOneSQLBuilder":
|
214
|
+
"""Add WHERE column >= value"""
|
215
|
+
return self.where(f"{column} >= ?", value)
|
216
|
+
|
217
|
+
def where_lt(self, column: str, value: Any) -> "MatrixOneSQLBuilder":
|
218
|
+
"""Add WHERE column < value"""
|
219
|
+
return self.where(f"{column} < ?", value)
|
220
|
+
|
221
|
+
def where_lte(self, column: str, value: Any) -> "MatrixOneSQLBuilder":
|
222
|
+
"""Add WHERE column <= value"""
|
223
|
+
return self.where(f"{column} <= ?", value)
|
224
|
+
|
225
|
+
def where_in(self, column: str, values: List[Any]) -> "MatrixOneSQLBuilder":
|
226
|
+
"""Add WHERE column IN (values)"""
|
227
|
+
if not values:
|
228
|
+
return self.where("1=0") # Always false for empty IN
|
229
|
+
placeholders = ",".join(["?" for _ in values])
|
230
|
+
return self.where(f"{column} IN ({placeholders})", *values)
|
231
|
+
|
232
|
+
def where_nin(self, column: str, values: List[Any]) -> "MatrixOneSQLBuilder":
|
233
|
+
"""Add WHERE column NOT IN (values)"""
|
234
|
+
if not values:
|
235
|
+
return self.where("1=1") # Always true for empty NOT IN
|
236
|
+
placeholders = ",".join(["?" for _ in values])
|
237
|
+
return self.where(f"{column} NOT IN ({placeholders})", *values)
|
238
|
+
|
239
|
+
def where_like(self, column: str, pattern: str) -> "MatrixOneSQLBuilder":
|
240
|
+
"""Add WHERE column LIKE pattern"""
|
241
|
+
return self.where(f"{column} LIKE ?", pattern)
|
242
|
+
|
243
|
+
def where_between(self, column: str, start: Any, end: Any) -> "MatrixOneSQLBuilder":
|
244
|
+
"""Add WHERE column BETWEEN start AND end"""
|
245
|
+
return self.where(f"{column} BETWEEN ? AND ?", start, end)
|
246
|
+
|
247
|
+
def where_and(self, *conditions: str) -> "MatrixOneSQLBuilder":
|
248
|
+
"""Add WHERE condition with AND logic"""
|
249
|
+
if conditions:
|
250
|
+
combined = " AND ".join(f"({cond})" for cond in conditions)
|
251
|
+
return self.where(combined)
|
252
|
+
return self
|
253
|
+
|
254
|
+
def where_or(self, *conditions: str) -> "MatrixOneSQLBuilder":
|
255
|
+
"""Add WHERE condition with OR logic"""
|
256
|
+
if conditions:
|
257
|
+
combined = " OR ".join(f"({cond})" for cond in conditions)
|
258
|
+
return self.where(combined)
|
259
|
+
return self
|
260
|
+
|
261
|
+
# GROUP BY clause methods
|
262
|
+
def group_by(self, *columns: str) -> "MatrixOneSQLBuilder":
|
263
|
+
"""Add GROUP BY columns"""
|
264
|
+
self._group_by_columns.extend(columns)
|
265
|
+
return self
|
266
|
+
|
267
|
+
# HAVING clause methods
|
268
|
+
def having(self, condition: str, *params: Any) -> "MatrixOneSQLBuilder":
|
269
|
+
"""
|
270
|
+
Add HAVING condition to the SQL query.
|
271
|
+
|
272
|
+
The HAVING clause is used to filter groups after GROUP BY operations,
|
273
|
+
similar to WHERE clause but applied to aggregated results.
|
274
|
+
|
275
|
+
Args:
|
276
|
+
|
277
|
+
condition (str): The HAVING condition as a string.
|
278
|
+
Can include '?' placeholders for parameter substitution.
|
279
|
+
*params (Any): Parameters to replace '?' placeholders in the condition.
|
280
|
+
|
281
|
+
Returns:
|
282
|
+
|
283
|
+
MatrixOneSQLBuilder: Self for method chaining.
|
284
|
+
|
285
|
+
Examples:
|
286
|
+
|
287
|
+
# Basic HAVING with placeholders
|
288
|
+
builder.group_by("department")
|
289
|
+
builder.having("COUNT(*) > ?", 5)
|
290
|
+
builder.having("AVG(age) > ?", 25)
|
291
|
+
|
292
|
+
# HAVING without placeholders
|
293
|
+
builder.group_by("department")
|
294
|
+
builder.having("COUNT(*) > 5")
|
295
|
+
builder.having("AVG(age) > 25")
|
296
|
+
|
297
|
+
# Multiple HAVING conditions
|
298
|
+
builder.group_by("department")
|
299
|
+
builder.having("COUNT(*) > ?", 5)
|
300
|
+
builder.having("AVG(age) > ?", 25)
|
301
|
+
builder.having("MAX(age) < ?", 65)
|
302
|
+
|
303
|
+
# Complex HAVING conditions
|
304
|
+
builder.group_by("department", "status")
|
305
|
+
builder.having("COUNT(*) > ? AND AVG(salary) > ?", 10, 50000)
|
306
|
+
builder.having("SUM(revenue) > ?", 1000000)
|
307
|
+
|
308
|
+
Notes:
|
309
|
+
- HAVING clauses are typically used with GROUP BY operations
|
310
|
+
- Use '?' placeholders for safer parameter substitution
|
311
|
+
- Multiple HAVING conditions are combined with AND logic
|
312
|
+
- This is a low-level SQL builder - for ORM usage, prefer MatrixOneQuery
|
313
|
+
|
314
|
+
Raises:
|
315
|
+
|
316
|
+
ValueError: If condition is not a string
|
317
|
+
"""
|
318
|
+
self._having_conditions.append(condition)
|
319
|
+
self._having_params.extend(params)
|
320
|
+
return self
|
321
|
+
|
322
|
+
# ORDER BY clause methods
|
323
|
+
def order_by(self, *columns: str) -> "MatrixOneSQLBuilder":
|
324
|
+
"""Add ORDER BY columns"""
|
325
|
+
self._order_by_columns.extend(columns)
|
326
|
+
return self
|
327
|
+
|
328
|
+
def order_by_distance(self, alias: str = "distance") -> "MatrixOneSQLBuilder":
|
329
|
+
"""Add ORDER BY distance (for vector queries)"""
|
330
|
+
return self.order_by(alias)
|
331
|
+
|
332
|
+
# LIMIT/OFFSET clause methods
|
333
|
+
def limit(self, count: int) -> "MatrixOneSQLBuilder":
|
334
|
+
"""Add LIMIT clause"""
|
335
|
+
self._limit_count = count
|
336
|
+
return self
|
337
|
+
|
338
|
+
def offset(self, count: int) -> "MatrixOneSQLBuilder":
|
339
|
+
"""Add OFFSET clause"""
|
340
|
+
self._offset_count = count
|
341
|
+
return self
|
342
|
+
|
343
|
+
# CTE (Common Table Expression) methods
|
344
|
+
def with_cte(self, name: str, sql: str, *params: Any) -> "MatrixOneSQLBuilder":
|
345
|
+
"""Add CTE (Common Table Expression)"""
|
346
|
+
self._ctes.append({"name": name, "sql": sql, "params": list(params)})
|
347
|
+
return self
|
348
|
+
|
349
|
+
# Vector query specific methods
|
350
|
+
def vector_similarity_search(
|
351
|
+
self,
|
352
|
+
table_name: str,
|
353
|
+
vector_column: str,
|
354
|
+
query_vector: List[float],
|
355
|
+
distance_func: DistanceFunction = DistanceFunction.L2_SQ,
|
356
|
+
limit: int = 10,
|
357
|
+
select_columns: Optional[List[str]] = None,
|
358
|
+
where_conditions: Optional[List[str]] = None,
|
359
|
+
where_params: Optional[List[Any]] = None,
|
360
|
+
) -> "MatrixOneSQLBuilder":
|
361
|
+
"""Build vector similarity search query"""
|
362
|
+
# Reset and build vector query
|
363
|
+
self.reset()
|
364
|
+
|
365
|
+
# Set up SELECT clause
|
366
|
+
if select_columns:
|
367
|
+
self.select(*select_columns)
|
368
|
+
# Ensure vector column is included for distance calculation
|
369
|
+
if vector_column not in select_columns:
|
370
|
+
self.select(vector_column)
|
371
|
+
else:
|
372
|
+
self.select_all()
|
373
|
+
|
374
|
+
# Add distance calculation
|
375
|
+
self.add_vector_distance(vector_column, query_vector, distance_func)
|
376
|
+
|
377
|
+
# Set FROM table
|
378
|
+
self.from_table(table_name)
|
379
|
+
|
380
|
+
# Add WHERE conditions if provided
|
381
|
+
if where_conditions:
|
382
|
+
for condition in where_conditions:
|
383
|
+
self._where_conditions.append(condition)
|
384
|
+
if where_params:
|
385
|
+
self._where_params.extend(where_params)
|
386
|
+
|
387
|
+
# Add ORDER BY distance and LIMIT
|
388
|
+
self.order_by_distance().limit(limit)
|
389
|
+
|
390
|
+
return self
|
391
|
+
|
392
|
+
# SQL building methods
|
393
|
+
def build(self) -> Tuple[str, List[Any]]:
|
394
|
+
"""Build the final SQL query and return (sql, params)"""
|
395
|
+
if self._query_type == "INSERT":
|
396
|
+
return self._build_insert()
|
397
|
+
elif self._query_type == "UPDATE":
|
398
|
+
return self._build_update()
|
399
|
+
elif self._query_type == "DELETE":
|
400
|
+
return self._build_delete()
|
401
|
+
else: # SELECT
|
402
|
+
return self._build_select()
|
403
|
+
|
404
|
+
def _build_select(self) -> Tuple[str, List[Any]]:
|
405
|
+
"""Build SELECT query"""
|
406
|
+
sql_parts = []
|
407
|
+
all_params = []
|
408
|
+
|
409
|
+
# Build WITH clause (CTEs)
|
410
|
+
if self._ctes:
|
411
|
+
with_parts = []
|
412
|
+
for cte in self._ctes:
|
413
|
+
with_parts.append(f"{cte['name']} AS ({cte['sql']})")
|
414
|
+
all_params.extend(cte["params"])
|
415
|
+
sql_parts.append("WITH " + ", ".join(with_parts))
|
416
|
+
|
417
|
+
# Build SELECT clause
|
418
|
+
if self._select_columns:
|
419
|
+
select_clause = "SELECT " + ", ".join(self._select_columns)
|
420
|
+
else:
|
421
|
+
select_clause = "SELECT *"
|
422
|
+
sql_parts.append(select_clause)
|
423
|
+
|
424
|
+
# Build FROM clause
|
425
|
+
if self._from_table:
|
426
|
+
from_clause = f"FROM {self._from_table}"
|
427
|
+
if self._from_snapshot:
|
428
|
+
from_clause += f"{{snapshot = '{self._from_snapshot}'}}"
|
429
|
+
sql_parts.append(from_clause)
|
430
|
+
|
431
|
+
# Build JOIN clauses
|
432
|
+
if self._joins:
|
433
|
+
sql_parts.append(" ".join(self._joins))
|
434
|
+
|
435
|
+
# Build WHERE clause
|
436
|
+
if self._where_conditions:
|
437
|
+
where_clause = "WHERE " + " AND ".join(self._where_conditions)
|
438
|
+
sql_parts.append(where_clause)
|
439
|
+
all_params.extend(self._where_params)
|
440
|
+
|
441
|
+
# Build GROUP BY clause
|
442
|
+
if self._group_by_columns:
|
443
|
+
group_clause = "GROUP BY " + ", ".join(self._group_by_columns)
|
444
|
+
sql_parts.append(group_clause)
|
445
|
+
|
446
|
+
# Build HAVING clause
|
447
|
+
if self._having_conditions:
|
448
|
+
having_clause = "HAVING " + " AND ".join(self._having_conditions)
|
449
|
+
sql_parts.append(having_clause)
|
450
|
+
all_params.extend(self._having_params)
|
451
|
+
|
452
|
+
# Build ORDER BY clause
|
453
|
+
if self._order_by_columns:
|
454
|
+
order_clause = "ORDER BY " + ", ".join(self._order_by_columns)
|
455
|
+
sql_parts.append(order_clause)
|
456
|
+
|
457
|
+
# Build LIMIT clause
|
458
|
+
if self._limit_count is not None:
|
459
|
+
sql_parts.append(f"LIMIT {self._limit_count}")
|
460
|
+
|
461
|
+
# Build OFFSET clause
|
462
|
+
if self._offset_count is not None:
|
463
|
+
sql_parts.append(f"OFFSET {self._offset_count}")
|
464
|
+
|
465
|
+
# Combine all parts
|
466
|
+
sql = " ".join(sql_parts)
|
467
|
+
|
468
|
+
return sql, all_params
|
469
|
+
|
470
|
+
def _build_insert(self) -> Tuple[str, List[Any]]:
|
471
|
+
"""Build INSERT query"""
|
472
|
+
if not self._insert_values:
|
473
|
+
raise ValueError("No values provided for INSERT")
|
474
|
+
|
475
|
+
# Handle both dict and list formats
|
476
|
+
if self._insert_columns and self._insert_values:
|
477
|
+
# List format: columns and values are separate
|
478
|
+
columns_str = ", ".join(self._insert_columns)
|
479
|
+
values_parts = []
|
480
|
+
params = []
|
481
|
+
|
482
|
+
for values_row in self._insert_values:
|
483
|
+
placeholders = []
|
484
|
+
for value in values_row:
|
485
|
+
if value is None:
|
486
|
+
placeholders.append("NULL")
|
487
|
+
else:
|
488
|
+
placeholders.append("?")
|
489
|
+
params.append(value)
|
490
|
+
values_parts.append(f"({', '.join(placeholders)})")
|
491
|
+
|
492
|
+
values_str = ", ".join(values_parts)
|
493
|
+
sql = f"INSERT INTO {self._from_table} ({columns_str}) VALUES {values_str}"
|
494
|
+
return sql, params
|
495
|
+
else:
|
496
|
+
# Dict format: values are dictionaries
|
497
|
+
all_columns = set()
|
498
|
+
for values in self._insert_values:
|
499
|
+
if isinstance(values, dict):
|
500
|
+
all_columns.update(values.keys())
|
501
|
+
|
502
|
+
columns = list(all_columns)
|
503
|
+
columns_str = ", ".join(columns)
|
504
|
+
|
505
|
+
# Build VALUES clause
|
506
|
+
values_parts = []
|
507
|
+
params = []
|
508
|
+
|
509
|
+
for values in self._insert_values:
|
510
|
+
placeholders = []
|
511
|
+
for col in columns:
|
512
|
+
if col in values:
|
513
|
+
placeholders.append("?")
|
514
|
+
params.append(values[col])
|
515
|
+
else:
|
516
|
+
placeholders.append("NULL")
|
517
|
+
values_parts.append(f"({', '.join(placeholders)})")
|
518
|
+
|
519
|
+
values_str = ", ".join(values_parts)
|
520
|
+
sql = f"INSERT INTO {self._from_table} ({columns_str}) VALUES {values_str}"
|
521
|
+
return sql, params
|
522
|
+
|
523
|
+
def _build_update(self) -> Tuple[str, List[Any]]:
|
524
|
+
"""Build UPDATE query"""
|
525
|
+
if not self._update_set_columns:
|
526
|
+
raise ValueError("No SET clauses provided for UPDATE")
|
527
|
+
|
528
|
+
# Build SET clause
|
529
|
+
set_clause = ", ".join(self._update_set_columns)
|
530
|
+
|
531
|
+
# Build WHERE clause
|
532
|
+
where_clause = ""
|
533
|
+
params = self._update_set_values.copy()
|
534
|
+
if self._where_conditions:
|
535
|
+
where_clause = "WHERE " + " AND ".join(self._where_conditions)
|
536
|
+
params.extend(self._where_params)
|
537
|
+
|
538
|
+
sql = f"UPDATE {self._from_table} SET {set_clause} {where_clause}"
|
539
|
+
return sql, params
|
540
|
+
|
541
|
+
def _build_delete(self) -> Tuple[str, List[Any]]:
|
542
|
+
"""Build DELETE query"""
|
543
|
+
# Build WHERE clause
|
544
|
+
where_clause = ""
|
545
|
+
params = []
|
546
|
+
if self._where_conditions:
|
547
|
+
where_clause = "WHERE " + " AND ".join(self._where_conditions)
|
548
|
+
params.extend(self._where_params)
|
549
|
+
|
550
|
+
sql = f"DELETE FROM {self._from_table} {where_clause}"
|
551
|
+
return sql, params
|
552
|
+
|
553
|
+
def build_with_parameter_substitution(self) -> str:
|
554
|
+
"""
|
555
|
+
Build SQL with direct parameter substitution for MatrixOne compatibility.
|
556
|
+
|
557
|
+
MatrixOne doesn't support parameterized queries in all contexts,
|
558
|
+
so this method substitutes parameters directly into the SQL string.
|
559
|
+
"""
|
560
|
+
sql, params = self.build()
|
561
|
+
|
562
|
+
# Replace parameters directly in SQL for MatrixOne compatibility
|
563
|
+
if params:
|
564
|
+
for param in params:
|
565
|
+
if isinstance(param, str):
|
566
|
+
# Escape single quotes in string parameters
|
567
|
+
escaped_param = param.replace("'", "''")
|
568
|
+
sql = sql.replace("?", f"'{escaped_param}'", 1)
|
569
|
+
else:
|
570
|
+
sql = sql.replace("?", str(param), 1)
|
571
|
+
|
572
|
+
return sql
|
573
|
+
|
574
|
+
# Utility methods
|
575
|
+
def clone(self) -> "MatrixOneSQLBuilder":
|
576
|
+
"""Create a copy of the current builder"""
|
577
|
+
clone = MatrixOneSQLBuilder()
|
578
|
+
clone._select_columns = self._select_columns.copy()
|
579
|
+
clone._from_table = self._from_table
|
580
|
+
clone._from_snapshot = self._from_snapshot
|
581
|
+
clone._joins = self._joins.copy()
|
582
|
+
clone._where_conditions = self._where_conditions.copy()
|
583
|
+
clone._where_params = self._where_params.copy()
|
584
|
+
clone._group_by_columns = self._group_by_columns.copy()
|
585
|
+
clone._having_conditions = self._having_conditions.copy()
|
586
|
+
clone._having_params = self._having_params.copy()
|
587
|
+
clone._order_by_columns = self._order_by_columns.copy()
|
588
|
+
clone._limit_count = self._limit_count
|
589
|
+
clone._offset_count = self._offset_count
|
590
|
+
clone._ctes = self._ctes.copy()
|
591
|
+
return clone
|
592
|
+
|
593
|
+
def __str__(self) -> str:
|
594
|
+
"""String representation of the query"""
|
595
|
+
return self.build_with_parameter_substitution()
|
596
|
+
|
597
|
+
|
598
|
+
# Convenience functions for common operations
|
599
|
+
def build_vector_similarity_query(
|
600
|
+
table_name: str,
|
601
|
+
vector_column: str,
|
602
|
+
query_vector: List[float],
|
603
|
+
distance_func: DistanceFunction = DistanceFunction.L2_SQ,
|
604
|
+
limit: int = 10,
|
605
|
+
select_columns: Optional[List[str]] = None,
|
606
|
+
where_conditions: Optional[List[str]] = None,
|
607
|
+
where_params: Optional[List[Any]] = None,
|
608
|
+
use_parameter_substitution: bool = True,
|
609
|
+
) -> Union[str, Tuple[str, List[Any]]]:
|
610
|
+
"""
|
611
|
+
Convenience function to build vector similarity search query.
|
612
|
+
|
613
|
+
Args:
|
614
|
+
|
615
|
+
table_name: Name of the table to query
|
616
|
+
vector_column: Name of the vector column
|
617
|
+
query_vector: Query vector for similarity search
|
618
|
+
distance_func: Distance function to use
|
619
|
+
limit: Maximum number of results
|
620
|
+
select_columns: Columns to select (None for all)
|
621
|
+
where_conditions: Additional WHERE conditions
|
622
|
+
where_params: Parameters for WHERE conditions
|
623
|
+
use_parameter_substitution: Whether to substitute parameters directly
|
624
|
+
|
625
|
+
Returns:
|
626
|
+
|
627
|
+
SQL string (if use_parameter_substitution=True) or (sql, params) tuple
|
628
|
+
"""
|
629
|
+
builder = MatrixOneSQLBuilder().vector_similarity_search(
|
630
|
+
table_name=table_name,
|
631
|
+
vector_column=vector_column,
|
632
|
+
query_vector=query_vector,
|
633
|
+
distance_func=distance_func,
|
634
|
+
limit=limit,
|
635
|
+
select_columns=select_columns,
|
636
|
+
where_conditions=where_conditions,
|
637
|
+
where_params=where_params,
|
638
|
+
)
|
639
|
+
|
640
|
+
if use_parameter_substitution:
|
641
|
+
return builder.build_with_parameter_substitution()
|
642
|
+
else:
|
643
|
+
return builder.build()
|
644
|
+
|
645
|
+
|
646
|
+
def build_select_query(
|
647
|
+
table_name: str,
|
648
|
+
select_columns: Optional[List[str]] = None,
|
649
|
+
where_conditions: Optional[List[str]] = None,
|
650
|
+
where_params: Optional[List[Any]] = None,
|
651
|
+
order_by: Optional[List[str]] = None,
|
652
|
+
limit: Optional[int] = None,
|
653
|
+
use_parameter_substitution: bool = True,
|
654
|
+
) -> Union[str, Tuple[str, List[Any]]]:
|
655
|
+
"""
|
656
|
+
Convenience function to build SELECT query.
|
657
|
+
|
658
|
+
Args:
|
659
|
+
|
660
|
+
table_name: Name of the table to query
|
661
|
+
select_columns: Columns to select (None for all)
|
662
|
+
where_conditions: WHERE conditions
|
663
|
+
where_params: Parameters for WHERE conditions
|
664
|
+
order_by: ORDER BY columns
|
665
|
+
limit: LIMIT count
|
666
|
+
use_parameter_substitution: Whether to substitute parameters directly
|
667
|
+
|
668
|
+
Returns:
|
669
|
+
|
670
|
+
SQL string (if use_parameter_substitution=True) or (sql, params) tuple
|
671
|
+
"""
|
672
|
+
builder = MatrixOneSQLBuilder()
|
673
|
+
|
674
|
+
if select_columns:
|
675
|
+
builder.select(*select_columns)
|
676
|
+
else:
|
677
|
+
builder.select_all()
|
678
|
+
|
679
|
+
builder.from_table(table_name)
|
680
|
+
|
681
|
+
if where_conditions:
|
682
|
+
for condition in where_conditions:
|
683
|
+
builder._where_conditions.append(condition)
|
684
|
+
if where_params:
|
685
|
+
builder._where_params.extend(where_params)
|
686
|
+
|
687
|
+
if order_by:
|
688
|
+
builder.order_by(*order_by)
|
689
|
+
|
690
|
+
if limit:
|
691
|
+
builder.limit(limit)
|
692
|
+
|
693
|
+
if use_parameter_substitution:
|
694
|
+
return builder.build_with_parameter_substitution()
|
695
|
+
else:
|
696
|
+
return builder.build()
|
697
|
+
|
698
|
+
|
699
|
+
def build_insert_query(
|
700
|
+
table_name: str,
|
701
|
+
values: Union[Dict[str, Any], List[Dict[str, Any]]],
|
702
|
+
use_parameter_substitution: bool = False,
|
703
|
+
) -> Union[str, Tuple[str, List[Any]]]:
|
704
|
+
"""
|
705
|
+
Build INSERT query using unified SQL builder.
|
706
|
+
|
707
|
+
Args:
|
708
|
+
|
709
|
+
table_name: Name of the table
|
710
|
+
values: Single row dict or list of row dicts
|
711
|
+
use_parameter_substitution: Whether to substitute parameters directly
|
712
|
+
|
713
|
+
Returns:
|
714
|
+
|
715
|
+
SQL string (if use_parameter_substitution=True) or (sql, params) tuple
|
716
|
+
"""
|
717
|
+
builder = MatrixOneSQLBuilder()
|
718
|
+
builder.insert_into(table_name)
|
719
|
+
|
720
|
+
if isinstance(values, dict):
|
721
|
+
builder.values(**values)
|
722
|
+
else:
|
723
|
+
for row in values:
|
724
|
+
builder.values(**row)
|
725
|
+
|
726
|
+
if use_parameter_substitution:
|
727
|
+
return builder.build_with_parameter_substitution()
|
728
|
+
else:
|
729
|
+
return builder.build()
|
730
|
+
|
731
|
+
|
732
|
+
def build_update_query(
|
733
|
+
table_name: str,
|
734
|
+
set_values: Dict[str, Any],
|
735
|
+
where_conditions: Optional[List[str]] = None,
|
736
|
+
where_params: Optional[List[Any]] = None,
|
737
|
+
use_parameter_substitution: bool = False,
|
738
|
+
) -> Union[str, Tuple[str, List[Any]]]:
|
739
|
+
"""
|
740
|
+
Build UPDATE query using unified SQL builder.
|
741
|
+
|
742
|
+
Args:
|
743
|
+
|
744
|
+
table_name: Name of the table
|
745
|
+
set_values: Dictionary of column=value pairs to update
|
746
|
+
where_conditions: WHERE conditions
|
747
|
+
where_params: Parameters for WHERE conditions
|
748
|
+
use_parameter_substitution: Whether to substitute parameters directly
|
749
|
+
|
750
|
+
Returns:
|
751
|
+
|
752
|
+
SQL string (if use_parameter_substitution=True) or (sql, params) tuple
|
753
|
+
"""
|
754
|
+
builder = MatrixOneSQLBuilder()
|
755
|
+
builder.update(table_name).set(**set_values)
|
756
|
+
|
757
|
+
if where_conditions:
|
758
|
+
for condition in where_conditions:
|
759
|
+
builder._where_conditions.append(condition)
|
760
|
+
if where_params:
|
761
|
+
builder._where_params.extend(where_params)
|
762
|
+
|
763
|
+
if use_parameter_substitution:
|
764
|
+
return builder.build_with_parameter_substitution()
|
765
|
+
else:
|
766
|
+
return builder.build()
|
767
|
+
|
768
|
+
|
769
|
+
def build_delete_query(
|
770
|
+
table_name: str,
|
771
|
+
where_conditions: Optional[List[str]] = None,
|
772
|
+
where_params: Optional[List[Any]] = None,
|
773
|
+
use_parameter_substitution: bool = False,
|
774
|
+
) -> Union[str, Tuple[str, List[Any]]]:
|
775
|
+
"""
|
776
|
+
Build DELETE query using unified SQL builder.
|
777
|
+
|
778
|
+
Args:
|
779
|
+
|
780
|
+
table_name: Name of the table
|
781
|
+
where_conditions: WHERE conditions
|
782
|
+
where_params: Parameters for WHERE conditions
|
783
|
+
use_parameter_substitution: Whether to substitute parameters directly
|
784
|
+
|
785
|
+
Returns:
|
786
|
+
|
787
|
+
SQL string (if use_parameter_substitution=True) or (sql, params) tuple
|
788
|
+
"""
|
789
|
+
builder = MatrixOneSQLBuilder()
|
790
|
+
builder.delete_from(table_name)
|
791
|
+
|
792
|
+
if where_conditions:
|
793
|
+
for condition in where_conditions:
|
794
|
+
builder._where_conditions.append(condition)
|
795
|
+
if where_params:
|
796
|
+
builder._where_params.extend(where_params)
|
797
|
+
|
798
|
+
if use_parameter_substitution:
|
799
|
+
return builder.build_with_parameter_substitution()
|
800
|
+
else:
|
801
|
+
return builder.build()
|
802
|
+
|
803
|
+
|
804
|
+
def build_create_index_query(
|
805
|
+
index_name: str, table_name: str, column_name: str, index_type: str = "ivfflat", **kwargs
|
806
|
+
) -> str:
|
807
|
+
"""
|
808
|
+
Convenience function to build CREATE INDEX query.
|
809
|
+
|
810
|
+
Args:
|
811
|
+
|
812
|
+
index_name: Name of the index
|
813
|
+
table_name: Name of the table
|
814
|
+
column_name: Name of the column to index
|
815
|
+
index_type: Type of index (ivfflat, hnsw, etc.)
|
816
|
+
**kwargs: Additional index parameters
|
817
|
+
|
818
|
+
Returns:
|
819
|
+
|
820
|
+
CREATE INDEX SQL string
|
821
|
+
"""
|
822
|
+
sql_parts = [f"CREATE INDEX {index_name} USING {index_type} ON {table_name}({column_name})"]
|
823
|
+
|
824
|
+
# Add parameters based on index type
|
825
|
+
if index_type.lower() == "ivfflat" and "lists" in kwargs:
|
826
|
+
sql_parts.append(f"lists = {kwargs['lists']}")
|
827
|
+
elif index_type.lower() == "hnsw":
|
828
|
+
if "m" in kwargs:
|
829
|
+
sql_parts.append(f"M {kwargs['m']}")
|
830
|
+
if "ef_construction" in kwargs:
|
831
|
+
sql_parts.append(f"EF_CONSTRUCTION {kwargs['ef_construction']}")
|
832
|
+
if "ef_search" in kwargs:
|
833
|
+
sql_parts.append(f"EF_SEARCH {kwargs['ef_search']}")
|
834
|
+
elif index_type.lower() == "fulltext":
|
835
|
+
# For fulltext indexes, use CREATE FULLTEXT INDEX syntax
|
836
|
+
sql_parts = [f"CREATE FULLTEXT INDEX {index_name} ON {table_name}({column_name})"]
|
837
|
+
if "algorithm" in kwargs:
|
838
|
+
sql_parts.append(f"WITH PARSER {kwargs['algorithm']}")
|
839
|
+
|
840
|
+
# Add operation type
|
841
|
+
if "op_type" in kwargs:
|
842
|
+
sql_parts.append(f"op_type '{kwargs['op_type']}'")
|
843
|
+
|
844
|
+
return " ".join(sql_parts)
|