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

torand / FasterSQL / 17073502945

19 Aug 2025 02:56PM UTC coverage: 68.2%. Remained the same
17073502945

push

github

torand
chore: adjust sonar cloud config to avoid noise

299 of 598 branches covered (50.0%)

Branch coverage included in aggregate %.

1629 of 2229 relevant lines covered (73.08%)

3.97 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
import io.github.torand.fastersql.sql.Sql;
27

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

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

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

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

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

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

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

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

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

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

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

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

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

143
        validate();
2✔
144

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

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

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

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

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

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

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

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

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

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

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

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

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

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

209
    @Override
210
    public String toString() {
211
        return toString(new AnsiIsoDialect());
×
212
    }
213
}
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