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

torand / FasterSQL / 15346133148

30 May 2025 11:50AM UTC coverage: 70.397% (+0.1%) from 70.298%
15346133148

push

github

torand
chore: test javadoc publish

235 of 420 branches covered (55.95%)

Branch coverage included in aggregate %.

1237 of 1671 relevant lines covered (74.03%)

3.9 hits per line

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

88.33
/src/main/java/io/github/torand/fastersql/statement/InsertBatchStatement.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.dialect.AnsiIsoDialect;
19
import io.github.torand.fastersql.dialect.OracleDialect;
20
import io.github.torand.fastersql.model.Column;
21
import io.github.torand.fastersql.model.Table;
22
import io.github.torand.fastersql.sql.Context;
23

24
import java.util.Collection;
25
import java.util.List;
26
import java.util.Optional;
27
import java.util.function.Function;
28
import java.util.stream.Stream;
29

30
import static io.github.torand.fastersql.sql.Command.INSERT;
31
import static io.github.torand.fastersql.util.collection.CollectionHelper.asList;
32
import static io.github.torand.fastersql.util.collection.CollectionHelper.concat;
33
import static io.github.torand.fastersql.util.collection.CollectionHelper.isEmpty;
34
import static io.github.torand.fastersql.util.collection.CollectionHelper.streamSafely;
35
import static io.github.torand.fastersql.util.contract.Requires.requireNonEmpty;
36
import static java.util.Objects.isNull;
37
import static java.util.Objects.requireNonNull;
38
import static java.util.stream.Collectors.joining;
39

40
/**
41
 * Implements an INSERT statement for batch (multi-row) insertion.
42
 * @param <T> the batch entity type.
43
 */
44
public class InsertBatchStatement<T> implements PreparableStatement {
45
    private final Table<?> table;
46
    private final List<ColumnValueExtractor<? super T>> columnValueExtractors;
47
    private final List<? extends T> entities;
48

49
    InsertBatchStatement(Collection<? extends T> entities, Table<?> table, Collection<ColumnValueExtractor<? super T>> columnValueExtractors) {
2✔
50
        this.entities = asList(requireNonEmpty(entities, "No entities specified"));
8✔
51
        this.table = requireNonNull(table, "No table specified");
6✔
52
        this.columnValueExtractors = asList(columnValueExtractors);
4✔
53
    }
1✔
54

55
    /**
56
     * Registers a column-value mapper.
57
     * @param column the column.
58
     * @param valueExtractor the value extractor.
59
     * @return the modified statement.
60
     */
61
    public InsertBatchStatement<T> value(Column column, Function<? super T, Object> valueExtractor) {
62
        requireNonNull(column, "No column specified");
4✔
63
        requireNonNull(valueExtractor, "No value extractor specified");
4✔
64

65
        List<ColumnValueExtractor<? super T>> concatenated = concat(columnValueExtractors, new ColumnValueExtractor<>(column, valueExtractor));
14✔
66
        return new InsertBatchStatement<>(entities, table, concatenated);
9✔
67
    }
68

69
    @Override
70
    public String sql(Context context) {
71
        final Context localContext = context.withCommand(INSERT);
4✔
72
        validate();
2✔
73

74
        if (context.getDialect() instanceof OracleDialect) {
4✔
75

76
            // INSERT ALL
77
            //   INTO t (col1, col2, col3) VALUES ('val1_1', 'val1_2', 'val1_3')
78
            //   INTO t (col1, col2, col3) VALUES ('val2_1', 'val2_2', 'val2_3')
79
            //   INTO t (col1, col2, col3) VALUES ('val3_1', 'val3_2', 'val3_3')
80
            // SELECT 1 FROM DUAL;
81

82
            StringBuilder sb = new StringBuilder();
4✔
83
            sb.append("insert all");
4✔
84
            entities().forEach(e -> {
7✔
85
                sb.append(" into ").append(table.sql(localContext));
9✔
86
                sb.append(" (");
4✔
87
                sb.append(columnValueExtractors().map(cve -> cve.column().sql(localContext)).collect(joining(", ")));
17✔
88
                sb.append(") values (");
4✔
89
                sb.append(columnValueExtractors().map(cve -> cve.valueSql(localContext, e)).collect(joining(", ")));
18✔
90
                sb.append(")");
4✔
91
            });
1✔
92

93
            sb.append(" select 1 from DUAL");
4✔
94

95
            return sb.toString();
3✔
96
        } else {
97

98
            // INSERT INTO t
99
            //   (col1, col2, col3)
100
            // VALUES
101
            //   ('val1_1', 'val1_2', 'val1_3'),
102
            //   ('val2_1', 'val2_2', 'val2_3'),
103
            //   ('val3_1', 'val3_2', 'val3_3');
104

105
            StringBuilder sb = new StringBuilder();
4✔
106
            sb.append("insert into ").append(table.sql(context));
9✔
107
            sb.append(" (");
4✔
108
            sb.append(columnValueExtractors().map(cve -> cve.column().sql(localContext)).collect(joining(", ")));
17✔
109
            sb.append(") values ");
4✔
110
            sb.append(entities().map(e -> "("
12✔
111
                + columnValueExtractors().map(cve -> cve.valueSql(localContext, e)).collect(joining(", "))
15✔
112
                + ")")
113
                .collect(joining(", ")));
3✔
114

115
            return sb.toString();
3✔
116
        }
117
    }
118

119
    private Stream<? extends T> entities() {
120
        return streamSafely(entities);
4✔
121
    }
122

123
    private Stream<ColumnValueExtractor<? super T>> columnValueExtractors() {
124
        return streamSafely(columnValueExtractors);
4✔
125
    }
126

127
    @Override
128
    public Stream<Object> params(Context context) {
129
        return entities()
5✔
130
            .flatMap(e -> columnValueExtractors()
6✔
131
                .map(cve -> cve.valueParam(e))
6✔
132
                .flatMap(Optional::stream));
1✔
133
    }
134

135
    private void validate() {
136
        if (isNull(table)) {
4!
137
            throw new IllegalStateException("No table specified");
×
138
        }
139
        if (isEmpty(columnValueExtractors)) {
4!
140
            throw new IllegalStateException("No values specified");
×
141
        }
142
        validateColumnTableRelations(columnValueExtractors().map(ColumnValueExtractor::column));
6✔
143
    }
1✔
144

145
    private void validateColumnTableRelations(Stream<Column> columns) {
146
        columns
3✔
147
            .filter(c -> !table.name().equals(c.table().name()))
11!
148
            .findFirst()
2✔
149
            .ifPresent(c -> {
1✔
150
                throw new IllegalStateException("Column " + c.name() + " belongs to table " + c.table().name() + ", not the table specified by the INTO clause");
×
151
            });
152
    }
1✔
153

154
    @Override
155
    public String toString() {
156
        return toString(new AnsiIsoDialect());
×
157
    }
158
}
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