Skip to content

e4c5/hql-parser

Repository files navigation

HQL/JPQL Parser

A standalone Java library for parsing HQL/JPQL, extracting query metadata, and converting queries to PostgreSQL SQL.

Published to Maven Central as com.raditha:hql-parser.

Built for standalone use: This library brings JPQL/HQL parsing, query analysis, metadata extraction, and PostgreSQL SQL conversion into a plain Java dependency that can be used in tools, CLIs, CI pipelines, migration utilities, and backend services without needing to boot a full application.

Why teams use this library

  • Build query-aware tooling: power CLIs, linters, analyzers, and migration helpers with HQL/JPQL parsing
  • Understand queries programmatically: extract entities, fields, aliases, and parameters for validation or automation
  • Translate ORM queries for downstream systems: convert HQL/JPQL into PostgreSQL SQL using explicit mappings
  • Run outside application runtime: use it in batch jobs, CI, developer tooling, and backend utilities as a plain Java dependency
  • Adopt incrementally: start with syntax validation or metadata extraction, then add conversion where needed

Features

  • Complete HQL/JPQL Grammar Support: Parse SELECT, UPDATE, DELETE, and INSERT statements
  • Query Analysis: Extract entities, fields, parameters, and aliases from queries
  • PostgreSQL Conversion: Convert HQL/JPQL queries to PostgreSQL SQL
  • Entity Detection: Identify all Java entity classes involved in a query
  • Field Mapping: Track entity fields referenced in queries
  • Parameter Extraction: Identify both named (:param) and positional (?1) parameters
  • Join Support: Handle INNER, LEFT, and RIGHT joins
  • Aggregate Functions: Support for COUNT, SUM, AVG, MAX, MIN
  • Query Validation: Check if queries are syntactically correct

Requirements

  • Java 21 or higher
  • Maven 3.6+

IDE Setup

IntelliJ IDEA Users: If you're getting "Cannot find symbol" errors for ANTLR-generated classes, see INTELLIJ_SETUP.md for configuration instructions.

Installation

Maven Central

Add the dependency to your pom.xml:

<dependency>
    <groupId>com.raditha</groupId>
    <artifactId>hql-parser</artifactId>
    <version>1.0.0</version>
</dependency>

No additional repository configuration is required when consuming the library from Maven Central.

Build from Source

git clone <repository-url>
cd hql-parser
mvn clean install

Quick Start

1. Parse and Validate Queries

import com.raditha.hql.parser.HQLParser;

HQLParser parser = new HQLParser();

String query = "SELECT u FROM User u WHERE u.age > 18";

if (parser.isValid(query)) {
    System.out.println("Query is valid!");
}

2. Analyze Queries

Extract entities, fields, and parameters from HQL queries:

import com.raditha.hql.parser.HQLParser;
import com.raditha.hql.model.MetaData;

HQLParser parser = new HQLParser();

String query = "SELECT u.name, u.email FROM User u WHERE u.active = true AND u.age > :minAge";

MetaData analysis = parser.analyze(query);

System.out.println("Query Type: " + analysis.getQueryType());
System.out.println("Entities: " + analysis.getEntityNames());
System.out.println("Fields: " + analysis.getEntityFields());
System.out.println("Parameters: " + analysis.getParameters());

Output:

Query Type: SELECT
Entities: [User]
Fields: {User=[name, email, active, age]}
Parameters: [minAge]

3. Convert to PostgreSQL

Convert HQL/JPQL queries to PostgreSQL SQL:

import com.raditha.hql.parser.HQLParser;
import com.raditha.hql.model.MetaData;
import com.raditha.hql.converter.HQLToPostgreSQLConverter;

HQLParser parser = new HQLParser();
HQLToPostgreSQLConverter converter = new HQLToPostgreSQLConverter();

// Register entity-to-table mappings
converter.registerEntityMapping("User", "users");
converter.registerEntityMapping("Order", "orders");

// Register field-to-column mappings
converter.registerFieldMapping("User", "userName", "user_name");
converter.registerFieldMapping("User", "firstName", "first_name");

String hql = "SELECT u.userName FROM User u WHERE u.active = true";

// First analyze the query
MetaData analysis = parser.analyze(hql);

// Then convert using both the query and analysis
String sql = converter.convert(hql, analysis);

System.out.println("SQL: " + sql);

Output:

SQL: SELECT u.user_name FROM users u WHERE u.active = true

Usage Examples

Complex Query with Joins

HQLParser parser = new HQLParser();

String query = "SELECT u.name, o.total " +
              "FROM User u " +
              "INNER JOIN u.orders o " +
              "WHERE u.active = true AND o.total > 100 " +
              "ORDER BY o.total DESC";

MetaData analysis = parser.analyze(query);

System.out.println("Entities: " + analysis.getEntityNames());
System.out.println("Aliases: " + analysis.getAliases());

Constructor Expressions

HQLParser parser = new HQLParser();

// Constructor expressions allow creating DTOs directly in queries
String query = "SELECT NEW com.finance.dto.AccountDTO(a.accountNumber, a.balance) " +
              "FROM Account a WHERE a.status = 'ACTIVE'";

MetaData analysis = parser.analyze(query);
System.out.println("Query Type: " + analysis.getQueryType()); // SELECT
System.out.println("Entities: " + analysis.getEntityNames()); // [Account]
System.out.println("Fields: " + analysis.getEntityFields()); // {Account=[accountNumber, balance, status]}

// Works with BETWEEN clause and parameters
String query2 = "SELECT NEW dto.TransactionDTO(t.id, t.amount) " +
               "FROM Transaction t WHERE t.transactionDate BETWEEN :start AND :end";
MetaData analysis2 = parser.analyze(query2);
System.out.println("Parameters: " + analysis2.getParameters()); // [start, end]

Update Queries

HQLParser parser = new HQLParser();

String query = "UPDATE User SET active = false WHERE lastLogin < :cutoffDate";

MetaData analysis = parser.analyze(query);
System.out.println("Query Type: " + analysis.getQueryType()); // UPDATE
System.out.println("Parameters: " + analysis.getParameters()); // [cutoffDate]

Delete Queries

// DELETE without alias
String query = "DELETE FROM User WHERE age < 18";

MetaData analysis = parser.analyze(query);
System.out.println("Query Type: " + analysis.getQueryType()); // DELETE

// DELETE with alias (for complex WHERE clauses)
String query2 = "DELETE FROM Purchase p WHERE p.status = 'CANCELLED' AND p.createdDate < :cutoffDate";
MetaData analysis2 = parser.analyze(query2);
System.out.println("Fields: " + analysis2.getEntityFields()); // {Purchase=[status, createdDate]}

Supported HQL/JPQL Features

Query Types

  • SELECT statements with projection
  • Constructor expressions with SELECT NEW ClassName(args...)
  • UPDATE statements (with/without alias)
  • DELETE statements (with/without alias)
  • INSERT ... SELECT statements (full conversion support)

Clauses

  • SELECT with DISTINCT
  • Constructor expressions for creating DTOs directly in queries
  • FROM with entity aliases
  • WHERE with complex predicates
  • GROUP BY
  • HAVING
  • ORDER BY (ASC/DESC, NULLS FIRST/LAST)

Joins

  • INNER JOIN
  • LEFT [OUTER] JOIN
  • RIGHT [OUTER] JOIN
  • FETCH joins

Operators

  • Comparison: =, !=, <>, <, <=, >, >=
  • Logical: AND, OR, NOT
  • Arithmetic: +, -, *, /, %
  • Special: BETWEEN, IN, LIKE, IS NULL, MEMBER OF, EXISTS

Functions

  • Aggregate: COUNT, SUM, AVG, MAX, MIN
  • String: UPPER, LOWER, TRIM, LENGTH, CONCAT, SUBSTRING
  • Date/Time: CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP
  • Math: ABS, SQRT, MOD
  • Other: COALESCE, NULLIF, CAST, SIZE

Note: The parser supports all these functions in the grammar, and the PostgreSQL converter implements conversion for all listed functions. The SIZE() function and MEMBER OF operator produce HQL-specific SQL that may require application-level handling since they don't have direct PostgreSQL equivalents.

Parameters

  • Named parameters: :paramName (SQL keywords such as :from, :to, :in are supported as parameter names)
  • Positional parameters: ?1, ?2, etc.

API Documentation

HQLParser

Main parser class for HQL/JPQL queries.

Methods:

  • ParseTree parse(String query) - Parse query and return parse tree
  • MetaData analyze(String query) - Analyze query and extract metadata
  • boolean isValid(String query) - Validate query syntax

MetaData

Contains analysis results of a parsed query.

Methods:

  • QueryType getQueryType() - Get query type (SELECT, UPDATE, DELETE, INSERT)
  • Set<String> getEntityNames() - Get all entity names referenced
  • Map<String, Set<String>> getEntityFields() - Get fields for each entity
  • List<String> getAliases() - Get all aliases used
  • Set<String> getParameters() - Get all parameters
  • Map<String, String> getAliasToEntity() - Get mapping of aliases to entity names
  • String getEntityForAlias(String alias) - Get entity name for a given alias

HQLToPostgreSQLConverter

Converts HQL/JPQL to PostgreSQL SQL.

Methods:

  • void registerEntityMapping(String entityName, String tableName) - Map entity to table
  • void registerFieldMapping(String entityName, String fieldName, String columnName) - Map field to column
  • String convert(String hqlQuery, MetaData analysis) - Convert HQL to SQL using query analysis

Project Structure

hql-parser/
├── src/
│   ├── main/
│   │   ├── java/com/raditha/hql/
│   │   │   ├── converter/       # SQL conversion
│   │   │   ├── examples/        # Usage examples
│   │   │   ├── model/           # Data models
│   │   │   └── parser/          # Core parser and analysis
│   │   └── antlr4/com/raditha/hql/grammar/
│   │       └── HQL.g4           # ANTLR grammar definition
│   └── test/
│       └── java/com/raditha/hql/
│           ├── HQLParserTest.java
│           ├── AdvancedHQLParserTest.java
│           ├── PostgreSQLConverterTest.java
│           ├── AdvancedConverterTest.java
│           └── JoinAnalysisTest.java
├── pom.xml
└── README.md

Running Tests

mvn test

Availability

hql-parser is available on Maven Central under:

  • Group ID: com.raditha
  • Artifact ID: hql-parser
  • Current version: 1.0.0

Running Examples

mvn compile exec:java -Dexec.mainClass="com.raditha.hql.examples.UsageExamples"

Building

# Compile and generate parser from grammar
mvn clean compile

# Run tests
mvn test

# Create JAR
mvn package

Current Limitations

Parser Limitations

  1. Parameter Naming: Avoid using HQL keywords (like end, and, or) as parameter names. Use descriptive names like :endDate, :startDate instead.

  2. Subquery Limitations: While subqueries are parsed, entity/field extraction from deeply nested subqueries may be incomplete.

  3. Collection Join Analysis: For implicit joins (e.g., u.orders), the parser infers entity names using simple heuristics (singularization + capitalization). This may not work for irregular plurals or custom naming.

Converter Limitations

  1. Implicit Join ON Clauses: HQL allows implicit joins without ON clauses (using JPA metadata). The converter cannot generate ON clauses automatically - you must provide explicit ON clauses for SQL compatibility.

    // HQL (implicit ON clause based on metadata)
    "FROM User u INNER JOIN u.orders o"
    
    // SQL requires explicit ON clause
    "FROM users u INNER JOIN orders o ON o.user_id = u.id"
  2. Parameter Format: The converter keeps HQL parameter format (:param, ?1) rather than converting to PostgreSQL format ($1, $2). You'll need to handle parameter binding separately.

  3. FETCH Joins: FETCH joins are HQL-specific for eager loading. The converter ignores the FETCH keyword, converting them to regular joins.

  4. Entity Metadata Requirements: The converter requires explicit entity-to-table and field-to-column mappings. It does not introspect JPA annotations or Hibernate configuration.

  5. Nested Paths: Paths like u.address.city are parsed but may not convert correctly if intermediate relationships aren't mapped.

  6. Collection Functions: HQL-specific functions like SIZE(), MEMBER OF may not have direct PostgreSQL equivalents.

  7. Function Support: All grammar-defined HQL/JPQL functions are converted to their PostgreSQL equivalents, including aggregate functions (COUNT, SUM, AVG, MAX, MIN), string functions (UPPER, LOWER, TRIM, LENGTH, CONCAT, SUBSTRING), date/time functions (CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP), math functions (ABS, SQRT, MOD), and type functions (COALESCE, NULLIF, CAST). HQL-specific functions such as SIZE() and MEMBER OF are passed through as-is and may require application-level handling.

Field Extraction Behavior

  1. UPDATE/DELETE Statements:

    • With alias (e.g., UPDATE User u SET u.active = false): Fields referenced with alias are extracted
    • Without alias (e.g., UPDATE User SET active = false): Unqualified fields are extracted and mapped in conversion
  2. Unqualified Fields: In UPDATE/DELETE statements without aliases, unqualified field names are mapped to columns using registered field mappings. This is the expected behavior but differs from SELECT queries where fields are typically qualified with aliases.

Grammar Coverage

While the parser supports most common HQL/JPQL features, some advanced features are not yet implemented:

  • TREAT() operator for polymorphic queries
  • INDEX() function for indexed collections
  • KEY() and VALUE() functions for map collections
  • TYPE() operator for inheritance queries
  • Bulk INSERT with VALUES clause (only INSERT ... SELECT supported)

Grammar

The HQL/JPQL grammar is defined in src/main/antlr4/com/raditha/hql/grammar/HQL.g4 using ANTLR4.

The grammar supports:

  • Case-insensitive keywords
  • Line and block comments
  • String literals with escape sequences
  • Numeric literals (integers and decimals)
  • Identifiers and paths

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the Apache License 2.0.

Author

Raditha Dissanayake

Acknowledgments

  • Built with ANTLR4
  • Implements HQL/JPQL grammar based on Hibernate and JPA specifications

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors