• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

torand / FasterSQL / 17012572989

16 Aug 2025 08:26PM UTC coverage: 68.265% (-0.008%) from 68.273%
17012572989

push

github

torand
refactor: sonar cloud issues

299 of 598 branches covered (50.0%)

Branch coverage included in aggregate %.

33 of 47 new or added lines in 18 files covered. (70.21%)

1 existing line in 1 file now uncovered.

1680 of 2301 relevant lines covered (73.01%)

3.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.61
/src/main/java/io/github/torand/fastersql/statement/SelectSetOpStatement.java
1
/*
2
 * Copyright (c) 2024-2025 Tore Eide Andersen
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package io.github.torand.fastersql.statement;
17

18
import io.github.torand.fastersql.alias.Alias;
19
import io.github.torand.fastersql.alias.ColumnAlias;
20
import io.github.torand.fastersql.dialect.AnsiIsoDialect;
21
import io.github.torand.fastersql.order.Order;
22
import io.github.torand.fastersql.projection.Projection;
23
import io.github.torand.fastersql.setoperation.SetOperation;
24
import io.github.torand.fastersql.setoperation.SetOperator;
25
import io.github.torand.fastersql.sql.Context;
26

27
import java.util.LinkedList;
28
import java.util.List;
29
import java.util.Optional;
30
import java.util.Set;
31
import java.util.function.ToLongFunction;
32
import java.util.stream.Stream;
33

34
import static io.github.torand.fastersql.dialect.Capability.SET_OPERATION_PARENTHESES;
35
import static io.github.torand.fastersql.sql.Command.SELECT_SET_OP;
36
import static io.github.torand.javacommons.collection.CollectionHelper.asList;
37
import static io.github.torand.javacommons.collection.CollectionHelper.concat;
38
import static io.github.torand.javacommons.collection.CollectionHelper.nonEmpty;
39
import static io.github.torand.javacommons.contract.Requires.requireNonEmpty;
40
import static io.github.torand.javacommons.stream.StreamHelper.streamSafely;
41
import static java.util.stream.Collectors.joining;
42
import static java.util.stream.Collectors.toSet;
43

44
/**
45
 * Implements a SELECT statement with set operations.
46
 */
47
public class SelectSetOpStatement implements PreparableStatement {
48
    private final SelectStatement selectStatement;
49
    private final List<SetOperation> setOperations;
50
    private final List<Order> orders;
51

52
    SelectSetOpStatement(SelectStatement selectStatement, List<SetOperation> setOperations, List<Order> orders) {
2✔
53
        this.selectStatement = selectStatement;
3✔
54
        this.setOperations = asList(setOperations);
4✔
55
        this.orders = asList(orders);
4✔
56
    }
1✔
57

58
    /**
59
     * Adds one or more ORDER clauses.
60
     * @param orders the ORDER clauses.
61
     * @return the modified statement.
62
     */
63
    public SelectSetOpStatement orderBy(Order... orders) {
64
        requireNonEmpty(orders, "No orders specified");
6✔
65

66
        List<Order> concatenated = concat(this.orders, orders);
5✔
67
        return new SelectSetOpStatement(selectStatement, setOperations, concatenated);
9✔
68
    }
69

70
    /**
71
     * Creates a UNION set operation between this and the specified statement.
72
     * @param operand the set operation operand.
73
     * @return the modified statement.
74
     */
75
    public SelectSetOpStatement union(SelectStatement operand) {
76
        SetOperation setOperation = new SetOperation(operand, SetOperator.UNION);
×
77
        List<SetOperation> concatenated = concat(this.setOperations, setOperation);
×
78
        return new SelectSetOpStatement(selectStatement, concatenated, orders);
×
79
    }
80

81
    /**
82
     * Creates a UNION ALL set operation between this and the specified statement.
83
     * @param operand the set operation operand.
84
     * @return the modified statement.
85
     */
86
    public SelectSetOpStatement unionAll(SelectStatement operand) {
87
        SetOperation setOperation = new SetOperation(operand, SetOperator.UNION).all();
7✔
88
        List<SetOperation> concatenated = concat(this.setOperations, setOperation);
10✔
89
        return new SelectSetOpStatement(selectStatement, concatenated, orders);
9✔
90
    }
91

92
    /**
93
     * Creates an INTERSECT set operation between this and the specified statement.
94
     * Note that INTERSECT has precedence over UNION and EXCEPT.
95
     * @param operand the set operation operand.
96
     * @return the modified statement.
97
     */
98
    public SelectSetOpStatement intersect(SelectStatement operand) {
99
        SetOperation setOperation = new SetOperation(operand, SetOperator.INTERSECT);
×
100
        List<SetOperation> concatenated = concat(this.setOperations, setOperation);
×
101
        return new SelectSetOpStatement(selectStatement, concatenated, orders);
×
102
    }
103

104
    /**
105
     * Creates an INTERSECT ALL set operation between this and the specified statement.
106
     * Note that INTERSECT has precedence over UNION and EXCEPT.
107
     * @param operand the set operation operand.
108
     * @return the modified statement.
109
     */
110
    public SelectSetOpStatement intersectAll(SelectStatement operand) {
111
        SetOperation setOperation = new SetOperation(operand, SetOperator.INTERSECT).all();
×
112
        List<SetOperation> concatenated = concat(this.setOperations, setOperation);
×
113
        return new SelectSetOpStatement(selectStatement, concatenated, orders);
×
114
    }
115

116
    /**
117
     * Creates an EXCEPT set operation between this and the specified statement.
118
     * @param operand the set operation operand.
119
     * @return the modified statement.
120
     */
121
    public SelectSetOpStatement except(SelectStatement operand) {
122
        SetOperation setOperation = new SetOperation(operand, SetOperator.EXCEPT);
6✔
123
        List<SetOperation> concatenated = concat(this.setOperations, setOperation);
10✔
124
        return new SelectSetOpStatement(selectStatement, concatenated, orders);
9✔
125
    }
126

127
    /**
128
     * Creates an EXCEPT ALL set operation between this and the specified statement.
129
     * @param operand the set operation operand.
130
     * @return the modified statement.
131
     */
132
    public SelectSetOpStatement exceptAll(SelectStatement operand) {
133
        SetOperation setOperation = new SetOperation(operand, SetOperator.EXCEPT).all();
×
134
        List<SetOperation> concatenated = concat(this.setOperations, setOperation);
×
135
        return new SelectSetOpStatement(selectStatement, concatenated, orders);
×
136
    }
137

138
    @Override
139
    public String sql(Context context) {
140
        final Context localContext = context.withCommand(SELECT_SET_OP);
4✔
141

142
        validate();
2✔
143

144
        StringBuilder sb = new StringBuilder();
4✔
145
        if (localContext.getDialect().supports(SET_OPERATION_PARENTHESES)) {
5✔
146
            sb.append("(");
4✔
147
        }
148

149
        sb.append(selectStatement.sql(context));
7✔
150

151
        if (localContext.getDialect().supports(SET_OPERATION_PARENTHESES)) {
5✔
152
            sb.append(")");
4✔
153
        }
154

155
        sb.append(" ");
4✔
156

157
        sb.append(streamSafely(setOperations)
8✔
158
            .map(j -> j.sql(localContext))
6✔
159
            .collect(joining(" ")));
3✔
160

161
        if (nonEmpty(orders)) {
4!
162
            sb.append(" order by ")
5✔
163
              .append(streamSafely(orders)
5✔
164
              .map(o -> o.sql(localContext))
6✔
165
              .collect(joining(", ")));
3✔
166
        }
167

168
        return sb.toString();
3✔
169
    }
170

171
    @Override
172
    public Stream<Object> params(Context context) {
173
        List<Object> params = new LinkedList<>();
4✔
174

175
        selectStatement.params(context).forEach(params::add);
10✔
176
        streamSafely(setOperations).flatMap(so -> so.params(context)).forEach(params::add);
16✔
177

178
        return params.stream();
3✔
179
    }
180

181
    private void validate() {
182
        ToLongFunction<SelectStatement> projCount = st -> st.projections().count();
6✔
183
        ToLongFunction<SetOperation> setOpProjCount = so -> projCount.applyAsLong(so.operand());
8✔
184

185
        long expectedProjCount = projCount.applyAsLong(selectStatement);
5✔
186
        if (streamSafely(setOperations).map(setOpProjCount::applyAsLong).anyMatch(actualProjCount -> actualProjCount != expectedProjCount)) {
20!
UNCOV
187
            throw new IllegalStateException("SELECT statements in a set operation can't have different number of projections");
×
188
        }
189

190
        if (nonEmpty(orders)) {
4!
191
            Set<String> orderableAliases = selectStatement.projections()
4✔
192
                .map(Projection::alias)
2✔
193
                .flatMap(Optional::stream)
2✔
194
                .map(Alias::name)
1✔
195
                .collect(toSet());
4✔
196

197
            streamSafely(orders)
4✔
198
                .flatMap(o -> o.aliasRefs())
5✔
199
                .map(ColumnAlias::name)
3✔
200
                .filter(a -> !orderableAliases.contains(a))
7!
201
                .findFirst()
2✔
202
                .ifPresent(a -> {
1✔
203
                    throw new IllegalStateException("Set operation ORDER BY column alias " + a + " is not specified in the first SELECT clause");
×
204
                });
205
        }
206
    }
1✔
207

208
    @Override
209
    public String toString() {
210
        return toString(new AnsiIsoDialect());
×
211
    }
212
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc