Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add duplicate column detection in EntityProvider #1267

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.seasar.doma.jdbc.DuplicateColumnHandler;
import org.seasar.doma.jdbc.JdbcMappingVisitor;
import org.seasar.doma.jdbc.Naming;
import org.seasar.doma.jdbc.ResultMappingException;
Expand All @@ -49,6 +50,8 @@ public class EntityProvider<ENTITY> extends AbstractObjectProvider<ENTITY> {

protected final UnknownColumnHandler unknownColumnHandler;

protected final DuplicateColumnHandler duplicateColumnHandler;

protected Map<Integer, EntityPropertyType<ENTITY, ?>> indexMap;

public EntityProvider(EntityType<ENTITY> entityType, Query query, boolean resultMappingEnsured) {
Expand All @@ -58,6 +61,7 @@ public EntityProvider(EntityType<ENTITY> entityType, Query query, boolean result
this.resultMappingEnsured = resultMappingEnsured;
this.jdbcMappingVisitor = query.getConfig().getDialect().getJdbcMappingVisitor();
this.unknownColumnHandler = query.getConfig().getUnknownColumnHandler();
this.duplicateColumnHandler = query.getConfig().getDuplicateColumnHandler();
}

@Override
Expand Down Expand Up @@ -91,10 +95,14 @@ protected ENTITY build(ResultSet resultSet) throws SQLException {
HashMap<String, EntityPropertyType<ENTITY, ?>> columnNameMap = createColumnNameMap(entityType);
Set<EntityPropertyType<ENTITY, ?>> unmappedPropertySet =
resultMappingEnsured ? new HashSet<>(columnNameMap.values()) : new HashSet<>();
Set<String> seenColumnNames = new HashSet<>();
int count = resultSetMeta.getColumnCount();
for (int i = 1; i < count + 1; i++) {
String columnName = resultSetMeta.getColumnLabel(i);
String lowerCaseColumnName = columnName.toLowerCase();
if (!seenColumnNames.add(lowerCaseColumnName)) {
duplicateColumnHandler.handle(query, columnName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you pass lowerCaseColumnName instead of columnName? The reason for this request is to align with the invocation of unknownColumnHandler.handle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
EntityPropertyType<ENTITY, ?> propertyType = columnNameMap.get(lowerCaseColumnName);
if (propertyType == null) {
if (ROWNUMBER_COLUMN_NAME.equals(lowerCaseColumnName)) {
Expand Down
9 changes: 9 additions & 0 deletions doma-core/src/main/java/org/seasar/doma/jdbc/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,15 @@ default UnknownColumnHandler getUnknownColumnHandler() {
return ConfigSupport.defaultUnknownColumnHandler;
}

/**
* Returns the duplicate column handler.
*
* @return the duplicate column handler
*/
default DuplicateColumnHandler getDuplicateColumnHandler() {
return ConfigSupport.defaultDuplicateColumnHandler;
}

/**
* Returns the naming convention controller.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public final class ConfigSupport {
public static final UnknownColumnHandler defaultUnknownColumnHandler =
new UnknownColumnHandler() {};

public static final DuplicateColumnHandler defaultDuplicateColumnHandler =
new DuplicateColumnHandler() {};

public static final Naming defaultNaming = Naming.DEFAULT;

public static final MapKeyNaming defaultMapKeyNaming = new MapKeyNaming() {};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright Doma Authors
*
* 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.
*/
package org.seasar.doma.jdbc;

import org.seasar.doma.message.Message;

/** Thrown to indicate that a column is duplicated in a result set. */
public class DuplicateColumnException extends JdbcException {

private static final long serialVersionUID = 1L;

protected final String columnName;

protected final String rawSql;

protected final String formattedSql;

protected final String sqlFilePath;

public DuplicateColumnException(
SqlLogType logType,
String columnName,
String rawSql,
String formattedSql,
String sqlFilePath) {
super(Message.DOMA2237, columnName, sqlFilePath, choiceSql(logType, rawSql, formattedSql));
this.columnName = columnName;
this.rawSql = rawSql;
this.formattedSql = formattedSql;
this.sqlFilePath = sqlFilePath;
}

/**
* Returns the unknown column name.
*
* @return the unknown column name
*/
public String getColumnName() {
return columnName;
}

/**
* Returns the raw SQL string.
*
* @return the raw SQL string
*/
public String getRawSql() {
return rawSql;
}

/**
* Returns the formatted SQL string
*
* @return the formatted SQL or {@code null} if this exception is thrown in the batch process
*/
public String getFormattedSql() {
return formattedSql;
}

/**
* Returns the SQL file path.
*
* @return the SQL file path or {@code null} if the SQL is auto generated
*/
public String getSqlFilePath() {
return sqlFilePath;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright Doma Authors
*
* 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.
*/
package org.seasar.doma.jdbc;

import org.seasar.doma.jdbc.query.Query;

/** A handler for the column that is duplicated in a result set. */
public interface DuplicateColumnHandler {

/**
* Handles the duplicate column.
*
* @param query the query
* @param duplicateColumnName the name of the unknown column
*/
default void handle(Query query, String duplicateColumnName) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Doma Authors
*
* 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.
*/
package org.seasar.doma.jdbc;

import org.seasar.doma.jdbc.query.Query;

/** A handler for the column that is duplicated in a result set. */
public class ThrowingDuplicateColumnHandler implements DuplicateColumnHandler {

/**
* Handles the duplicate column.
*
* @param query the query
* @param duplicateColumnName the name of the duplicate column
* @throws DuplicateColumnException if this handler does not allow the duplicate column
*/
@Override
public void handle(Query query, String duplicateColumnName) {
Sql<?> sql = query.getSql();
throw new DuplicateColumnException(
query.getConfig().getExceptionSqlLogType(),
duplicateColumnName,
sql.getRawSql(),
sql.getFormattedSql(),
sql.getSqlFilePath());
}
}
3 changes: 3 additions & 0 deletions doma-core/src/main/java/org/seasar/doma/message/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,9 @@ public enum Message implements MessageResource {
DOMA2235("The dialect \"{0}\" does not support auto-increment when inserting multiple rows."),

DOMA2236("The dialect \"{0}\" does not support multi-row insert statement."),
DOMA2237(
"Duplicate column name \"{0}\" found in ResultSetMetaData. Column names must be unique."
+ "\nPATH=[{1}].\nSQL=[{2}]"),

// expression
DOMA3001(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
*/
package org.seasar.doma.internal.jdbc.command;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.seasar.doma.internal.util.AssertionUtil.assertEquals;

import example.entity.Emp;
import example.entity._Emp;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Collections;
import org.junit.jupiter.api.Test;
import org.seasar.doma.FetchType;
Expand All @@ -31,10 +33,13 @@
import org.seasar.doma.internal.jdbc.mock.MockResultSetMetaData;
import org.seasar.doma.internal.jdbc.mock.RowData;
import org.seasar.doma.jdbc.Config;
import org.seasar.doma.jdbc.DuplicateColumnException;
import org.seasar.doma.jdbc.DuplicateColumnHandler;
import org.seasar.doma.jdbc.PreparedSql;
import org.seasar.doma.jdbc.SelectOptions;
import org.seasar.doma.jdbc.SqlKind;
import org.seasar.doma.jdbc.SqlLogType;
import org.seasar.doma.jdbc.ThrowingDuplicateColumnHandler;
import org.seasar.doma.jdbc.UnknownColumnException;
import org.seasar.doma.jdbc.UnknownColumnHandler;
import org.seasar.doma.jdbc.entity.EntityType;
Expand Down Expand Up @@ -111,6 +116,46 @@ public void testGetEntity_EmptyUnknownColumnHandler() throws Exception {
assertEquals(100, emp.getVersion());
}

@Test
public void testCreateIndexMap_DuplicateColumnNameException() throws SQLException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test case name includes "DuplicateColumnNameException," but the test does not throw a DuplicateColumnNameException, which makes it confusing. Could you consider renaming it for clarity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MockResultSetMetaData metaData = new MockResultSetMetaData();
metaData.columns.add(new ColumnMetaData("id"));
metaData.columns.add(new ColumnMetaData("name"));
metaData.columns.add(new ColumnMetaData("name")); // Duplicate column name
metaData.columns.add(new ColumnMetaData("version"));
MockResultSet resultSet = new MockResultSet(metaData);
resultSet.rows.add(new RowData(1, "aaa", "bbb", 100));
resultSet.next();

_Emp entityType = _Emp.getSingletonInternal();
EntityProvider<Emp> provider =
new EntityProvider<>(entityType, new MySelectQuery(new MockConfig()), false);

provider.createIndexMap(metaData, entityType);
Emp emp = provider.get(resultSet);

assertEquals(1, emp.getId());
assertEquals("bbb", emp.getName());
assertEquals(100, emp.getVersion());
}

@Test
public void testCreateIndexMap_DuplicateColumnHandler() throws SQLException {
MockResultSetMetaData metaData = new MockResultSetMetaData();
metaData.columns.add(new ColumnMetaData("id"));
metaData.columns.add(new ColumnMetaData("name"));
metaData.columns.add(new ColumnMetaData("name")); // Duplicate column name
metaData.columns.add(new ColumnMetaData("version"));

_Emp entityType = _Emp.getSingletonInternal();
EntityProvider<Emp> provider =
new EntityProvider<>(
entityType, new MySelectQuery(new SetDuplicateColumnHandlerConfig()), false);

assertThrows(
DuplicateColumnException.class, () -> provider.createIndexMap(metaData, entityType));
}

protected static class MySelectQuery implements SelectQuery {

private final Config config;
Expand Down Expand Up @@ -213,4 +258,11 @@ public UnknownColumnHandler getUnknownColumnHandler() {
return new EmptyUnknownColumnHandler();
}
}

protected static class SetDuplicateColumnHandlerConfig extends MockConfig {
@Override
public DuplicateColumnHandler getDuplicateColumnHandler() {
return new ThrowingDuplicateColumnHandler();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright Doma Authors
*
* 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.
*/
package org.seasar.doma.jdbc;

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

public class DuplicateColumnExceptionTest {

@Test
public void test() {
DuplicateColumnException e =
new DuplicateColumnException(SqlLogType.FORMATTED, "aaa", "bbb", "ccc", "ddd");
System.out.println(e.getMessage());
assertEquals("aaa", e.getColumnName());
assertEquals("bbb", e.getRawSql());
assertEquals("ccc", e.getFormattedSql());
assertEquals("ddd", e.getSqlFilePath());
}
}
Loading