001/* 002 * Copyright 2015-2022 Transmogrify LLC, 2022-2024 Revetware 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 javax.annotation.Nonnull; 020import javax.annotation.Nullable; 021import javax.annotation.concurrent.NotThreadSafe; 022import javax.annotation.concurrent.ThreadSafe; 023import java.time.Duration; 024import java.util.ArrayList; 025import java.util.List; 026import java.util.Objects; 027import java.util.Optional; 028import java.util.stream.Collectors; 029 030import static java.lang.String.format; 031import static java.util.Objects.requireNonNull; 032 033/** 034 * A collection of SQL statement execution diagnostics. 035 * 036 * @author <a href="https://www.revetkn.com">Mark Allen</a> 037 * @since 1.0.0 038 */ 039@ThreadSafe 040public class StatementLog<T> { 041 @Nonnull 042 private final StatementContext<T> statementContext; 043 @Nonnull 044 private final Duration totalDuration; 045 @Nullable 046 private final Duration connectionAcquisitionDuration; 047 @Nullable 048 private final Duration preparationDuration; 049 @Nullable 050 private final Duration executionDuration; 051 @Nullable 052 private final Duration resultSetMappingDuration; 053 @Nullable 054 private final Integer batchSize; 055 @Nullable 056 private final Exception exception; 057 058 /** 059 * Creates a {@code StatementLog} for the given {@code builder}. 060 * 061 * @param builder the builder used to construct this {@code StatementLog} 062 */ 063 private StatementLog(@Nonnull Builder builder) { 064 requireNonNull(builder); 065 066 this.statementContext = requireNonNull(builder.statementContext); 067 this.connectionAcquisitionDuration = builder.connectionAcquisitionDuration; 068 this.preparationDuration = builder.preparationDuration; 069 this.executionDuration = builder.executionDuration; 070 this.resultSetMappingDuration = builder.resultSetMappingDuration; 071 this.batchSize = builder.batchSize; 072 this.exception = builder.exception; 073 074 Duration totalDuration = Duration.ZERO; 075 076 if (this.connectionAcquisitionDuration != null) 077 totalDuration = totalDuration.plus(this.connectionAcquisitionDuration); 078 079 if (this.preparationDuration != null) 080 totalDuration = totalDuration.plus(this.preparationDuration); 081 082 if (this.executionDuration != null) 083 totalDuration = totalDuration.plus(this.executionDuration); 084 085 if (this.resultSetMappingDuration != null) 086 totalDuration = totalDuration.plus(this.resultSetMappingDuration); 087 088 this.totalDuration = totalDuration; 089 } 090 091 /** 092 * Creates a {@link StatementLog} builder for the given {@code statementContext}. 093 * 094 * @param statementContext current SQL context 095 * @return a {@link StatementLog} builder 096 */ 097 @Nonnull 098 public static <T> Builder forStatementContext(@Nonnull StatementContext<T> statementContext) { 099 requireNonNull(statementContext); 100 return new Builder(statementContext); 101 } 102 103 @Override 104 public String toString() { 105 List<String> components = new ArrayList<>(8); 106 107 components.add(format("statementContext=%s", getStatementContext())); 108 components.add(format("totalDuration=%s", getTotalDuration())); 109 110 Duration connectionAcquisitionDuration = getConnectionAcquisitionDuration().orElse(null); 111 112 if (connectionAcquisitionDuration != null) 113 components.add(format("connectionAcquisitionDuration=%s", connectionAcquisitionDuration)); 114 115 Duration preparationDuration = getPreparationDuration().orElse(null); 116 117 if (preparationDuration != null) 118 components.add(format("preparationDuration=%s", preparationDuration)); 119 120 Duration executionDuration = getExecutionDuration().orElse(null); 121 122 if (executionDuration != null) 123 components.add(format("executionDuration=%s", executionDuration)); 124 125 Duration resultSetMappingDuration = getResultSetMappingDuration().orElse(null); 126 127 if (resultSetMappingDuration != null) 128 components.add(format("resultSetMappingDuration=%s", resultSetMappingDuration)); 129 130 Integer batchSize = getBatchSize().orElse(null); 131 132 if (batchSize != null) 133 components.add(format("batchSize=%s", batchSize)); 134 135 Exception exception = getException().orElse(null); 136 137 if (exception != null) 138 components.add(format("exception=%s", exception)); 139 140 return format("%s{%s}", getClass().getSimpleName(), components.stream().collect(Collectors.joining(", "))); 141 } 142 143 @Override 144 public boolean equals(Object object) { 145 if (this == object) 146 return true; 147 148 if (!(object instanceof StatementLog)) 149 return false; 150 151 StatementLog statementLog = (StatementLog) object; 152 153 return Objects.equals(getStatementContext(), statementLog.getStatementContext()) 154 && Objects.equals(getConnectionAcquisitionDuration(), statementLog.getConnectionAcquisitionDuration()) 155 && Objects.equals(getPreparationDuration(), statementLog.getPreparationDuration()) 156 && Objects.equals(getExecutionDuration(), statementLog.getExecutionDuration()) 157 && Objects.equals(getResultSetMappingDuration(), statementLog.getResultSetMappingDuration()) 158 && Objects.equals(getBatchSize(), statementLog.getBatchSize()) 159 && Objects.equals(getException(), statementLog.getException()); 160 } 161 162 @Override 163 public int hashCode() { 164 return Objects.hash(getStatementContext(), getConnectionAcquisitionDuration(), getPreparationDuration(), 165 getExecutionDuration(), getResultSetMappingDuration(), getBatchSize(), getException()); 166 } 167 168 /** 169 * How long did it take to acquire a {@link java.sql.Connection} from the {@link javax.sql.DataSource}? 170 * 171 * @return how long it took to acquire a {@link java.sql.Connection}, if available 172 */ 173 @Nonnull 174 public Optional<Duration> getConnectionAcquisitionDuration() { 175 return Optional.ofNullable(this.connectionAcquisitionDuration); 176 } 177 178 /** 179 * How long did it take to bind data to the {@link java.sql.PreparedStatement}? 180 * 181 * @return how long it took to bind data to the {@link java.sql.PreparedStatement}, if available 182 */ 183 @Nonnull 184 public Optional<Duration> getPreparationDuration() { 185 return Optional.ofNullable(this.preparationDuration); 186 } 187 188 /** 189 * How long did it take to execute the SQL statement? 190 * 191 * @return how long it took to execute the SQL statement, if available 192 */ 193 public Optional<Duration> getExecutionDuration() { 194 return Optional.ofNullable(this.executionDuration); 195 } 196 197 /** 198 * How long did it take to extract data from the {@link java.sql.ResultSet}? 199 * 200 * @return how long it took to extract data from the {@link java.sql.ResultSet}, if available 201 */ 202 public Optional<Duration> getResultSetMappingDuration() { 203 return Optional.ofNullable(this.resultSetMappingDuration); 204 } 205 206 /** 207 * How long did it take to perform the database operation in total? 208 * <p> 209 * This is the sum of {@link #getConnectionAcquisitionDuration()} + {@link #getPreparationDuration()} + 210 * {@link #getExecutionDuration()} + {@link #getResultSetMappingDuration()}. 211 * 212 * @return how long the database operation took in total 213 */ 214 @Nonnull 215 public Duration getTotalDuration() { 216 return this.totalDuration; 217 } 218 219 /** 220 * The SQL statement that was executed. 221 * 222 * @return the SQL statement that was executed. 223 */ 224 @Nonnull 225 public StatementContext<T> getStatementContext() { 226 return this.statementContext; 227 } 228 229 /** 230 * The size of the batch operation. 231 * 232 * @return how many records were processed as part of the batch operation, if available 233 */ 234 @Nonnull 235 public Optional<Integer> getBatchSize() { 236 return Optional.ofNullable(this.batchSize); 237 } 238 239 /** 240 * The exception that occurred during SQL statement execution. 241 * 242 * @return the exception that occurred during SQL statement execution, if available 243 */ 244 @Nonnull 245 public Optional<Exception> getException() { 246 return Optional.ofNullable(this.exception); 247 } 248 249 /** 250 * Builder used to construct instances of {@link StatementLog}. 251 * <p> 252 * This class is intended for use by a single thread. 253 * 254 * @author <a href="https://www.revetkn.com">Mark Allen</a> 255 * @since 1.0.0 256 */ 257 @NotThreadSafe 258 public static class Builder<T> { 259 @Nonnull 260 private final StatementContext<T> statementContext; 261 @Nullable 262 private Duration connectionAcquisitionDuration; 263 @Nullable 264 private Duration preparationDuration; 265 @Nullable 266 private Duration executionDuration; 267 @Nullable 268 private Duration resultSetMappingDuration; 269 @Nullable 270 private Integer batchSize; 271 @Nullable 272 private Exception exception; 273 274 /** 275 * Creates a {@code Builder} for the given {@code statementContext}. 276 * 277 * @param statementContext current SQL context 278 */ 279 private Builder(@Nonnull StatementContext<T> statementContext) { 280 requireNonNull(statementContext); 281 this.statementContext = statementContext; 282 } 283 284 /** 285 * Specifies how long it took to acquire a {@link java.sql.Connection} from the {@link javax.sql.DataSource}. 286 * 287 * @param connectionAcquisitionDuration how long it took to acquire a {@link java.sql.Connection}, if available 288 * @return this {@code Builder}, for chaining 289 */ 290 @Nonnull 291 public Builder connectionAcquisitionDuration(@Nullable Duration connectionAcquisitionDuration) { 292 this.connectionAcquisitionDuration = connectionAcquisitionDuration; 293 return this; 294 } 295 296 /** 297 * Specifies how long it took to bind data to a {@link java.sql.PreparedStatement}. 298 * 299 * @param preparationDuration how long it took to bind data to a {@link java.sql.PreparedStatement}, if available 300 * @return this {@code Builder}, for chaining 301 */ 302 public Builder preparationDuration(@Nullable Duration preparationDuration) { 303 this.preparationDuration = preparationDuration; 304 return this; 305 } 306 307 /** 308 * Specifies how long it took to execute a SQL statement. 309 * 310 * @param executionDuration how long it took to execute a SQL statement, if available 311 * @return this {@code Builder}, for chaining 312 */ 313 public Builder executionDuration(@Nullable Duration executionDuration) { 314 this.executionDuration = executionDuration; 315 return this; 316 } 317 318 /** 319 * Specifies how long it took to extract data from a {@link java.sql.ResultSet}. 320 * 321 * @param resultSetMappingDuration how long it took to extract data from a {@link java.sql.ResultSet}, if available 322 * @return this {@code Builder}, for chaining 323 */ 324 public Builder resultSetMappingDuration(@Nullable Duration resultSetMappingDuration) { 325 this.resultSetMappingDuration = resultSetMappingDuration; 326 return this; 327 } 328 329 /** 330 * Specifies the size of the batch operation. 331 * 332 * @param batchSize how many records were processed as part of the batch operation, if available 333 * @return this {@code Builder}, for chaining 334 */ 335 public Builder batchSize(@Nullable Integer batchSize) { 336 this.batchSize = batchSize; 337 return this; 338 } 339 340 /** 341 * Specifies the exception that occurred during SQL statement execution. 342 * 343 * @param exception the exception that occurred during SQL statement execution, if available 344 * @return this {@code Builder}, for chaining 345 */ 346 public Builder exception(@Nullable Exception exception) { 347 this.exception = exception; 348 return this; 349 } 350 351 /** 352 * Constructs a {@code StatementLog} instance. 353 * 354 * @return a {@code StatementLog} instance 355 */ 356 @Nonnull 357 public StatementLog build() { 358 return new StatementLog(this); 359 } 360 } 361}