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.
Files changed (122) hide show
  1. matrixone/__init__.py +155 -0
  2. matrixone/account.py +723 -0
  3. matrixone/async_client.py +3913 -0
  4. matrixone/async_metadata_manager.py +311 -0
  5. matrixone/async_orm.py +123 -0
  6. matrixone/async_vector_index_manager.py +633 -0
  7. matrixone/base_client.py +208 -0
  8. matrixone/client.py +4672 -0
  9. matrixone/config.py +452 -0
  10. matrixone/connection_hooks.py +286 -0
  11. matrixone/exceptions.py +89 -0
  12. matrixone/logger.py +782 -0
  13. matrixone/metadata.py +820 -0
  14. matrixone/moctl.py +219 -0
  15. matrixone/orm.py +2277 -0
  16. matrixone/pitr.py +646 -0
  17. matrixone/pubsub.py +771 -0
  18. matrixone/restore.py +411 -0
  19. matrixone/search_vector_index.py +1176 -0
  20. matrixone/snapshot.py +550 -0
  21. matrixone/sql_builder.py +844 -0
  22. matrixone/sqlalchemy_ext/__init__.py +161 -0
  23. matrixone/sqlalchemy_ext/adapters.py +163 -0
  24. matrixone/sqlalchemy_ext/dialect.py +534 -0
  25. matrixone/sqlalchemy_ext/fulltext_index.py +895 -0
  26. matrixone/sqlalchemy_ext/fulltext_search.py +1686 -0
  27. matrixone/sqlalchemy_ext/hnsw_config.py +194 -0
  28. matrixone/sqlalchemy_ext/ivf_config.py +252 -0
  29. matrixone/sqlalchemy_ext/table_builder.py +351 -0
  30. matrixone/sqlalchemy_ext/vector_index.py +1721 -0
  31. matrixone/sqlalchemy_ext/vector_type.py +948 -0
  32. matrixone/version.py +580 -0
  33. matrixone_python_sdk-0.1.0.dist-info/METADATA +706 -0
  34. matrixone_python_sdk-0.1.0.dist-info/RECORD +122 -0
  35. matrixone_python_sdk-0.1.0.dist-info/WHEEL +5 -0
  36. matrixone_python_sdk-0.1.0.dist-info/entry_points.txt +5 -0
  37. matrixone_python_sdk-0.1.0.dist-info/licenses/LICENSE +200 -0
  38. matrixone_python_sdk-0.1.0.dist-info/top_level.txt +2 -0
  39. tests/__init__.py +19 -0
  40. tests/offline/__init__.py +20 -0
  41. tests/offline/conftest.py +77 -0
  42. tests/offline/test_account.py +703 -0
  43. tests/offline/test_async_client_query_comprehensive.py +1218 -0
  44. tests/offline/test_basic.py +54 -0
  45. tests/offline/test_case_sensitivity.py +227 -0
  46. tests/offline/test_connection_hooks_offline.py +287 -0
  47. tests/offline/test_dialect_schema_handling.py +609 -0
  48. tests/offline/test_explain_methods.py +346 -0
  49. tests/offline/test_filter_logical_in.py +237 -0
  50. tests/offline/test_fulltext_search_comprehensive.py +795 -0
  51. tests/offline/test_ivf_config.py +249 -0
  52. tests/offline/test_join_methods.py +281 -0
  53. tests/offline/test_join_sqlalchemy_compatibility.py +276 -0
  54. tests/offline/test_logical_in_method.py +237 -0
  55. tests/offline/test_matrixone_version_parsing.py +264 -0
  56. tests/offline/test_metadata_offline.py +557 -0
  57. tests/offline/test_moctl.py +300 -0
  58. tests/offline/test_moctl_simple.py +251 -0
  59. tests/offline/test_model_support_offline.py +359 -0
  60. tests/offline/test_model_support_simple.py +225 -0
  61. tests/offline/test_pinecone_filter_offline.py +377 -0
  62. tests/offline/test_pitr.py +585 -0
  63. tests/offline/test_pubsub.py +712 -0
  64. tests/offline/test_query_update.py +283 -0
  65. tests/offline/test_restore.py +445 -0
  66. tests/offline/test_snapshot_comprehensive.py +384 -0
  67. tests/offline/test_sql_escaping_edge_cases.py +551 -0
  68. tests/offline/test_sqlalchemy_integration.py +382 -0
  69. tests/offline/test_sqlalchemy_vector_integration.py +434 -0
  70. tests/offline/test_table_builder.py +198 -0
  71. tests/offline/test_unified_filter.py +398 -0
  72. tests/offline/test_unified_transaction.py +495 -0
  73. tests/offline/test_vector_index.py +238 -0
  74. tests/offline/test_vector_operations.py +688 -0
  75. tests/offline/test_vector_type.py +174 -0
  76. tests/offline/test_version_core.py +328 -0
  77. tests/offline/test_version_management.py +372 -0
  78. tests/offline/test_version_standalone.py +652 -0
  79. tests/online/__init__.py +20 -0
  80. tests/online/conftest.py +216 -0
  81. tests/online/test_account_management.py +194 -0
  82. tests/online/test_advanced_features.py +344 -0
  83. tests/online/test_async_client_interfaces.py +330 -0
  84. tests/online/test_async_client_online.py +285 -0
  85. tests/online/test_async_model_insert_online.py +293 -0
  86. tests/online/test_async_orm_online.py +300 -0
  87. tests/online/test_async_simple_query_online.py +802 -0
  88. tests/online/test_async_transaction_simple_query.py +300 -0
  89. tests/online/test_basic_connection.py +130 -0
  90. tests/online/test_client_online.py +238 -0
  91. tests/online/test_config.py +90 -0
  92. tests/online/test_config_validation.py +123 -0
  93. tests/online/test_connection_hooks_new_online.py +217 -0
  94. tests/online/test_dialect_schema_handling_online.py +331 -0
  95. tests/online/test_filter_logical_in_online.py +374 -0
  96. tests/online/test_fulltext_comprehensive.py +1773 -0
  97. tests/online/test_fulltext_label_online.py +433 -0
  98. tests/online/test_fulltext_search_online.py +842 -0
  99. tests/online/test_ivf_stats_online.py +506 -0
  100. tests/online/test_logger_integration.py +311 -0
  101. tests/online/test_matrixone_query_orm.py +540 -0
  102. tests/online/test_metadata_online.py +579 -0
  103. tests/online/test_model_insert_online.py +255 -0
  104. tests/online/test_mysql_driver_validation.py +213 -0
  105. tests/online/test_orm_advanced_features.py +2022 -0
  106. tests/online/test_orm_cte_integration.py +269 -0
  107. tests/online/test_orm_online.py +270 -0
  108. tests/online/test_pinecone_filter.py +708 -0
  109. tests/online/test_pubsub_operations.py +352 -0
  110. tests/online/test_query_methods.py +225 -0
  111. tests/online/test_query_update_online.py +433 -0
  112. tests/online/test_search_vector_index.py +557 -0
  113. tests/online/test_simple_fulltext_online.py +915 -0
  114. tests/online/test_snapshot_comprehensive.py +998 -0
  115. tests/online/test_sqlalchemy_engine_integration.py +336 -0
  116. tests/online/test_sqlalchemy_integration.py +425 -0
  117. tests/online/test_transaction_contexts.py +1219 -0
  118. tests/online/test_transaction_insert_methods.py +356 -0
  119. tests/online/test_transaction_query_methods.py +288 -0
  120. tests/online/test_unified_filter_online.py +529 -0
  121. tests/online/test_vector_comprehensive.py +706 -0
  122. tests/online/test_version_management.py +291 -0
@@ -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)