uqal-core 0.1.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 (120) hide show
  1. uqal_core-0.1.0/.env.example +12 -0
  2. uqal_core-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +52 -0
  3. uqal_core-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
  4. uqal_core-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  5. uqal_core-0.1.0/.github/workflows/publish.yml +29 -0
  6. uqal_core-0.1.0/.gitignore +27 -0
  7. uqal_core-0.1.0/.python-version +1 -0
  8. uqal_core-0.1.0/LICENSE +21 -0
  9. uqal_core-0.1.0/PKG-INFO +106 -0
  10. uqal_core-0.1.0/README.md +91 -0
  11. uqal_core-0.1.0/SECURITY.md +23 -0
  12. uqal_core-0.1.0/hr_schema.uqal +1 -0
  13. uqal_core-0.1.0/pyproject.toml +46 -0
  14. uqal_core-0.1.0/scripts/dev/check_query.py +102 -0
  15. uqal_core-0.1.0/scripts/dev/inspect_grammar.py +80 -0
  16. uqal_core-0.1.0/scripts/dev/validate_module.py +326 -0
  17. uqal_core-0.1.0/src/uqal_core/ast/__init__.py +0 -0
  18. uqal_core-0.1.0/src/uqal_core/ast/condition_registry.py +42 -0
  19. uqal_core-0.1.0/src/uqal_core/ast/module_nodes.py +33 -0
  20. uqal_core-0.1.0/src/uqal_core/ast/nodes.py +424 -0
  21. uqal_core-0.1.0/src/uqal_core/ast/transformer.py +616 -0
  22. uqal_core-0.1.0/src/uqal_core/cache/cache_manager.py +226 -0
  23. uqal_core-0.1.0/src/uqal_core/cli/__init__.py +0 -0
  24. uqal_core-0.1.0/src/uqal_core/cli/commands/__init__.py +0 -0
  25. uqal_core-0.1.0/src/uqal_core/cli/commands/add_connection.py +446 -0
  26. uqal_core-0.1.0/src/uqal_core/cli/commands/add_module.py +169 -0
  27. uqal_core-0.1.0/src/uqal_core/cli/commands/cache.py +189 -0
  28. uqal_core-0.1.0/src/uqal_core/cli/commands/list_modules.py +36 -0
  29. uqal_core-0.1.0/src/uqal_core/cli/commands/repl.py +578 -0
  30. uqal_core-0.1.0/src/uqal_core/cli/commands/run.py +145 -0
  31. uqal_core-0.1.0/src/uqal_core/cli/commands/script.py +278 -0
  32. uqal_core-0.1.0/src/uqal_core/cli/main.py +51 -0
  33. uqal_core-0.1.0/src/uqal_core/config/__init__.py +0 -0
  34. uqal_core-0.1.0/src/uqal_core/config/config_manager.py +248 -0
  35. uqal_core-0.1.0/src/uqal_core/config/connection_schema.py +114 -0
  36. uqal_core-0.1.0/src/uqal_core/config/loader.py +162 -0
  37. uqal_core-0.1.0/src/uqal_core/engine.py +261 -0
  38. uqal_core-0.1.0/src/uqal_core/execution/__init__.py +0 -0
  39. uqal_core-0.1.0/src/uqal_core/execution/executor.py +637 -0
  40. uqal_core-0.1.0/src/uqal_core/execution/result_set.py +155 -0
  41. uqal_core-0.1.0/src/uqal_core/module_interface.py +221 -0
  42. uqal_core-0.1.0/src/uqal_core/module_loader.py +255 -0
  43. uqal_core-0.1.0/src/uqal_core/modules/standard/dummy/__init__.py +0 -0
  44. uqal_core-0.1.0/src/uqal_core/modules/standard/dummy/connection_schema.py +30 -0
  45. uqal_core-0.1.0/src/uqal_core/modules/standard/dummy/module.py +134 -0
  46. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/__init__.py +0 -0
  47. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/capabilities.py +24 -0
  48. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/connection_schema.py +96 -0
  49. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/grammar_extension.lark +5 -0
  50. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/module.py +525 -0
  51. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/native_validator.py +40 -0
  52. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/schema_sync.py +258 -0
  53. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/translator.py +235 -0
  54. uqal_core-0.1.0/src/uqal_core/modules/standard/mongodb/type_mapping.py +58 -0
  55. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/__init__.py +0 -0
  56. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/capabilities.py +25 -0
  57. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/connection_schema.py +68 -0
  58. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/grammar_extension.lark +9 -0
  59. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/module.py +282 -0
  60. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/native_validator.py +47 -0
  61. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/schema_sync.py +203 -0
  62. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/translator.py +476 -0
  63. uqal_core-0.1.0/src/uqal_core/modules/standard/neo4j/type_mapping.py +70 -0
  64. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/__init__.py +0 -0
  65. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/capabilities.py +30 -0
  66. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/complexity.py +85 -0
  67. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/connection_schema.py +98 -0
  68. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/grammar_extension.lark +5 -0
  69. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/module.py +370 -0
  70. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/native_validator.py +86 -0
  71. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/schema_sync.py +182 -0
  72. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/translator.py +446 -0
  73. uqal_core-0.1.0/src/uqal_core/modules/standard/postgresql/type_mapping.py +68 -0
  74. uqal_core-0.1.0/src/uqal_core/parser/__init__.py +0 -0
  75. uqal_core-0.1.0/src/uqal_core/parser/base_grammar.lark +198 -0
  76. uqal_core-0.1.0/src/uqal_core/parser/fragments/.gitkeep +0 -0
  77. uqal_core-0.1.0/src/uqal_core/parser/fragments/conditions.lark +14 -0
  78. uqal_core-0.1.0/src/uqal_core/parser/fragments/expressions.lark +23 -0
  79. uqal_core-0.1.0/src/uqal_core/parser/fragments/params.lark +19 -0
  80. uqal_core-0.1.0/src/uqal_core/parser/fragments/types.lark +19 -0
  81. uqal_core-0.1.0/src/uqal_core/parser/grammar_builder.py +179 -0
  82. uqal_core-0.1.0/src/uqal_core/planner/__init__.py +0 -0
  83. uqal_core-0.1.0/src/uqal_core/planner/query_planner.py +394 -0
  84. uqal_core-0.1.0/src/uqal_core/registry/__init__.py +0 -0
  85. uqal_core-0.1.0/src/uqal_core/registry/connection_registry.py +99 -0
  86. uqal_core-0.1.0/src/uqal_core/registry/module_registry.py +114 -0
  87. uqal_core-0.1.0/src/uqal_core/schema/__init__.py +0 -0
  88. uqal_core-0.1.0/src/uqal_core/schema/schema_store.py +134 -0
  89. uqal_core-0.1.0/src/uqal_core/scripts/editor.py +211 -0
  90. uqal_core-0.1.0/src/uqal_core/scripts/script_manager.py +93 -0
  91. uqal_core-0.1.0/src/uqal_core/typecheck/__init__.py +0 -0
  92. uqal_core-0.1.0/src/uqal_core/typecheck/checker.py +493 -0
  93. uqal_core-0.1.0/src/uqal_core/types.py +132 -0
  94. uqal_core-0.1.0/tests/__init__.py +0 -0
  95. uqal_core-0.1.0/tests/conftest.py +139 -0
  96. uqal_core-0.1.0/tests/e2e/__init__.py +0 -0
  97. uqal_core-0.1.0/tests/e2e/test_full_workflow.py +119 -0
  98. uqal_core-0.1.0/tests/integration/__init__.py +0 -0
  99. uqal_core-0.1.0/tests/integration/test_cli.py +127 -0
  100. uqal_core-0.1.0/tests/integration/test_module_compliance.py +238 -0
  101. uqal_core-0.1.0/tests/integration/test_module_loader.py +57 -0
  102. uqal_core-0.1.0/tests/unit/__init__.py +0 -0
  103. uqal_core-0.1.0/tests/unit/test_config_manager.py +200 -0
  104. uqal_core-0.1.0/tests/unit/test_create_view.py +216 -0
  105. uqal_core-0.1.0/tests/unit/test_executor.py +214 -0
  106. uqal_core-0.1.0/tests/unit/test_executor_output.py +288 -0
  107. uqal_core-0.1.0/tests/unit/test_module_nodes.py +85 -0
  108. uqal_core-0.1.0/tests/unit/test_mongodb_module.py +322 -0
  109. uqal_core-0.1.0/tests/unit/test_neo4j_module.py +282 -0
  110. uqal_core-0.1.0/tests/unit/test_neo4j_rel_traversal.py +457 -0
  111. uqal_core-0.1.0/tests/unit/test_neo4j_translator.py +285 -0
  112. uqal_core-0.1.0/tests/unit/test_output_format.py +190 -0
  113. uqal_core-0.1.0/tests/unit/test_planner.py +188 -0
  114. uqal_core-0.1.0/tests/unit/test_result_set.py +69 -0
  115. uqal_core-0.1.0/tests/unit/test_schema_store.py +82 -0
  116. uqal_core-0.1.0/tests/unit/test_transformer.py +164 -0
  117. uqal_core-0.1.0/tests/unit/test_typecheck.py +161 -0
  118. uqal_core-0.1.0/tests/unit/test_types.py +56 -0
  119. uqal_core-0.1.0/uqal_config.example.json +17 -0
  120. uqal_core-0.1.0/uv.lock +455 -0
@@ -0,0 +1,12 @@
1
+ # UQAL secrets template
2
+ # Copy this file to .env and fill in your real values
3
+ # Never commit .env to git
4
+
5
+ # Example for a connection named "db1":
6
+ # UQAL_DB1_USER=
7
+ # UQAL_DB1_PASSWORD=
8
+ # UQAL_DB1_SSL_CERT=
9
+
10
+ # Example for a connection named "graph":
11
+ # UQAL_GRAPH_USER=
12
+ # UQAL_GRAPH_PASSWORD=
@@ -0,0 +1,52 @@
1
+ name: Bug Report
2
+ description: Something is not working as expected in uqal-core.
3
+ title: "bug: "
4
+ labels: ["bug"]
5
+ body:
6
+ - type: input
7
+ id: version
8
+ attributes:
9
+ label: uqal-core version
10
+ placeholder: "e.g. 0.3.1"
11
+ validations:
12
+ required: true
13
+
14
+ - type: input
15
+ id: python
16
+ attributes:
17
+ label: Python version
18
+ placeholder: "e.g. 3.12.2"
19
+ validations:
20
+ required: true
21
+
22
+ - type: textarea
23
+ id: description
24
+ attributes:
25
+ label: What happened?
26
+ description: A clear description of the bug.
27
+ validations:
28
+ required: true
29
+
30
+ - type: textarea
31
+ id: reproduction
32
+ attributes:
33
+ label: Steps to reproduce
34
+ placeholder: |
35
+ 1. Load module ...
36
+ 2. Run query ...
37
+ 3. See error
38
+ validations:
39
+ required: true
40
+
41
+ - type: textarea
42
+ id: expected
43
+ attributes:
44
+ label: Expected behaviour
45
+ validations:
46
+ required: true
47
+
48
+ - type: textarea
49
+ id: logs
50
+ attributes:
51
+ label: Error output / stack trace
52
+ render: shell
@@ -0,0 +1,38 @@
1
+ name: Feature Request
2
+ description: Suggest an improvement or new capability for uqal-core.
3
+ title: "feat: "
4
+ labels: ["enhancement"]
5
+ body:
6
+ - type: textarea
7
+ id: problem
8
+ attributes:
9
+ label: What problem does this solve?
10
+ description: Describe the limitation or gap you are running into.
11
+ validations:
12
+ required: true
13
+
14
+ - type: textarea
15
+ id: solution
16
+ attributes:
17
+ label: Proposed solution
18
+ description: How should it work? Include example syntax or API if relevant.
19
+ validations:
20
+ required: true
21
+
22
+ - type: textarea
23
+ id: alternatives
24
+ attributes:
25
+ label: Alternatives considered
26
+ description: Other approaches you thought about and why you ruled them out.
27
+
28
+ - type: checkboxes
29
+ id: scope
30
+ attributes:
31
+ label: Affected area
32
+ options:
33
+ - label: Parser / Grammar
34
+ - label: AST / Transformer
35
+ - label: Module interface
36
+ - label: Module loader
37
+ - label: CLI
38
+ - label: Dev tooling
@@ -0,0 +1,13 @@
1
+ ## What
2
+
3
+ <!-- What does this PR change? -->
4
+
5
+ ## Why
6
+
7
+ <!-- Why is this change needed? Link an issue if one exists. Closes #... -->
8
+
9
+ ## Checklist
10
+
11
+ - [ ] Tests added or updated
12
+ - [ ] `uv run pytest` passes locally
13
+ - [ ] No debug code or temporary files included
@@ -0,0 +1,29 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ name: Build and publish
10
+ runs-on: ubuntu-latest
11
+ environment: release
12
+ permissions:
13
+ id-token: write # required for PyPI Trusted Publishing
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v4
24
+
25
+ - name: Build
26
+ run: uv build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,27 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ test_grammar.py
13
+ test_grammar_files.py
14
+
15
+ test_grammar_builder.py
16
+
17
+ tests/logs/
18
+ tests/coverage_html/
19
+
20
+ .env
21
+
22
+ uqal_config.json
23
+
24
+ .coverage
25
+ tests/coverage_html/
26
+
27
+ .uqal/
@@ -0,0 +1 @@
1
+ 3.14.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 UQAL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: uqal-core
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: click>=8.4.2
8
+ Requires-Dist: lark>=1.3.1
9
+ Requires-Dist: neo4j>=6.2.0
10
+ Requires-Dist: prompt-toolkit>=3.0.52
11
+ Requires-Dist: psycopg2-binary>=2.9.12
12
+ Requires-Dist: pymongo>=4.17.0
13
+ Requires-Dist: textual>=8.2.8
14
+ Description-Content-Type: text/markdown
15
+
16
+ # uqal-core
17
+
18
+ The core engine of [UQAL](https://uqal-lang.github.io/uqal-docs) — a unified query abstraction layer that lets you write one query syntax and run it against any supported database.
19
+
20
+ `uqal-core` contains the parser, grammar builder, AST, module loader, and CLI. It is the runtime that all UQAL modules plug into.
21
+
22
+ ---
23
+
24
+ ## What is UQAL?
25
+
26
+ UQAL is a database-agnostic query language. Instead of writing SQL for PostgreSQL, Cypher for Neo4j, and MQL for MongoDB separately, you write UQAL once and the appropriate module translates it into the native query for the connected database.
27
+
28
+ ```
29
+ mydb.users.get_table(where active = true, fields id, name)
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ uv add uqal-core
38
+ ```
39
+
40
+ Requires Python 3.12+.
41
+
42
+ ---
43
+
44
+ ## Quick Start
45
+
46
+ ```bash
47
+ # Start the interactive UQAL shell
48
+ uqal shell
49
+
50
+ # Run a query file
51
+ uqal run query.uqal
52
+
53
+ # List loaded modules
54
+ uqal list-modules
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Modules
60
+
61
+ Database support comes from modules. Standard modules ship with `uqal-core`. Community modules are installed separately:
62
+
63
+ ```bash
64
+ uv add uqal-postgis
65
+ uqal add-module community.postgis
66
+ ```
67
+
68
+ Browse available modules: [uqal-lang/uqal-modules](https://github.com/uqal-lang/uqal-modules)
69
+
70
+ ---
71
+
72
+ ## Development Setup
73
+
74
+ ```bash
75
+ git clone https://github.com/uqal-lang/uqal-core
76
+ cd uqal-core
77
+ uv sync
78
+ uv run pytest
79
+ ```
80
+
81
+ Dev scripts are in `scripts/dev/`:
82
+
83
+ ```bash
84
+ # Parse and translate a query
85
+ uv run python scripts/dev/check_query.py "mydb.users.get_table()"
86
+
87
+ # Inspect the combined grammar
88
+ uv run python scripts/dev/inspect_grammar.py
89
+
90
+ # Validate a module implementation
91
+ uv run python scripts/dev/validate_module.py standard.postgresql
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Contributing
97
+
98
+ `uqal-core` is maintained by the core team. See [CONTRIBUTING](https://uqal-lang.github.io/uqal-docs/contributing) for the development workflow.
99
+
100
+ To contribute a new database module, head to [uqal-lang/uqal-modules](https://github.com/uqal-lang/uqal-modules).
101
+
102
+ ---
103
+
104
+ ## License
105
+
106
+ [MIT](LICENSE)
@@ -0,0 +1,91 @@
1
+ # uqal-core
2
+
3
+ The core engine of [UQAL](https://uqal-lang.github.io/uqal-docs) — a unified query abstraction layer that lets you write one query syntax and run it against any supported database.
4
+
5
+ `uqal-core` contains the parser, grammar builder, AST, module loader, and CLI. It is the runtime that all UQAL modules plug into.
6
+
7
+ ---
8
+
9
+ ## What is UQAL?
10
+
11
+ UQAL is a database-agnostic query language. Instead of writing SQL for PostgreSQL, Cypher for Neo4j, and MQL for MongoDB separately, you write UQAL once and the appropriate module translates it into the native query for the connected database.
12
+
13
+ ```
14
+ mydb.users.get_table(where active = true, fields id, name)
15
+ ```
16
+
17
+ ---
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ uv add uqal-core
23
+ ```
24
+
25
+ Requires Python 3.12+.
26
+
27
+ ---
28
+
29
+ ## Quick Start
30
+
31
+ ```bash
32
+ # Start the interactive UQAL shell
33
+ uqal shell
34
+
35
+ # Run a query file
36
+ uqal run query.uqal
37
+
38
+ # List loaded modules
39
+ uqal list-modules
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Modules
45
+
46
+ Database support comes from modules. Standard modules ship with `uqal-core`. Community modules are installed separately:
47
+
48
+ ```bash
49
+ uv add uqal-postgis
50
+ uqal add-module community.postgis
51
+ ```
52
+
53
+ Browse available modules: [uqal-lang/uqal-modules](https://github.com/uqal-lang/uqal-modules)
54
+
55
+ ---
56
+
57
+ ## Development Setup
58
+
59
+ ```bash
60
+ git clone https://github.com/uqal-lang/uqal-core
61
+ cd uqal-core
62
+ uv sync
63
+ uv run pytest
64
+ ```
65
+
66
+ Dev scripts are in `scripts/dev/`:
67
+
68
+ ```bash
69
+ # Parse and translate a query
70
+ uv run python scripts/dev/check_query.py "mydb.users.get_table()"
71
+
72
+ # Inspect the combined grammar
73
+ uv run python scripts/dev/inspect_grammar.py
74
+
75
+ # Validate a module implementation
76
+ uv run python scripts/dev/validate_module.py standard.postgresql
77
+ ```
78
+
79
+ ---
80
+
81
+ ## Contributing
82
+
83
+ `uqal-core` is maintained by the core team. See [CONTRIBUTING](https://uqal-lang.github.io/uqal-docs/contributing) for the development workflow.
84
+
85
+ To contribute a new database module, head to [uqal-lang/uqal-modules](https://github.com/uqal-lang/uqal-modules).
86
+
87
+ ---
88
+
89
+ ## License
90
+
91
+ [MIT](LICENSE)
@@ -0,0 +1,23 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ |---------|-----------|
7
+ | latest (`main`) | ✓ |
8
+ | older releases | ✗ |
9
+
10
+ ## Reporting a Vulnerability
11
+
12
+ **Do not open a public GitHub issue for security vulnerabilities.**
13
+
14
+ Use GitHub's private vulnerability reporting instead:
15
+ **[Report a vulnerability](https://github.com/uqal-lang/uqal-core/security/advisories/new)**
16
+
17
+ Please include:
18
+ - A description of the vulnerability
19
+ - Steps to reproduce
20
+ - Potential impact
21
+ - A suggested fix if you have one
22
+
23
+ We will respond within **72 hours** and aim to release a fix within **14 days** depending on severity.
@@ -0,0 +1 @@
1
+ uqal_hr.sql("SELECT table_name, column_name, data_type FROM information_schema.columns WHERE table_schema = 'public' ORDER BY table_name, ordinal_position")
@@ -0,0 +1,46 @@
1
+ [project]
2
+ name = "uqal-core"
3
+ version = "0.1.0"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "click>=8.4.2",
9
+ "lark>=1.3.1",
10
+ "neo4j>=6.2.0",
11
+ "prompt-toolkit>=3.0.52",
12
+ "psycopg2-binary>=2.9.12",
13
+ "pymongo>=4.17.0",
14
+ "textual>=8.2.8",
15
+ ]
16
+
17
+ [dependency-groups]
18
+ dev = [
19
+ "pytest>=9.1.1",
20
+ "pytest-cov>=6.0.0",
21
+ ]
22
+
23
+ [build-system]
24
+ requires = ["hatchling"]
25
+ build-backend = "hatchling.build"
26
+
27
+ [tool.hatch.build.targets.wheel]
28
+ packages = ["src/uqal_core"]
29
+
30
+ [project.scripts]
31
+ uqal = "uqal_core.cli.main:cli"
32
+
33
+ [tool.pytest.ini_options]
34
+ addopts = "-v --tb=short --cov=src/uqal_core --cov-report=term-missing --cov-report=html:tests/coverage_html"
35
+ testpaths = ["tests"]
36
+ log_cli = true
37
+ log_cli_level = "INFO"
38
+ log_file = "tests/logs/test_results.log"
39
+ log_file_level = "INFO"
40
+ log_file_format = "%(asctime)s %(levelname)s %(message)s"
41
+ log_file_date_format = "%Y-%m-%d %H:%M:%S"
42
+ markers = [
43
+ "unit: fast isolated tests with no external dependencies",
44
+ "integration: tests combining multiple components",
45
+ "e2e: full end-to-end workflow tests",
46
+ ]
@@ -0,0 +1,102 @@
1
+ """
2
+ Parse and translate a UQAL query — shows the parse tree, AST nodes,
3
+ and the translated native query output.
4
+
5
+ Useful for testing module translation during development without
6
+ needing a real database connection.
7
+
8
+ Usage:
9
+ uv run python scripts/dev/check_query.py "mydb.users.get_table()"
10
+ uv run python scripts/dev/check_query.py "mydb.users.get_table(where active = true, fields id, name)"
11
+ uv run python scripts/dev/check_query.py "let x = 1 + 2 output x"
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
20
+
21
+
22
+ def header(title: str) -> None:
23
+ print(f"\n\033[1;34m{'─' * 60}\033[0m")
24
+ print(f"\033[1;34m {title}\033[0m")
25
+ print(f"\033[1;34m{'─' * 60}\033[0m")
26
+
27
+
28
+ def check_query(query: str, module_names: list[str] | None = None) -> None:
29
+ from uqal_core.module_loader import ModuleLoader
30
+ from uqal_core.registry.module_registry import ModuleRegistry
31
+ from uqal_core.parser.grammar_builder import GrammarBuilder
32
+ from uqal_core.ast.transformer import UQALTransformer
33
+
34
+ modules_to_load = module_names or ["standard.postgresql", "standard.mongodb", "standard.neo4j"]
35
+
36
+ registry = ModuleRegistry()
37
+ loader = ModuleLoader(registry=registry)
38
+ loader.load(modules_to_load)
39
+
40
+ # Use all registered modules (includes transitive dependencies)
41
+ loaded_modules = [registry.get_module(m) for m in registry.list_modules()]
42
+
43
+ builder = GrammarBuilder()
44
+ parser = builder.build(loaded_modules)
45
+ transformer = UQALTransformer()
46
+
47
+ header("Input query")
48
+ print(f" {query}")
49
+
50
+ header("Parse tree")
51
+ try:
52
+ tree = parser.parse(query)
53
+ print(tree.pretty())
54
+ except Exception as e:
55
+ print(f"\033[91m ✗ Parse failed: {e}\033[0m")
56
+ return
57
+
58
+ header("AST nodes")
59
+ try:
60
+ program = transformer.transform(tree)
61
+ for i, stmt in enumerate(program.statements):
62
+ print(f" [{i}] {type(stmt).__name__}: {stmt}")
63
+ except Exception as e:
64
+ print(f"\033[91m ✗ Transform failed: {e}\033[0m")
65
+ return
66
+
67
+ header("Translation")
68
+ try:
69
+ for i, stmt in enumerate(program.statements):
70
+ # Find the module that handles this statement
71
+ for module in loaded_modules:
72
+ try:
73
+ result = module.translate(stmt)
74
+ if result is not None:
75
+ query_str, params = result
76
+ print(f" Module: {module.get_manifest().name}")
77
+ print(f" Query: {query_str}")
78
+ if params:
79
+ print(f" Params: {params}")
80
+ break
81
+ except (NotImplementedError, AttributeError):
82
+ continue
83
+ except Exception as e:
84
+ print(f" (translation not available for this statement type: {e})")
85
+
86
+ print()
87
+
88
+
89
+ if __name__ == "__main__":
90
+ if len(sys.argv) < 2:
91
+ print("Usage: uv run python scripts/dev/check_query.py \"<query>\"")
92
+ print(" uv run python scripts/dev/check_query.py \"<query>\" --module standard.neo4j")
93
+ sys.exit(1)
94
+
95
+ query = sys.argv[1]
96
+ modules = None
97
+
98
+ if "--module" in sys.argv:
99
+ idx = sys.argv.index("--module")
100
+ modules = sys.argv[idx + 1:]
101
+
102
+ check_query(query, modules)
@@ -0,0 +1,80 @@
1
+ """
2
+ Print the complete combined UQAL grammar for all (or selected) modules.
3
+ Useful for debugging grammar rules, extension points, and conflicts.
4
+
5
+ Usage:
6
+ uv run python scripts/dev/inspect_grammar.py
7
+ uv run python scripts/dev/inspect_grammar.py standard.neo4j
8
+ uv run python scripts/dev/inspect_grammar.py standard.postgresql standard.neo4j
9
+ uv run python scripts/dev/inspect_grammar.py --extension-points
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
18
+
19
+ _EXTENSION_POINTS = [
20
+ "condition",
21
+ "expr",
22
+ "statement",
23
+ "db_call",
24
+ ]
25
+
26
+
27
+ def inspect_grammar(module_names: list[str], show_extension_points: bool = False) -> None:
28
+ from uqal_core.module_loader import ModuleLoader
29
+ from uqal_core.registry.module_registry import ModuleRegistry
30
+ from uqal_core.parser.grammar_builder import GrammarBuilder
31
+
32
+ registry = ModuleRegistry()
33
+ loader = ModuleLoader(registry=registry)
34
+ loader.load(module_names)
35
+ loaded = [registry.get_module(m) for m in module_names if registry.get_module(m)]
36
+
37
+ builder = GrammarBuilder()
38
+
39
+ if show_extension_points:
40
+ print("\n\033[1;34mExtension points and their contributors:\033[0m\n")
41
+ for point in _EXTENSION_POINTS:
42
+ contributors = []
43
+ for module in loaded:
44
+ caps = module.get_capabilities()
45
+ exts = getattr(caps, "grammar_extensions", {})
46
+ rules = exts.get(point, [])
47
+ if rules:
48
+ contributors.append(f"{module.get_manifest().name}: {rules}")
49
+ if contributors:
50
+ print(f" \033[33m{point}\033[0m")
51
+ for c in contributors:
52
+ print(f" → {c}")
53
+ else:
54
+ print(f" \033[90m{point} (no extensions)\033[0m")
55
+ print()
56
+ return
57
+
58
+ print(f"\n\033[1;34mLoaded modules: {[m.get_manifest().name for m in loaded]}\033[0m\n")
59
+
60
+ try:
61
+ grammar_text = builder.get_combined_grammar(loaded)
62
+ print(grammar_text)
63
+ except AttributeError:
64
+ # Fallback: build and show what we can
65
+ parser = builder.build(loaded)
66
+ print("(Grammar built successfully — source text not directly accessible)")
67
+ print(f"\nParser type: {type(parser)}")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ args = sys.argv[1:]
72
+
73
+ show_ext_points = "--extension-points" in args
74
+ if show_ext_points:
75
+ args = [a for a in args if a != "--extension-points"]
76
+
77
+ if not args:
78
+ args = ["standard.postgresql", "standard.mongodb", "standard.neo4j"]
79
+
80
+ inspect_grammar(args, show_extension_points=show_ext_points)