CONNX Demo is a small Flask application that turns natural-language questions into SQL, runs the generated SQL against a CONNX-accessible database, and displays the results in a browser.
The application currently uses Azure OpenAI for SQL generation and ODBC/CONNX for database access. It also includes safety checks so model-generated SQL is validated before it reaches the database.
- A user enters a question in the web form, for example: "show employee ids".
- The app sends the question plus the allowed database schema to Azure OpenAI.
- Azure OpenAI returns SQL.
- The app validates that the SQL is a single read-only
SELECTagainst allowed tables. - The app executes the query through ODBC.
- The result page shows the generated SQL, returned rows, timing, and token usage.
There is also a JSON API endpoint:
POST /api/natural_language_query
with a JSON body like:
{
"question": "show employee ids"
}- Show a list of employees that own GREEN coloured vehicles.
Questions work best when they use table concepts and values present in
table_definitions.json. The model is instructed not to invent unknown tables,
columns, or relationships.
.
|-- app.py # Flask routes, web form, JSON API
|-- config.py # Runtime configuration loaded from environment variables
|-- sql_functions.py # Azure OpenAI, SQL validation, DB execution helpers
|-- table_definitions.json # Allowed tables/columns used in prompts and SQL safety checks
|-- templates/ # Flask HTML templates
|-- tests/ # pytest tests
|-- scripts/ # Explicit API, ODBC, JDBC, and environment smoke tests
|-- backups/ # Archived old scripts/templates, ignored by git
|-- requirements.txt # Python dependencies
|-- .env.example # Safe example environment configuration
`-- TODO.md # Cleanup and modernization checklist
Flask is the web framework. In this project, app.py defines:
/for the browser UI./api/natural_language_queryfor JSON clients.- Basic error handling.
The module exposes create_app(...) for tests and other WSGI-compatible
runtimes. Running python app.py creates the default app from .env and starts
Flask's development server.
Flask templates live in templates/. The route functions call render_template(...) to return HTML.
Secrets and machine-specific settings should not be hardcoded in Python files. This app reads settings from .env using python-dotenv.
Use .env.example as the template, then create your own .env.
Do not commit .env. It is ignored by .gitignore.
For Azure OpenAI, the value passed to the API is the deployment name, not necessarily the base model name.
Find it in Azure AI Foundry / Azure OpenAI Studio under your resource's deployments.
Example:
AZURE_OPENAI_DEPLOYMENT=gpt-5-nano-2Generated SQL is treated as untrusted. The app validates it in two places:
- Before route handlers execute the query.
- Again inside
execute_sql_query(...).
Current rules are intentionally conservative:
- Only one SQL statement.
- Must be a
SELECT. - Blocks write/admin keywords such as
INSERT,UPDATE,DELETE,DROP, andEXEC. - Only allows tables listed in
table_definitions.json.
- Python 3.10 or newer.
- Access to an Azure OpenAI resource and a deployed model.
- A CONNX/ODBC DSN or SQL Server-compatible ODBC connection.
- A matching 64-bit ODBC driver when using 64-bit Python.
On Windows, list the ODBC drivers visible to Python with:
python -c "import pyodbc; print(pyodbc.drivers())"From the project folder:
python -m venv .venv
.venv\Scripts\Activate.ps1If PowerShell blocks script execution, you may need to adjust your execution policy for the current user.
python -m pip install -r requirements.txtCopy .env.example to .env:
Copy-Item .env.example .envThen edit .env.
Required Flask setting:
SECRET_KEY=replace-with-a-random-secretGenerate a local value with:
python -c "import secrets; print(secrets.token_urlsafe(32))"Put the generated value in .env as SECRET_KEY. Do not commit .env, and do
not leave SECRET_KEY set to the placeholder value.
Optional Flask runtime settings (these defaults are safe for local use):
FLASK_HOST=127.0.0.1
FLASK_PORT=4999
FLASK_DEBUG=false
TABLE_DEFINITIONS_PATH=table_definitions.jsonSet FLASK_HOST=0.0.0.0 only when the development server intentionally needs
to accept connections from other machines. Do not expose Flask's development
server directly to the internet.
Required Azure OpenAI settings:
AZURE_OPENAI_API_KEY=replace-with-your-azure-openai-key
AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/
AZURE_OPENAI_DEPLOYMENT=replace-with-your-azure-deployment-name
AZURE_OPENAI_API_VERSION=2024-02-15-preview
AZURE_OPENAI_MAX_COMPLETION_TOKENS=500The endpoint must be the Azure OpenAI resource endpoint and should resolve from the machine running Flask.
The Azure API key, endpoint, and deployment are validated when SQL generation is requested. The app can start without contacting Azure, but a query cannot succeed until these settings and network access are valid.
The database connection is built in this order:
DB_CONNECTION_STRINGDB_DSNplus optionalDB_USER/DB_PASSWORD- Direct SQL Server settings:
DB_SERVER,DB_NAME,DB_USER,DB_PASSWORD
Preferred CONNX DSN setup:
DB_DSN=CONNXEmployeeMfDemo64
DB_USER=replace-with-db-user
DB_PASSWORD=replace-with-db-password
CONNX_DISABLE_SERVER_TRACE=trueWhen the connection string appears to be a CONNX connection, the app runs the
CONNX-documented select 1 {fn disableservertrace} statement before executing
the generated query. This keeps CONNX server-side tracing disabled for the
active connection and avoids noisy driver log attempts such as \CONNX.LOG.
Set CONNX_DISABLE_SERVER_TRACE=false only when you intentionally need CONNX
trace output while troubleshooting.
If CONNX still prints a log-file warning during the initial connection
handshake, check the local CONNX Configuration Manager settings. The installed
CONNX help says DEBUG=0 disables CONNX debug tracing and LSNDEBUG=0
disables listener tracing; nonzero values enable progressively more logging.
Alternative full connection string:
DB_CONNECTION_STRING=DSN=CONNXEmployeeMfDemo64;UID=replace-with-db-user;PWD=replace-with-db-passwordAlternative direct SQL Server setup:
DB_SERVER=localhost
DB_NAME=DemoDB
DB_USER=replace-with-db-user
DB_PASSWORD=replace-with-db-passwordpython app.pyWith the default settings, open:
http://localhost:4999
After changing .env, restart Flask. Environment variables are loaded at startup.
For a quick first-run check:
- Open
http://localhost:4999and confirm the form and schema appear. - Submit one of the sample questions.
- Confirm the result page shows generated SQL and database rows.
The first step needs only a valid SECRET_KEY and readable schema file. The
submitted query additionally needs working Azure OpenAI and database settings.
From PowerShell, after starting the app:
$body = @{ question = "Show a list of employees that own GREEN coloured vehicles" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri http://localhost:4999/api/natural_language_query -ContentType application/json -Body $bodyA successful response contains the generated sql_query, token and timing
metadata, and results as an array of objects. Invalid input or failed query
generation returns an error field. The API executes the generated query; it
is not a SQL-preview-only endpoint.
Run the self-contained unit tests with:
python -m pytest tests\test_app_config.py tests\test_api_routes.py tests\test_db_config.py tests\test_index_template.py tests\test_openai_sql_generation.py tests\test_sql_safety.py tests\test_connx.pyThese tests mock Azure OpenAI and database calls and should not require live
services. The explicit scripts under scripts/ are not collected by pytest and
may require a running app, live database, or optional JDBC dependencies.
Run a live API smoke test after starting the app with:
python scripts\api_smoke_test.pyRun ODBC and JDBC connection checks explicitly with:
python scripts\odbc_smoke_test.py
python -m pip install JayDeBeApi JPype1 # Optional; JDBC test only
python scripts\jdbc_smoke_test.pyThe app only allows tables and columns listed in table_definitions.json.
When adding or changing tables:
- Update
table_definitions.json. - Make sure table names match the fully qualified names used by the database.
- Add or update tests if query validation behavior changes.
- Restart Flask.
To use a schema file elsewhere, set TABLE_DEFINITIONS_PATH in .env. Each
entry must provide a fully qualified table_name and a columns object. A
table entry may also provide a relationship string used to guide join
generation.
The schema file is used for both prompting Azure OpenAI and validating generated SQL.
Check .env for:
AZURE_OPENAI_API_KEYAZURE_OPENAI_ENDPOINTAZURE_OPENAI_DEPLOYMENTAZURE_OPENAI_API_VERSION
Make sure AZURE_OPENAI_DEPLOYMENT is the deployment name from Azure, not a placeholder.
Verify the endpoint:
AZURE_OPENAI_ENDPOINT=https://your-resource-name.openai.azure.com/If the host does not resolve, the resource name may be wrong or network/DNS access may be blocked.
Some Azure models require max_completion_tokens instead of max_tokens. The app uses max_completion_tokens by default and has a fallback for older clients.
Tune output length with:
AZURE_OPENAI_MAX_COMPLETION_TOKENS=500Check the database connection precedence:
- If
DB_CONNECTION_STRINGis set, it wins. - Else, if
DB_DSNis set, DSN mode is used. - Else, direct SQL Server settings are used.
If the app tries the wrong database, check for leftover settings in .env.
Also confirm that the DSN is visible to the same Windows account and Python
architecture used to run the app.
- If the error mentions
SECRET_KEY, replace the placeholder with a generated value. - If the schema file cannot be found, start the app from the project directory or set an absolute
TABLE_DEFINITIONS_PATH. - If
FLASK_PORTis invalid or already in use, choose another numeric port and use that port in the browser/API URL.
The SQL validator is conservative. It rejects:
- Multiple statements.
- Non-
SELECTstatements. - Unknown tables.
- Write/admin keywords.
If you need CTEs, derived tables, quoted identifiers, or more SQL Server-specific syntax, update the validator and tests together.
- Do not commit
.env. - Rotate exposed keys if they were ever pasted into logs, chat, screenshots, or commits.
- Generated SQL must remain validated before execution.
- Run database users with the least privileges possible. Read-only credentials are strongly preferred for this app.
- Avoid logging prompts, connection strings, API keys, raw provider errors, or database driver messages.
The app is functional, and remaining maintenance work is tracked in TODO.md. Current themes include:
- Consolidating templates and moving inline frontend assets into static files.
- Converting or isolating legacy script-style integration tests.
- Expanding route-level error and result coverage.
- Finishing prompt-construction cleanup and repository hygiene.