This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openjpa.git
commit d0875670dd470473e305aea00d66984735a8e3dc Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Wed Apr 29 20:47:28 2020 +0200 OPENJPA-2812 snake_case support in dbdictionary for db column names --- .../openjpa/jdbc/meta/MappingDefaultsImpl.java | 16 +- .../org/apache/openjpa/jdbc/sql/DBDictionary.java | 37 +++++ .../apache/openjpa/jdbc/sql/TestDBDictionary.java | 35 +++++ .../apache/openjpa/jdbc/sql/TestSnakeCaseDDL.java | 162 +++++++++++++++++++++ 4 files changed, 246 insertions(+), 4 deletions(-) diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingDefaultsImpl.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingDefaultsImpl.java index ca1bf38..c9f872e 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingDefaultsImpl.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/meta/MappingDefaultsImpl.java @@ -630,14 +630,22 @@ public class MappingDefaultsImpl * Correct the given column's name. */ protected void correctName(Table table, Column col) { + DBIdentifier name = col.getIdentifier(); + boolean corrected = false; + if (dict.javaToDbColumnNameProcessing != null) { + name = dict.processDBColumnName(name); + corrected = true; + } if (!_defMissing || _removeHungarianNotation) { - DBIdentifier name = col.getIdentifier(); if (_removeHungarianNotation) name = DBIdentifier.removeHungarianNotation(name); - DBIdentifier correctedName = dict.getValidColumnName(name, table); - col.setIdentifier(correctedName); - table.addCorrectedColumnName(correctedName, true); + corrected = true; + } + if (corrected) { + name = dict.getValidColumnName(name, table, false); + col.setIdentifier(name); + table.addCorrectedColumnName(name, true); } } diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java index 82ee861..26e13fc 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/sql/DBDictionary.java @@ -135,6 +135,8 @@ import org.apache.openjpa.util.StoreException; import org.apache.openjpa.util.UnsupportedException; import org.apache.openjpa.util.UserException; +import static java.util.Locale.ROOT; + /** * Class which allows the creation of SQL dynamically, in a @@ -223,6 +225,7 @@ public class DBDictionary public String selectWords = null; public String fixedSizeTypeNames = null; public String schemaCase = SCHEMA_CASE_UPPER; + public String javaToDbColumnNameProcessing; public boolean setStringRightTruncationOn = true; public boolean fullResultCollectionInOrderByRelation = false; public boolean disableSchemaFactoryColumnTypeErrors = false; //OPENJPA-2627 @@ -3292,6 +3295,32 @@ public class DBDictionary return toDBName(getColumnIdentifier(column)); } + public String toSnakeCase(final String name) { + final StringBuilder out = new StringBuilder(name.length() + 3); + final boolean isDelimited = name.startsWith(getLeadingDelimiter()) && name.endsWith(getTrailingDelimiter()); + final String toConvert; + if (isDelimited) { + toConvert = name.substring(2, name.length() - 1); + out.append(name.substring(0, 2).toLowerCase(ROOT)); + } else { + toConvert = name.substring(1); + out.append(Character.toLowerCase(name.charAt(0))); + } + for (final char c : toConvert.toCharArray()) { + if (!Character.isLetter(c)) { // delimiter + out.append(c); + } else if (Character.isUpperCase(c)) { + out.append('_').append(Character.toLowerCase(c)); + } else { + out.append(c); + } + } + if (toConvert.length() != name.length() - 1) { + out.append(name.charAt(name.length() - 1)); + } + return out.toString(); + } + /** * Returns the full name of the table, including the schema (delimited * by {@link #catalogSeparator}). @@ -3389,6 +3418,14 @@ public class DBDictionary return getValidColumnName(DBIdentifier.newColumn(name), table, true).getName(); } + public DBIdentifier processDBColumnName(final DBIdentifier name) { + if ("snake_case".equalsIgnoreCase(javaToDbColumnNameProcessing)) { + return DBIdentifier.newColumn(toSnakeCase(name.getName())); + } + throw new IllegalArgumentException( + "Unsupported javaToDbColumnNameProcessing value: '" + javaToDbColumnNameProcessing + "'"); + } + /** * Make any necessary changes to the given column name to make it valid * for the current DB. The column name will be made unique for the diff --git a/openjpa-jdbc/src/test/java/org/apache/openjpa/jdbc/sql/TestDBDictionary.java b/openjpa-jdbc/src/test/java/org/apache/openjpa/jdbc/sql/TestDBDictionary.java new file mode 100644 index 0000000..291058a --- /dev/null +++ b/openjpa-jdbc/src/test/java/org/apache/openjpa/jdbc/sql/TestDBDictionary.java @@ -0,0 +1,35 @@ +/* + * 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 + * + * http://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.openjpa.jdbc.sql; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class TestDBDictionary { + @Test + public void snakeCase() { + final DBDictionary dictionary = new DBDictionary(); + assertEquals("foo", dictionary.toSnakeCase("foo")); + assertEquals("foo_bar", dictionary.toSnakeCase("fooBar")); + assertEquals("fooba_r", dictionary.toSnakeCase("FoobaR")); + assertEquals("o_f_o_ob", dictionary.toSnakeCase("oFOOb")); + assertEquals("\"foo_bar\"", dictionary.toSnakeCase("\"fooBar\"")); + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/sql/TestSnakeCaseDDL.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/sql/TestSnakeCaseDDL.java new file mode 100644 index 0000000..053ccfa --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/sql/TestSnakeCaseDDL.java @@ -0,0 +1,162 @@ +/* + * 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 + * + * http://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.openjpa.jdbc.sql; + +import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.derby.jdbc.EmbeddedDriver; +import org.apache.openjpa.persistence.PersistenceProviderImpl; +import org.apache.openjpa.persistence.PersistenceUnitInfoImpl; +import org.junit.Test; + +import javax.persistence.Entity; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Id; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static java.util.Collections.singleton; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TestSnakeCaseDDL { + @Test + public void ddlInSnakeCase() throws SQLException { + final PersistenceUnitInfoImpl persistenceUnitInfo = new PersistenceUnitInfoImpl(); + persistenceUnitInfo.setExcludeUnlistedClasses(true); + persistenceUnitInfo.addManagedClassName(MyEntity1.class.getName()); + persistenceUnitInfo.addManagedClassName(MyEntity2.class.getName()); + final BasicDataSource ds = new BasicDataSource(); + ds.setDriver(new EmbeddedDriver()); + ds.setUrl("jdbc:derby:memory:ddlInSnakeCase;create=true"); + persistenceUnitInfo.setJtaDataSource(ds); + persistenceUnitInfo.setProperty("openjpa.jdbc.DBDictionary", "derby(javaToDbColumnNameProcessing=snake_case)"); + persistenceUnitInfo.setProperty("openjpa.RuntimeUnenhancedClasses", "supported"); + new PersistenceProviderImpl().generateSchema(persistenceUnitInfo, new HashMap<>()); + final Collection<String> createdTables = new HashSet<>(); + final Map<String, Collection<String>> columns = new HashMap<>(); + try (final Connection connection = ds.getConnection()) { + try (final ResultSet tables = connection.getMetaData() + .getTables(null, null, "TestSnakeCaseDDL$MyEntity%", null)) { + while (tables.next()) { + final String table = tables.getString(3); + createdTables.add(table); + } + } + for (final String table : createdTables) { + try (final Statement statement = connection.createStatement()) { + try (final ResultSet rs = statement.executeQuery("select * from \"" + table + "\"")) { + final ResultSetMetaData metaData = rs.getMetaData(); + final Set<String> columnNames = new HashSet<>(); + columns.put(table, columnNames); + for (int i = 1; i <= metaData.getColumnCount(); i++) { + columnNames.add(metaData.getColumnName(i)); + } + } + } + } + } + final EntityManagerFactory entityManagerFactory = new PersistenceProviderImpl() + .createContainerEntityManagerFactory(persistenceUnitInfo, new HashMap()); + try { + { + final EntityManager em = entityManagerFactory.createEntityManager(); + em.getTransaction().begin(); + try { + final MyEntity1 entity = new MyEntity1(); + entity.setFooBar("1"); + entity.setThisField(123); + em.persist(entity); + em.getTransaction().commit(); + } catch (final RuntimeException re) { + em.getTransaction().rollback(); + throw re; + } finally { + em.close(); + } + } + { + final EntityManager em = entityManagerFactory.createEntityManager(); + try { + final MyEntity1 myEntity1 = em.find(MyEntity1.class, "1"); + assertNotNull(myEntity1); + assertEquals("1", myEntity1.getFooBar()); + assertEquals(123, myEntity1.getThisField()); + } finally { + em.close(); + } + } + try (final Connection connection = ds.getConnection(); + final Statement statement = connection.createStatement(); + final ResultSet rs = statement.executeQuery("select foo_bar, this_field from \"TestSnakeCaseDDL$MyEntity1\"")) { + assertTrue (rs.next()); + assertEquals("1", rs.getString(1)); + assertEquals(123, rs.getInt(2)); + assertFalse(rs.next()); + } + } finally { + entityManagerFactory.close(); + } + ds.close(); + assertEquals(2, columns.get("TestSnakeCaseDDL$MyEntity1").size()); + assertTrue(columns.get("TestSnakeCaseDDL$MyEntity1").contains("FOO_BAR")); + assertTrue(columns.get("TestSnakeCaseDDL$MyEntity1").contains("THIS_FIELD")); + assertEquals(singleton("ANOTHER_FIELD"), columns.get("TestSnakeCaseDDL$MyEntity2")); + } + + @Entity + public static class MyEntity1 { + @Id + private String fooBar; + + private int thisField; + + public int getThisField() { + return thisField; + } + + public void setThisField(int thisField) { + this.thisField = thisField; + } + + public String getFooBar() { + return fooBar; + } + + public void setFooBar(String fooBar) { + this.fooBar = fooBar; + } + } + + @Entity + public static class MyEntity2 { + @Id + private String anotherField; + } +}