Tech Articles


Building an AI Text-to-SQL Translator in PowerBuilder (Text to SQL)


Introduction

Artificial Intelligence can greatly facilitate access to data, allowing non-technical users to query information naturally. PowerBuilder developers can integrate "Text-to-SQL" capabilities directly into their applications, enabling a dynamic query system powered by Large Language Models (LLMs).

This article demonstrates how to build a smart client in PowerBuilder (w_text_to_sql) that allows users to select a view structure, ask a question in natural language, and receive the results in a DataWindow, interacting in real-time with Google's Gemini 2.5 Flash Lite model via its REST API.

What is Text-to-SQL?

It is the process of converting queries formulated in natural language into structured SQL commands. In our implementation, we follow these steps:

  1. Context: The LLM is provided with schemas, scripts, or SQL views (DDL), along with their exact columns available in the database.
  2. Generation: The LLM acts as a Senior DBA and generates an SQL statement (T-SQL) based on the user's question, mapping keywords to the correct columns.
  3. Execution: PowerBuilder validates the SQL, processes it, and dynamically executes it using the native DataWindows functionality.

General Architecture

The system operates directly between the PowerBuilder client and the cloud API, without the need for an intermediate backend to process prompts.


┌───────────────────────────────────────────────────────┐
│ PowerBuilder Client (PB 2022+)                        │
│ ┌───────────────────────────────────────────────────┐ │
│ │ w_text_to_sql                                     │ │
│ │ (Main Window)                                     │ │
│ │  - SQL script selection (View)                    │ │
│ │  - Column validation (SyntaxFromSQL)              │ │
│ │  - Prompt Construction (Rules, DDL)               │ │
│ │  - Request Generation (JsonGenerator)             │ │
│ │  - HTTP Request (HTTPClient)                      │ │
│ │  - Response Parsing (nvo_json)                    │ │
│ │  - Dynamic DataWindow Creation                    │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────│─────────────────────────────┘
                          │ HTTP REST / JSON
                          ▼
┌──────────────────────────────────────────────────────┐
│ Google AI Studio API                                 │
│ (Gemini 2.5 Flash Lite Model)                        │
└──────────────────────────────────────────────────────┘

Main Components

  • w_text_to_sql — Main window containing all the business logic, user interface (questions and grids), and orchestrating the HTTP communication.
  • JsonGenerator — Native PowerBuilder object used to serialize the DDL and the prompt without corrupting the JSON structure.
  • HTTPClient — Responsible for secure network communication to the Google Gemini API.
  • nvo_json — Non-visual object for natively parsing the structured response from the AI.
  • Dynamic DataWindow — To render on screen the results of the generated SQL using SyntaxFromSQL.

Prerequisites

Before starting, ensure you have the following elements installed/configured:

  1. PowerBuilder 2022 R3 or later (with support for the JsonGenerator and HTTPClient classes).
  2. A database connected via ODBC (Sybase/SAP SQL Anywhere or SQL Server) so that the SyntaxFromSQL function can validate and execute the queries.
  3. Google AI Studio API Key to authenticate requests to the Gemini model.

Implementation

Step 1: Reading and Analyzing the SQL View

In order for the AI to know what structure it can query, the system reads a .sql script provided by the user. The PowerBuilder application intelligently extracts the view name from the script by analyzing the text.


// Open the file and search for "CREATE VIEW" in each line
li_file_text = FileOpen(ls_ruta_archivo, TextMode!, Read!, Shared!)
IF li_file_text > 0 THEN
    DO WHILE FileReadEx(li_file_text, ls_linea) > 0
        IF Pos(Upper(ls_linea), "CREATE VIEW ") > 0 THEN
            // Logic to extract ls_view_name by skipping spaces
            // ...
            EXIT
        END IF
    LOOP
    FileClose(li_file_text)
END IF

Step 2: Early Column Validation

To narrow down the AI's margin of error (hallucinations), PowerBuilder queries the database in real-time to find out the real columns that the view has, executing a blind DataWindow:


// We execute SELECT TOP 0 * to get metadata without bringing data
ls_syntax_temp = SQLCA.SyntaxFromSQL("SELECT TOP 0 * FROM " + ls_view_name, "Style(Type=Grid)", ls_errors_temp)

IF Len(ls_errors_temp) = 0 THEN
    lds_temp.Create(ls_syntax_temp, ls_errors_temp)
    li_total_cols = Integer(lds_temp.Describe("DataWindow.Column.Count"))
    FOR li_col = 1 TO li_total_cols
        ls_col_name = lds_temp.Describe("#" + String(li_col) + ".Name")
        ls_columns += ls_col_name + ", "
    NEXT
END IF

Step 3: Prompt Engineering (System Prompt)

A rigorous prompt is built for the API that delimits Gemini's behavior, demanding that it act as a Senior DBA in T-SQL, and forcing it to use only the columns extracted in the previous step.


ls_prompt = "You act as a Senior DBA with 20 years of experience in SQL Server (T-SQL).~r~n"
ls_prompt += "I will provide you with the script of a database VIEW and a user question.~r~n"

// Schema and columns injection
ls_prompt += "The view is named: " + ls_view_name + "~r~n"
ls_prompt += "The ONLY columns that exist in this view are: " + ls_columns + "~r~n"

// Strict anti-hallucination rules
ls_prompt += "## RULES FOR GENERATING SQL ##~r~n"
ls_prompt += "1. THE QUERY MUST BE MADE TO THE VIEW " + ls_view_name + " and NOT to the base tables.~r~n"
ls_prompt += "3. SMART SEMANTIC MAPPING: If the user uses a similar term... mandatorily ASSIGN IT to the correct column...~r~n"
ls_prompt += "7. Return ONLY the SQL statement as plain text, without markdown.~r~n"

ls_prompt += "## USER QUESTION ##~r~n" + ls_pregunta

Step 4: Secure JSON Payload Creation

To ensure that the SQL script injected into the prompt does not corrupt the JSON format (due to line breaks and double quotes in the SQL code), the JsonGenerator object is used. This component handles automatic escaping.


JsonGenerator lnv_json_gen
lnv_json_gen = create JsonGenerator
ll_root = lnv_json_gen.CreateJsonObject()

// Create contents and parts array...
ll_contents = lnv_json_gen.AddItemArray(ll_root, "contents")
// ...

// Insert the prompt. PowerBuilder escapes everything automatically
lnv_json_gen.AddItemString(ll_part_obj, "text", ls_prompt)

// Temperature configuration at 0.0 for deterministic responses
ll_config = lnv_json_gen.AddItemObject(ll_root, "generationConfig")
lnv_json_gen.AddItemNumber(ll_config, "temperature", 0.0)

ls_json = lnv_json_gen.GetJsonString()

Step 5: API Invocation and Dynamic Rendering

The window invokes the Google service using HTTPClient, gets the response, extracts the pure query text (cleaning markdown marks if they exist), and finally, creates the dynamic grid.


// Get the DataWindow syntax at runtime
ls_syntax = SQLCA.SyntaxFromSQL(is_sql, "Style(Type=Grid)", ls_errors)

IF Len(ls_errors) > 0 THEN
    // Smart handling: detect if the DB engine complains about invalid columns
    IF Pos(ls_errors, "S0022") > 0 OR Pos(Lower(ls_errors), "nombre de columna") > 0 THEN
        MessageBox("Column not found", "The AI generated an SQL with non-existent columns...")
    END IF
ELSE
    // All OK, we create the grid
    dw_resultado.Create(ls_syntax, ls_errors)
    dw_resultado.SetTransObject(SQLCA)
    dw_resultado.Retrieve()
END IF

Key Patterns and Lessons Learned

Pattern Description
Hallucination Prevention with Data Metadata Injecting the real list of columns (using a silent SELECT TOP 0) and forcing the model to use valid names greatly minimizes failures in subsequent SQL execution.
Use of JsonGenerator Using native PB JsonGenerator instead of manual concatenation ensures that complex DDL scripts that may have special characters are serialized without corrupting the request.
Dynamic DataWindows The SyntaxFromSQL function is an ideal tool for this scenario, as it allows evaluating and executing dynamically generated SQL queries in real-time and displaying the grid directly in the window.
Predictive Error Handling Intercepting S0022 and S0002 error codes returned by the engine to translate them to the user, managing to differentiate API failures versus model hallucinations.

Source Code Structure


powerbuilder_ia/
├── src/
│   ├── w_text_to_sql.srw    # Main window of the AI to SQL translator
│   └── unf_replaceall.srf   # Global function for string cleaning
├── powerbuilder_ia.pbl      # Main object library
└── prompt_text_to_sql.txt   # Textual backup of the prompts to use

Additional Readings

Conclusion

Integrating Advanced Language Models like Gemini directly into a PowerBuilder window opens up a new range of possibilities, turning traditional reporting interfaces into self-manageable analysis tools for the end user.

By confining the AI within a strict context (providing it with the view's DDL and validated DB metadata) and taking advantage of native PB features like SyntaxFromSQL, we achieved a robust, secure, and scalable natural language to SQL converter.

 

For demo, please refer to Building an AI Text-to-SQL Translator in PowerBuilder.

 

Comments (0)
There are no comments posted here yet

Find Articles by Tag

SDK Database Table Windows 10 Icons PowerBuilder (Appeon) .NET Assembly Automated Testing PBVM Azure OLE Branch & Merge RibbonBar PowerBuilder SqlExecutor SqlModelMapper Authentication PowerBuilder Compiler SOAP DLL Validation Event SVN Android UI Modernization Service Authorization GhostScript Syntax Encryption RichTextEdit Control DevOps Elevate Conference TreeView Bug PostgreSQL ODBC driver PDF Stored Procedure External Functions 32-bit Transaction Menu Filter Database Connection License Data CI/CD Resize PDFlib Database Table Data Charts JSON Debug Import JSON Icon Variable Model Linux OS Error Export JSON JSONGenerator BLOB C# PBDOM Event Handling Mobile XML PBNI Database Debugger PowerServer Web OAuth OAuth 2.0 .NET DataStore Platform Performance Configuration Database Profile SnapDevelop Import Source Control DataWindow JSONParser COM UI Themes PowerServer Mobile Database Table Schema PowerScript (PS) Export Interface SQL Server Installation RibbonBar Builder Testing Debugging NativePDF PostgreSQL Web API REST RESTClient Web Service Proxy Script UI Windows OS SQL 64-bit ODBC TortoiseGit Open Source Jenkins Deployment Outlook Encoding Git DataType OrcaScript Event Handler Oracle DragDrop MessageBox TLS/SSL WebBrowser Application Excel File Text Array DataWindow JSON .NET Std Framework HTTPClient Sort Expression CoderObject TFS Database Object IDE Messagging ActiveX Database Painter API Source Code iOS Visual Studio SnapObjects Graph PFC Class Window WinAPI CrypterObject InfoMaker Migration Design Trial Repository