Skip to content

Commit 9172127

Browse files
authored
Revert "Feat/refractor batch grpc async support (#48)" (#49)
This reverts commit 4a1fa92.
1 parent 4a1fa92 commit 9172127

19 files changed

Lines changed: 461 additions & 3848 deletions

README.md

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
[![PyPI version](https://img.shields.io/pypi/v/qql-cli?color=blue&label=PyPI)](https://pypi.org/project/qql-cli/)
66
[![Python 3.12+](https://img.shields.io/pypi/pyversions/qql-cli)](https://pypi.org/project/qql-cli/)
77
[![MIT License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
8-
[![Tests](https://img.shields.io/badge/tests-635%20passing-brightgreen)](tests/)
8+
[![Tests](https://img.shields.io/badge/tests-549%20passing-brightgreen)](tests/)
99

10-
Write `INSERT`, `SELECT`, `SEARCH`, `SCROLL`, `RECOMMEND`, `UPDATE`, `DELETE`, and `CREATE COLLECTION` statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, grouped search (GROUP BY), cross-encoder reranking, quantization (scalar, turbo, binary, product), SQL-style `WHERE` filters, script execution, collection dump/restore, async execution, gRPC transport, parameterized queries, and batched query execution.
10+
Write `INSERT`, `SELECT`, `SEARCH`, `SCROLL`, `RECOMMEND`, `UPDATE`, `DELETE`, and `CREATE COLLECTION` statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, grouped search (GROUP BY), cross-encoder reranking, quantization (scalar, turbo, binary, product), SQL-style `WHERE` filters, script execution, and collection dump/restore.
1111

1212
```
1313
qql> INSERT INTO COLLECTION notes VALUES {'text': 'Qdrant is a vector database', 'author': 'alice', 'year': 2024}
@@ -50,23 +50,16 @@ Your query string
5050

5151
When you run `INSERT`, the `text` field is automatically converted into a dense vector using [Fastembed](https://github.com/qdrant/fastembed). In **hybrid mode** (`USING HYBRID`), a sparse BM25 vector is also generated alongside the dense vector, and searches use Qdrant's Reciprocal Rank Fusion (RRF) by default to merge the results of both retrieval methods. You can switch hybrid search to DBSF with `FUSION 'dbsf'`.
5252

53-
QQL also exposes a **programmatic API** for use inside Python applications — no CLI required. Use `Connection` for sync code, `AsyncConnection` for async apps, and batch helpers when you want QQL to combine compatible operations into fewer Qdrant requests:
53+
QQL also exposes a **programmatic API** for use inside Python applications — no CLI required:
5454

5555
```python
56-
from qql import Connection, QQLBatch
56+
from qql import Connection
5757

5858
with Connection("http://localhost:6333") as conn:
5959
conn.run_query("INSERT INTO COLLECTION notes VALUES {'text': 'Qdrant is fast'}")
60-
result = conn.run_parameterized_query(
61-
"SEARCH notes SIMILAR TO :query LIMIT 5",
62-
{"query": "vector database"},
63-
)
64-
65-
with QQLBatch(conn) as batch:
66-
neurology = batch.add("SEARCH notes SIMILAR TO 'neurology' LIMIT 5")
67-
cardiology = batch.add("SEARCH notes SIMILAR TO 'cardiology' LIMIT 5")
68-
69-
print(neurology.result.data, cardiology.result.data)
60+
result = conn.run_query("SEARCH notes SIMILAR TO 'vector database' LIMIT 5")
61+
for hit in result.data:
62+
print(hit["score"], hit["payload"])
7063
```
7164

7265
---
@@ -110,8 +103,8 @@ Full documentation lives in the [`docs/`](docs/) folder and at **[pavanjava.gith
110103
| [SEARCH / SELECT / SCROLL / RECOMMEND / Hybrid / GROUP BY / RERANK](docs/search.md) | Semantic search, grouped search, point retrieval, pagination, hybrid, reranking, recommendations |
111104
| [WHERE Filters](docs/filters.md) | Full SQL-style filter operators |
112105
| [Collections & Quantization](docs/collections.md) | SHOW, CREATE, DROP, QUANTIZE (scalar/turbo/binary/product), CREATE INDEX, UPDATE VECTOR, UPDATE PAYLOAD |
113-
| [Scripts: EXECUTE / DUMP](docs/scripts.md) | Script files, `BEGIN BATCH` blocks, collection backup/restore |
114-
| [Programmatic Usage](docs/programmatic.md) | Sync/async Python APIs, parameterized queries, batching, gRPC |
106+
| [Scripts: EXECUTE / DUMP](docs/scripts.md) | Script files, collection backup/restore |
107+
| [Programmatic Usage](docs/programmatic.md) | Use QQL as a Python library via `Connection` or `run_query()` |
115108
| [Reference: Models / Config / Errors](docs/reference.md) | Embedding models, config file, error reference |
116109

117110
---
@@ -183,12 +176,6 @@ DELETE FROM articles WHERE year < 2020
183176
-- Scripts
184177
EXECUTE /path/to/script.qql
185178
DUMP articles /path/to/backup.qql
186-
187-
-- Batch block
188-
BEGIN BATCH;
189-
SEARCH articles SIMILAR TO 'query one' LIMIT 5;
190-
SEARCH articles SIMILAR TO 'query two' LIMIT 5;
191-
END BATCH
192179
```
193180

194181
---
@@ -201,7 +188,7 @@ Tests do not require a running Qdrant instance — the Qdrant client is mocked.
201188
pytest tests/ -v
202189
```
203190

204-
Expected: **635 tests passing**.
191+
Expected: **549 tests passing**.
205192

206193
---
207194

docs/getting-started.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title: "Getting Started"
55

66
# Getting Started with QQL
77

8-
QQL is a SQL-like query language and CLI for [Qdrant](https://qdrant.tech). Instead of writing Python SDK calls you write natural query statements to insert, search, manage, and delete vector data. It can also be used as a sync or async Python library with batching, parameterized queries, and optional gRPC transport.
8+
QQL is a SQL-like query language and CLI for [Qdrant](https://qdrant.tech). Instead of writing Python SDK calls you write natural query statements to insert, search, manage, and delete vector data.
99

1010
---
1111

@@ -172,12 +172,6 @@ SHOW COLLECTION notes
172172

173173
-- Retrieve a point by ID
174174
SELECT * FROM notes WHERE id = 1
175-
176-
-- Run compatible queries as one batch
177-
BEGIN BATCH;
178-
SEARCH notes SIMILAR TO 'vector databases' LIMIT 5;
179-
SEARCH notes SIMILAR TO 'semantic search' LIMIT 5;
180-
END BATCH
181175
```
182176

183177
---
@@ -188,6 +182,5 @@ END BATCH
188182
- [SEARCH / SELECT / SCROLL / RECOMMEND / Hybrid / RERANK](search.md) — querying
189183
- [WHERE Filters](filters.md) — payload filtering
190184
- [Collections & Quantization](collections.md) — managing collections
191-
- [Scripts: EXECUTE / DUMP](scripts.md) — automating with script files and batch blocks
192-
- [Programmatic Usage](programmatic.md) — sync/async APIs, batching, parameterized queries, gRPC
185+
- [Scripts: EXECUTE / DUMP](scripts.md) — automating with script files
193186
- [Embedding Models](reference.md#embedding-models) — model reference

docs/programmatic.md

Lines changed: 1 addition & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ single connection to Qdrant once and reuses it for every `run_query()` call —
1616
more efficient than the legacy `run_query()` function, which creates a new
1717
client on every invocation.
1818

19-
Use `AsyncConnection` when your application already runs on `asyncio`.
20-
2119
### Basic usage
2220

2321
```python
@@ -72,22 +70,6 @@ with Connection("https://<your-cluster>.qdrant.io", secret="<your-api-key>") as
7270
print(result.data)
7371
```
7472

75-
### gRPC transport
76-
77-
QQL can ask the Qdrant client to prefer gRPC for lower request overhead:
78-
79-
```python
80-
from qql import Connection
81-
82-
with Connection(
83-
"http://localhost:6333",
84-
prefer_grpc=True,
85-
grpc_port=6334,
86-
) as conn:
87-
result = conn.run_query("SHOW COLLECTIONS")
88-
print(result.data)
89-
```
90-
9173
### Internal or self-signed certificates
9274

9375
Prefer a custom CA bundle when your Qdrant endpoint uses an internal or
@@ -197,118 +179,10 @@ with Connection("http://localhost:6333") as conn:
197179
| `url` | `str` | `"http://localhost:6333"` | Qdrant instance URL |
198180
| `secret` | `str \| None` | `None` | API key; `None` for unauthenticated |
199181
| `default_model` | `str \| None` | `None``sentence-transformers/all-MiniLM-L6-v2` | Dense embedding model used when no `USING MODEL` clause is given |
200-
| `prefer_grpc` | `bool` | `False` | Passes `prefer_grpc=True` to the Qdrant client |
201-
| `grpc_port` | `int` | `6334` | gRPC port used when `prefer_grpc=True` |
202182
| `verify` | `bool \| str` | `True` | TLS verification setting; use `False` to skip verification or a CA bundle path for internal/self-signed certificates |
203183
| `default_dense_vector_name` | `str` | `"dense"` | Dense vector name used when QQL creates a collection and no explicit `USING VECTOR` name is given |
204184
| `default_sparse_vector_name` | `str` | `"sparse"` | Sparse vector name used when QQL creates a hybrid collection and no explicit sparse vector name is given |
205185

206-
---
207-
208-
## Parameterized Queries
209-
210-
Parameterized helpers render `:name` placeholders before parsing the QQL statement. String values are quoted and escaped; booleans are rendered as `true` / `false`.
211-
212-
```python
213-
from qql import Connection
214-
215-
with Connection("http://localhost:6333") as conn:
216-
result = conn.run_parameterized_query(
217-
"SEARCH notes SIMILAR TO :query LIMIT 5 WHERE author = :author",
218-
{"query": "vector database", "author": "alice"},
219-
)
220-
221-
results = conn.run_parameterized_batch(
222-
"SEARCH notes SIMILAR TO :query LIMIT 5 WHERE category = :category",
223-
[
224-
{"query": "brain stroke", "category": "Neurology"},
225-
{"query": "heart attack", "category": "Cardiology"},
226-
],
227-
)
228-
```
229-
230-
Parameterized queries are a convenience for building QQL strings safely in application code; they are not sent to Qdrant as server-side prepared statements.
231-
232-
---
233-
234-
## Batch Execution
235-
236-
`run_queries_batch()` parses multiple QQL strings into a `BatchBlockStmt`. The executor groups compatible statements:
237-
238-
- compatible `SEARCH` / `RECOMMEND` statements use Qdrant `query_batch_points`
239-
- compatible `INSERT` statements become one `INSERT BULK`
240-
- mixed or incompatible statements still execute in order
241-
242-
```python
243-
from qql import Connection
244-
245-
with Connection("http://localhost:6333") as conn:
246-
results = conn.run_queries_batch([
247-
"SEARCH docs SIMILAR TO 'neurology' LIMIT 5",
248-
"SEARCH docs SIMILAR TO 'cardiology' LIMIT 5",
249-
])
250-
251-
for result in results:
252-
print(result.message)
253-
```
254-
255-
For ergonomic batching in application code, use `QQLBatch`:
256-
257-
```python
258-
from qql import Connection, QQLBatch
259-
260-
with Connection("http://localhost:6333") as conn:
261-
with QQLBatch(conn) as batch:
262-
neuro = batch.add("SEARCH docs SIMILAR TO 'neurology' LIMIT 5")
263-
cardio = batch.add("SEARCH docs SIMILAR TO 'cardiology' LIMIT 5")
264-
265-
print(neuro.result.data)
266-
print(cardio.result.data)
267-
```
268-
269-
Each proxy's `.result` becomes available after the context manager exits.
270-
271-
---
272-
273-
## Async API
274-
275-
`AsyncConnection` mirrors the sync API for `asyncio` applications and uses `AsyncQdrantClient` under the hood.
276-
277-
```python
278-
from qql import AsyncConnection
279-
280-
async with AsyncConnection("http://localhost:6333") as conn:
281-
await conn.run_query(
282-
"INSERT INTO COLLECTION notes VALUES {'text': 'async QQL'}"
283-
)
284-
result = await conn.run_query(
285-
"SEARCH notes SIMILAR TO 'async vector search' LIMIT 5"
286-
)
287-
print(result.data)
288-
```
289-
290-
Async batching and parameterized helpers are also available:
291-
292-
```python
293-
from qql import AsyncConnection, QQLAsyncBatch
294-
295-
async with AsyncConnection("http://localhost:6333", prefer_grpc=True) as conn:
296-
result = await conn.run_parameterized_query(
297-
"SEARCH docs SIMILAR TO :query LIMIT 5",
298-
{"query": "clinical notes"},
299-
)
300-
301-
async with QQLAsyncBatch(conn) as batch:
302-
first = batch.add("SEARCH docs SIMILAR TO 'neurology' LIMIT 5")
303-
second = batch.add("SEARCH docs SIMILAR TO 'cardiology' LIMIT 5")
304-
305-
print(first.result.data, second.result.data)
306-
```
307-
308-
The async executor preserves the same `ExecutionResult` shape as the sync executor.
309-
310-
---
311-
312186
### Power-user: `executor` property
313187

314188
For low-level access to the pipeline, use `conn.executor` directly:
@@ -401,8 +275,7 @@ class ExecutionResult:
401275
|---|---|
402276
| INSERT (dense) | `{"id": int \| "<uuid>", "collection": "<name>"}` |
403277
| INSERT (hybrid) | `{"id": int \| "<uuid>", "collection": "<name>"}` |
404-
| INSERT BULK | `{"ids": [int \| "<uuid>", ...]}` |
405-
| BEGIN BATCH / programmatic batch | `[ExecutionResult, ...]` |
278+
| INSERT BULK | `None` (count in `result.message`) |
406279
| SELECT | `{"id": str, "payload": dict}` or `None` when not found |
407280
| SEARCH | `[{"id": str, "score": float, "payload": dict}, ...]` |
408281
| SCROLL | `{"points": [{"id": str, "payload": dict}, ...], "next_offset": str \| int \| None}` |

docs/reference.md

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ title: "Reference"
55

66
# Reference — Models, Config, Project Structure, Errors
77

8-
Default embedding models, configuration parameters, public APIs, project layout, and common error codes for troubleshooting.
8+
Default embedding models, configuration parameters, project layout, and common error codes for troubleshooting.
99

1010
---
1111

@@ -149,56 +149,30 @@ You can edit this file directly to change the default model without reconnecting
149149

150150
---
151151

152-
## Public Python API
153-
154-
| API | Description |
155-
|---|---|
156-
| `Connection` | Stateful sync QQL client backed by `QdrantClient` |
157-
| `AsyncConnection` | Stateful async QQL client backed by `AsyncQdrantClient` |
158-
| `QQLBatch` | Sync context manager for collecting statements and resolving per-statement results after execution |
159-
| `QQLAsyncBatch` | Async context manager equivalent of `QQLBatch` |
160-
| `Executor` | Low-level sync AST executor |
161-
| `AsyncExecutor` | Low-level async AST executor |
162-
| `ExecutionResult` | Standard result object returned by all operations |
163-
164-
Both sync and async connections support:
165-
166-
- `run_query(query)`
167-
- `run_queries_batch([query, ...])`
168-
- `run_parameterized_query(template, params)`
169-
- `run_parameterized_batch(template, [params, ...])`
170-
- `prefer_grpc=True` and `grpc_port=<port>` connection options
171-
172-
---
173-
174152
## Project Structure
175153

176-
```text
154+
```
177155
qql/
178156
├── pyproject.toml # Package config; installs the `qql` CLI command
179157
├── src/
180158
│ └── qql/
181-
│ ├── __init__.py # Public API exports: sync, async, batching, parser/executor
159+
│ ├── __init__.py # Public API: Connection, run_query()
182160
│ ├── cli.py # CLI entry point: connect, disconnect, execute, dump, REPL
183161
│ ├── config.py # QQLConfig dataclass + ~/.qql/config.json I/O
184-
│ ├── connection.py # Sync Connection, QQLBatch, parameterized query helpers
185-
│ ├── async_connection.py # AsyncConnection and QQLAsyncBatch
162+
│ ├── connection.py # Connection class — stateful programmatic API
186163
│ ├── exceptions.py # QQLError, QQLSyntaxError, QQLRuntimeError
187164
│ ├── lexer.py # Tokenizer: string → List[Token]
188165
│ ├── ast_nodes.py # Frozen dataclasses for each statement and filter type
189166
│ ├── parser.py # Recursive descent parser: tokens → AST node
190167
│ ├── embedder.py # Embedder (dense) + SparseEmbedder (BM25) + CrossEncoderEmbedder (rerank)
191-
│ ├── executor.py # Sync AST node → Qdrant client call
192-
│ ├── async_executor.py # Async AST node → AsyncQdrantClient call
193-
│ ├── utils.py # Shared pure helpers for parsing, filters, batching, vectors
168+
│ ├── executor.py # AST node → Qdrant client call + filter + hybrid search
194169
│ ├── script.py # Script runner: parse and execute .qql files statement by statement
195170
│ └── dumper.py # Collection exporter: scroll all points → .qql INSERT BULK script
196171
└── tests/
197172
├── test_lexer.py # Tokenizer unit tests
198173
├── test_parser.py # Parser unit tests
199174
├── test_executor.py # Executor unit tests (mocked Qdrant client)
200175
├── test_connection.py # Connection class unit tests (mocked Qdrant client)
201-
├── test_async_connection.py # AsyncConnection / AsyncExecutor tests
202176
├── test_script.py # Script runner unit tests
203177
└── test_dumper.py # Dumper unit tests
204178
```
@@ -213,7 +187,7 @@ Tests do not require a running Qdrant instance — the Qdrant client is mocked.
213187
pytest tests/ -v
214188
```
215189

216-
Expected output: **635 tests passing**.
190+
Expected output: **604 tests passing**.
217191

218192
---
219193

@@ -246,6 +220,3 @@ Expected output: **635 tests passing**.
246220
| `Unknown index type '...'` | Invalid schema type in CREATE INDEX | Use one of: `keyword`, `integer`, `float`, `bool`, `text`, `geo`, `datetime`, `uuid` |
247221
| `Unknown CREATE INDEX option '...'` | Unsupported advanced option for the chosen payload index type | Check which `WITH { ... }` keys are supported for `keyword`, `uuid`, or `text` |
248222
| `Qdrant error during CREATE INDEX: ...` | Qdrant rejected the index creation | Check field name and collection state |
249-
| `Unterminated batch block; expected END BATCH` | A `BEGIN BATCH` block was not closed | Add `END BATCH` at the end of the block |
250-
| `Batch has not been executed yet.` | Read a `QQLBatch` proxy result before leaving the context manager | Access `.result` only after the `with QQLBatch(...)` block exits |
251-
| `AsyncBatch has not been executed yet.` | Read a `QQLAsyncBatch` proxy result before leaving the async context manager | Access `.result` only after the `async with QQLAsyncBatch(...)` block exits |

0 commit comments

Comments
 (0)