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

jhannes / fluent-jdbc / #213

16 Jul 2026 05:43PM UTC coverage: 91.667% (+0.1%) from 91.572%
#213

push

jhannes
upgrade to JDK 17 in GitHub workflow to support latest junit

1221 of 1332 relevant lines covered (91.67%)

0.92 hits per line

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

89.66
/src/main/java/org/fluentjdbc/DbContextJoinedSelectBuilder.java
1
package org.fluentjdbc;
2

3
import javax.annotation.CheckReturnValue;
4
import javax.annotation.Nonnull;
5
import java.sql.Connection;
6
import java.sql.ResultSetMetaData;
7
import java.util.List;
8
import java.util.Map;
9
import java.util.stream.Stream;
10

11
/**
12
 * {@link DbContextTableQueryBuilder} used to generate joined queries using SQL-92 standard
13
 * <code>SELECT * FROM table1 a JOIN table2 b ON a.column = b.column</code>. To specify
14
 * columns for selection and tables for retrieval of columns, use {@link DbContextTableAlias}
15
 * and {@link DatabaseColumnReference}.
16
 *
17
 * <p><strong>Only works well on JDBC drivers that implement {@link ResultSetMetaData#getTableName(int)}</strong>,
18
 * as this is used to calculate column indexes for aliased tables. This includes PostgreSQL, H2, HSQLDB, and SQLite,
19
 * but not Oracle or SQL Server.</p>
20
 *
21
 * <p>Pull requests are welcome for a substitute for SQL Server and Oracle.</p>
22
 *
23
 * <h2>Usage example:</h2>
24

25
 * <pre>
26
 * {@link DbContextTable}Alias p = productsTable.alias("p");
27
 * DbContextTableAlias o = ordersTable.alias("o");
28
 * return context
29
 *         .join(linesAlias.column("product_id"), p.column("product_id"))
30
 *         .join(linesAlias.column("order_id"), o.column("order_id"))
31
 *         .list(row -&gt; new OrderLineEntity(
32
 *                 OrderRepository.toOrder(row.table(o)),
33
 *                 ProductRepository.toProduct(row.table(p)),
34
 *                 toOrderLine(row.table(linesAlias))
35
 *         ));
36
 * </pre>
37
 */
38
@CheckReturnValue
39
public class DbContextJoinedSelectBuilder implements DbContextListableSelect<DbContextJoinedSelectBuilder> {
40
    protected final DbContextTable table;
41
    protected final DatabaseJoinedQueryBuilder builder;
42

43
    public DbContextJoinedSelectBuilder(@Nonnull DbContextTable table, @Nonnull DbContextTableAlias tableAlias) {
44
        this(table, new DatabaseJoinedQueryBuilder(table.getTable(), tableAlias.getTableAlias()));
1✔
45
    }
1✔
46

47
    public DbContextJoinedSelectBuilder(@Nonnull DbContextTable table, @Nonnull DatabaseJoinedQueryBuilder builder) {
1✔
48
        this.table = table;
1✔
49
        this.builder = builder;
1✔
50
    }
1✔
51

52
    /**
53
     * Adds an additional table to the join as an inner join. Inner joins require a matching row
54
     * in both tables and will leave out rows from one of the table where there is no corresponding
55
     * table in the other
56
     */
57
    public DbContextJoinedSelectBuilder join(DatabaseColumnReference a, DatabaseColumnReference b) {
58
        return query(builder.join(a, b));
1✔
59
    }
60

61
    /**
62
     * Adds an additional table to the join as an inner join with the initial table. Inner joins require that
63
     * all columns in both tables match and will leave out rows from one of the table where there is no corresponding
64
     * table in the other
65
     */
66
    public DbContextJoinedSelectBuilder join(List<String> leftFields, DbContextTableAlias joinedTable, List<String> rightFields) {
67
        return query(builder.join(leftFields, joinedTable.getTableAlias(), rightFields));
×
68
    }
69

70
    /**
71
     * Adds an additional table to the join as an inner join with the specified table. Inner joins require that
72
     * all columns  in both tables match and will leave out rows from one of the table where there is no
73
     * corresponding table in the other
74
     */
75
    public DbContextJoinedSelectBuilder join(DbContextTableAlias leftTable, List<String> leftFields, DbContextTableAlias joinedTable, List<String> rightFields) {
76
        return query(builder.join(leftTable.getTableAlias(), leftFields, joinedTable.getTableAlias(), rightFields));
1✔
77
    }
78

79
    /**
80
     * Adds an additional table to the join as a left join. Left join only require a matching row in
81
     * the first/left table. If there is no matching row in the second/right table, all columns are
82
     * returned as null in this table. When calling {@link DatabaseRow#table(DatabaseTableAlias)} on
83
     * the resulting row, <code>null</code> is returned
84
     */
85
    public DbContextJoinedSelectBuilder leftJoin(DatabaseColumnReference a, DatabaseColumnReference b) {
86
        return query(builder.leftJoin(a, b));
1✔
87
    }
88

89
    /**
90
     * Adds an additional table to the join as a left join. Left join only require a matching row in
91
     * the first/left table. If there is no matching row in the second/right table, all columns are
92
     * returned as null in this table. When calling {@link DatabaseRow#table(DatabaseTableAlias)} on
93
     * the resulting row, <code>null</code> is returned
94
     */
95
    public DbContextJoinedSelectBuilder leftJoin(List<String> leftFields, DbContextTableAlias joinedTable, List<String> rightFields) {
96
        return query(builder.leftJoin(leftFields, joinedTable.getTableAlias(), rightFields));
×
97
    }
98

99
    /**
100
     * Executes <code>SELECT count(*) FROM ...</code> on the query and returns the result
101
     */
102
    @Override
103
    public int getCount() {
104
        return builder.getCount(getConnection());
1✔
105
    }
106

107
    /**
108
     * Execute the query and map each return value over the {@link DatabaseResult.RowMapper} function to return a stream. Example:
109
     *
110
     * <pre>
111
     *     t.join(t.column("id"), o.column("parent_id"))
112
     *          .where("status", status)
113
     *          .stream(row -&gt; row.table(joinedTable).getInstant("created_at"))
114
     * </pre>
115
     */
116
    @Override
117
    public <OBJECT> Stream<OBJECT> stream(DatabaseResult.RowMapper<OBJECT> mapper) {
118
        return builder.stream(getConnection(), mapper);
1✔
119
    }
120

121
    /**
122
     * Execute the query and map each return value over the {@link DatabaseResult.RowMapper} function to return a list. Example:
123
     *
124
     * <pre>
125
     *     {@link DbContextTableAlias} t = table.alias("t");
126
     *     DbContextTableAlias o = otherTable.alias("o");
127
     *     List&lt;Instant&gt; creationTimes = t.join(t.column("id"), o.column("parent_id"))
128
     *          .where(t.column("name"), name)
129
     *          .list(row -&gt; row.table(o).getInstant("created_at"));
130
     * </pre>
131
     */
132
    @Override
133
    public <OBJECT> List<OBJECT> list(DatabaseResult.RowMapper<OBJECT> mapper) {
134
        return builder.list(getConnection(), mapper);
1✔
135
    }
136

137
    /**
138
     * Executes the <code>SELECT * FROM ...</code> statement and calls back to
139
     * {@link DatabaseResult.RowConsumer} for each returned row
140
     */
141
    @Override
142
    public void forEach(DatabaseResult.RowConsumer consumer) {
143
        builder.forEach(getConnection(), consumer);
1✔
144
    }
1✔
145

146
    /**
147
     * Adds the parameter to the WHERE-clause and all the parameter list.
148
     * E.g. <code>where(new DatabaseQueryParameter("created_at between ? and ?", List.of(earliestDate, latestDate)))</code>
149
     */
150
    @Override
151
    public DbContextJoinedSelectBuilder where(DatabaseQueryParameter parameter) {
152
        builder.where(parameter);
1✔
153
        return this;
1✔
154
    }
155

156
    /**
157
     * For each field adds "<code>WHERE fieldName = value</code>" to the query
158
     */
159
    @Override
160
    public DbContextJoinedSelectBuilder whereAll(List<String> fields, List<Object> values) {
161
        return query(builder.whereAll(fields, values));
1✔
162
    }
163

164
    /**
165
     * For each key and value adds "<code>WHERE fieldName = value</code>" to the query
166
     */
167
    @Override
168
    public DbContextJoinedSelectBuilder whereAll(Map<String, ?> fields) {
169
        return query(builder.whereAll(fields));
×
170
    }
171

172
    /**
173
     * Returns or creates a query object to be used to add {@link #where(String, Object)} statements and operations
174
     */
175
    @Override
176
    public DbContextJoinedSelectBuilder query() {
177
        return this;
1✔
178
    }
179

180
    /**
181
     * If the query returns no rows, returns {@link SingleRow#absent}, if exactly one row is returned, maps it and return it,
182
     * if more than one is returned, throws `IllegalStateException`
183
     *
184
     * @param mapper Function object to map a single returned row to an object
185
     * @return the mapped row if one row is returned, {@link SingleRow#absent} otherwise
186
     * @throws MultipleRowsReturnedException if more than one row was matched the query
187
     */
188
    @Override
189
    @Nonnull
190
    public <OBJECT> SingleRow<OBJECT> singleObject(DatabaseResult.RowMapper<OBJECT> mapper) {
191
        return builder.singleObject(getConnection(), mapper);
1✔
192
    }
193

194
    /**
195
     * If you haven't called {@link #orderBy}, the results of {@link DatabaseListableQueryBuilder#list}
196
     * will be unpredictable. Call <code>unordered()</code> if you are okay with this.
197
     */
198
    public DbContextJoinedSelectBuilder unordered() {
199
        return query(builder.unordered());
1✔
200
    }
201

202
    /**
203
     * Adds an <code>order by</code> clause to the query. Needed in order to list results
204
     * in a predictable order.
205
     */
206
    @Override
207
    public DbContextJoinedSelectBuilder orderBy(String orderByClause) {
208
        return query(builder.orderBy(orderByClause));
1✔
209
    }
210

211
    /**
212
     * Adds <code>FETCH ... ROWS ONLY</code> clause to the <code>SELECT</code> statement.
213
     * FETCH FIRST was introduced in
214
     * <a href="https://en.wikipedia.org/wiki/Select_%28SQL%29#Limiting_result_rows">SQL:2008</a>
215
     * and is supported by Postgresql 8.4, Oracle 12c, IBM DB2, HSQLDB, H2, and SQL Server 2012.
216
     */
217
    public DbContextJoinedSelectBuilder limit(int rowCount) {
218
        return skipAndLimit(0, rowCount);
1✔
219
    }
220

221
    /**
222
     * Adds <code>OFFSET ... ROWS FETCH ... ROWS ONLY</code> clause to the <code>SELECT</code>
223
     * statement. FETCH FIRST was introduced in
224
     * <a href="https://en.wikipedia.org/wiki/Select_%28SQL%29#Limiting_result_rows">SQL:2008</a>
225
     * and is supported by Postgresql 8.4, Oracle 12c, IBM DB2, HSQLDB, H2, and SQL Server 2012.
226
     */
227
    public DbContextJoinedSelectBuilder skipAndLimit(int offset, int rowCount) {
228
        return query(builder.skipAndLimit(offset, rowCount));
1✔
229
    }
230

231
    /**
232
     * Adds an <code>order by</code> clause to the query. Needed in order to list results
233
     * in a predictable order.
234
     */
235
    public DbContextJoinedSelectBuilder orderBy(DatabaseColumnReference column) {
236
        return orderBy(column.getQualifiedColumnName());
1✔
237
    }
238

239
    private DbContextJoinedSelectBuilder query(@SuppressWarnings("unused") DatabaseJoinedQueryBuilder builder) {
240
        return this;
1✔
241
    }
242

243
    private Connection getConnection() {
244
        return table.getDbContext().getThreadConnection();
1✔
245
    }
246
}
247

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc