From b109886ec12ff41234c71e613234d7cb88ec7b9a Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Thu, 16 Feb 2023 10:01:53 +0100 Subject: [PATCH] [jOOQ/jOOQ#14652] Add a SQLExceptionLoggerListener that logs additional information to help debug constraint violations --- .../src/main/java/org/jooq/conf/Settings.java | 44 +++- .../java/org/jooq/impl/ExecuteListeners.java | 12 + .../org/jooq/impl/FieldMapsForInsert.java | 23 +- .../java/org/jooq/impl/InsertQueryImpl.java | 2 +- .../jooq/impl/SQLExceptionLoggerListener.java | 208 ++++++++++++++++++ .../org/jooq/xsd/jooq-runtime-3.18.0.xsd | 6 +- 6 files changed, 288 insertions(+), 7 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/impl/SQLExceptionLoggerListener.java diff --git a/jOOQ/src/main/java/org/jooq/conf/Settings.java b/jOOQ/src/main/java/org/jooq/conf/Settings.java index eadc3a1b38..661178c0cc 100644 --- a/jOOQ/src/main/java/org/jooq/conf/Settings.java +++ b/jOOQ/src/main/java/org/jooq/conf/Settings.java @@ -342,6 +342,8 @@ public class Settings @XmlElement(defaultValue = "true") protected Boolean executeLogging = true; @XmlElement(defaultValue = "true") + protected Boolean executeLoggingSQLExceptions = true; + @XmlElement(defaultValue = "true") protected Boolean diagnosticsLogging = true; @XmlElement(defaultValue = "DEFAULT") @XmlSchemaType(name = "string") @@ -4171,7 +4173,7 @@ public class Settings } /** - * When set to true, this will add jOOQ's default logging ExecuteListeners. + * When set to true, this will add jOOQ's default {@link org.jooq.tools.LoggerListener} for debug logging. This is meant for use in development only. * * @return * possible object is @@ -4194,6 +4196,30 @@ public class Settings this.executeLogging = value; } + /** + * [#14420] Whether constraint violations and other {@link java.sql.SQLException} should produce additional log information about the column name and data causing the problem. Unlike {@link #executeLogging}, this is meant for use in production as well as development. This feature is available only in commercial distributions. + * + * @return + * possible object is + * {@link Boolean } + * + */ + public Boolean isExecuteLoggingSQLExceptions() { + return executeLoggingSQLExceptions; + } + + /** + * Sets the value of the executeLoggingSQLExceptions property. + * + * @param value + * allowed object is + * {@link Boolean } + * + */ + public void setExecuteLoggingSQLExceptions(Boolean value) { + this.executeLoggingSQLExceptions = value; + } + /** * When set to true, this will add jOOQ's default logging DiagnosticsListeners. * @@ -6777,6 +6803,11 @@ public class Settings return this; } + public Settings withExecuteLoggingSQLExceptions(Boolean value) { + setExecuteLoggingSQLExceptions(value); + return this; + } + public Settings withDiagnosticsLogging(Boolean value) { setDiagnosticsLogging(value); return this; @@ -7516,6 +7547,7 @@ public class Settings builder.append("executeListenerStartInvocationOrder", executeListenerStartInvocationOrder); builder.append("executeListenerEndInvocationOrder", executeListenerEndInvocationOrder); builder.append("executeLogging", executeLogging); + builder.append("executeLoggingSQLExceptions", executeLoggingSQLExceptions); builder.append("diagnosticsLogging", diagnosticsLogging); builder.append("diagnosticsConnection", diagnosticsConnection); builder.append("updateRecordVersion", updateRecordVersion); @@ -8831,6 +8863,15 @@ public class Settings return false; } } + if (executeLoggingSQLExceptions == null) { + if (other.executeLoggingSQLExceptions!= null) { + return false; + } + } else { + if (!executeLoggingSQLExceptions.equals(other.executeLoggingSQLExceptions)) { + return false; + } + } if (diagnosticsLogging == null) { if (other.diagnosticsLogging!= null) { return false; @@ -9684,6 +9725,7 @@ public class Settings result = ((prime*result)+((executeListenerStartInvocationOrder == null)? 0 :executeListenerStartInvocationOrder.hashCode())); result = ((prime*result)+((executeListenerEndInvocationOrder == null)? 0 :executeListenerEndInvocationOrder.hashCode())); result = ((prime*result)+((executeLogging == null)? 0 :executeLogging.hashCode())); + result = ((prime*result)+((executeLoggingSQLExceptions == null)? 0 :executeLoggingSQLExceptions.hashCode())); result = ((prime*result)+((diagnosticsLogging == null)? 0 :diagnosticsLogging.hashCode())); result = ((prime*result)+((diagnosticsConnection == null)? 0 :diagnosticsConnection.hashCode())); result = ((prime*result)+((updateRecordVersion == null)? 0 :updateRecordVersion.hashCode())); diff --git a/jOOQ/src/main/java/org/jooq/impl/ExecuteListeners.java b/jOOQ/src/main/java/org/jooq/impl/ExecuteListeners.java index 352387241c..e8d9960827 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ExecuteListeners.java +++ b/jOOQ/src/main/java/org/jooq/impl/ExecuteListeners.java @@ -59,9 +59,13 @@ import org.jooq.tools.LoggerListener; * @author Lukas Eder */ final class ExecuteListeners implements ExecuteListener { + private static final ExecuteListener EMPTY_LISTENER = new DefaultExecuteListener(); private static final JooqLogger LOGGER_LISTENER_LOGGER = JooqLogger.getLogger(LoggerListener.class); + + + private final ExecuteListener[][] listeners; // In some setups, these two events may get mixed up chronologically by the @@ -116,6 +120,14 @@ final class ExecuteListeners implements ExecuteListener { (list = init(list)).add(new LoggerListener()); } + + + + + + + + for (ExecuteListenerProvider provider : ctx.configuration().executeListenerProviders()) // Could be null after deserialisation diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java b/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java index 2e6694734f..3ab6dcda72 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldMapsForInsert.java @@ -54,12 +54,14 @@ import static org.jooq.impl.DSL.select; import static org.jooq.impl.Keywords.K_DEFAULT_VALUES; import static org.jooq.impl.Keywords.K_VALUES; import static org.jooq.impl.QueryPartCollectionView.wrap; +import static org.jooq.impl.Tools.EMPTY_FIELD; import static org.jooq.impl.Tools.anyMatch; import static org.jooq.impl.Tools.filter; import static org.jooq.impl.Tools.flatten; import static org.jooq.impl.Tools.flattenCollection; import static org.jooq.impl.Tools.flattenFieldOrRows; import static org.jooq.impl.Tools.lazy; +import static org.jooq.impl.Tools.row0; import java.util.AbstractList; import java.util.AbstractMap; @@ -86,6 +88,7 @@ import org.jooq.Param; // ... import org.jooq.Record; import org.jooq.RenderContext.CastMode; +import org.jooq.Row; import org.jooq.SQLDialect; import org.jooq.Select; import org.jooq.Table; @@ -531,9 +534,23 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple nextRow++; } - final List, Field>> maps() { - initNextRow(); + final List rows() { + List, Field>> maps = maps(); + return new AbstractList() { + @Override + public Row get(int index) { + return row0(maps.get(index).values().toArray(EMPTY_FIELD)); + } + + @Override + public int size() { + return rows; + } + }; + } + + final List, Field>> maps() { return new AbstractList, Field>>() { @Override public Map, Field> get(int index) { @@ -548,8 +565,6 @@ final class FieldMapsForInsert extends AbstractQueryPart implements UNotYetImple } final Map, Field> map(final int index) { - initNextRow(); - return new AbstractMap, Field>() { transient Set, Field>> entrySet; diff --git a/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java b/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java index 203cc52633..b1ba763927 100644 --- a/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/InsertQueryImpl.java @@ -1215,7 +1215,7 @@ implements @Override public final UnmodifiableList $values() { - return QOM.unmodifiable(new ArrayList<>()); + return QOM.unmodifiable(insertMaps.rows()); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLExceptionLoggerListener.java b/jOOQ/src/main/java/org/jooq/impl/SQLExceptionLoggerListener.java new file mode 100644 index 0000000000..2e56e94bc8 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/SQLExceptionLoggerListener.java @@ -0,0 +1,208 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: https://www.jooq.org/legal/licensing + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.impl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.18.0.xsd b/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.18.0.xsd index a8ab7be1a8..aa1ab8bd74 100644 --- a/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.18.0.xsd +++ b/jOOQ/src/main/resources/org/jooq/xsd/jooq-runtime-3.18.0.xsd @@ -1220,7 +1220,11 @@ case of which, this defaults to INLINED]]> - + + + + +