|
42 | 42 | DropCollectionStmt, |
43 | 43 | FilterExpr, |
44 | 44 | InExpr, |
| 45 | + InsertBulkStmt, |
45 | 46 | InsertStmt, |
46 | 47 | IsEmptyExpr, |
47 | 48 | IsNotEmptyExpr, |
@@ -77,6 +78,8 @@ def __init__(self, client: QdrantClient, config: QQLConfig) -> None: |
77 | 78 | self._config = config |
78 | 79 |
|
79 | 80 | def execute(self, node: ASTNode) -> ExecutionResult: |
| 81 | + if isinstance(node, InsertBulkStmt): |
| 82 | + return self._execute_insert_bulk(node) |
80 | 83 | if isinstance(node, InsertStmt): |
81 | 84 | return self._execute_insert(node) |
82 | 85 | if isinstance(node, CreateCollectionStmt): |
@@ -170,6 +173,84 @@ def _execute_insert(self, node: InsertStmt) -> ExecutionResult: |
170 | 173 | data={"id": point_id, "collection": node.collection}, |
171 | 174 | ) |
172 | 175 |
|
| 176 | + def _execute_insert_bulk(self, node: InsertBulkStmt) -> ExecutionResult: |
| 177 | + if not node.values_list: |
| 178 | + raise QQLRuntimeError("INSERT BULK VALUES list is empty") |
| 179 | + for i, vals in enumerate(node.values_list): |
| 180 | + if "text" not in vals: |
| 181 | + raise QQLRuntimeError( |
| 182 | + f"INSERT BULK: item at index {i} is missing required 'text' field" |
| 183 | + ) |
| 184 | + |
| 185 | + # ── Hybrid bulk INSERT: dense + sparse vectors ───────────────────── |
| 186 | + if node.hybrid: |
| 187 | + dense_model = node.model or self._config.default_model |
| 188 | + sparse_model_name = node.sparse_model or SparseEmbedder.DEFAULT_MODEL |
| 189 | + dense_embedder = Embedder(dense_model) |
| 190 | + sparse_embedder = SparseEmbedder(sparse_model_name) |
| 191 | + |
| 192 | + points: list[PointStruct] = [] |
| 193 | + for vals in node.values_list: |
| 194 | + dense_vector = dense_embedder.embed(vals["text"]) |
| 195 | + sparse_obj = sparse_embedder.embed(vals["text"]) |
| 196 | + sparse_vector = SparseVector( |
| 197 | + indices=sparse_obj["indices"], values=sparse_obj["values"] |
| 198 | + ) |
| 199 | + point_id = str(uuid.uuid4()) |
| 200 | + points.append( |
| 201 | + PointStruct( |
| 202 | + id=point_id, |
| 203 | + vector={"dense": dense_vector, "sparse": sparse_vector}, |
| 204 | + payload=dict(vals), |
| 205 | + ) |
| 206 | + ) |
| 207 | + |
| 208 | + if not self._client.collection_exists(node.collection): |
| 209 | + first_dense = dense_embedder.embed(node.values_list[0]["text"]) |
| 210 | + self._client.create_collection( |
| 211 | + collection_name=node.collection, |
| 212 | + vectors_config={ |
| 213 | + "dense": VectorParams(size=len(first_dense), distance=Distance.COSINE) |
| 214 | + }, |
| 215 | + sparse_vectors_config={ |
| 216 | + "sparse": SparseVectorParams(modifier=Modifier.IDF) |
| 217 | + }, |
| 218 | + ) |
| 219 | + |
| 220 | + try: |
| 221 | + self._client.upsert(collection_name=node.collection, points=points) |
| 222 | + except UnexpectedResponse as e: |
| 223 | + raise QQLRuntimeError(f"Qdrant error during INSERT BULK: {e}") from e |
| 224 | + |
| 225 | + return ExecutionResult( |
| 226 | + success=True, |
| 227 | + message=f"Inserted {len(points)} points (hybrid)", |
| 228 | + ) |
| 229 | + |
| 230 | + # ── Standard dense-only bulk INSERT ─────────────────────────────── |
| 231 | + model_name = node.model or self._config.default_model |
| 232 | + embedder = Embedder(model_name) |
| 233 | + |
| 234 | + points = [] |
| 235 | + for vals in node.values_list: |
| 236 | + vector = embedder.embed(vals["text"]) |
| 237 | + point_id = str(uuid.uuid4()) |
| 238 | + points.append( |
| 239 | + PointStruct(id=point_id, vector=vector, payload=dict(vals)) |
| 240 | + ) |
| 241 | + |
| 242 | + self._ensure_collection(node.collection, len(points[0].vector)) |
| 243 | + |
| 244 | + try: |
| 245 | + self._client.upsert(collection_name=node.collection, points=points) |
| 246 | + except UnexpectedResponse as e: |
| 247 | + raise QQLRuntimeError(f"Qdrant error during INSERT BULK: {e}") from e |
| 248 | + |
| 249 | + return ExecutionResult( |
| 250 | + success=True, |
| 251 | + message=f"Inserted {len(points)} points", |
| 252 | + ) |
| 253 | + |
173 | 254 | def _execute_create(self, node: CreateCollectionStmt) -> ExecutionResult: |
174 | 255 | if self._client.collection_exists(node.collection): |
175 | 256 | return ExecutionResult( |
|
0 commit comments