You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by rm...@apache.org on 2020/04/29 18:47:40 UTC
[openjpa] 02/02: OPENJPA-2812 snake_case support in dbdictionary
for db column names
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 <rm...@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;
+ }
+}