jdaugherty commented on code in PR #16631: URL: https://github.com/apache/grails-core/pull/16631#discussion_r4372245786
########## grails-data-hibernate7/core/src/main/groovy/org/apache/grails/data/hibernate7/core/querytail/QueryTailStatementInspector.groovy: ########## @@ -0,0 +1,319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ +package org.apache.grails.data.hibernate7.core.querytail + +import groovy.transform.CompileStatic +import groovy.transform.PackageScope +import org.hibernate.SessionFactory +import org.hibernate.SessionFactoryObserver +import org.hibernate.cfg.AvailableSettings +import org.hibernate.cfg.Configuration +import org.hibernate.dialect.AbstractTransactSQLDialect +import org.hibernate.dialect.CockroachDialect +import org.hibernate.dialect.DB2Dialect +import org.hibernate.dialect.Dialect +import org.hibernate.dialect.H2Dialect +import org.hibernate.dialect.HANADialect +import org.hibernate.dialect.HSQLDialect +import org.hibernate.dialect.MySQLDialect +import org.hibernate.dialect.OracleDialect +import org.hibernate.dialect.PostgreSQLDialect +import org.hibernate.dialect.SpannerDialect +import org.hibernate.dialect.SpannerPostgreSQLDialect +import org.hibernate.engine.spi.SessionFactoryImplementor +import org.hibernate.resource.jdbc.spi.StatementInspector +import org.springframework.beans.BeanUtils +import org.springframework.util.ClassUtils + +import org.apache.grails.data.core.querytail.QueryTailCapture +import org.apache.grails.data.core.querytail.QueryTailSettings +import org.apache.grails.data.core.querytail.QueryTailSyntax +import org.grails.datastore.mapping.core.exceptions.ConfigurationException + +/** + * The statement inspector GORM installs on a session factory when query-tail capture is enabled. It + * records every statement Hibernate is about to run to the {@link QueryTailCapture} of the + * application, without its values, and returns the statement as it found it: the database runs + * it unchanged. An inspector the application configured itself through + * {@code hibernate.session_factory.statement_inspector} keeps running: it sees the statement + * first, and the statement it returns is the one run and recorded. + * + * <p>The session factory owns the capture. The inspector observes it: it opens the capture when + * the session factory is created, takes from its dialect how the database quotes values, and + * closes the capture when the session factory closes, so a session factory that fails to start + * leaves no capture open either. An observer the application set on the configuration keeps + * being called.</p> + * + * <p>Nothing of this is installed unless {@link QueryTailSettings#SETTING_ENABLED} is set, so an + * application that does not capture pays nothing per statement.</p> + * + * @since 8.1 + */ +@CompileStatic +class QueryTailStatementInspector implements StatementInspector, SessionFactoryObserver { + + @Serial + private static final long serialVersionUID = 1L + + /** The community dialects, which are not a dependency, that the inspector tells apart by name. */ + private static final String COMMUNITY_INFORMIX = 'org.hibernate.community.dialect.InformixDialect' + private static final String COMMUNITY_SQL_ANYWHERE = 'org.hibernate.community.dialect.SybaseAnywhereDialect' + + /** The connection source name statements are recorded under. */ + final String dataSourceName + + /** The file statements are recorded to. */ + final File file + + /** The inspector the application configured, or null. */ + final StatementInspector delegate + + /** The session factory observer the application configured, or null. */ + final SessionFactoryObserver observer + + private transient volatile QueryTailCapture capture + private transient boolean closed + + /** + * @param dataSourceName the connection source the inspected session factory belongs to + * @param file the capture file statements are recorded to + * @param delegate the inspector the application configured, or null + * @param observer the session factory observer the application configured, or null + */ + QueryTailStatementInspector(String dataSourceName, File file, StatementInspector delegate, + SessionFactoryObserver observer) { + this.dataSourceName = dataSourceName + this.file = file + this.delegate = delegate + this.observer = observer + } + + /** + * Installs the inspector on the configuration of a session factory when capture is enabled, as + * its statement inspector and as the observer of the session factory it builds. + * + * @param dataSourceName the connection source the session factory is built for + * @param settings the query-tail settings of the connection source + * @param configuration the configuration the session factory is built from + * @return the installed inspector, or null when capture is not enabled + * @throws ConfigurationException when the inspector the application configured cannot be created + */ + static QueryTailStatementInspector install(String dataSourceName, QueryTailSettings settings, + Configuration configuration) { + if (settings == null || !settings.enabled) { + return null + } + Properties properties = configuration.properties + StatementInspector configured = resolve(properties.get(AvailableSettings.STATEMENT_INSPECTOR)) Review Comment: Follow-up in ea3558f3c6. The capture now chooses the inspector to wrap when the session factory is built, not when the connection source is configured. That covers every way a `configClass` can set an inspector: - **`setStatementInspector`:** called in the constructor, or in an overridden `buildSessionFactory` before it calls `super` (Hibernate 7). - **Registry builder:** a setting applied to the builder that an overridden `createStandardServiceRegistryBuilder` returns (Hibernate 5 and 7). Before this change, GORM applied the configuration's properties, which held the capture, over the builder's settings. Once capture was on, the application's inspector stopped running. The installer now only registers the capture on GORM's configuration. Once the configuration has merged the registry settings, it hands them to the capture. The capture then wraps the inspector the session factory would otherwise run: - the one set on the configuration, on Hibernate 7; - else the one the merged registry settings name. A new data-driven feature on both versions checks, for each way of configuring an inspector, that the inspector that runs with capture on is the one that runs with it off. On b8c346b48c, the registry-builder row fails on both versions, and so does the overridden-`buildSessionFactory` row on Hibernate 7. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
