Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.util.Optional;

import org.eclipse.daanse.jdbc.db.dialect.api.generator.KnownFunction;
import org.eclipse.daanse.jdbc.db.dialect.api.type.Datatype;
import org.eclipse.daanse.jdbc.db.api.type.Datatype;
import org.eclipse.daanse.sql.statement.api.expression.ArithmeticOperator;
import org.eclipse.daanse.sql.statement.api.expression.Predicate;
import org.eclipse.daanse.sql.statement.api.expression.SqlExpression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ public static Predicate in(SqlExpression expression, SqlExpression... values) {
/**
* Row-value / tuple {@code IN}:
* {@code (c1, c2, ...) IN ((v11, ...), (v21, ...), ...)}. Every row must match
* {@code columns} in arity. Gate at the call site on
* {@code dialect.supportsMultiValueInExpr()}.
* {@code columns} in arity. Build tuples child-first (most specific column
* first); dialects without multi-value IN render the parent-first OR-of-ANDs
* expansion instead (see {@code Predicate.InTuple}).
*/
public static Predicate inTuple(List<SqlExpression> columns, List<List<SqlExpression>> rows) {
List<List<SqlExpression>> copy = new java.util.ArrayList<>(rows.size());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import java.util.Optional;

import org.eclipse.daanse.jdbc.db.dialect.api.generator.StatementHint;
import org.eclipse.daanse.jdbc.db.dialect.api.type.BestFitColumnType;
import org.eclipse.daanse.jdbc.db.api.type.BestFitColumnType;
import org.eclipse.daanse.sql.statement.api.expression.Predicate;
import org.eclipse.daanse.sql.statement.api.expression.SqlExpression;
import org.eclipse.daanse.sql.statement.api.model.ColumnAlias;
Expand Down Expand Up @@ -60,6 +60,8 @@ public final class SelectStatementBuilder {
private final List<GroupBy.GroupKey> groupKeys = new ArrayList<>();
private final List<GroupBy.GroupingSet> groupingSets = new ArrayList<>();
private final List<GroupBy.GroupingFunction> groupingFunctions = new ArrayList<>();
private boolean completeNonAggregatesGroupBy;
private final java.util.Set<Integer> groupByCompletionExempt = new java.util.HashSet<>();
private final List<Predicate> having = new ArrayList<>();
private final List<OrderKey> orderKeys = new ArrayList<>();
private RowLimit rowLimit;
Expand Down Expand Up @@ -152,6 +154,31 @@ public SelectStatementBuilder groupOn(SqlExpression expression) {
return this;
}

/**
* Marks the group by as needing per-dialect completion: on dialects that do not allow
* non-aggregate select columns outside {@code GROUP BY}, the renderer appends every
* non-aggregate projection not already a key. Set this when the query genuinely groups
* (i.e. a member-enumeration read that placed keys/ordinals as group keys); leave it off
* for a query whose GROUP BY is incidental (e.g. a single non-level-dependent property on a
* non-grouping read), so the renderer does not over-group.
*/
public SelectStatementBuilder completeNonAggregatesGroupBy() {
this.completeNonAggregatesGroupBy = true;
return this;
}

/**
* Exempts one projection from the dialect GROUP-BY completion (see
* {@link #completeNonAggregatesGroupBy()}). Use for a projection that is semantically an
* aggregate the renderer cannot recognise structurally — an arithmetic expression WRAPPING
* aggregates (e.g. a native TopCount/Order measure {@code (sum(a)-sum(b))/sum(b)}) — which
* must not be added to GROUP BY on restrictive dialects. No effect on permissive dialects.
*/
public SelectStatementBuilder excludeFromGroupByCompletion(ProjectionRef ref) {
groupByCompletionExempt.add(ref.ordinal());
return this;
}

public SelectStatementBuilder addGroupingSet(List<SqlExpression> keys) {
groupingSets.add(new GroupBy.GroupingSet(List.copyOf(keys)));
return this;
Expand Down Expand Up @@ -267,8 +294,18 @@ public int projectionCount() {
/** Produces the immutable {@link SelectStatement}. */
public SelectStatement build() {
GroupBy groupBy = new GroupBy(List.copyOf(groupKeys), List.copyOf(groupingSets),
List.copyOf(groupingFunctions));
return new SelectStatement(distinct, List.copyOf(projections), Optional.ofNullable(from), List.copyOf(filters),
List.copyOf(groupingFunctions), completeNonAggregatesGroupBy);
List<Projection> finalProjections = projections;
if (!groupByCompletionExempt.isEmpty()) {
finalProjections = new java.util.ArrayList<>(projections.size());
for (int i = 0; i < projections.size(); i++) {
Projection p = projections.get(i);
finalProjections.add(groupByCompletionExempt.contains(i)
? new Projection(p.expression(), p.columnType(), p.alias(), p.comment(), true)
: p);
}
}
return new SelectStatement(distinct, List.copyOf(finalProjections), Optional.ofNullable(from), List.copyOf(filters),
groupBy, List.copyOf(having), List.copyOf(orderKeys), Optional.ofNullable(rowLimit),
Optional.ofNullable(headerComment), new java.util.IdentityHashMap<>(filterComments),
Optional.ofNullable(footerComment), List.copyOf(statementHints));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ record In(SqlExpression expression, List<SqlExpression> values) implements Predi
/**
* Row-value / tuple {@code IN}:
* {@code (c1, c2, ...) IN ((v11, v12, ...), (v21, v22, ...), ...)}. Each row in
* {@code rows} must have the same arity as {@code columns}. Gate construction
* at the call site on {@code dialect.supportsMultiValueInExpr()}; dialects
* without it need an OR-of-ANDs expansion instead.
* {@code rows} must have the same arity as {@code columns}. Build tuples
* child-first (most specific column first, e.g. {@code (quarter, the_year)}).
* On a dialect without {@code supportsMultiValueInExpr()} the renderer degrades
* this to an OR-of-ANDs expansion whose pairs read parent-first (reverse tuple
* order).
*/
record InTuple(List<SqlExpression> columns, List<List<SqlExpression>> rows) implements Predicate {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
import java.util.List;
import java.util.Optional;

import org.eclipse.daanse.jdbc.db.dialect.api.generator.BitOperation;
import org.eclipse.daanse.jdbc.db.dialect.api.sql.OrderedColumn;
import org.eclipse.daanse.jdbc.db.dialect.api.type.Datatype;
import org.eclipse.daanse.jdbc.db.api.sql.BitOperation;
import org.eclipse.daanse.jdbc.db.api.sql.OrderedColumn;
import org.eclipse.daanse.jdbc.db.api.type.Datatype;
import org.eclipse.daanse.sql.statement.api.model.SelectStatement;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,20 @@
* supports them)
* @param groupingFunctions {@code GROUPING(expr)} columns added to the select
* list
* @param completeNonAggregates when {@code true}, dialects that do NOT allow non-aggregate
* select columns outside {@code GROUP BY}
* ({@code !allowsSelectNotInGroupBy()}) have every non-aggregate
* projection not already a key appended to the group by at render time.
* The canonical (permissive-dialect) form groups only the keys the
* builder placed; the renderer completes it per dialect.
*/
public record GroupBy(List<GroupKey> keys, List<GroupingSet> groupingSets, List<GroupingFunction> groupingFunctions) {
public record GroupBy(List<GroupKey> keys, List<GroupingSet> groupingSets, List<GroupingFunction> groupingFunctions,
boolean completeNonAggregates) {

/** Backward-compatible: no dialect-completed grouping. */
public GroupBy(List<GroupKey> keys, List<GroupingSet> groupingSets, List<GroupingFunction> groupingFunctions) {
this(keys, groupingSets, groupingFunctions, false);
}

/** True if there is nothing to group by. */
public boolean isEmpty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import java.util.Optional;

import org.eclipse.daanse.jdbc.db.dialect.api.type.BestFitColumnType;
import org.eclipse.daanse.jdbc.db.api.type.BestFitColumnType;
import org.eclipse.daanse.sql.statement.api.expression.SqlExpression;

/**
Expand All @@ -34,12 +34,26 @@
* @param comment an optional explanatory comment (rollup provenance),
* emitted only when the renderer is asked to emit comments;
* never part of the executed SQL
* @param groupByCompletionExempt when {@code true}, the dialect GROUP-BY
* completion (which adds every non-aggregate projection to
* GROUP BY for engines that require it) skips this projection.
* Set for a projection that is semantically an aggregate the
* renderer cannot recognise structurally — e.g. an arithmetic
* expression WRAPPING aggregates (a native TopCount/Order
* measure like {@code (sum(a)-sum(b))/sum(b)}), which must not
* be grouped. Never affects permissive dialects.
*/
public record Projection(SqlExpression expression, BestFitColumnType columnType, Optional<ColumnAlias> alias,
Optional<String> comment) {
Optional<String> comment, boolean groupByCompletionExempt) {

/** Backwards-compatible form without the completion-exempt flag (defaults to {@code false}). */
public Projection(SqlExpression expression, BestFitColumnType columnType, Optional<ColumnAlias> alias,
Optional<String> comment) {
this(expression, columnType, alias, comment, false);
}

/** Backwards-compatible form without a comment. */
public Projection(SqlExpression expression, BestFitColumnType columnType, Optional<ColumnAlias> alias) {
this(expression, columnType, alias, Optional.empty());
this(expression, columnType, alias, Optional.empty(), false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
* {@code "de_DE"}).
*/
public record SortSpec(SortDirection direction, boolean nullable, NullOrder nullOrder, boolean prepend,
String nullSortValue, org.eclipse.daanse.jdbc.db.dialect.api.type.Datatype nullSortDatatype,
String nullSortValue, org.eclipse.daanse.jdbc.db.api.type.Datatype nullSortDatatype,
Optional<String> collation) {

/** Compatibility constructor without a collation. */
public SortSpec(SortDirection direction, boolean nullable, NullOrder nullOrder, boolean prepend,
String nullSortValue, org.eclipse.daanse.jdbc.db.dialect.api.type.Datatype nullSortDatatype) {
String nullSortValue, org.eclipse.daanse.jdbc.db.api.type.Datatype nullSortDatatype) {
this(direction, nullable, nullOrder, prepend, nullSortValue, nullSortDatatype, Optional.empty());
}

Expand Down Expand Up @@ -75,7 +75,7 @@ public SortSpec prepended() {
* @return a copy ordering nulls as if they held {@code value} (of type
* {@code datatype}), via the dialect's order-value generator.
*/
public SortSpec withNullSortValue(String value, org.eclipse.daanse.jdbc.db.dialect.api.type.Datatype datatype) {
public SortSpec withNullSortValue(String value, org.eclipse.daanse.jdbc.db.api.type.Datatype datatype) {
return new SortSpec(direction, nullable, nullOrder, prepend, value, datatype, collation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/
package org.eclipse.daanse.sql.statement.api.render;

import org.eclipse.daanse.jdbc.db.dialect.api.type.Datatype;
import org.eclipse.daanse.jdbc.db.api.type.Datatype;

/**
* One bind parameter of a rendered statement, in placeholder order.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import java.util.List;

import org.eclipse.daanse.jdbc.db.dialect.api.type.BestFitColumnType;
import org.eclipse.daanse.jdbc.db.api.type.BestFitColumnType;

/**
* The result of rendering a statement: the SQL text, the column types to read
Expand Down
66 changes: 66 additions & 0 deletions statement/demo/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?xml version="1.0"?>
<!--
/*********************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.daanse</groupId>
<artifactId>org.eclipse.daanse.sql.statement</artifactId>
<version>${revision}</version>
</parent>
<artifactId>org.eclipse.daanse.sql.statement.demo</artifactId>
<name>Daanse SQL Query Builder Demo</name>
<description>Runnable demonstration of the SQL query builder: assembles
queries with the
dialect-free API and renders them with the ANSI dialect. Shows, in comments,
how a
future adapter (e.g. ROLAP or ORM) would translate its own model into the
builder
primitives. Not a production module.</description>

<dependencies>
<dependency>
<groupId>org.eclipse.daanse</groupId>
<artifactId>org.eclipse.daanse.sql.statement.api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.daanse</groupId>
<artifactId>org.eclipse.daanse.sql.statement.impl</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
</dependency>
<dependency>
<groupId>org.eclipse.daanse</groupId>
<artifactId>org.eclipse.daanse.jdbc.db.dialect.db.common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.eclipse.daanse</groupId>
<artifactId>org.eclipse.daanse.jdbc.db.dialect.db.mssqlserver</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.daanse</groupId>
<artifactId>org.eclipse.daanse.jdbc.db.dialect.db.mysql</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Loading
Loading