Skip to main content

A Python-based query builder for PostgreSQL, SQLite, and MySQL.

Project description

Build-a-Query

A Python-based query builder designed to represent, compile, and execute SQL queries using a dialect-agnostic Abstract Syntax Tree (AST). Supports PostgreSQL, SQLite, and MySQL.

Features

  • Dialect-Agnostic AST: Build queries using high-level Python objects.
  • Full DML Support: Create SELECT, INSERT, UPDATE, and DELETE statements.
  • Advanced Querying: Support for CTEs (WITH), Subqueries, Set Operations (UNION, INTERSECT, EXCEPT), and Window Functions (OVER).
  • Rich Expression Logic: Includes CASE expressions, IN, BETWEEN, and type casting.
  • DDL Support: Basic schema management with CREATE TABLE and DROP TABLE.
  • Visitor Pattern Traversal: Extensible architecture for analysis and compilation.
  • Secure Compilation: Automatic parameterization to prevent SQL injection.
  • Execution Layer: Built-in support for executing compiled queries via psycopg (PostgreSQL), mysql-connector-python (MySQL), and the standard library sqlite3 (SQLite).

Dialect Notes

  • MySQL does not support INTERSECT / EXCEPT or DROP TABLE ... CASCADE in this implementation (the compiler raises ValueError).
  • SQLite does not support DROP TABLE ... CASCADE (the compiler raises ValueError).

Installation

For Users

Install Build-a-Query via pip:

pip install buildaquery

Requirements:

  • Python 3.12+
  • PostgreSQL database: A running PostgreSQL instance (version 12+ recommended). You can set this up locally, via Docker, or use a cloud service.
    • Example with Docker: docker run --name postgres -e POSTGRES_PASSWORD=yourpassword -d -p 5432:5432 postgres:15
  • psycopg (automatically installed as a dependency) - the PostgreSQL adapter for Python.
  • MySQL database: A running MySQL instance (version 8.0+ recommended).
    • Example with Docker: docker run --name mysql -e MYSQL_ROOT_PASSWORD=yourpassword -e MYSQL_DATABASE=buildaquery -d -p 3306:3306 mysql:8.0
  • mysql-connector-python (automatically installed as a dependency) - the MySQL adapter for Python.
  • python-dotenv (automatically installed as a dependency) - for loading environment variables from a .env file.
  • SQLite: Uses Python's standard library sqlite3 module.
    • 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).

Environment Variables

To connect to your PostgreSQL database, set the following environment variables (or use a .env file with python-dotenv):

  • DB_HOST: PostgreSQL host (e.g., localhost)
  • DB_PORT: PostgreSQL port (e.g., 5432)
  • DB_NAME: Database name (e.g., mydatabase)
  • DB_USER: Database username (e.g., postgres)
  • DB_PASSWORD: Database password (e.g., yourpassword)

Example .env file:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=buildaquery
DB_USER=postgres
DB_PASSWORD=yourpassword

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.

For Developers

Clone the repository and set up the development environment:

git clone https://github.com/yourusername/buildaquery.git
cd buildaquery

Install dependencies using Poetry:

poetry install

Activate the virtual environment:

poetry shell

Quick Start

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).

from dotenv import load_dotenv
import os
from buildaquery.execution.postgres import PostgresExecutor
from buildaquery.abstract_syntax_tree.models import (
    CreateStatementNode, TableNode, ColumnDefinitionNode,
    InsertStatementNode, ColumnNode, LiteralNode,
    SelectStatementNode, StarNode, DropStatementNode
)

# Load environment variables
load_dotenv()

# Build connection string from environment variables
db_host = os.getenv('DB_HOST')
db_port = os.getenv('DB_PORT')
db_name = os.getenv('DB_NAME')
db_user = os.getenv('DB_USER')
db_password = os.getenv('DB_PASSWORD')

connection_string = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}"

# Set up executor with your PostgreSQL connection
executor = PostgresExecutor(connection_info=connection_string)

# Define table
users_table = TableNode(name="users")

# Create table
create_stmt = CreateStatementNode(
    table=users_table,
    columns=[
        ColumnDefinitionNode(name="id", data_type="SERIAL", primary_key=True),
        ColumnDefinitionNode(name="name", data_type="TEXT", not_null=True),
        ColumnDefinitionNode(name="age", data_type="INTEGER")
    ]
)
executor.execute(create_stmt)

# Insert data
insert_stmt = InsertStatementNode(
    table=users_table,
    columns=[ColumnNode(name="name"), ColumnNode(name="age")],
    values=[LiteralNode(value="Alice"), LiteralNode(value=30)]
)
executor.execute(insert_stmt)

# Query data
select_stmt = SelectStatementNode(
    select_list=[StarNode()],  # SELECT *
    from_table=users_table
)
results = executor.execute(select_stmt)
print(results)  # [(1, 'Alice', 30)]

# Drop table
drop_stmt = DropStatementNode(table=users_table, if_exists=True)
executor.execute(drop_stmt)

SQLite Quick Start

from buildaquery.execution.sqlite import SqliteExecutor
from buildaquery.abstract_syntax_tree.models import (
    CreateStatementNode, TableNode, ColumnDefinitionNode,
    InsertStatementNode, ColumnNode, LiteralNode,
    SelectStatementNode, StarNode, DropStatementNode
)

executor = SqliteExecutor(connection_info="static/test-sqlite/db.sqlite")

users_table = TableNode(name="users")
create_stmt = CreateStatementNode(
    table=users_table,
    columns=[
        ColumnDefinitionNode(name="id", data_type="INTEGER", primary_key=True),
        ColumnDefinitionNode(name="name", data_type="TEXT", not_null=True),
        ColumnDefinitionNode(name="age", data_type="INTEGER")
    ]
)
executor.execute(create_stmt)

insert_stmt = InsertStatementNode(
    table=users_table,
    columns=[ColumnNode(name="name"), ColumnNode(name="age")],
    values=[LiteralNode(value="Alice"), LiteralNode(value=30)]
)
executor.execute(insert_stmt)

select_stmt = SelectStatementNode(
    select_list=[StarNode()],
    from_table=users_table
)
print(executor.execute(select_stmt))

drop_stmt = DropStatementNode(table=users_table, if_exists=True)
executor.execute(drop_stmt)

MySQL Quick Start

from buildaquery.execution.mysql import MySqlExecutor
from buildaquery.abstract_syntax_tree.models import (
    CreateStatementNode, TableNode, ColumnDefinitionNode,
    InsertStatementNode, ColumnNode, LiteralNode,
    SelectStatementNode, StarNode, DropStatementNode
)

executor = MySqlExecutor(connection_info="mysql://root:password@127.0.0.1:3306/buildaquery")

users_table = TableNode(name="users")
create_stmt = CreateStatementNode(
    table=users_table,
    columns=[
        ColumnDefinitionNode(name="id", data_type="INT AUTO_INCREMENT", primary_key=True),
        ColumnDefinitionNode(name="name", data_type="VARCHAR(255)", not_null=True),
        ColumnDefinitionNode(name="age", data_type="INT")
    ]
)
executor.execute(create_stmt)

insert_stmt = InsertStatementNode(
    table=users_table,
    columns=[ColumnNode(name="name"), ColumnNode(name="age")],
    values=[LiteralNode(value="Alice"), LiteralNode(value=30)]
)
executor.execute(insert_stmt)

select_stmt = SelectStatementNode(
    select_list=[StarNode()],
    from_table=users_table
)
print(executor.execute(select_stmt))

drop_stmt = DropStatementNode(table=users_table, if_exists=True)
executor.execute(drop_stmt)

For more examples, see the examples/ directory (including examples/sample_mysql.py).

Development Setup

Prerequisites

  • Python 3.12+
  • Poetry (for dependency management)
  • Docker (for running integration tests)

Setting Up the Environment

  1. Clone the repository:

    git clone https://github.com/yourusername/buildaquery.git
    cd buildaquery
    
  2. Install dependencies:

    poetry install
    
  3. Activate the virtual environment:

    poetry shell
    

Running Tests

Unit Tests

Run unit tests for all modules:

poetry run pytest buildaquery/tests

Integration Tests

Integration tests require PostgreSQL and MySQL databases (and the mysql-connector-python driver). Start the test databases using Docker:

docker-compose up -d

Then run integration tests:

poetry run pytest tests

SQLite integration tests use the file-based database at static/test-sqlite/db.sqlite.

All Tests

Run all tests (unit and integration):

poetry run all-tests

Running Examples

Execute the sample script:

poetry run python examples/sample_query.py

Project Structure

  • buildaquery/abstract_syntax_tree/: Defines query nodes and AST models.
  • buildaquery/traversal/: Base classes for AST traversal (Visitor/Transformer pattern).
  • buildaquery/compiler/: Dialect-specific SQL generation (PostgreSQL, SQLite, MySQL).
  • buildaquery/execution/: Database connection and execution logic.
  • tests/: Exhaustive unit and integration tests.
  • examples/: Practical demonstrations of the library.
  • scripts/: Utility scripts for testing and maintenance.

Contributing

Contributions are welcome! Please see the contributing guidelines for more information.

License

This project is licensed under the MIT License - see the LICENSE.txt file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

buildaquery-0.3.0.tar.gz (24.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

buildaquery-0.3.0-py3-none-any.whl (42.5 kB view details)

Uploaded Python 3

File details

Details for the file buildaquery-0.3.0.tar.gz.

File metadata

  • Download URL: buildaquery-0.3.0.tar.gz
  • Upload date:
  • Size: 24.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.12.8 Windows/11

File hashes

Hashes for buildaquery-0.3.0.tar.gz
Algorithm Hash digest
SHA256 bd9d4f0a512f77dc3f2e05aa17552249b0f75b8c8a39fab275acd77240737a81
MD5 3313c968f50ae1fb95035ca998894bcf
BLAKE2b-256 1e6aa7f05ed070f87a68e9129f6ddd5773b9967cbadb3c4a67d329199ec5cd4d

See more details on using hashes here.

File details

Details for the file buildaquery-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: buildaquery-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 42.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.12.8 Windows/11

File hashes

Hashes for buildaquery-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ebbfed0fa37a776ebc54f18ae1c052da730a36fbb814b0239dfb85b9bb283834
MD5 86243f1d044f352b1dac3f2862bcd2fa
BLAKE2b-256 24c6c6b0987aeb5575ff376037b0d8b687e5976dd7805535cbb7efb399cf6fcc

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page