qql-cli 1.2.0__tar.gz → 1.3.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.
- {qql_cli-1.2.0 → qql_cli-1.3.0}/PKG-INFO +157 -1
- {qql_cli-1.2.0 → qql_cli-1.3.0}/README.md +156 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/pyproject.toml +1 -1
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/cli.py +168 -0
- qql_cli-1.3.0/src/qql/dumper.py +210 -0
- qql_cli-1.3.0/src/qql/script.py +156 -0
- qql_cli-1.3.0/tests/test_dumper.py +224 -0
- qql_cli-1.3.0/tests/test_script.py +183 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/.github/workflows/ci.yml +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/.github/workflows/publish.yml +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/.gitignore +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/LICENSE +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/main.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/__init__.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/ast_nodes.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/config.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/embedder.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/exceptions.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/executor.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/lexer.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/src/qql/parser.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/tests/__init__.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/tests/test_executor.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/tests/test_lexer.py +0 -0
- {qql_cli-1.2.0 → qql_cli-1.3.0}/tests/test_parser.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qql-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: A SQL-like query language CLI wrapper for Qdrant vector database
|
|
5
5
|
Project-URL: Homepage, https://github.com/pavanjava/qql
|
|
6
6
|
Project-URL: Repository, https://github.com/pavanjava/qql
|
|
@@ -83,6 +83,7 @@ qql> SEARCH notes SIMILAR TO 'vector databases' LIMIT 5 USING HYBRID RERANK
|
|
|
83
83
|
- [The QQL Shell](#the-qql-shell)
|
|
84
84
|
- [All QQL Operations](#all-qql-operations)
|
|
85
85
|
- [INSERT — add a point](#insert--add-a-point)
|
|
86
|
+
- [INSERT BULK — batch insert](#insert-bulk--batch-insert-multiple-points)
|
|
86
87
|
- [SEARCH — find similar points](#search--find-similar-points)
|
|
87
88
|
- [Query-Time Search Params (`EXACT`, `WITH`)](#query-time-search-params-exact-with)
|
|
88
89
|
- [WHERE Clause Filters](#where-clause-filters)
|
|
@@ -92,6 +93,9 @@ qql> SEARCH notes SIMILAR TO 'vector databases' LIMIT 5 USING HYBRID RERANK
|
|
|
92
93
|
- [CREATE COLLECTION — create a collection](#create-collection--create-a-collection)
|
|
93
94
|
- [DROP COLLECTION — delete a collection](#drop-collection--delete-a-collection)
|
|
94
95
|
- [DELETE — remove a point](#delete--remove-a-point)
|
|
96
|
+
- [Script Files](#script-files)
|
|
97
|
+
- [EXECUTE — run a script file](#execute--run-a-qql-script-file)
|
|
98
|
+
- [DUMP COLLECTION — export to script](#dump-collection--export-collection-to-a-qql-script-file)
|
|
95
99
|
- [Embedding Models](#embedding-models)
|
|
96
100
|
- [Value Types in Dictionaries](#value-types-in-dictionaries)
|
|
97
101
|
- [Configuration File](#configuration-file)
|
|
@@ -886,6 +890,158 @@ To find a point's ID, run a SEARCH first and copy the ID from the results table.
|
|
|
886
890
|
|
|
887
891
|
---
|
|
888
892
|
|
|
893
|
+
## Script Files
|
|
894
|
+
|
|
895
|
+
QQL supports reading from and writing to `.qql` script files, making it easy to automate bulk operations, seed databases, and back up collections.
|
|
896
|
+
|
|
897
|
+
---
|
|
898
|
+
|
|
899
|
+
### EXECUTE — run a .qql script file
|
|
900
|
+
|
|
901
|
+
Execute a file containing multiple QQL statements in sequence. Each statement is parsed and executed in order. `--` comments are stripped before parsing.
|
|
902
|
+
|
|
903
|
+
**CLI usage:**
|
|
904
|
+
```bash
|
|
905
|
+
qql execute /path/to/script.qql
|
|
906
|
+
|
|
907
|
+
# Stop on first error instead of continuing through all statements
|
|
908
|
+
qql execute /path/to/script.qql --stop-on-error
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
**In-shell usage (inside the QQL REPL):**
|
|
912
|
+
```
|
|
913
|
+
qql> EXECUTE /path/to/script.qql
|
|
914
|
+
qql> \e /path/to/script.qql
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
**Script format:**
|
|
918
|
+
|
|
919
|
+
```sql
|
|
920
|
+
-- This is a comment — the entire line is ignored
|
|
921
|
+
-- ============================================================
|
|
922
|
+
-- QQL Script — populate articles collection
|
|
923
|
+
-- ============================================================
|
|
924
|
+
|
|
925
|
+
-- Step 1: create the collection
|
|
926
|
+
CREATE COLLECTION articles
|
|
927
|
+
|
|
928
|
+
-- Step 2: bulk insert records
|
|
929
|
+
INSERT BULK INTO COLLECTION articles VALUES [
|
|
930
|
+
{'text': 'Neural networks learn representations', 'year': 2023},
|
|
931
|
+
{'text': 'Attention mechanisms in transformers', 'year': 2024}
|
|
932
|
+
]
|
|
933
|
+
|
|
934
|
+
-- Step 3: verify
|
|
935
|
+
SHOW COLLECTIONS
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
**Rules:**
|
|
939
|
+
- `--` to end-of-line is a comment and is ignored (inline or full-line)
|
|
940
|
+
- Statements can span multiple lines (e.g. `INSERT BULK ... VALUES [...]`)
|
|
941
|
+
- Blank lines between statements are ignored
|
|
942
|
+
- By default all statements run even if one fails; use `--stop-on-error` to halt early
|
|
943
|
+
|
|
944
|
+
**Example output:**
|
|
945
|
+
```
|
|
946
|
+
Executing: /path/to/script.qql
|
|
947
|
+
|
|
948
|
+
[1/3] CREATE COLLECTION articles
|
|
949
|
+
✓ Collection 'articles' created (384-dimensional vectors, cosine distance)
|
|
950
|
+
[2/3] INSERT BULK INTO COLLECTION articles VALUES [ …
|
|
951
|
+
✓ Inserted 2 points
|
|
952
|
+
[3/3] SHOW COLLECTIONS
|
|
953
|
+
✓ 1 collection(s) found
|
|
954
|
+
|
|
955
|
+
Done. 3/3 statement(s) succeeded.
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
### DUMP COLLECTION — export collection to a .qql script file
|
|
961
|
+
|
|
962
|
+
Export every point in a collection to a `.qql` script file. The generated file is valid QQL — it can be re-imported with `qql execute` to restore or migrate the collection. Points are written in batches of 50 as `INSERT BULK` statements.
|
|
963
|
+
|
|
964
|
+
**CLI usage:**
|
|
965
|
+
```bash
|
|
966
|
+
qql dump <collection_name> <output.qql>
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
**In-shell usage (inside the QQL REPL):**
|
|
970
|
+
```
|
|
971
|
+
qql> DUMP COLLECTION <name> <output.qql>
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
**Example:**
|
|
975
|
+
```bash
|
|
976
|
+
qql dump medical_records /tmp/medical_records.qql
|
|
977
|
+
```
|
|
978
|
+
|
|
979
|
+
```
|
|
980
|
+
Dumping: 'medical_records' → /tmp/medical_records.qql
|
|
981
|
+
|
|
982
|
+
Collection type : hybrid (dense + sparse)
|
|
983
|
+
Points : 41
|
|
984
|
+
Batches : 1 (50 points/batch)
|
|
985
|
+
|
|
986
|
+
[1/1] wrote 41 point(s)
|
|
987
|
+
|
|
988
|
+
Done. 41 point(s) written.
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
**Generated file structure:**
|
|
992
|
+
```sql
|
|
993
|
+
-- ============================================================
|
|
994
|
+
-- QQL Dump — collection: medical_records
|
|
995
|
+
-- Generated : 2026-04-19 14:32:11
|
|
996
|
+
-- Points : 41
|
|
997
|
+
-- Type : hybrid (dense + sparse)
|
|
998
|
+
-- Note : Re-importing re-embeds all text using the
|
|
999
|
+
-- configured model (see: qql connect).
|
|
1000
|
+
-- ============================================================
|
|
1001
|
+
|
|
1002
|
+
CREATE COLLECTION medical_records HYBRID
|
|
1003
|
+
|
|
1004
|
+
-- Batch 1 / 1 (records 1–41)
|
|
1005
|
+
INSERT BULK INTO COLLECTION medical_records VALUES [
|
|
1006
|
+
{
|
|
1007
|
+
'text': 'Alzheimers disease is characterized by...',
|
|
1008
|
+
'title': 'Alzheimers Disease Overview',
|
|
1009
|
+
'department': 'neurology',
|
|
1010
|
+
'year': 2023,
|
|
1011
|
+
'peer_reviewed': true
|
|
1012
|
+
},
|
|
1013
|
+
...
|
|
1014
|
+
] USING HYBRID
|
|
1015
|
+
|
|
1016
|
+
-- ============================================================
|
|
1017
|
+
-- End of dump
|
|
1018
|
+
-- Written : 41
|
|
1019
|
+
-- Skipped : 0 (no 'text' field)
|
|
1020
|
+
-- ============================================================
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
**Round-trip workflow — backup and restore:**
|
|
1024
|
+
```bash
|
|
1025
|
+
# 1. Dump the collection
|
|
1026
|
+
qql dump medical_records backup.qql
|
|
1027
|
+
|
|
1028
|
+
# 2. Drop it
|
|
1029
|
+
qql> DROP COLLECTION medical_records
|
|
1030
|
+
|
|
1031
|
+
# 3. Restore from the dump
|
|
1032
|
+
qql execute backup.qql
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
**Rules and notes:**
|
|
1036
|
+
- Points without a `'text'` payload field are **skipped** (counted in the footer comment).
|
|
1037
|
+
- Hybrid collections produce `CREATE COLLECTION <name> HYBRID` and `INSERT BULK ... USING HYBRID` statements.
|
|
1038
|
+
- Dense collections produce plain `CREATE COLLECTION <name>` and `INSERT BULK` statements.
|
|
1039
|
+
- All payload value types are preserved: strings, integers, floats, booleans (`true`/`false`), `null`, lists, and nested dicts.
|
|
1040
|
+
- Re-importing re-embeds all text using your currently configured model — use the same model as the original collection to preserve semantic accuracy.
|
|
1041
|
+
- Parent directories of the output path are created automatically.
|
|
1042
|
+
|
|
1043
|
+
---
|
|
1044
|
+
|
|
889
1045
|
## Embedding Models
|
|
890
1046
|
|
|
891
1047
|
QQL uses [Fastembed](https://github.com/qdrant/fastembed) to convert text into vectors locally — no external API call is needed.
|
|
@@ -35,6 +35,7 @@ qql> SEARCH notes SIMILAR TO 'vector databases' LIMIT 5 USING HYBRID RERANK
|
|
|
35
35
|
- [The QQL Shell](#the-qql-shell)
|
|
36
36
|
- [All QQL Operations](#all-qql-operations)
|
|
37
37
|
- [INSERT — add a point](#insert--add-a-point)
|
|
38
|
+
- [INSERT BULK — batch insert](#insert-bulk--batch-insert-multiple-points)
|
|
38
39
|
- [SEARCH — find similar points](#search--find-similar-points)
|
|
39
40
|
- [Query-Time Search Params (`EXACT`, `WITH`)](#query-time-search-params-exact-with)
|
|
40
41
|
- [WHERE Clause Filters](#where-clause-filters)
|
|
@@ -44,6 +45,9 @@ qql> SEARCH notes SIMILAR TO 'vector databases' LIMIT 5 USING HYBRID RERANK
|
|
|
44
45
|
- [CREATE COLLECTION — create a collection](#create-collection--create-a-collection)
|
|
45
46
|
- [DROP COLLECTION — delete a collection](#drop-collection--delete-a-collection)
|
|
46
47
|
- [DELETE — remove a point](#delete--remove-a-point)
|
|
48
|
+
- [Script Files](#script-files)
|
|
49
|
+
- [EXECUTE — run a script file](#execute--run-a-qql-script-file)
|
|
50
|
+
- [DUMP COLLECTION — export to script](#dump-collection--export-collection-to-a-qql-script-file)
|
|
47
51
|
- [Embedding Models](#embedding-models)
|
|
48
52
|
- [Value Types in Dictionaries](#value-types-in-dictionaries)
|
|
49
53
|
- [Configuration File](#configuration-file)
|
|
@@ -838,6 +842,158 @@ To find a point's ID, run a SEARCH first and copy the ID from the results table.
|
|
|
838
842
|
|
|
839
843
|
---
|
|
840
844
|
|
|
845
|
+
## Script Files
|
|
846
|
+
|
|
847
|
+
QQL supports reading from and writing to `.qql` script files, making it easy to automate bulk operations, seed databases, and back up collections.
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
### EXECUTE — run a .qql script file
|
|
852
|
+
|
|
853
|
+
Execute a file containing multiple QQL statements in sequence. Each statement is parsed and executed in order. `--` comments are stripped before parsing.
|
|
854
|
+
|
|
855
|
+
**CLI usage:**
|
|
856
|
+
```bash
|
|
857
|
+
qql execute /path/to/script.qql
|
|
858
|
+
|
|
859
|
+
# Stop on first error instead of continuing through all statements
|
|
860
|
+
qql execute /path/to/script.qql --stop-on-error
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
**In-shell usage (inside the QQL REPL):**
|
|
864
|
+
```
|
|
865
|
+
qql> EXECUTE /path/to/script.qql
|
|
866
|
+
qql> \e /path/to/script.qql
|
|
867
|
+
```
|
|
868
|
+
|
|
869
|
+
**Script format:**
|
|
870
|
+
|
|
871
|
+
```sql
|
|
872
|
+
-- This is a comment — the entire line is ignored
|
|
873
|
+
-- ============================================================
|
|
874
|
+
-- QQL Script — populate articles collection
|
|
875
|
+
-- ============================================================
|
|
876
|
+
|
|
877
|
+
-- Step 1: create the collection
|
|
878
|
+
CREATE COLLECTION articles
|
|
879
|
+
|
|
880
|
+
-- Step 2: bulk insert records
|
|
881
|
+
INSERT BULK INTO COLLECTION articles VALUES [
|
|
882
|
+
{'text': 'Neural networks learn representations', 'year': 2023},
|
|
883
|
+
{'text': 'Attention mechanisms in transformers', 'year': 2024}
|
|
884
|
+
]
|
|
885
|
+
|
|
886
|
+
-- Step 3: verify
|
|
887
|
+
SHOW COLLECTIONS
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
**Rules:**
|
|
891
|
+
- `--` to end-of-line is a comment and is ignored (inline or full-line)
|
|
892
|
+
- Statements can span multiple lines (e.g. `INSERT BULK ... VALUES [...]`)
|
|
893
|
+
- Blank lines between statements are ignored
|
|
894
|
+
- By default all statements run even if one fails; use `--stop-on-error` to halt early
|
|
895
|
+
|
|
896
|
+
**Example output:**
|
|
897
|
+
```
|
|
898
|
+
Executing: /path/to/script.qql
|
|
899
|
+
|
|
900
|
+
[1/3] CREATE COLLECTION articles
|
|
901
|
+
✓ Collection 'articles' created (384-dimensional vectors, cosine distance)
|
|
902
|
+
[2/3] INSERT BULK INTO COLLECTION articles VALUES [ …
|
|
903
|
+
✓ Inserted 2 points
|
|
904
|
+
[3/3] SHOW COLLECTIONS
|
|
905
|
+
✓ 1 collection(s) found
|
|
906
|
+
|
|
907
|
+
Done. 3/3 statement(s) succeeded.
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
---
|
|
911
|
+
|
|
912
|
+
### DUMP COLLECTION — export collection to a .qql script file
|
|
913
|
+
|
|
914
|
+
Export every point in a collection to a `.qql` script file. The generated file is valid QQL — it can be re-imported with `qql execute` to restore or migrate the collection. Points are written in batches of 50 as `INSERT BULK` statements.
|
|
915
|
+
|
|
916
|
+
**CLI usage:**
|
|
917
|
+
```bash
|
|
918
|
+
qql dump <collection_name> <output.qql>
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
**In-shell usage (inside the QQL REPL):**
|
|
922
|
+
```
|
|
923
|
+
qql> DUMP COLLECTION <name> <output.qql>
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
**Example:**
|
|
927
|
+
```bash
|
|
928
|
+
qql dump medical_records /tmp/medical_records.qql
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
```
|
|
932
|
+
Dumping: 'medical_records' → /tmp/medical_records.qql
|
|
933
|
+
|
|
934
|
+
Collection type : hybrid (dense + sparse)
|
|
935
|
+
Points : 41
|
|
936
|
+
Batches : 1 (50 points/batch)
|
|
937
|
+
|
|
938
|
+
[1/1] wrote 41 point(s)
|
|
939
|
+
|
|
940
|
+
Done. 41 point(s) written.
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
**Generated file structure:**
|
|
944
|
+
```sql
|
|
945
|
+
-- ============================================================
|
|
946
|
+
-- QQL Dump — collection: medical_records
|
|
947
|
+
-- Generated : 2026-04-19 14:32:11
|
|
948
|
+
-- Points : 41
|
|
949
|
+
-- Type : hybrid (dense + sparse)
|
|
950
|
+
-- Note : Re-importing re-embeds all text using the
|
|
951
|
+
-- configured model (see: qql connect).
|
|
952
|
+
-- ============================================================
|
|
953
|
+
|
|
954
|
+
CREATE COLLECTION medical_records HYBRID
|
|
955
|
+
|
|
956
|
+
-- Batch 1 / 1 (records 1–41)
|
|
957
|
+
INSERT BULK INTO COLLECTION medical_records VALUES [
|
|
958
|
+
{
|
|
959
|
+
'text': 'Alzheimers disease is characterized by...',
|
|
960
|
+
'title': 'Alzheimers Disease Overview',
|
|
961
|
+
'department': 'neurology',
|
|
962
|
+
'year': 2023,
|
|
963
|
+
'peer_reviewed': true
|
|
964
|
+
},
|
|
965
|
+
...
|
|
966
|
+
] USING HYBRID
|
|
967
|
+
|
|
968
|
+
-- ============================================================
|
|
969
|
+
-- End of dump
|
|
970
|
+
-- Written : 41
|
|
971
|
+
-- Skipped : 0 (no 'text' field)
|
|
972
|
+
-- ============================================================
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
**Round-trip workflow — backup and restore:**
|
|
976
|
+
```bash
|
|
977
|
+
# 1. Dump the collection
|
|
978
|
+
qql dump medical_records backup.qql
|
|
979
|
+
|
|
980
|
+
# 2. Drop it
|
|
981
|
+
qql> DROP COLLECTION medical_records
|
|
982
|
+
|
|
983
|
+
# 3. Restore from the dump
|
|
984
|
+
qql execute backup.qql
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
**Rules and notes:**
|
|
988
|
+
- Points without a `'text'` payload field are **skipped** (counted in the footer comment).
|
|
989
|
+
- Hybrid collections produce `CREATE COLLECTION <name> HYBRID` and `INSERT BULK ... USING HYBRID` statements.
|
|
990
|
+
- Dense collections produce plain `CREATE COLLECTION <name>` and `INSERT BULK` statements.
|
|
991
|
+
- All payload value types are preserved: strings, integers, floats, booleans (`true`/`false`), `null`, lists, and nested dicts.
|
|
992
|
+
- Re-importing re-embeds all text using your currently configured model — use the same model as the original collection to preserve semantic accuracy.
|
|
993
|
+
- Parent directories of the output path are created automatically.
|
|
994
|
+
|
|
995
|
+
---
|
|
996
|
+
|
|
841
997
|
## Embedding Models
|
|
842
998
|
|
|
843
999
|
QQL uses [Fastembed](https://github.com/qdrant/fastembed) to convert text into vectors locally — no external API call is needed.
|
|
@@ -56,6 +56,15 @@ Available statements:
|
|
|
56
56
|
[yellow]DELETE FROM[/yellow] <name> [yellow]WHERE id =[/yellow] '<id>'
|
|
57
57
|
Delete a point by its ID.
|
|
58
58
|
|
|
59
|
+
Script files (in-shell):
|
|
60
|
+
[yellow]EXECUTE[/yellow] <path> or [yellow]\\e[/yellow] <path>
|
|
61
|
+
Run a .qql script file. Statements are executed in order.
|
|
62
|
+
Lines starting with [yellow]--[/yellow] are treated as comments and ignored.
|
|
63
|
+
|
|
64
|
+
[yellow]DUMP[/yellow] <name> <output.qql> or [yellow]DUMP COLLECTION[/yellow] <name> <output.qql>
|
|
65
|
+
Export all points in a collection to a .qql script file.
|
|
66
|
+
The file can be re-imported with EXECUTE.
|
|
67
|
+
|
|
59
68
|
Keyboard shortcuts:
|
|
60
69
|
← → arrows move cursor within the current line
|
|
61
70
|
↑ ↓ arrows scroll through command history
|
|
@@ -119,6 +128,109 @@ def disconnect() -> None:
|
|
|
119
128
|
console.print("Disconnected. Config removed.")
|
|
120
129
|
|
|
121
130
|
|
|
131
|
+
# ── execute ────────────────────────────────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
@main.command()
|
|
134
|
+
@click.argument("file", type=click.Path(exists=True, readable=True))
|
|
135
|
+
@click.option(
|
|
136
|
+
"--stop-on-error",
|
|
137
|
+
is_flag=True,
|
|
138
|
+
default=False,
|
|
139
|
+
help="Halt execution on the first statement error (default: continue all).",
|
|
140
|
+
)
|
|
141
|
+
def execute(file: str, stop_on_error: bool) -> None:
|
|
142
|
+
"""Execute a .qql script file against the connected Qdrant instance.
|
|
143
|
+
|
|
144
|
+
Lines beginning with -- are treated as comments and skipped.
|
|
145
|
+
Each QQL statement is executed in order and its result is printed.
|
|
146
|
+
"""
|
|
147
|
+
from qdrant_client import QdrantClient
|
|
148
|
+
|
|
149
|
+
cfg = load_config()
|
|
150
|
+
if cfg is None:
|
|
151
|
+
err_console.print(
|
|
152
|
+
"[bold red]Not connected.[/bold red] "
|
|
153
|
+
"Run: [bold]qql connect --url <url>[/bold]"
|
|
154
|
+
)
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
client = QdrantClient(url=cfg.url, api_key=cfg.secret)
|
|
159
|
+
client.get_collections()
|
|
160
|
+
except Exception as e:
|
|
161
|
+
err_console.print(f"[bold red]Connection failed:[/bold red] {e}")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
from .executor import Executor
|
|
165
|
+
from .script import run_script
|
|
166
|
+
|
|
167
|
+
executor = Executor(client, cfg)
|
|
168
|
+
console.print(f"[bold cyan]Executing:[/bold cyan] {file}\n")
|
|
169
|
+
|
|
170
|
+
ok, fail = run_script(file, executor, console, err_console, stop_on_error)
|
|
171
|
+
total = ok + fail
|
|
172
|
+
|
|
173
|
+
if fail == 0:
|
|
174
|
+
console.print(
|
|
175
|
+
f"\n[bold green]Done.[/bold green] "
|
|
176
|
+
f"{total}/{total} statement(s) succeeded."
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
console.print(
|
|
180
|
+
f"\n[bold yellow]Done.[/bold yellow] "
|
|
181
|
+
f"{ok}/{total} succeeded, [bold red]{fail} failed[/bold red]."
|
|
182
|
+
)
|
|
183
|
+
sys.exit(1)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ── dump ───────────────────────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
@main.command()
|
|
189
|
+
@click.argument("collection")
|
|
190
|
+
@click.argument("output", type=click.Path())
|
|
191
|
+
def dump(collection: str, output: str) -> None:
|
|
192
|
+
"""Dump a collection to a .qql script file.
|
|
193
|
+
|
|
194
|
+
OUTPUT is the path for the generated .qql file.
|
|
195
|
+
The file contains CREATE COLLECTION + INSERT BULK statements and can be
|
|
196
|
+
re-imported with: qql execute <output>
|
|
197
|
+
"""
|
|
198
|
+
from qdrant_client import QdrantClient
|
|
199
|
+
|
|
200
|
+
cfg = load_config()
|
|
201
|
+
if cfg is None:
|
|
202
|
+
err_console.print(
|
|
203
|
+
"[bold red]Not connected.[/bold red] "
|
|
204
|
+
"Run: [bold]qql connect --url <url>[/bold]"
|
|
205
|
+
)
|
|
206
|
+
sys.exit(1)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
client = QdrantClient(url=cfg.url, api_key=cfg.secret)
|
|
210
|
+
client.get_collections()
|
|
211
|
+
except Exception as e:
|
|
212
|
+
err_console.print(f"[bold red]Connection failed:[/bold red] {e}")
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
from .dumper import dump_collection
|
|
216
|
+
|
|
217
|
+
console.print(
|
|
218
|
+
f"[bold cyan]Dumping:[/bold cyan] '{collection}' → {output}\n"
|
|
219
|
+
)
|
|
220
|
+
written, skipped = dump_collection(collection, output, client, console, err_console)
|
|
221
|
+
|
|
222
|
+
if written == 0 and skipped == 0:
|
|
223
|
+
# collection not found — error already printed by dump_collection
|
|
224
|
+
sys.exit(1)
|
|
225
|
+
|
|
226
|
+
console.print(
|
|
227
|
+
f"\n[bold green]Done.[/bold green] "
|
|
228
|
+
f"{written} point(s) written"
|
|
229
|
+
+ (f", [yellow]{skipped} skipped[/yellow] (no 'text' field)" if skipped else "")
|
|
230
|
+
+ f"."
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
122
234
|
# ── REPL ───────────────────────────────────────────────────────────────────────
|
|
123
235
|
|
|
124
236
|
def _launch_repl(cfg: QQLConfig) -> None:
|
|
@@ -161,6 +273,62 @@ def _launch_repl(cfg: QQLConfig) -> None:
|
|
|
161
273
|
console.print(HELP_TEXT)
|
|
162
274
|
continue
|
|
163
275
|
|
|
276
|
+
# ── EXECUTE <path> / \e <path> — run a .qql script file ──────────
|
|
277
|
+
if low.startswith("execute ") or low.startswith("\\e "):
|
|
278
|
+
script_path = query.split(None, 1)[1].strip()
|
|
279
|
+
from .script import run_script
|
|
280
|
+
ok, fail = run_script(script_path, executor, console, err_console)
|
|
281
|
+
total = ok + fail
|
|
282
|
+
if fail == 0:
|
|
283
|
+
console.print(
|
|
284
|
+
f"[bold green]Done.[/bold green] "
|
|
285
|
+
f"{total}/{total} statement(s) succeeded."
|
|
286
|
+
)
|
|
287
|
+
else:
|
|
288
|
+
console.print(
|
|
289
|
+
f"[bold yellow]Done.[/bold yellow] "
|
|
290
|
+
f"{ok}/{total} succeeded, [bold red]{fail} failed[/bold red]."
|
|
291
|
+
)
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
# ── DUMP [COLLECTION] <name> <file> — export collection to .qql ──
|
|
295
|
+
# Accepts both:
|
|
296
|
+
# DUMP COLLECTION <name> <output.qql>
|
|
297
|
+
# DUMP <name> <output.qql>
|
|
298
|
+
if low.startswith("dump "):
|
|
299
|
+
parts = query.split(None, 3) # up to 4 tokens
|
|
300
|
+
if len(parts) >= 2 and parts[1].lower() == "collection":
|
|
301
|
+
# DUMP COLLECTION <name> <file>
|
|
302
|
+
if len(parts) < 4:
|
|
303
|
+
err_console.print(
|
|
304
|
+
"[bold red]Usage:[/bold red] DUMP COLLECTION <name> <output.qql>"
|
|
305
|
+
)
|
|
306
|
+
continue
|
|
307
|
+
coll_name, out_path = parts[2], parts[3]
|
|
308
|
+
else:
|
|
309
|
+
# DUMP <name> <file>
|
|
310
|
+
if len(parts) < 3:
|
|
311
|
+
err_console.print(
|
|
312
|
+
"[bold red]Usage:[/bold red] DUMP <name> <output.qql>"
|
|
313
|
+
)
|
|
314
|
+
continue
|
|
315
|
+
coll_name, out_path = parts[1], parts[2]
|
|
316
|
+
from .dumper import dump_collection
|
|
317
|
+
console.print(
|
|
318
|
+
f"[bold cyan]Dumping:[/bold cyan] '{coll_name}' → {out_path}\n"
|
|
319
|
+
)
|
|
320
|
+
written, skipped = dump_collection(
|
|
321
|
+
coll_name, out_path, client, console, err_console
|
|
322
|
+
)
|
|
323
|
+
if written > 0 or skipped == 0:
|
|
324
|
+
console.print(
|
|
325
|
+
f"[bold green]Done.[/bold green] "
|
|
326
|
+
f"{written} point(s) written"
|
|
327
|
+
+ (f", [yellow]{skipped} skipped[/yellow] (no 'text' field)" if skipped else "")
|
|
328
|
+
+ "."
|
|
329
|
+
)
|
|
330
|
+
continue
|
|
331
|
+
|
|
164
332
|
_run_and_print(executor, query)
|
|
165
333
|
|
|
166
334
|
|