# How the Dify DB-Query Plugin Parses and Validates SQL Queries Using sqlparse

> Learn how the Dify DB-Query plugin uses sqlparse to parse and validate SQL queries. It tokenizes SQL, enforces single statements, and restricts operations to SELECT before database connection.

- Repository: [Junjie.M/dify-plugin-tools-dbquery](https://github.com/junjiem/dify-plugin-tools-dbquery)
- Tags: internals
- Published: 2026-03-05

---

**The Dify DB-Query plugin leverages the `sqlparse` library to tokenize SQL strings, enforce single-statement execution, and restrict operations to SELECT queries before any database connection is opened.**

The `junjiem/dify-plugin-tools-dbquery` repository provides a secure database query tool for the Dify AI platform. To prevent injection attacks and unauthorized data modifications, the plugin implements a strict validation pipeline that parses and validates SQL queries using `sqlparse`. This validation occurs in both the standard and pre-authentication variants of the tool.

## SQL Validation Pipeline in SqlQueryTool

The validation logic resides in [`db_query/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py) (standard mode) and [`db_query_pre_auth/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query_pre_auth/tools/sql_query.py) (pre-authentication mode). Both implementations follow an identical three-step validation process using the `sqlparse` library.

### Importing and Parsing SQL Statements

At the top of the module, the plugin imports `sqlparse` to handle tokenization:

```python
import sqlparse

```

Source: [line 4](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py#L4)

When a user submits a query, the `_invoke` method parses the raw string into statement objects:

```python
statements = sqlparse.parse(query_sql)

```

The `sqlparse.parse` function returns a tuple of Statement objects, enabling structured analysis of the SQL syntax without relying on fragile regular expressions. Source: [line 40](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py#L40)

### Enforcing Single-Statement Execution

To prevent batch injection attacks, the plugin strictly enforces single-statement execution. After parsing, it validates the statement count:

```python
if len(statements) != 1:
    raise ValueError("Only a single query SQL can be filled")

```

If the input contains multiple statements separated by semicolons, the tool raises a `ValueError` immediately, preventing the execution of chained commands. Source: [line 42](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py#L42)

### Restricting Operations to SELECT Statements

The plugin restricts all queries to read-only SELECT operations. It inspects the statement type using `sqlparse`'s built-in classification:

```python
statement = statements[0]
if statement.get_type() != 'SELECT':
    raise ValueError("Query SQL can only be a single SELECT statement")

```

This check occurs at [lines 44-45](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py#L44-L45), ensuring that INSERT, UPDATE, DELETE, or DDL commands are blocked before the plugin establishes a database connection.

## Implementation Across Standard and Pre-Auth Variants

The validation logic is duplicated across both tool variants to maintain consistent security guarantees. The standard variant in [`db_query/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py) receives credentials via tool parameters, while the pre-auth variant in [`db_query_pre_auth/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query_pre_auth/tools/sql_query.py) reads credentials from `self.runtime.credentials`. Despite this difference in credential sourcing, both use identical `sqlparse` validation chains at lines 40-45.

## Practical Code Examples

### Standard Mode with Inline Credentials

```python
from db_query.tools.sql_query import SqlQueryTool

tool = SqlQueryTool()
params = {
    "db_type": "postgresql",
    "db_host": "localhost",
    "db_port": 5432,
    "db_username": "admin",
    "db_password": "secret",
    "db_name": "sales",
    "query_sql": "SELECT id, amount FROM orders WHERE status = 'paid'",
    "output_format": "markdown",
}

for msg in tool._invoke(params):
    print(msg.text)

```

### Pre-Auth Mode with Runtime Credentials

```python
from db_query_pre_auth.tools.sql_query import SqlQueryTool

runtime = type("Runtime", (), {"credentials": {
    "db_type": "mysql",
    "db_host": "db.example.com",
    "db_port": "3306",
    "db_username": "user",
    "db_password": "pwd",
    "db_name": "inventory",
    "db_properties": "",
}})()

tool = SqlQueryTool()
tool.runtime = runtime
params = {"query_sql": "SELECT * FROM products", "output_format": "json"}

for msg in tool._invoke(params):
    print(msg.json)

```

Both examples trigger the same validation pipeline. Attempting to execute `INSERT INTO products VALUES (...)` or `SELECT * FROM users; DROP TABLE users;` raises a `ValueError` before any database connection opens.

## Why sqlparse for SQL Validation

Using `sqlparse` instead of regex-based validation provides three critical advantages for the Dify plugin:

- **Robust Tokenisation**: Handles quoted identifiers, SQL comments, and complex whitespace without false positives that often plague pattern-matching approaches.
- **Accurate Statement Classification**: The `Statement.get_type()` method reliably distinguishes between SELECT, INSERT, UPDATE, and other operation types based on the parsed token stream.
- **Extensibility**: The parsed Statement object enables future enhancements such as table name whitelisting or column-level permission checks without rewriting the validation engine.

## Summary

- The Dify DB-Query plugin uses `sqlparse` to parse and validate SQL queries before database execution.
- Validation occurs in [`db_query/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/sql_query.py) and [`db_query_pre_auth/tools/sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query_pre_auth/tools/sql_query.py) at lines 40-45.
- The plugin enforces single-statement execution by checking `len(statements) == 1`.
- Only SELECT statements are permitted; `statement.get_type() != 'SELECT'` triggers a `ValueError`.
- Both standard and pre-auth variants share identical validation logic, differing only in credential sourcing from tool parameters versus runtime credentials.

## Frequently Asked Questions

### What happens if I submit multiple SQL statements separated by semicolons?

The plugin raises a `ValueError` with the message "Only a single query SQL can be filled" before establishing any database connection. This prevents batch injection attacks where malicious users attempt to append DROP or DELETE commands after a legitimate SELECT statement.

### Can I use CTEs (WITH clauses) or subqueries with this plugin?

Yes. As long as the top-level statement type evaluates to `'SELECT'` through `sqlparse.parse()`, Common Table Expressions and subqueries are fully supported. The validation checks the outer statement classification, not the internal complexity of the query structure.

### Where does the actual database execution happen after validation?

After validation in [`sql_query.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/sql_query.py), the plugin calls helper functions in [`db_query/tools/db_util.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query/tools/db_util.py) (or [`db_query_pre_auth/tools/db_util.py`](https://github.com/junjiem/dify-plugin-tools-dbquery/blob/main/db_query_pre_auth/tools/db_util.py) for pre-auth mode) to create the SQLAlchemy engine and execute the sanitized query against the target database.

### Is sqlparse sufficient to prevent all SQL injection attacks?

While `sqlparse` provides robust parsing and statement classification for the Dify plugin, it primarily serves as a structural validator ensuring only single SELECT statements pass. The plugin relies on parameterized queries and proper database user permissions to provide defense in depth against injection attacks.