buildaquery 0.2.0__tar.gz → 0.4.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.
- {buildaquery-0.2.0 → buildaquery-0.4.0}/PKG-INFO +104 -7
- {buildaquery-0.2.0 → buildaquery-0.4.0}/README.md +114 -19
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/abstract_syntax_tree/README.md +1 -1
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/compiler/README.md +34 -1
- buildaquery-0.4.0/buildaquery/compiler/__init__.py +6 -0
- buildaquery-0.4.0/buildaquery/compiler/mysql/README.md +25 -0
- buildaquery-0.4.0/buildaquery/compiler/mysql/__init__.py +1 -0
- buildaquery-0.4.0/buildaquery/compiler/mysql/mysql_compiler.py +342 -0
- buildaquery-0.4.0/buildaquery/compiler/oracle/README.md +31 -0
- buildaquery-0.4.0/buildaquery/compiler/oracle/__init__.py +3 -0
- buildaquery-0.4.0/buildaquery/compiler/oracle/oracle_compiler.py +348 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/compiler/postgres/README.md +1 -1
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/compiler/sqlite/README.md +2 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/execution/README.md +24 -1
- buildaquery-0.4.0/buildaquery/execution/__init__.py +6 -0
- buildaquery-0.4.0/buildaquery/execution/mysql.py +185 -0
- buildaquery-0.4.0/buildaquery/execution/oracle.py +189 -0
- buildaquery-0.4.0/buildaquery/tests/test_compiler_mysql.py +341 -0
- buildaquery-0.4.0/buildaquery/tests/test_compiler_oracle.py +374 -0
- buildaquery-0.4.0/buildaquery/tests/test_execution_mysql.py +51 -0
- buildaquery-0.4.0/buildaquery/tests/test_execution_oracle.py +51 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/traversal/README.md +1 -1
- {buildaquery-0.2.0 → buildaquery-0.4.0}/pyproject.toml +11 -9
- buildaquery-0.2.0/buildaquery/compiler/__init__.py +0 -0
- buildaquery-0.2.0/buildaquery/execution/__init__.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/LICENSE.txt +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/__init__.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/abstract_syntax_tree/__init__.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/abstract_syntax_tree/models.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/compiler/compiled_query.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/compiler/postgres/postgres_compiler.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/compiler/sqlite/__init__.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/compiler/sqlite/sqlite_compiler.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/execution/base.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/execution/postgres.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/execution/sqlite.py +0 -0
- /buildaquery-0.2.0/buildaquery/tests/test_ast.py → /buildaquery-0.4.0/buildaquery/tests/test_ast_postgres.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/tests/test_compiler_postgres.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/tests/test_compiler_sqlite.py +0 -0
- /buildaquery-0.2.0/buildaquery/tests/test_execution.py → /buildaquery-0.4.0/buildaquery/tests/test_execution_postgres.py +0 -0
- /buildaquery-0.2.0/buildaquery/tests/test_traversal.py → /buildaquery-0.4.0/buildaquery/tests/test_traversal_postgres.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/traversal/__init__.py +0 -0
- {buildaquery-0.2.0 → buildaquery-0.4.0}/buildaquery/traversal/visitor_pattern.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: buildaquery
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A Python-based query builder for PostgreSQL.
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: A Python-based query builder for PostgreSQL, SQLite, MySQL, and Oracle.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE.txt
|
|
7
7
|
Author: Anirudh Bhattacharya
|
|
@@ -21,6 +21,8 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
22
|
Requires-Dist: Pygments (==2.19.2)
|
|
23
23
|
Requires-Dist: colorama (==0.4.6)
|
|
24
|
+
Requires-Dist: mysql-connector-python (==9.4.0)
|
|
25
|
+
Requires-Dist: oracledb (>=3.4.2,<4.0.0)
|
|
24
26
|
Requires-Dist: packaging (==26.0)
|
|
25
27
|
Requires-Dist: pluggy (==1.6.0)
|
|
26
28
|
Requires-Dist: psycopg (==3.3.3)
|
|
@@ -33,7 +35,7 @@ Description-Content-Type: text/markdown
|
|
|
33
35
|
|
|
34
36
|
# Build-a-Query
|
|
35
37
|
|
|
36
|
-
A Python-based query builder designed to represent, compile, and execute SQL queries using a dialect-agnostic Abstract Syntax Tree (AST). Supports PostgreSQL and
|
|
38
|
+
A Python-based query builder designed to represent, compile, and execute SQL queries using a dialect-agnostic Abstract Syntax Tree (AST). Supports PostgreSQL, SQLite, MySQL, and Oracle.
|
|
37
39
|
|
|
38
40
|
## Features
|
|
39
41
|
|
|
@@ -44,7 +46,12 @@ A Python-based query builder designed to represent, compile, and execute SQL que
|
|
|
44
46
|
- **DDL Support**: Basic schema management with `CREATE TABLE` and `DROP TABLE`.
|
|
45
47
|
- **Visitor Pattern Traversal**: Extensible architecture for analysis and compilation.
|
|
46
48
|
- **Secure Compilation**: Automatic parameterization to prevent SQL injection.
|
|
47
|
-
- **Execution Layer**: Built-in support for executing compiled queries via `psycopg` (PostgreSQL) and the standard library `sqlite3` (SQLite).
|
|
49
|
+
- **Execution Layer**: Built-in support for executing compiled queries via `psycopg` (PostgreSQL), `mysql-connector-python` (MySQL), `oracledb` (Oracle), and the standard library `sqlite3` (SQLite).
|
|
50
|
+
|
|
51
|
+
## Dialect Notes
|
|
52
|
+
- MySQL does not support `INTERSECT` / `EXCEPT` or `DROP TABLE ... CASCADE` in this implementation (the compiler raises `ValueError`).
|
|
53
|
+
- SQLite does not support `DROP TABLE ... CASCADE` (the compiler raises `ValueError`).
|
|
54
|
+
- Oracle does not support `IF EXISTS` / `IF NOT EXISTS` in `DROP TABLE`/`CREATE TABLE` (the compiler raises `ValueError`), and `EXCEPT` is compiled as `MINUS`.
|
|
48
55
|
|
|
49
56
|
## Installation
|
|
50
57
|
|
|
@@ -61,6 +68,12 @@ pip install buildaquery
|
|
|
61
68
|
- **PostgreSQL database**: A running PostgreSQL instance (version 12+ recommended). You can set this up locally, via Docker, or use a cloud service.
|
|
62
69
|
- Example with Docker: `docker run --name postgres -e POSTGRES_PASSWORD=yourpassword -d -p 5432:5432 postgres:15`
|
|
63
70
|
- `psycopg` (automatically installed as a dependency) - the PostgreSQL adapter for Python.
|
|
71
|
+
- **MySQL database**: A running MySQL instance (version 8.0+ recommended).
|
|
72
|
+
- Example with Docker: `docker run --name mysql -e MYSQL_ROOT_PASSWORD=yourpassword -e MYSQL_DATABASE=buildaquery -d -p 3306:3306 mysql:8.0`
|
|
73
|
+
- `mysql-connector-python` (automatically installed as a dependency) - the MySQL adapter for Python.
|
|
74
|
+
- **Oracle database**: A running Oracle instance (Oracle XE is suitable for development).
|
|
75
|
+
- Example with Docker (Oracle XE): `docker run --name oracle-xe -e ORACLE_PASSWORD=yourpassword -e APP_USER=buildaquery -e APP_USER_PASSWORD=yourpassword -d -p 1521:1521 gvenzl/oracle-xe:21-slim`
|
|
76
|
+
- `oracledb` (automatically installed as a dependency) - the Oracle adapter for Python.
|
|
64
77
|
- `python-dotenv` (automatically installed as a dependency) - for loading environment variables from a `.env` file.
|
|
65
78
|
- **SQLite**: Uses Python's standard library `sqlite3` module.
|
|
66
79
|
- **SQLite Version**: SQLite 3.x via Python's `sqlite3` module (the exact SQLite version depends on your Python build; check `sqlite3.sqlite_version` at runtime).
|
|
@@ -84,6 +97,10 @@ DB_USER=postgres
|
|
|
84
97
|
DB_PASSWORD=yourpassword
|
|
85
98
|
```
|
|
86
99
|
|
|
100
|
+
For MySQL, you can use a connection URL directly in code (e.g., `mysql://user:password@host:3306/dbname`) or set your own environment variables and construct the URL similarly.
|
|
101
|
+
|
|
102
|
+
For Oracle, use a connection URL in the format `oracle://user:password@host:port/service_name` (for example: `oracle://buildaquery:password@127.0.0.1:1521/XEPDB1`).
|
|
103
|
+
|
|
87
104
|
### For Developers
|
|
88
105
|
|
|
89
106
|
Clone the repository and set up the development environment:
|
|
@@ -209,7 +226,87 @@ drop_stmt = DropStatementNode(table=users_table, if_exists=True)
|
|
|
209
226
|
executor.execute(drop_stmt)
|
|
210
227
|
```
|
|
211
228
|
|
|
212
|
-
|
|
229
|
+
### MySQL Quick Start
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from buildaquery.execution.mysql import MySqlExecutor
|
|
233
|
+
from buildaquery.abstract_syntax_tree.models import (
|
|
234
|
+
CreateStatementNode, TableNode, ColumnDefinitionNode,
|
|
235
|
+
InsertStatementNode, ColumnNode, LiteralNode,
|
|
236
|
+
SelectStatementNode, StarNode, DropStatementNode
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
executor = MySqlExecutor(connection_info="mysql://root:password@127.0.0.1:3306/buildaquery")
|
|
240
|
+
|
|
241
|
+
users_table = TableNode(name="users")
|
|
242
|
+
create_stmt = CreateStatementNode(
|
|
243
|
+
table=users_table,
|
|
244
|
+
columns=[
|
|
245
|
+
ColumnDefinitionNode(name="id", data_type="INT AUTO_INCREMENT", primary_key=True),
|
|
246
|
+
ColumnDefinitionNode(name="name", data_type="VARCHAR(255)", not_null=True),
|
|
247
|
+
ColumnDefinitionNode(name="age", data_type="INT")
|
|
248
|
+
]
|
|
249
|
+
)
|
|
250
|
+
executor.execute(create_stmt)
|
|
251
|
+
|
|
252
|
+
insert_stmt = InsertStatementNode(
|
|
253
|
+
table=users_table,
|
|
254
|
+
columns=[ColumnNode(name="name"), ColumnNode(name="age")],
|
|
255
|
+
values=[LiteralNode(value="Alice"), LiteralNode(value=30)]
|
|
256
|
+
)
|
|
257
|
+
executor.execute(insert_stmt)
|
|
258
|
+
|
|
259
|
+
select_stmt = SelectStatementNode(
|
|
260
|
+
select_list=[StarNode()],
|
|
261
|
+
from_table=users_table
|
|
262
|
+
)
|
|
263
|
+
print(executor.execute(select_stmt))
|
|
264
|
+
|
|
265
|
+
drop_stmt = DropStatementNode(table=users_table, if_exists=True)
|
|
266
|
+
executor.execute(drop_stmt)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Oracle Quick Start
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from buildaquery.execution.oracle import OracleExecutor
|
|
273
|
+
from buildaquery.abstract_syntax_tree.models import (
|
|
274
|
+
CreateStatementNode, TableNode, ColumnDefinitionNode,
|
|
275
|
+
InsertStatementNode, ColumnNode, LiteralNode,
|
|
276
|
+
SelectStatementNode, StarNode, DropStatementNode
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
executor = OracleExecutor(connection_info="oracle://buildaquery:password@127.0.0.1:1521/XEPDB1")
|
|
280
|
+
|
|
281
|
+
users_table = TableNode(name="users")
|
|
282
|
+
create_stmt = CreateStatementNode(
|
|
283
|
+
table=users_table,
|
|
284
|
+
columns=[
|
|
285
|
+
ColumnDefinitionNode(name="id", data_type="NUMBER", primary_key=True),
|
|
286
|
+
ColumnDefinitionNode(name="name", data_type="VARCHAR2(255)", not_null=True),
|
|
287
|
+
ColumnDefinitionNode(name="age", data_type="NUMBER")
|
|
288
|
+
]
|
|
289
|
+
)
|
|
290
|
+
executor.execute(create_stmt)
|
|
291
|
+
|
|
292
|
+
insert_stmt = InsertStatementNode(
|
|
293
|
+
table=users_table,
|
|
294
|
+
columns=[ColumnNode(name="id"), ColumnNode(name="name"), ColumnNode(name="age")],
|
|
295
|
+
values=[LiteralNode(value=1), LiteralNode(value="Alice"), LiteralNode(value=30)]
|
|
296
|
+
)
|
|
297
|
+
executor.execute(insert_stmt)
|
|
298
|
+
|
|
299
|
+
select_stmt = SelectStatementNode(
|
|
300
|
+
select_list=[StarNode()],
|
|
301
|
+
from_table=users_table
|
|
302
|
+
)
|
|
303
|
+
print(executor.execute(select_stmt))
|
|
304
|
+
|
|
305
|
+
drop_stmt = DropStatementNode(table=users_table, cascade=True)
|
|
306
|
+
executor.execute(drop_stmt)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
For more examples, see the `examples/` directory (including `examples/sample_mysql.py` and `examples/sample_oracle.py`).
|
|
213
310
|
|
|
214
311
|
## Development Setup
|
|
215
312
|
|
|
@@ -249,7 +346,7 @@ poetry run pytest buildaquery/tests
|
|
|
249
346
|
|
|
250
347
|
#### Integration Tests
|
|
251
348
|
|
|
252
|
-
Integration tests require
|
|
349
|
+
Integration tests require PostgreSQL, MySQL, and Oracle databases (and the respective drivers). Start the test databases using Docker:
|
|
253
350
|
|
|
254
351
|
```bash
|
|
255
352
|
docker-compose up -d
|
|
@@ -283,7 +380,7 @@ poetry run python examples/sample_query.py
|
|
|
283
380
|
|
|
284
381
|
- `buildaquery/abstract_syntax_tree/`: Defines query nodes and AST models.
|
|
285
382
|
- `buildaquery/traversal/`: Base classes for AST traversal (Visitor/Transformer pattern).
|
|
286
|
-
- `buildaquery/compiler/`: Dialect-specific SQL generation (PostgreSQL
|
|
383
|
+
- `buildaquery/compiler/`: Dialect-specific SQL generation (PostgreSQL, SQLite, MySQL, Oracle).
|
|
287
384
|
- `buildaquery/execution/`: Database connection and execution logic.
|
|
288
385
|
- `tests/`: Exhaustive unit and integration tests.
|
|
289
386
|
- `examples/`: Practical demonstrations of the library.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Build-a-Query
|
|
2
2
|
|
|
3
|
-
A Python-based query builder designed to represent, compile, and execute SQL queries using a dialect-agnostic Abstract Syntax Tree (AST). Supports PostgreSQL and
|
|
3
|
+
A Python-based query builder designed to represent, compile, and execute SQL queries using a dialect-agnostic Abstract Syntax Tree (AST). Supports PostgreSQL, SQLite, MySQL, and Oracle.
|
|
4
4
|
|
|
5
|
-
## Features
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
7
|
- **Dialect-Agnostic AST**: Build queries using high-level Python objects.
|
|
8
8
|
- **Full DML Support**: Create `SELECT`, `INSERT`, `UPDATE`, and `DELETE` statements.
|
|
@@ -11,7 +11,12 @@ A Python-based query builder designed to represent, compile, and execute SQL que
|
|
|
11
11
|
- **DDL Support**: Basic schema management with `CREATE TABLE` and `DROP TABLE`.
|
|
12
12
|
- **Visitor Pattern Traversal**: Extensible architecture for analysis and compilation.
|
|
13
13
|
- **Secure Compilation**: Automatic parameterization to prevent SQL injection.
|
|
14
|
-
- **Execution Layer**: Built-in support for executing compiled queries via `psycopg` (PostgreSQL) and the standard library `sqlite3` (SQLite).
|
|
14
|
+
- **Execution Layer**: Built-in support for executing compiled queries via `psycopg` (PostgreSQL), `mysql-connector-python` (MySQL), `oracledb` (Oracle), and the standard library `sqlite3` (SQLite).
|
|
15
|
+
|
|
16
|
+
## Dialect Notes
|
|
17
|
+
- MySQL does not support `INTERSECT` / `EXCEPT` or `DROP TABLE ... CASCADE` in this implementation (the compiler raises `ValueError`).
|
|
18
|
+
- SQLite does not support `DROP TABLE ... CASCADE` (the compiler raises `ValueError`).
|
|
19
|
+
- Oracle does not support `IF EXISTS` / `IF NOT EXISTS` in `DROP TABLE`/`CREATE TABLE` (the compiler raises `ValueError`), and `EXCEPT` is compiled as `MINUS`.
|
|
15
20
|
|
|
16
21
|
## Installation
|
|
17
22
|
|
|
@@ -28,13 +33,19 @@ pip install buildaquery
|
|
|
28
33
|
- **PostgreSQL database**: A running PostgreSQL instance (version 12+ recommended). You can set this up locally, via Docker, or use a cloud service.
|
|
29
34
|
- Example with Docker: `docker run --name postgres -e POSTGRES_PASSWORD=yourpassword -d -p 5432:5432 postgres:15`
|
|
30
35
|
- `psycopg` (automatically installed as a dependency) - the PostgreSQL adapter for Python.
|
|
36
|
+
- **MySQL database**: A running MySQL instance (version 8.0+ recommended).
|
|
37
|
+
- Example with Docker: `docker run --name mysql -e MYSQL_ROOT_PASSWORD=yourpassword -e MYSQL_DATABASE=buildaquery -d -p 3306:3306 mysql:8.0`
|
|
38
|
+
- `mysql-connector-python` (automatically installed as a dependency) - the MySQL adapter for Python.
|
|
39
|
+
- **Oracle database**: A running Oracle instance (Oracle XE is suitable for development).
|
|
40
|
+
- Example with Docker (Oracle XE): `docker run --name oracle-xe -e ORACLE_PASSWORD=yourpassword -e APP_USER=buildaquery -e APP_USER_PASSWORD=yourpassword -d -p 1521:1521 gvenzl/oracle-xe:21-slim`
|
|
41
|
+
- `oracledb` (automatically installed as a dependency) - the Oracle adapter for Python.
|
|
31
42
|
- `python-dotenv` (automatically installed as a dependency) - for loading environment variables from a `.env` file.
|
|
32
43
|
- **SQLite**: Uses Python's standard library `sqlite3` module.
|
|
33
44
|
- **SQLite Version**: SQLite 3.x via Python's `sqlite3` module (the exact SQLite version depends on your Python build; check `sqlite3.sqlite_version` at runtime).
|
|
34
45
|
|
|
35
46
|
### Environment Variables
|
|
36
47
|
|
|
37
|
-
To connect to your PostgreSQL database, set the following environment variables (or use a `.env` file with `python-dotenv`):
|
|
48
|
+
To connect to your PostgreSQL database, set the following environment variables (or use a `.env` file with `python-dotenv`):
|
|
38
49
|
|
|
39
50
|
- `DB_HOST`: PostgreSQL host (e.g., `localhost`)
|
|
40
51
|
- `DB_PORT`: PostgreSQL port (e.g., `5432`)
|
|
@@ -42,14 +53,18 @@ To connect to your PostgreSQL database, set the following environment variables
|
|
|
42
53
|
- `DB_USER`: Database username (e.g., `postgres`)
|
|
43
54
|
- `DB_PASSWORD`: Database password (e.g., `yourpassword`)
|
|
44
55
|
|
|
45
|
-
Example `.env` file:
|
|
46
|
-
```
|
|
47
|
-
DB_HOST=localhost
|
|
48
|
-
DB_PORT=5432
|
|
49
|
-
DB_NAME=buildaquery
|
|
50
|
-
DB_USER=postgres
|
|
51
|
-
DB_PASSWORD=yourpassword
|
|
52
|
-
```
|
|
56
|
+
Example `.env` file:
|
|
57
|
+
```
|
|
58
|
+
DB_HOST=localhost
|
|
59
|
+
DB_PORT=5432
|
|
60
|
+
DB_NAME=buildaquery
|
|
61
|
+
DB_USER=postgres
|
|
62
|
+
DB_PASSWORD=yourpassword
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
For MySQL, you can use a connection URL directly in code (e.g., `mysql://user:password@host:3306/dbname`) or set your own environment variables and construct the URL similarly.
|
|
66
|
+
|
|
67
|
+
For Oracle, use a connection URL in the format `oracle://user:password@host:port/service_name` (for example: `oracle://buildaquery:password@127.0.0.1:1521/XEPDB1`).
|
|
53
68
|
|
|
54
69
|
### For Developers
|
|
55
70
|
|
|
@@ -72,7 +87,7 @@ Activate the virtual environment:
|
|
|
72
87
|
poetry shell
|
|
73
88
|
```
|
|
74
89
|
|
|
75
|
-
## Quick Start
|
|
90
|
+
## Quick Start
|
|
76
91
|
|
|
77
92
|
Here's a simple example of creating a table, inserting data, querying it, and dropping the table. This example uses environment variables for database connection (see Environment Variables section above).
|
|
78
93
|
|
|
@@ -176,7 +191,87 @@ drop_stmt = DropStatementNode(table=users_table, if_exists=True)
|
|
|
176
191
|
executor.execute(drop_stmt)
|
|
177
192
|
```
|
|
178
193
|
|
|
179
|
-
|
|
194
|
+
### MySQL Quick Start
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from buildaquery.execution.mysql import MySqlExecutor
|
|
198
|
+
from buildaquery.abstract_syntax_tree.models import (
|
|
199
|
+
CreateStatementNode, TableNode, ColumnDefinitionNode,
|
|
200
|
+
InsertStatementNode, ColumnNode, LiteralNode,
|
|
201
|
+
SelectStatementNode, StarNode, DropStatementNode
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
executor = MySqlExecutor(connection_info="mysql://root:password@127.0.0.1:3306/buildaquery")
|
|
205
|
+
|
|
206
|
+
users_table = TableNode(name="users")
|
|
207
|
+
create_stmt = CreateStatementNode(
|
|
208
|
+
table=users_table,
|
|
209
|
+
columns=[
|
|
210
|
+
ColumnDefinitionNode(name="id", data_type="INT AUTO_INCREMENT", primary_key=True),
|
|
211
|
+
ColumnDefinitionNode(name="name", data_type="VARCHAR(255)", not_null=True),
|
|
212
|
+
ColumnDefinitionNode(name="age", data_type="INT")
|
|
213
|
+
]
|
|
214
|
+
)
|
|
215
|
+
executor.execute(create_stmt)
|
|
216
|
+
|
|
217
|
+
insert_stmt = InsertStatementNode(
|
|
218
|
+
table=users_table,
|
|
219
|
+
columns=[ColumnNode(name="name"), ColumnNode(name="age")],
|
|
220
|
+
values=[LiteralNode(value="Alice"), LiteralNode(value=30)]
|
|
221
|
+
)
|
|
222
|
+
executor.execute(insert_stmt)
|
|
223
|
+
|
|
224
|
+
select_stmt = SelectStatementNode(
|
|
225
|
+
select_list=[StarNode()],
|
|
226
|
+
from_table=users_table
|
|
227
|
+
)
|
|
228
|
+
print(executor.execute(select_stmt))
|
|
229
|
+
|
|
230
|
+
drop_stmt = DropStatementNode(table=users_table, if_exists=True)
|
|
231
|
+
executor.execute(drop_stmt)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Oracle Quick Start
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from buildaquery.execution.oracle import OracleExecutor
|
|
238
|
+
from buildaquery.abstract_syntax_tree.models import (
|
|
239
|
+
CreateStatementNode, TableNode, ColumnDefinitionNode,
|
|
240
|
+
InsertStatementNode, ColumnNode, LiteralNode,
|
|
241
|
+
SelectStatementNode, StarNode, DropStatementNode
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
executor = OracleExecutor(connection_info="oracle://buildaquery:password@127.0.0.1:1521/XEPDB1")
|
|
245
|
+
|
|
246
|
+
users_table = TableNode(name="users")
|
|
247
|
+
create_stmt = CreateStatementNode(
|
|
248
|
+
table=users_table,
|
|
249
|
+
columns=[
|
|
250
|
+
ColumnDefinitionNode(name="id", data_type="NUMBER", primary_key=True),
|
|
251
|
+
ColumnDefinitionNode(name="name", data_type="VARCHAR2(255)", not_null=True),
|
|
252
|
+
ColumnDefinitionNode(name="age", data_type="NUMBER")
|
|
253
|
+
]
|
|
254
|
+
)
|
|
255
|
+
executor.execute(create_stmt)
|
|
256
|
+
|
|
257
|
+
insert_stmt = InsertStatementNode(
|
|
258
|
+
table=users_table,
|
|
259
|
+
columns=[ColumnNode(name="id"), ColumnNode(name="name"), ColumnNode(name="age")],
|
|
260
|
+
values=[LiteralNode(value=1), LiteralNode(value="Alice"), LiteralNode(value=30)]
|
|
261
|
+
)
|
|
262
|
+
executor.execute(insert_stmt)
|
|
263
|
+
|
|
264
|
+
select_stmt = SelectStatementNode(
|
|
265
|
+
select_list=[StarNode()],
|
|
266
|
+
from_table=users_table
|
|
267
|
+
)
|
|
268
|
+
print(executor.execute(select_stmt))
|
|
269
|
+
|
|
270
|
+
drop_stmt = DropStatementNode(table=users_table, cascade=True)
|
|
271
|
+
executor.execute(drop_stmt)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
For more examples, see the `examples/` directory (including `examples/sample_mysql.py` and `examples/sample_oracle.py`).
|
|
180
275
|
|
|
181
276
|
## Development Setup
|
|
182
277
|
|
|
@@ -214,9 +309,9 @@ Run unit tests for all modules:
|
|
|
214
309
|
poetry run pytest buildaquery/tests
|
|
215
310
|
```
|
|
216
311
|
|
|
217
|
-
#### Integration Tests
|
|
218
|
-
|
|
219
|
-
Integration tests require
|
|
312
|
+
#### Integration Tests
|
|
313
|
+
|
|
314
|
+
Integration tests require PostgreSQL, MySQL, and Oracle databases (and the respective drivers). Start the test databases using Docker:
|
|
220
315
|
|
|
221
316
|
```bash
|
|
222
317
|
docker-compose up -d
|
|
@@ -250,9 +345,9 @@ poetry run python examples/sample_query.py
|
|
|
250
345
|
|
|
251
346
|
- `buildaquery/abstract_syntax_tree/`: Defines query nodes and AST models.
|
|
252
347
|
- `buildaquery/traversal/`: Base classes for AST traversal (Visitor/Transformer pattern).
|
|
253
|
-
- `buildaquery/compiler/`: Dialect-specific SQL generation (PostgreSQL
|
|
348
|
+
- `buildaquery/compiler/`: Dialect-specific SQL generation (PostgreSQL, SQLite, MySQL, Oracle).
|
|
254
349
|
- `buildaquery/execution/`: Database connection and execution logic.
|
|
255
|
-
- `tests/`: Exhaustive unit and integration tests.
|
|
350
|
+
- `tests/`: Exhaustive unit and integration tests.
|
|
256
351
|
- `examples/`: Practical demonstrations of the library.
|
|
257
352
|
- `scripts/`: Utility scripts for testing and maintenance.
|
|
258
353
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The `abstract_syntax_tree` module defines the data structures used to represent SQL queries as an Abstract Syntax Tree. This representation is agnostic of any specific SQL dialect and serves as the intermediate format that the query builder constructs and the compiler translates into SQL.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Dialect Notes**: The AST is dialect-agnostic and compilers target PostgreSQL, SQLite, and MySQL.
|
|
6
6
|
|
|
7
7
|
## Core Concepts
|
|
8
8
|
|
|
@@ -44,12 +44,35 @@ The initial implementation supports PostgreSQL.
|
|
|
44
44
|
- **Core AST Coverage**: Supports the same AST nodes as PostgreSQL where SQLite syntax allows.
|
|
45
45
|
- **TOP Translation**: Maps `TopClauseNode` to `LIMIT`, with optional implicit `ORDER BY`.
|
|
46
46
|
- **CASCADE Handling**: Raises a `ValueError` when `DropStatementNode.cascade=True`, because SQLite does not support `DROP TABLE ... CASCADE`.
|
|
47
|
+
|
|
48
|
+
### MySQL (`MySqlCompiler`)
|
|
49
|
+
|
|
50
|
+
#### Key Features:
|
|
51
|
+
- **`%s` Placeholders**: Uses MySQL-compatible parameter style.
|
|
52
|
+
- **Core AST Coverage**: Supports the same AST nodes as PostgreSQL where MySQL syntax allows.
|
|
53
|
+
- **TOP Translation**: Maps `TopClauseNode` to `LIMIT`, with optional implicit `ORDER BY`.
|
|
54
|
+
- **Set Operation Limits**: Raises a `ValueError` for `INTERSECT` and `EXCEPT` (unsupported in MySQL).
|
|
55
|
+
- **CASCADE Handling**: Raises a `ValueError` when `DropStatementNode.cascade=True`, because MySQL does not support `DROP TABLE ... CASCADE`.
|
|
56
|
+
|
|
57
|
+
### Oracle (`OracleCompiler`)
|
|
58
|
+
|
|
59
|
+
#### Key Features:
|
|
60
|
+
- **`:1` Placeholders**: Uses Oracle-style positional bind parameters.
|
|
61
|
+
- **LIMIT/OFFSET Translation**: Uses `OFFSET ... ROWS` and `FETCH FIRST ... ROWS ONLY`.
|
|
62
|
+
- **TOP Translation**: Maps `TopClauseNode` to `FETCH FIRST`, with optional implicit `ORDER BY`.
|
|
63
|
+
- **Set Operation Notes**:
|
|
64
|
+
- `EXCEPT` is translated to `MINUS`.
|
|
65
|
+
- `INTERSECT ALL` and `MINUS ALL` raise `ValueError`.
|
|
66
|
+
- **Table Aliases**: Emits `table alias` (Oracle does not allow `AS` for table aliases).
|
|
67
|
+
- **IF EXISTS/IF NOT EXISTS**: Raises `ValueError` for `DropStatementNode.if_exists=True` and `CreateStatementNode.if_not_exists=True`.
|
|
47
68
|
|
|
48
69
|
## Usage Example
|
|
49
70
|
|
|
50
71
|
```python
|
|
51
72
|
from buildaquery.compiler.postgres.postgres_compiler import PostgresCompiler
|
|
52
73
|
from buildaquery.compiler.sqlite.sqlite_compiler import SqliteCompiler
|
|
74
|
+
from buildaquery.compiler.mysql.mysql_compiler import MySqlCompiler
|
|
75
|
+
from buildaquery.compiler.oracle.oracle_compiler import OracleCompiler
|
|
53
76
|
|
|
54
77
|
compiler = PostgresCompiler()
|
|
55
78
|
compiled = compiler.compile(ast_root)
|
|
@@ -61,4 +84,14 @@ sqlite_compiler = SqliteCompiler()
|
|
|
61
84
|
compiled = sqlite_compiler.compile(ast_root)
|
|
62
85
|
print(compiled.sql) # "SELECT * FROM users WHERE id = ?"
|
|
63
86
|
print(compiled.params) # [123]
|
|
64
|
-
|
|
87
|
+
|
|
88
|
+
mysql_compiler = MySqlCompiler()
|
|
89
|
+
compiled = mysql_compiler.compile(ast_root)
|
|
90
|
+
print(compiled.sql) # "SELECT * FROM users WHERE id = %s"
|
|
91
|
+
print(compiled.params) # [123]
|
|
92
|
+
|
|
93
|
+
oracle_compiler = OracleCompiler()
|
|
94
|
+
compiled = oracle_compiler.compile(ast_root)
|
|
95
|
+
print(compiled.sql) # "SELECT * FROM users WHERE id = :1"
|
|
96
|
+
print(compiled.params) # [123]
|
|
97
|
+
```
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from buildaquery.compiler.postgres.postgres_compiler import PostgresCompiler
|
|
2
|
+
from buildaquery.compiler.sqlite.sqlite_compiler import SqliteCompiler
|
|
3
|
+
from buildaquery.compiler.mysql.mysql_compiler import MySqlCompiler
|
|
4
|
+
from buildaquery.compiler.oracle.oracle_compiler import OracleCompiler
|
|
5
|
+
|
|
6
|
+
__all__ = ["PostgresCompiler", "SqliteCompiler", "MySqlCompiler", "OracleCompiler"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# MySQL Compiler
|
|
2
|
+
|
|
3
|
+
The `MySqlCompiler` translates the AST into MySQL-compatible SQL with `%s` placeholders.
|
|
4
|
+
|
|
5
|
+
## Notes
|
|
6
|
+
|
|
7
|
+
- **Placeholders**: Uses `%s` for parameters (compatible with `mysql-connector-python`).
|
|
8
|
+
- **TOP Translation**: `TopClauseNode` is translated into `LIMIT`, with optional implicit `ORDER BY`.
|
|
9
|
+
- **Unsupported Operations**:
|
|
10
|
+
- `INTERSECT` and `EXCEPT` raise `ValueError` (MySQL does not support them).
|
|
11
|
+
- `DROP TABLE ... CASCADE` raises `ValueError`.
|
|
12
|
+
|
|
13
|
+
## Example
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from buildaquery.compiler.mysql.mysql_compiler import MySqlCompiler
|
|
17
|
+
from buildaquery.abstract_syntax_tree.models import SelectStatementNode, StarNode, TableNode
|
|
18
|
+
|
|
19
|
+
compiler = MySqlCompiler()
|
|
20
|
+
query = SelectStatementNode(select_list=[StarNode()], from_table=TableNode(name="users"))
|
|
21
|
+
compiled = compiler.compile(query)
|
|
22
|
+
|
|
23
|
+
print(compiled.sql) # SELECT * FROM users
|
|
24
|
+
print(compiled.params) # []
|
|
25
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Intentionally left empty to mark mysql as a package.
|