qql-cli 2.0.0__tar.gz → 2.2.0__tar.gz

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 (40) hide show
  1. {qql_cli-2.0.0 → qql_cli-2.2.0}/PKG-INFO +21 -9
  2. {qql_cli-2.0.0 → qql_cli-2.2.0}/README.md +18 -6
  3. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/collections.md +52 -14
  4. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/getting-started.md +8 -2
  5. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/index.html +3 -3
  6. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/programmatic.md +18 -0
  7. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/reference.md +8 -3
  8. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/scripts.md +3 -0
  9. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/search.md +57 -5
  10. {qql_cli-2.0.0 → qql_cli-2.2.0}/pyproject.toml +3 -3
  11. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/ast_nodes.py +21 -2
  12. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/cli.py +43 -3
  13. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/dumper.py +11 -6
  14. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/executor.py +105 -5
  15. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/lexer.py +17 -1
  16. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/parser.py +97 -14
  17. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/script.py +3 -1
  18. {qql_cli-2.0.0 → qql_cli-2.2.0}/tests/test_dumper.py +39 -5
  19. {qql_cli-2.0.0 → qql_cli-2.2.0}/tests/test_executor.py +227 -0
  20. {qql_cli-2.0.0 → qql_cli-2.2.0}/tests/test_lexer.py +22 -0
  21. {qql_cli-2.0.0 → qql_cli-2.2.0}/tests/test_parser.py +150 -1
  22. {qql_cli-2.0.0 → qql_cli-2.2.0}/tests/test_script.py +24 -0
  23. {qql_cli-2.0.0 → qql_cli-2.2.0}/.github/workflows/ci.yml +0 -0
  24. {qql_cli-2.0.0 → qql_cli-2.2.0}/.github/workflows/publish.yml +0 -0
  25. {qql_cli-2.0.0 → qql_cli-2.2.0}/.gitignore +0 -0
  26. {qql_cli-2.0.0 → qql_cli-2.2.0}/LICENSE +0 -0
  27. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/_config.yml +0 -0
  28. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/filters.md +0 -0
  29. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/insert.md +0 -0
  30. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/robots.txt +0 -0
  31. {qql_cli-2.0.0 → qql_cli-2.2.0}/docs/sitemap.xml +0 -0
  32. {qql_cli-2.0.0 → qql_cli-2.2.0}/main.py +0 -0
  33. {qql_cli-2.0.0 → qql_cli-2.2.0}/resources/Features.md +0 -0
  34. {qql_cli-2.0.0 → qql_cli-2.2.0}/resources/sample.qql +0 -0
  35. {qql_cli-2.0.0 → qql_cli-2.2.0}/resources/sample_v2.qql +0 -0
  36. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/__init__.py +0 -0
  37. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/config.py +0 -0
  38. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/embedder.py +0 -0
  39. {qql_cli-2.0.0 → qql_cli-2.2.0}/src/qql/exceptions.py +0 -0
  40. {qql_cli-2.0.0 → qql_cli-2.2.0}/tests/__init__.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: qql-cli
3
- Version: 2.0.0
4
- Summary: QQL is a SQL-like query language and CLI for Qdrant vector database. Write INSERT, SEARCH, RECOMMEND, DELETE, and CREATE COLLECTION statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, binary, product), WHERE clause filters, script execution, and collection dump/restore.
3
+ Version: 2.2.0
4
+ Summary: QQL is a SQL-like query language and CLI for Qdrant vector database. Write INSERT, SEARCH, RECOMMEND, DELETE, and CREATE COLLECTION statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, turbo, binary, product), WHERE clause filters, script execution, and collection dump/restore.
5
5
  Project-URL: Homepage, https://github.com/pavanjava/qql
6
6
  Project-URL: Repository, https://github.com/pavanjava/qql
7
7
  Project-URL: Documentation, https://pavanjava.github.io/qql
@@ -45,7 +45,7 @@ Classifier: Topic :: Utilities
45
45
  Requires-Python: >=3.12
46
46
  Requires-Dist: click>=8.1.0
47
47
  Requires-Dist: prompt-toolkit>=3.0.0
48
- Requires-Dist: qdrant-client[fastembed]>=1.13.0
48
+ Requires-Dist: qdrant-client[fastembed]>=1.18.0
49
49
  Requires-Dist: rich>=13.0.0
50
50
  Description-Content-Type: text/markdown
51
51
 
@@ -56,9 +56,9 @@ Description-Content-Type: text/markdown
56
56
  [![PyPI version](https://img.shields.io/pypi/v/qql-cli?color=blue&label=PyPI)](https://pypi.org/project/qql-cli/)
57
57
  [![Python 3.12+](https://img.shields.io/pypi/pyversions/qql-cli)](https://pypi.org/project/qql-cli/)
58
58
  [![MIT License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
59
- [![Tests](https://img.shields.io/badge/tests-375%20passing-brightgreen)](tests/)
59
+ [![Tests](https://img.shields.io/badge/tests-405%20passing-brightgreen)](tests/)
60
60
 
61
- Write `INSERT`, `SEARCH`, `RECOMMEND`, `DELETE`, and `CREATE COLLECTION` statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, binary, product), SQL-style `WHERE` filters, script execution, and collection dump/restore.
61
+ Write `INSERT`, `SELECT`, `SEARCH`, `SCROLL`, `RECOMMEND`, `DELETE`, and `CREATE COLLECTION` statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, turbo, binary, product), SQL-style `WHERE` filters, script execution, and collection dump/restore.
62
62
 
63
63
  ```
64
64
  qql> INSERT INTO COLLECTION notes VALUES {'text': 'Qdrant is a vector database', 'author': 'alice', 'year': 2024}
@@ -99,7 +99,7 @@ Your query string
99
99
  Qdrant instance
100
100
  ```
101
101
 
102
- 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) to merge the results of both retrieval methods.
102
+ 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'`.
103
103
 
104
104
  ---
105
105
 
@@ -133,9 +133,9 @@ Full documentation lives in the [`docs/`](docs/) folder and at **[pavanjava.gith
133
133
  |---|---|
134
134
  | [Getting Started](docs/getting-started.md) | Installation, connecting, first queries |
135
135
  | [INSERT / INSERT BULK](docs/insert.md) | Adding documents, batch inserts, payload types |
136
- | [SEARCH / RECOMMEND / Hybrid / RERANK](docs/search.md) | Semantic search, hybrid, reranking, recommendations |
136
+ | [SEARCH / SELECT / SCROLL / RECOMMEND / Hybrid / RERANK](docs/search.md) | Semantic search, point retrieval, pagination, hybrid, reranking, recommendations |
137
137
  | [WHERE Filters](docs/filters.md) | Full SQL-style filter operators |
138
- | [Collections & Quantization](docs/collections.md) | CREATE, DROP, QUANTIZE (scalar/binary/product), CREATE INDEX |
138
+ | [Collections & Quantization](docs/collections.md) | CREATE, DROP, QUANTIZE (scalar/turbo/binary/product), CREATE INDEX |
139
139
  | [Scripts: EXECUTE / DUMP](docs/scripts.md) | Script files, collection backup/restore |
140
140
  | [Programmatic Usage](docs/programmatic.md) | Use QQL as a Python library |
141
141
  | [Reference: Models / Config / Errors](docs/reference.md) | Embedding models, config file, error reference |
@@ -153,15 +153,27 @@ INSERT BULK INTO COLLECTION articles VALUES [{'text': '...'}, {'text': '...'}]
153
153
  SEARCH articles SIMILAR TO 'query' LIMIT 10
154
154
  SEARCH articles SIMILAR TO 'query' LIMIT 10 WHERE year >= 2020
155
155
  SEARCH articles SIMILAR TO 'query' LIMIT 10 USING HYBRID
156
+ SEARCH articles SIMILAR TO 'query' LIMIT 10 USING HYBRID FUSION 'dbsf'
156
157
  SEARCH articles SIMILAR TO 'query' LIMIT 10 USING HYBRID RERANK
157
158
 
159
+ -- Scroll
160
+ SCROLL FROM articles LIMIT 50
161
+ SCROLL FROM articles WHERE year >= 2024 LIMIT 50
162
+ SCROLL FROM articles AFTER 'cursor-id' LIMIT 50
163
+
158
164
  -- Recommend
159
165
  RECOMMEND FROM articles POSITIVE IDS (1001, 1002) LIMIT 5
160
166
 
167
+ -- Select (retrieve a point by ID)
168
+ SELECT * FROM articles WHERE id = '3f2e1a4b-...'
169
+
161
170
  -- Collections
162
171
  CREATE COLLECTION articles
163
172
  CREATE COLLECTION articles HYBRID
164
173
  CREATE COLLECTION articles QUANTIZE SCALAR
174
+ CREATE COLLECTION articles QUANTIZE TURBO
175
+ CREATE COLLECTION articles QUANTIZE TURBO BITS 2
176
+ CREATE COLLECTION articles QUANTIZE TURBO BITS 1.5 ALWAYS RAM
165
177
  CREATE INDEX ON COLLECTION articles FOR year TYPE integer
166
178
  SHOW COLLECTIONS
167
179
  DROP COLLECTION articles
@@ -185,7 +197,7 @@ Tests do not require a running Qdrant instance — the Qdrant client is mocked.
185
197
  pytest tests/ -v
186
198
  ```
187
199
 
188
- Expected: **375 tests passing**.
200
+ Expected: **405 tests passing**.
189
201
 
190
202
  ---
191
203
 
@@ -5,9 +5,9 @@
5
5
  [![PyPI version](https://img.shields.io/pypi/v/qql-cli?color=blue&label=PyPI)](https://pypi.org/project/qql-cli/)
6
6
  [![Python 3.12+](https://img.shields.io/pypi/pyversions/qql-cli)](https://pypi.org/project/qql-cli/)
7
7
  [![MIT License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
8
- [![Tests](https://img.shields.io/badge/tests-375%20passing-brightgreen)](tests/)
8
+ [![Tests](https://img.shields.io/badge/tests-405%20passing-brightgreen)](tests/)
9
9
 
10
- Write `INSERT`, `SEARCH`, `RECOMMEND`, `DELETE`, and `CREATE COLLECTION` statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, binary, product), SQL-style `WHERE` filters, script execution, and collection dump/restore.
10
+ Write `INSERT`, `SELECT`, `SEARCH`, `SCROLL`, `RECOMMEND`, `DELETE`, and `CREATE COLLECTION` statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, turbo, binary, product), SQL-style `WHERE` filters, script execution, and collection dump/restore.
11
11
 
12
12
  ```
13
13
  qql> INSERT INTO COLLECTION notes VALUES {'text': 'Qdrant is a vector database', 'author': 'alice', 'year': 2024}
@@ -48,7 +48,7 @@ Your query string
48
48
  Qdrant instance
49
49
  ```
50
50
 
51
- 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) to merge the results of both retrieval methods.
51
+ 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'`.
52
52
 
53
53
  ---
54
54
 
@@ -82,9 +82,9 @@ Full documentation lives in the [`docs/`](docs/) folder and at **[pavanjava.gith
82
82
  |---|---|
83
83
  | [Getting Started](docs/getting-started.md) | Installation, connecting, first queries |
84
84
  | [INSERT / INSERT BULK](docs/insert.md) | Adding documents, batch inserts, payload types |
85
- | [SEARCH / RECOMMEND / Hybrid / RERANK](docs/search.md) | Semantic search, hybrid, reranking, recommendations |
85
+ | [SEARCH / SELECT / SCROLL / RECOMMEND / Hybrid / RERANK](docs/search.md) | Semantic search, point retrieval, pagination, hybrid, reranking, recommendations |
86
86
  | [WHERE Filters](docs/filters.md) | Full SQL-style filter operators |
87
- | [Collections & Quantization](docs/collections.md) | CREATE, DROP, QUANTIZE (scalar/binary/product), CREATE INDEX |
87
+ | [Collections & Quantization](docs/collections.md) | CREATE, DROP, QUANTIZE (scalar/turbo/binary/product), CREATE INDEX |
88
88
  | [Scripts: EXECUTE / DUMP](docs/scripts.md) | Script files, collection backup/restore |
89
89
  | [Programmatic Usage](docs/programmatic.md) | Use QQL as a Python library |
90
90
  | [Reference: Models / Config / Errors](docs/reference.md) | Embedding models, config file, error reference |
@@ -102,15 +102,27 @@ INSERT BULK INTO COLLECTION articles VALUES [{'text': '...'}, {'text': '...'}]
102
102
  SEARCH articles SIMILAR TO 'query' LIMIT 10
103
103
  SEARCH articles SIMILAR TO 'query' LIMIT 10 WHERE year >= 2020
104
104
  SEARCH articles SIMILAR TO 'query' LIMIT 10 USING HYBRID
105
+ SEARCH articles SIMILAR TO 'query' LIMIT 10 USING HYBRID FUSION 'dbsf'
105
106
  SEARCH articles SIMILAR TO 'query' LIMIT 10 USING HYBRID RERANK
106
107
 
108
+ -- Scroll
109
+ SCROLL FROM articles LIMIT 50
110
+ SCROLL FROM articles WHERE year >= 2024 LIMIT 50
111
+ SCROLL FROM articles AFTER 'cursor-id' LIMIT 50
112
+
107
113
  -- Recommend
108
114
  RECOMMEND FROM articles POSITIVE IDS (1001, 1002) LIMIT 5
109
115
 
116
+ -- Select (retrieve a point by ID)
117
+ SELECT * FROM articles WHERE id = '3f2e1a4b-...'
118
+
110
119
  -- Collections
111
120
  CREATE COLLECTION articles
112
121
  CREATE COLLECTION articles HYBRID
113
122
  CREATE COLLECTION articles QUANTIZE SCALAR
123
+ CREATE COLLECTION articles QUANTIZE TURBO
124
+ CREATE COLLECTION articles QUANTIZE TURBO BITS 2
125
+ CREATE COLLECTION articles QUANTIZE TURBO BITS 1.5 ALWAYS RAM
114
126
  CREATE INDEX ON COLLECTION articles FOR year TYPE integer
115
127
  SHOW COLLECTIONS
116
128
  DROP COLLECTION articles
@@ -134,7 +146,7 @@ Tests do not require a running Qdrant instance — the Qdrant client is mocked.
134
146
  pytest tests/ -v
135
147
  ```
136
148
 
137
- Expected: **375 tests passing**.
149
+ Expected: **405 tests passing**.
138
150
 
139
151
  ---
140
152
 
@@ -67,27 +67,38 @@ When `USING MODEL` is omitted, the collection uses the **default embedding model
67
67
 
68
68
  ## Quantization — QUANTIZE clause
69
69
 
70
- Quantization reduces the memory footprint of vector collections and speeds up search at the cost of a small, controllable accuracy loss. QQL supports all three Qdrant quantization strategies via an optional `QUANTIZE` clause appended to `CREATE COLLECTION`.
70
+ Quantization reduces the memory footprint of vector collections and speeds up search at the cost of a small, controllable accuracy loss. QQL supports all four Qdrant quantization strategies via an optional `QUANTIZE` clause appended to `CREATE COLLECTION`.
71
71
 
72
- **Three strategies:**
72
+ **Four strategies:**
73
73
 
74
- | Type | Compression | Accuracy Loss | Best For |
74
+ | Type | Compression | Accuracy | Best For |
75
75
  |---|---|---|---|
76
- | `SCALAR` | 4× (float32 → int8) | < 1% | Most collections — best balance |
77
- | `BINARY` | 32× (float32 1-bit) | Higher | High-dimensional vectors (768+), speed priority |
76
+ | `SCALAR` | 4× (float32 → int8) | < 1% loss | Most collections — best balance |
77
+ | `TURBO` | 8–32× (4-bit to 1-bit) | Low–medium | Better recall than BINARY at same storage budget |
78
+ | `BINARY` | 32× (float32 → 1-bit) | Higher loss | Speed priority; centered distributions only |
78
79
  | `PRODUCT` | 4× (configurable) | Variable | Memory-constrained deployments |
79
80
 
80
81
  **Full syntax:**
81
82
  ```
82
83
  CREATE COLLECTION <name> ... QUANTIZE SCALAR [QUANTILE <0.0–1.0>] [ALWAYS RAM]
84
+ CREATE COLLECTION <name> ... QUANTIZE TURBO [BITS <1|1.5|2|4>] [ALWAYS RAM]
83
85
  CREATE COLLECTION <name> ... QUANTIZE BINARY [ALWAYS RAM]
84
86
  CREATE COLLECTION <name> ... QUANTIZE PRODUCT [ALWAYS RAM]
85
87
  ```
86
88
 
87
- - **`QUANTILE <float>`** — (scalar only) calibration quantile for the INT8 conversion; defaults to Qdrant's built-in default (0.99) when omitted.
88
- - **`ALWAYS RAM`**keep the **quantized** vectors in RAM at all times, regardless of the collection's `on_disk` setting. Improves search throughput at the cost of higher RAM usage for the compressed index. The original full-precision vectors are stored and managed independently of this flag. Supported by all three quantization types.
89
+ - **`QUANTILE <float>`** — (SCALAR only) calibration quantile for the INT8 conversion; defaults to Qdrant's built-in default (0.99) when omitted.
90
+ - **`BITS <depth>`**(TURBO only) bit depth passed to the Qdrant SDK:
91
+ - `4` — 4-bit (default when `BITS` is omitted; server applies its own default)
92
+ - `2` — 2-bit
93
+ - `1.5` — 1.5-bit
94
+ - `1` — 1-bit
95
+ > Compression ratios (8×, 16×, 24×, 32×) and recall characteristics are
96
+ > Qdrant server-side behaviors. QQL maps the `BITS` value to the SDK model and
97
+ > passes it to Qdrant; actual results depend on your Qdrant server version.
98
+ - **`ALWAYS RAM`** — keep the **quantized** vectors in RAM at all times, regardless of the collection's `on_disk` setting. Improves search throughput at the cost of higher RAM usage for the compressed index. The original full-precision vectors are stored and managed independently of this flag. Supported by all four quantization types.
89
99
  - **`QUANTIZE`** always appears **after** all other clauses (`HYBRID`, `USING MODEL`, etc.).
90
100
  - For `PRODUCT`, the compression ratio is fixed at **4×** in this version.
101
+ - For `TURBO`, Cosine, Dot, and Euclidean distance are supported by the Qdrant server when TurboQuant is enabled.
91
102
  - When used with `HYBRID` collections, quantization applies only to the **dense** vector.
92
103
 
93
104
  **Examples:**
@@ -102,6 +113,26 @@ Scalar with explicit calibration and quantized vectors pinned to RAM:
102
113
  CREATE COLLECTION research_papers QUANTIZE SCALAR QUANTILE 0.95 ALWAYS RAM
103
114
  ```
104
115
 
116
+ TurboQuant — default 4-bit (8× compression, good recall):
117
+ ```sql
118
+ CREATE COLLECTION research_papers QUANTIZE TURBO
119
+ ```
120
+
121
+ TurboQuant — 2-bit (16× compression):
122
+ ```sql
123
+ CREATE COLLECTION research_papers QUANTIZE TURBO BITS 2
124
+ ```
125
+
126
+ TurboQuant — 1.5-bit (24× compression) with quantized vectors pinned to RAM:
127
+ ```sql
128
+ CREATE COLLECTION research_papers QUANTIZE TURBO BITS 1.5 ALWAYS RAM
129
+ ```
130
+
131
+ TurboQuant — 1-bit (32× compression, same ratio as BINARY but better recall):
132
+ ```sql
133
+ CREATE COLLECTION research_papers QUANTIZE TURBO BITS 1
134
+ ```
135
+
105
136
  Binary quantization for large high-dimensional embeddings:
106
137
  ```sql
107
138
  CREATE COLLECTION research_papers QUANTIZE BINARY
@@ -115,22 +146,29 @@ CREATE COLLECTION research_papers QUANTIZE PRODUCT ALWAYS RAM
115
146
  Combined with hybrid collection:
116
147
  ```sql
117
148
  CREATE COLLECTION research_papers HYBRID QUANTIZE SCALAR
149
+ CREATE COLLECTION research_papers HYBRID QUANTIZE TURBO BITS 2
118
150
  ```
119
151
 
120
152
  Combined with a pinned model:
121
153
  ```sql
122
154
  CREATE COLLECTION research_papers USING MODEL 'BAAI/bge-base-en-v1.5' QUANTIZE SCALAR QUANTILE 0.99
155
+ CREATE COLLECTION research_papers USING MODEL 'BAAI/bge-base-en-v1.5' QUANTIZE TURBO BITS 2
156
+ ```
157
+
158
+ Combined with hybrid + dense model:
159
+ ```sql
160
+ CREATE COLLECTION research_papers USING HYBRID DENSE MODEL 'BAAI/bge-base-en-v1.5' QUANTIZE TURBO
123
161
  ```
124
162
 
125
163
  **Valid combinations:**
126
164
 
127
- | Base form | + QUANTIZE SCALAR | + QUANTIZE BINARY | + QUANTIZE PRODUCT |
128
- |---|---|---|---|
129
- | `CREATE COLLECTION name` | ✓ | ✓ | ✓ |
130
- | `... HYBRID` | ✓ | ✓ | ✓ |
131
- | `... USING MODEL 'x'` | ✓ | ✓ | ✓ |
132
- | `... USING HYBRID` | ✓ | ✓ | ✓ |
133
- | `... USING HYBRID DENSE MODEL 'x'` | ✓ | ✓ | ✓ |
165
+ | Base form | + SCALAR | + TURBO | + BINARY | + PRODUCT |
166
+ |---|---|---|---|---|
167
+ | `CREATE COLLECTION name` | ✓ | ✓ | ✓ | ✓ |
168
+ | `... HYBRID` | ✓ | ✓ | ✓ | ✓ |
169
+ | `... USING MODEL 'x'` | ✓ | ✓ | ✓ | ✓ |
170
+ | `... USING HYBRID` | ✓ | ✓ | ✓ | ✓ |
171
+ | `... USING HYBRID DENSE MODEL 'x'` | ✓ | ✓ | ✓ | ✓ |
134
172
 
135
173
  > INSERT and SEARCH on quantized collections work exactly the same as on non-quantized ones — no changes to INSERT or SEARCH syntax are needed.
136
174
 
@@ -24,7 +24,7 @@ Your query string
24
24
  Qdrant instance
25
25
  ```
26
26
 
27
- 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) to merge the results of both retrieval methods.
27
+ 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 override that with `FUSION 'dbsf'` on hybrid searches.
28
28
 
29
29
  ---
30
30
 
@@ -138,8 +138,14 @@ SEARCH notes SIMILAR TO 'vector storage engines' LIMIT 3
138
138
  -- Filter results
139
139
  SEARCH notes SIMILAR TO 'vector databases' LIMIT 5 WHERE year >= 2023
140
140
 
141
+ -- Browse with pagination
142
+ SCROLL FROM notes LIMIT 10
143
+
141
144
  -- List all collections
142
145
  SHOW COLLECTIONS
146
+
147
+ -- Retrieve a point by ID
148
+ SELECT * FROM notes WHERE id = 1
143
149
  ```
144
150
 
145
151
  ---
@@ -147,7 +153,7 @@ SHOW COLLECTIONS
147
153
  ## Next Steps
148
154
 
149
155
  - [INSERT / INSERT BULK](insert.md) — adding documents
150
- - [SEARCH / RECOMMEND / Hybrid / RERANK](search.md) — querying
156
+ - [SEARCH / SELECT / SCROLL / RECOMMEND / Hybrid / RERANK](search.md) — querying
151
157
  - [WHERE Filters](filters.md) — payload filtering
152
158
  - [Collections & Quantization](collections.md) — managing collections
153
159
  - [Scripts: EXECUTE / DUMP](scripts.md) — automating with script files
@@ -114,7 +114,7 @@
114
114
  <a href="https://pypi.org/project/qql-cli/"><img src="https://img.shields.io/pypi/v/qql-cli?color=blue&label=PyPI" alt="PyPI version" /></a>
115
115
  <a href="https://pypi.org/project/qql-cli/"><img src="https://img.shields.io/pypi/pyversions/qql-cli" alt="Python versions" /></a>
116
116
  <a href="https://github.com/pavanjava/qql/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" /></a>
117
- <a href="https://github.com/pavanjava/qql/actions"><img src="https://img.shields.io/badge/tests-375%20passing-brightgreen" alt="375 tests" /></a>
117
+ <a href="https://github.com/pavanjava/qql/actions"><img src="https://img.shields.io/badge/tests-405%20passing-brightgreen" alt="405 tests" /></a>
118
118
  </div>
119
119
 
120
120
  <pre><span class="cmt"># Install</span>
@@ -148,8 +148,8 @@
148
148
  <p>Adding documents, batch inserts, payload types</p>
149
149
  </a>
150
150
  <a class="card" href="search">
151
- <h3>SEARCH / RECOMMEND</h3>
152
- <p>Semantic search, hybrid search, reranking, recommendations</p>
151
+ <h3>SEARCH / SELECT / SCROLL / RECOMMEND</h3>
152
+ <p>Semantic search, point retrieval, pagination, hybrid search, reranking, recommendations</p>
153
153
  </a>
154
154
  <a class="card" href="filters">
155
155
  <h3>WHERE Filters</h3>
@@ -40,6 +40,15 @@ result = run_query(
40
40
  for hit in result.data:
41
41
  print(hit["score"], hit["payload"])
42
42
 
43
+ # Scroll / pagination
44
+ result = run_query(
45
+ "SCROLL FROM notes LIMIT 2",
46
+ url="http://localhost:6333",
47
+ )
48
+ for point in result.data["points"]:
49
+ print(point["id"], point["payload"])
50
+ print(result.data["next_offset"])
51
+
43
52
  # Bulk insert (all records embedded and upserted in one call)
44
53
  result = run_query(
45
54
  """INSERT BULK INTO COLLECTION notes VALUES [
@@ -58,6 +67,13 @@ result = run_query(
58
67
  for hit in result.data:
59
68
  print(hit["score"], hit["payload"])
60
69
 
70
+ # Retrieve a point by ID
71
+ result = run_query(
72
+ "SELECT * FROM notes WHERE id = 1",
73
+ url="http://localhost:6333",
74
+ )
75
+ print(result.data) # {"id": "1", "payload": {...}}
76
+
61
77
  # Delete by filter
62
78
  result = run_query(
63
79
  "DELETE FROM notes WHERE year < 2023",
@@ -111,7 +127,9 @@ class ExecutionResult:
111
127
  | INSERT (dense) | `{"id": int \| "<uuid>", "collection": "<name>"}` |
112
128
  | INSERT (hybrid) | `{"id": int \| "<uuid>", "collection": "<name>"}` |
113
129
  | INSERT BULK | `None` (count in `result.message`) |
130
+ | SELECT | `{"id": str, "payload": dict}` or `None` when not found |
114
131
  | SEARCH | `[{"id": str, "score": float, "payload": dict}, ...]` |
132
+ | SCROLL | `{"points": [{"id": str, "payload": dict}, ...], "next_offset": str \| None}` |
115
133
  | RECOMMEND | `[{"id": str, "score": float, "payload": dict}, ...]` |
116
134
  | SHOW COLLECTIONS | `["name1", "name2", ...]` |
117
135
  | CREATE COLLECTION | `None` |
@@ -36,6 +36,9 @@ SEARCH docs SIMILAR TO 'hello' LIMIT 5 USING MODEL 'BAAI/bge-small-en-v1.5'
36
36
  -- Hybrid with custom dense model
37
37
  SEARCH docs SIMILAR TO 'hello' LIMIT 5 USING HYBRID DENSE MODEL 'BAAI/bge-base-en-v1.5'
38
38
 
39
+ -- Hybrid with explicit fusion strategy
40
+ SEARCH docs SIMILAR TO 'hello' LIMIT 5 USING HYBRID FUSION 'dbsf'
41
+
39
42
  -- Hybrid with both custom
40
43
  SEARCH docs SIMILAR TO 'hello' LIMIT 5
41
44
  USING HYBRID DENSE MODEL 'BAAI/bge-base-en-v1.5' SPARSE MODEL 'prithivida/Splade_PP_en_v1'
@@ -159,7 +162,7 @@ Tests do not require a running Qdrant instance — the Qdrant client is mocked.
159
162
  pytest tests/ -v
160
163
  ```
161
164
 
162
- Expected output: **375 tests passing**.
165
+ Expected output: **405 tests passing**.
163
166
 
164
167
  ---
165
168
 
@@ -171,12 +174,14 @@ Expected output: **375 tests passing**.
171
174
  | `Connection failed: ...` | Qdrant unreachable at given URL | Check that Qdrant is running and the URL is correct |
172
175
  | `INSERT requires a 'text' field in VALUES` | `text` key missing from the VALUES dict | Add `'text': '...'` to your dict |
173
176
  | `Vector dimension mismatch: collection '...' expects X dims, but model produces Y dims` | Model used in INSERT differs from the one used to create the collection | Use `USING MODEL` to specify the same model as the collection was created with |
174
- | `Collection '...' does not exist` | SEARCH / DROP / DELETE on a non-existent collection | Check name spelling or run `SHOW COLLECTIONS` |
175
- | `Unexpected token '...'; expected a QQL statement keyword` | Unrecognized statement | Check the query syntax; QQL does not support SQL SELECT |
177
+ | `Collection '...' does not exist` | SEARCH / SCROLL / SELECT / DROP / DELETE on a non-existent collection | Check name spelling or run `SHOW COLLECTIONS` |
178
+ | `Unexpected token '...'; expected a QQL statement keyword` | Unrecognized statement | Check the query syntax and supported statement list |
179
+ | `SELECT requires a string or integer point id, got '...'` | `SELECT` used with a non-ID filter value | Use `SELECT * FROM <collection> WHERE id = '<id>'` or an integer ID |
176
180
  | `Unterminated string literal (at position N)` | A string is missing its closing quote | Close the string with a matching `'` or `"` |
177
181
  | `Unexpected character '@' (at position N)` | A character not part of QQL syntax | Remove or quote the offending character |
178
182
  | `Expected a filter operator after field '...'` | Unknown operator in WHERE clause | Use one of: `=`, `!=`, `>`, `>=`, `<`, `<=`, `IN`, `NOT IN`, `BETWEEN`, `IS NULL`, `IS NOT NULL`, `IS EMPTY`, `IS NOT EMPTY`, `MATCH` |
179
183
  | `Expected ')' ...` | Unclosed parenthesis in WHERE clause | Add the missing `)` to close the group |
180
184
  | `Qdrant error during SEARCH: ...` | Hybrid search on a non-hybrid collection, or wrong vector names | Ensure the collection was created with `HYBRID` before using `USING HYBRID` in INSERT/SEARCH |
185
+ | `Qdrant error during SCROLL: ...` | Qdrant rejected scroll request | Verify collection state, filter, and cursor (`AFTER`) value |
181
186
  | `Unknown index type '...'` | Invalid schema type in CREATE INDEX | Use one of: `keyword`, `integer`, `float`, `bool`, `text`, `geo`, `datetime` |
182
187
  | `Qdrant error during CREATE INDEX: ...` | Qdrant rejected the index creation | Check field name and collection state |
@@ -79,6 +79,9 @@ Export every point in a collection to a `.qql` script file. The generated file i
79
79
  **CLI usage:**
80
80
  ```bash
81
81
  qql dump <collection_name> <output.qql>
82
+
83
+ # Override the default 50 points/INSERT BULK batch
84
+ qql dump <collection_name> <output.qql> --batch-size 200
82
85
  ```
83
86
 
84
87
  **In-shell usage (inside the QQL REPL):**
@@ -1,4 +1,4 @@
1
- # SEARCH, RECOMMEND, Hybrid Search & Reranking
1
+ # SEARCH, SELECT, SCROLL, RECOMMEND, Hybrid Search & Reranking
2
2
 
3
3
  ---
4
4
 
@@ -14,7 +14,7 @@ SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n>
14
14
  SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> USING MODEL '<model_name>'
15
15
  SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> [USING MODEL '<model>'] WHERE <filter>
16
16
  SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> USING HYBRID
17
- SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> USING HYBRID [DENSE MODEL '<model>'] [SPARSE MODEL '<model>'] [WHERE <filter>]
17
+ SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> USING HYBRID [FUSION 'rrf|dbsf'] [DENSE MODEL '<model>'] [SPARSE MODEL '<model>'] [WHERE <filter>]
18
18
  SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> USING SPARSE [MODEL '<sparse_model>']
19
19
  SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> EXACT
20
20
  SEARCH <collection_name> SIMILAR TO '<query_text>' LIMIT <n> [USING ...] [WHERE <filter>] [RERANK] WITH { hnsw_ef: <n>, exact: true|false, acorn: true|false }
@@ -33,7 +33,7 @@ Search only papers published after 2020:
33
33
  SEARCH articles SIMILAR TO 'deep learning' LIMIT 10 WHERE year > 2020
34
34
  ```
35
35
 
36
- Hybrid search (combines dense semantic + sparse BM25 keyword retrieval via RRF):
36
+ Hybrid search (combines dense semantic + sparse BM25 keyword retrieval via RRF by default):
37
37
  ```sql
38
38
  SEARCH articles SIMILAR TO 'attention mechanism' LIMIT 10 USING HYBRID
39
39
  ```
@@ -70,6 +70,28 @@ Results are displayed as a table with three columns:
70
70
 
71
71
  ---
72
72
 
73
+ ## SELECT — retrieve a point by ID
74
+
75
+ Fetches a single point payload by exact point ID.
76
+
77
+ **Syntax:**
78
+ ```sql
79
+ SELECT * FROM <collection_name> WHERE id = '<point_id>'
80
+ SELECT * FROM <collection_name> WHERE id = <integer_id>
81
+ ```
82
+
83
+ **Examples:**
84
+ ```sql
85
+ SELECT * FROM articles WHERE id = '3f2e1a4b-8c91-4d0e-b123-abc123def456'
86
+ SELECT * FROM articles WHERE id = 42
87
+ ```
88
+
89
+ `SELECT` in this version is intentionally strict:
90
+ - only `*` projection is supported
91
+ - only `WHERE id = ...` is supported
92
+
93
+ ---
94
+
73
95
  ## Query-Time Search Params (`EXACT`, `WITH`)
74
96
 
75
97
  Use these when you want to debug retrieval quality or tune recall without changing collection-level settings.
@@ -98,15 +120,41 @@ SEARCH articles SIMILAR TO 'RAG' LIMIT 10 WHERE tag = 'li' WITH { acorn: true }
98
120
 
99
121
  ---
100
122
 
123
+ ## SCROLL — pagination / browsing
124
+
125
+ Use `SCROLL` to iterate through points in a collection page by page.
126
+
127
+ **Syntax:**
128
+ ```sql
129
+ SCROLL FROM <collection_name> LIMIT <n>
130
+ SCROLL FROM <collection_name> WHERE <filter> LIMIT <n>
131
+ SCROLL FROM <collection_name> AFTER '<point_id>' LIMIT <n>
132
+ SCROLL FROM <collection_name> WHERE <filter> AFTER <point_id> LIMIT <n>
133
+ ```
134
+
135
+ **Examples:**
136
+ ```sql
137
+ SCROLL FROM articles LIMIT 50
138
+ SCROLL FROM articles WHERE year >= 2024 LIMIT 50
139
+ SCROLL FROM articles AFTER 'cursor-id' LIMIT 50
140
+ ```
141
+
142
+ **Behavior:**
143
+ - Returns points in ID order with payloads.
144
+ - Returns a `next_offset` cursor when more points are available.
145
+ - Use `AFTER <next_offset>` to fetch the next page.
146
+
147
+ ---
148
+
101
149
  ## Hybrid Search (USING HYBRID)
102
150
 
103
- Hybrid search combines **dense semantic vectors** and **sparse BM25 keyword vectors** in a single query and merges the results with Qdrant's **Reciprocal Rank Fusion (RRF)** algorithm. This typically outperforms either method alone.
151
+ Hybrid search combines **dense semantic vectors** and **sparse BM25 keyword vectors** in a single query. By default QQL merges the two result sets with Qdrant's **Reciprocal Rank Fusion (RRF)** algorithm, and you can optionally switch to **DBSF** with a `FUSION` clause.
104
152
 
105
153
  ### How it works internally
106
154
 
107
155
  1. Both a dense vector (`TextEmbedding`) and a sparse BM25 vector (`SparseTextEmbedding`) are generated from your query text.
108
156
  2. Qdrant fetches the top candidates from each index independently (`prefetch limit = LIMIT × 4`).
109
- 3. The two result lists are merged using RRF a rank-based fusion that does not require score normalization.
157
+ 3. The two result lists are merged using the selected fusion strategy (`RRF` by default, or `DBSF` when requested).
110
158
  4. The final top-N results are returned.
111
159
 
112
160
  ### Step 1: Create a hybrid collection
@@ -139,6 +187,9 @@ SEARCH articles SIMILAR TO 'transformer architecture' LIMIT 10 USING HYBRID
139
187
  -- Hybrid search with a WHERE filter
140
188
  SEARCH articles SIMILAR TO 'attention' LIMIT 10 USING HYBRID WHERE year >= 2017
141
189
 
190
+ -- Hybrid with DBSF fusion
191
+ SEARCH articles SIMILAR TO 'hybrid retrieval' LIMIT 10 USING HYBRID FUSION 'dbsf'
192
+
142
193
  -- Hybrid with custom dense model
143
194
  SEARCH articles SIMILAR TO 'embeddings' LIMIT 5
144
195
  USING HYBRID DENSE MODEL 'BAAI/bge-base-en-v1.5'
@@ -154,6 +205,7 @@ SEARCH articles SIMILAR TO 'sparse retrieval' LIMIT 5
154
205
  |---|---|
155
206
  | Dense model | configured default (`sentence-transformers/all-MiniLM-L6-v2`) |
156
207
  | Sparse model | `Qdrant/bm25` |
208
+ | Fusion | `rrf` |
157
209
 
158
210
  ### Dense vs. hybrid — when to use which
159
211
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "qql-cli"
3
- version = "2.0.0"
4
- description = "QQL is a SQL-like query language and CLI for Qdrant vector database. Write INSERT, SEARCH, RECOMMEND, DELETE, and CREATE COLLECTION statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, binary, product), WHERE clause filters, script execution, and collection dump/restore."
3
+ version = "2.2.0"
4
+ description = "QQL is a SQL-like query language and CLI for Qdrant vector database. Write INSERT, SEARCH, RECOMMEND, DELETE, and CREATE COLLECTION statements instead of Python SDK calls. Supports hybrid dense+sparse vector search, cross-encoder reranking, quantization (scalar, turbo, binary, product), WHERE clause filters, script execution, and collection dump/restore."
5
5
  readme = "README.md"
6
6
  license = { file = "LICENSE" }
7
7
  requires-python = ">=3.12"
@@ -37,7 +37,7 @@ classifiers = [
37
37
  "Topic :: Text Processing :: Indexing",
38
38
  ]
39
39
  dependencies = [
40
- "qdrant-client[fastembed]>=1.13.0",
40
+ "qdrant-client[fastembed]>=1.18.0",
41
41
  "click>=8.1.0",
42
42
  "rich>=13.0.0",
43
43
  "prompt_toolkit>=3.0.0",
@@ -9,14 +9,16 @@ class QuantizationType(Enum):
9
9
  SCALAR = "scalar"
10
10
  BINARY = "binary"
11
11
  PRODUCT = "product"
12
+ TURBO = "turbo"
12
13
 
13
14
 
14
15
  @dataclass(frozen=True)
15
16
  class QuantizationConfig:
16
17
  """Quantization settings parsed from a QUANTIZE clause."""
17
18
  type: QuantizationType
18
- quantile: float | None = None # SCALAR only; None → Qdrant default (0.99)
19
- always_ram: bool = False # all types; default False
19
+ quantile: float | None = None # SCALAR only; None → Qdrant default (0.99)
20
+ always_ram: bool = False # all types; default False
21
+ turbo_bits: float | None = None # TURBO only; None → bits4 (Qdrant default 4-bit, 8×)
20
22
 
21
23
 
22
24
  @dataclass(frozen=True)
@@ -178,6 +180,20 @@ class ShowCollectionsStmt:
178
180
  pass
179
181
 
180
182
 
183
+ @dataclass(frozen=True)
184
+ class SelectStmt:
185
+ collection: str
186
+ point_id: str | int
187
+
188
+
189
+ @dataclass(frozen=True)
190
+ class ScrollStmt:
191
+ collection: str
192
+ limit: int
193
+ query_filter: FilterExpr | None = None
194
+ after: str | int | None = None
195
+
196
+
181
197
  @dataclass(frozen=True)
182
198
  class SearchStmt:
183
199
  collection: str
@@ -185,6 +201,7 @@ class SearchStmt:
185
201
  limit: int
186
202
  model: str | None # dense model; None → use config default
187
203
  hybrid: bool = False # if True, use prefetch+RRF hybrid search
204
+ fusion: str | None = None # hybrid fusion strategy; None → default rrf
188
205
  sparse_only: bool = False # if True, query only the sparse vector (no dense)
189
206
  sparse_model: str | None = None # sparse model for hybrid/sparse-only; None → SparseEmbedder.DEFAULT_MODEL
190
207
  query_filter: FilterExpr | None = None # optional WHERE clause; default keeps existing tests valid
@@ -223,6 +240,8 @@ ASTNode = (
223
240
  | CreateIndexStmt
224
241
  | DropCollectionStmt
225
242
  | ShowCollectionsStmt
243
+ | SelectStmt
244
+ | ScrollStmt
226
245
  | SearchStmt
227
246
  | RecommendStmt
228
247
  | DeleteStmt