001/*
002 * Copyright 2015-2018 Transmogrify LLC.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.pyranid;
018
019import static java.lang.String.format;
020import static java.util.Collections.emptyList;
021import static java.util.Collections.unmodifiableList;
022import static java.util.Objects.requireNonNull;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.Objects;
028import java.util.Optional;
029
030/**
031 * A collection of SQL statement execution diagnostics.
032 * <p>
033 * Created via builder, for example
034 *
035 * <pre>
036 * StatementLog statementLog = StatementLog.forSql(&quot;SELECT * FROM car WHERE id=?&quot;).parameters(singletonList(123)).build();
037 * </pre>
038 *
039 * @author <a href="http://revetkn.com">Mark Allen</a>
040 * @since 1.0.0
041 */
042public class StatementLog implements Serializable {
043  private static final long serialVersionUID = 1L;
044
045  private final Optional<Long> connectionAcquisitionTime;
046  private final Optional<Long> preparationTime;
047  private final Optional<Long> executionTime;
048  private final Optional<Long> resultSetMappingTime;
049  private final String sql;
050  private final List<Object> parameters;
051  private final Optional<Integer> batchSize;
052  private final Optional<Exception> exception;
053  private final Optional<StatementMetadata> statementMetadata;
054
055  /**
056   * Creates a {@code StatementLog} for the given {@code builder}.
057   *
058   * @param builder
059   *          the builder used to construct this {@code StatementLog}
060   */
061  private StatementLog(Builder builder) {
062    requireNonNull(builder);
063    this.connectionAcquisitionTime = builder.connectionAcquisitionTime;
064    this.preparationTime = builder.preparationTime;
065    this.executionTime = builder.executionTime;
066    this.resultSetMappingTime = builder.resultSetMappingTime;
067    this.sql = requireNonNull(builder.sql);
068    this.parameters = requireNonNull(builder.parameters);
069    this.batchSize = requireNonNull(builder.batchSize);
070    this.exception = requireNonNull(builder.exception);
071    this.statementMetadata = builder.statementMetadata;
072  }
073
074  @Override
075  public String toString() {
076    return format(
077      "%s{connectionAcquisitionTime=%s, preparationTime=%s, executionTime=%s, resultSetMappingTime=%s, totalTime=%s, sql=%s, "
078          + "parameters=%s, batchSize=%s, exception=%s, statementMetadata=%s}", getClass().getSimpleName(), connectionAcquisitionTime(),
079      preparationTime(), executionTime(), resultSetMappingTime(), totalTime(), sql(), parameters(), batchSize(), exception(), statementMetadata());
080  }
081
082  @Override
083  public boolean equals(Object object) {
084    if (this == object)
085      return true;
086
087    if (!(object instanceof StatementLog))
088      return false;
089
090    StatementLog statementLog = (StatementLog) object;
091
092    return Objects.equals(connectionAcquisitionTime(), statementLog.connectionAcquisitionTime())
093        && Objects.equals(preparationTime(), statementLog.preparationTime())
094        && Objects.equals(executionTime(), statementLog.executionTime())
095        && Objects.equals(resultSetMappingTime(), statementLog.resultSetMappingTime())
096        && Objects.equals(sql(), statementLog.sql()) && Objects.equals(parameters(), statementLog.parameters())
097        && Objects.equals(batchSize(), statementLog.batchSize())
098        && Objects.equals(exception(), statementLog.exception())
099        && Objects.equals(statementMetadata(), statementLog.statementMetadata());
100  }
101
102  @Override
103  public int hashCode() {
104    return Objects.hash(connectionAcquisitionTime(), preparationTime(), executionTime(), resultSetMappingTime(), sql(),
105      parameters(), batchSize(), exception(), statementMetadata());
106  }
107
108  /**
109   * Creates a {@link StatementLog} builder for the given {@code sql}.
110   *
111   * @param sql
112   *          the SQL statement
113   * @return a {@link StatementLog} builder
114   */
115  public static Builder forSql(String sql) {
116    return new Builder(requireNonNull(sql));
117  }
118
119  /**
120   * How long did it take to acquire a {@link java.sql.Connection} from the {@link javax.sql.DataSource}, in
121   * nanoseconds?
122   *
123   * @return how long it took to acquire a {@link java.sql.Connection}, if available
124   */
125  public Optional<Long> connectionAcquisitionTime() {
126    return connectionAcquisitionTime;
127  }
128
129  /**
130   * How long did it take to bind data to the {@link java.sql.PreparedStatement}, in nanoseconds?
131   *
132   * @return how long it took to bind data to the {@link java.sql.PreparedStatement}, if available
133   */
134  public Optional<Long> preparationTime() {
135    return preparationTime;
136  }
137
138  /**
139   * How long did it take to execute the SQL statement, in nanoseconds?
140   *
141   * @return how long it took to execute the SQL statement, if available
142   */
143  public Optional<Long> executionTime() {
144    return executionTime;
145  }
146
147  /**
148   * How long did it take to extract data from the {@link java.sql.ResultSet}, in nanoseconds?
149   *
150   * @return how long it took to extract data from the {@link java.sql.ResultSet}, if available
151   */
152  public Optional<Long> resultSetMappingTime() {
153    return resultSetMappingTime;
154  }
155
156  /**
157   * How long did it take to perform the database operation in total?
158   * <p>
159   * This is the sum of {@link #connectionAcquisitionTime()} + {@link #preparationTime()} +
160   * {@link #executionTime()} + {@link #resultSetMappingTime()}.
161   *
162   * @return how long the database operation took in total
163   */
164  public Long totalTime() {
165    return connectionAcquisitionTime().orElse(0L)
166        + preparationTime().orElse(0L)
167        + executionTime().orElse(0L)
168        + resultSetMappingTime().orElse(0L);
169  }
170
171  /**
172   * The SQL statement that was executed.
173   *
174   * @return the SQL statement that was executed.
175   */
176  public String sql() {
177    return sql;
178  }
179
180  /**
181   * The parameters bound to the SQL statement that was executed.
182   *
183   * @return the parameters bound to the SQL statement that was executed, or an empty {@code List} if none
184   */
185  public List<Object> parameters() {
186    return parameters;
187  }
188
189  /**
190   * The size of the batch operation.
191   *
192   * @return how many records were processed as part of the batch operation, if available
193   */
194  public Optional<Integer> batchSize() {
195    return batchSize;
196  }
197
198  /**
199   * The exception that occurred during SQL statement execution.
200   *
201   * @return the exception that occurred during SQL statement execution, if available
202   */
203  public Optional<Exception> exception() {
204    return exception;
205  }
206
207  /**
208   * The metadata associated with this SQL statement.
209   *
210   * @return he metadata associated with this SQL statement, if available
211   */
212  public Optional<StatementMetadata> statementMetadata() {
213    return statementMetadata;
214  }
215
216  /**
217   * Builder for {@link StatementLog} instances.
218   * <p>
219   * Created via {@link StatementLog#forSql(String)}, for example
220   *
221   * <pre>
222   * StatementLog.Builder builder = StatementLog.forSql(&quot;SELECT * FROM car WHERE id=?&quot;).parameters(singletonList(123));
223   * StatementLog statementLog = builder.build();
224   * </pre>
225   *
226   * @author <a href="http://revetkn.com">Mark Allen</a>
227   * @since 1.0.0
228   */
229  public static class Builder {
230    private final String sql;
231    private Optional<Long> connectionAcquisitionTime = Optional.empty();
232    private Optional<Long> preparationTime = Optional.empty();
233    private Optional<Long> executionTime = Optional.empty();
234    private Optional<Long> resultSetMappingTime = Optional.empty();
235    private List<Object> parameters = emptyList();
236    private Optional<Integer> batchSize = Optional.empty();
237    private Optional<Exception> exception = Optional.empty();
238    private Optional<StatementMetadata> statementMetadata;
239
240    /**
241     * Creates a {@code Builder} for the given {@code sql}.
242     *
243     * @param sql
244     *          the SQL statement
245     */
246    private Builder(String sql) {
247      this.sql = requireNonNull(sql);
248    }
249
250    /**
251     * Specifies how long it took to acquire a {@link java.sql.Connection} from the {@link javax.sql.DataSource}, in
252     * nanoseconds.
253     *
254     * @param connectionAcquisitionTime
255     *          how long it took to acquire a {@link java.sql.Connection}, if available
256     * @return this {@code Builder}, for chaining
257     */
258    public Builder connectionAcquisitionTime(Optional<Long> connectionAcquisitionTime) {
259      this.connectionAcquisitionTime = requireNonNull(connectionAcquisitionTime);
260      return this;
261    }
262
263    /**
264     * Specifies how long it took to bind data to a {@link java.sql.PreparedStatement}, in nanoseconds.
265     *
266     * @param preparationTime
267     *          how long it took to bind data to a {@link java.sql.PreparedStatement}, if available
268     * @return this {@code Builder}, for chaining
269     */
270    public Builder preparationTime(Optional<Long> preparationTime) {
271      this.preparationTime = requireNonNull(preparationTime);
272      return this;
273    }
274
275    /**
276     * Specifies how long it took to execute a SQL statement, in nanoseconds.
277     *
278     * @param executionTime
279     *          how long it took to execute a SQL statement, if available
280     * @return this {@code Builder}, for chaining
281     */
282    public Builder executionTime(Optional<Long> executionTime) {
283      this.executionTime = requireNonNull(executionTime);
284      return this;
285    }
286
287    /**
288     * Specifies how long it took to extract data from a {@link java.sql.ResultSet}, in nanoseconds.
289     *
290     * @param resultSetMappingTime
291     *          how long it took to extract data from a {@link java.sql.ResultSet}, if available
292     * @return this {@code Builder}, for chaining
293     */
294    public Builder resultSetMappingTime(Optional<Long> resultSetMappingTime) {
295      this.resultSetMappingTime = requireNonNull(resultSetMappingTime);
296      return this;
297    }
298
299    /**
300     * The parameters bound to the SQL statement that was executed.
301     *
302     * @param parameters
303     *          the parameters bound to the SQL statement that was executed, or an empty {@code List} if none
304     * @return this {@code Builder}, for chaining
305     */
306    public Builder parameters(List<Object> parameters) {
307      this.parameters = unmodifiableList(new ArrayList<>(requireNonNull(parameters)));
308      return this;
309    }
310
311    /**
312     * Specifies the size of the batch operation.
313     *
314     * @param batchSize
315     *          how many records were processed as part of the batch operation, if available
316     * @return this {@code Builder}, for chaining
317     */
318    public Builder batchSize(Optional<Integer> batchSize) {
319      this.batchSize = requireNonNull(batchSize);
320      return this;
321    }
322
323    /**
324     * Specifies the exception that occurred during SQL statement execution.
325     *
326     * @param exception
327     *          the exception that occurred during SQL statement execution, if available
328     * @return this {@code Builder}, for chaining
329     */
330    public Builder exception(Optional<Exception> exception) {
331      this.exception = requireNonNull(exception);
332      return this;
333    }
334
335    /**
336     * Specifies metadata associated with this SQL statement.
337     *
338     * @param statementMetadata
339     *          the metadata associated with this SQL statement, if available
340     * @return this {@code Builder}, for chaining
341     */
342    public Builder statementMetadata(Optional<StatementMetadata> statementMetadata) {
343      this.statementMetadata = requireNonNull(statementMetadata);
344      return this;
345    }
346
347    /**
348     * Constructs a {@code StatementLog} instance.
349     *
350     * @return a {@code StatementLog} instance
351     */
352    public StatementLog build() {
353      return new StatementLog(this);
354    }
355  }
356}